index.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. import baseComponent from '../helpers/baseComponent'
  2. import classNames from '../helpers/libs/classNames'
  3. import styleToCssString from '../helpers/libs/styleToCssString'
  4. import { bound } from '../helpers/shared/bound'
  5. import { useRef } from '../helpers/hooks/useDOM'
  6. import { props } from './props'
  7. const getDefaultActiveKey = (elements) => {
  8. const target = elements.filter((element) => !element.data.disabled)[0]
  9. if (target) {
  10. return target.data.key
  11. }
  12. return null
  13. }
  14. const activeKeyIsValid = (elements, key) => {
  15. return elements.map((element) => element.data.key).includes(key)
  16. }
  17. const getActiveKey = (elements, activeKey) => {
  18. const defaultActiveKey = getDefaultActiveKey(elements)
  19. return !activeKey ? defaultActiveKey : !activeKeyIsValid(elements, activeKey) ? defaultActiveKey : activeKey
  20. }
  21. baseComponent({
  22. relations: {
  23. '../tab/index': {
  24. type: 'child',
  25. observer() {
  26. this.callDebounceFn(this.updated)
  27. },
  28. },
  29. },
  30. properties: props,
  31. data: {
  32. activeKey: '',
  33. keys: [],
  34. scrollLeft: 0,
  35. scrollTop: 0,
  36. showPrevMask: false,
  37. showNextMask: false,
  38. scrollViewStyle: '',
  39. },
  40. observers: {
  41. current(newVal) {
  42. if (this.data.controlled) {
  43. this.updated(newVal)
  44. }
  45. },
  46. justify(newVal) {
  47. this.setStyles(newVal)
  48. },
  49. },
  50. computed: {
  51. classes: ['prefixCls, direction, scroll', function(prefixCls, direction, scroll) {
  52. const wrap = classNames(prefixCls, {
  53. [`${prefixCls}--${direction}`]: direction,
  54. [`${prefixCls}--scroll`]: scroll,
  55. })
  56. const scrollView = `${prefixCls}__scroll-view`
  57. const prev = classNames([`${prefixCls}__mask`, `${prefixCls}__mask--prev`])
  58. const next = classNames([`${prefixCls}__mask`, `${prefixCls}__mask--next`])
  59. return {
  60. wrap,
  61. scrollView,
  62. prev,
  63. next,
  64. }
  65. }],
  66. },
  67. methods: {
  68. onScrollFix() {
  69. const { direction } = this.data
  70. if (direction === 'horizontal') {
  71. if (!this.updateMask) {
  72. const { run: updateMask } = this.useThrottleFn(() => {
  73. this.tabsContainerRef().then((container) => {
  74. const scrollLeft = container.containerScrollLeft
  75. const showPrevMask = scrollLeft > 0
  76. const showNextMask = Math.round(scrollLeft + container.containerWidth) < Math.round(container.containerScrollWidth)
  77. this.setData(({
  78. showPrevMask,
  79. showNextMask,
  80. }))
  81. })
  82. }, 100, {
  83. trailing: true,
  84. leading: true,
  85. })
  86. this.updateMask = updateMask
  87. }
  88. this.updateMask.call(this)
  89. }
  90. if (direction === 'vertical') {
  91. if (!this.updateMask) {
  92. const { run: updateMask } = this.useThrottleFn(() => {
  93. this.tabsContainerRef().then((container) => {
  94. const scrollTop = container.containerScrollTop
  95. const showPrevMask = scrollTop > 0
  96. const showNextMask = Math.round(scrollTop + container.containerHeight) < Math.round(container.containerScrollHeight)
  97. this.setData(({
  98. showPrevMask,
  99. showNextMask,
  100. }))
  101. })
  102. }, 100, {
  103. trailing: true,
  104. leading: true,
  105. })
  106. this.updateMask = updateMask
  107. }
  108. this.updateMask.call(this)
  109. }
  110. },
  111. tabsContainerRef() {
  112. const { prefixCls } = this.data
  113. return useRef(`.${prefixCls}__scroll-view`, this)
  114. .then((container) => {
  115. const containerWidth = container.width
  116. const containerHeight = container.height
  117. const containerScrollWidth = container.scrollWidth
  118. const containerScrollHeight = container.scrollHeight
  119. const containerScrollLeft = container.scrollLeft
  120. const containerScrollTop = container.scrollTop
  121. const containerOffsetX = container.left
  122. const containerOffsetY = container.top
  123. return {
  124. containerWidth,
  125. containerHeight,
  126. containerScrollWidth,
  127. containerScrollHeight,
  128. containerScrollLeft,
  129. containerScrollTop,
  130. containerOffsetX,
  131. containerOffsetY,
  132. }
  133. })
  134. },
  135. setNextScroll(activeElement) {
  136. const { direction, scroll } = this.data
  137. if (!scroll) { return }
  138. let promise = Promise.all([
  139. this.tabsContainerRef(),
  140. activeElement.activeTabRef(),
  141. ])
  142. promise = promise.then(([container, activeTab]) => {
  143. if (direction === 'horizontal') {
  144. const maxScrollDistance = container.containerScrollWidth - container.containerWidth
  145. if (maxScrollDistance <= 0) { return [] }
  146. const nextScrollLeft = Math.round(bound(
  147. container.containerScrollLeft + (activeTab.activeTabLeft - container.containerOffsetX) - (container.containerWidth - activeTab.activeTabWidth) / 2,
  148. 0,
  149. maxScrollDistance
  150. ))
  151. return [nextScrollLeft, undefined]
  152. }
  153. if (direction === 'vertical') {
  154. const maxScrollDistance = container.containerScrollHeight - container.containerHeight
  155. if (maxScrollDistance <= 0) { return [] }
  156. const nextScrollTop = Math.round(bound(
  157. container.containerScrollTop + (activeTab.activeTabTop - container.containerOffsetY) - (container.containerHeight - activeTab.activeTabHeight) / 2,
  158. 0,
  159. maxScrollDistance
  160. ))
  161. return [undefined, nextScrollTop]
  162. }
  163. })
  164. promise = promise.then(([nextScrollLeft, nextScrollTop]) => {
  165. if (typeof nextScrollLeft !== 'undefined') {
  166. this.setData({
  167. scrollLeft: nextScrollLeft,
  168. })
  169. this.onScrollFix()
  170. }
  171. if (typeof nextScrollTop !== 'undefined') {
  172. this.setData({
  173. scrollTop: nextScrollTop,
  174. })
  175. this.onScrollFix()
  176. }
  177. })
  178. return promise
  179. },
  180. updated(value = this.data.activeKey) {
  181. const elements = this.getRelationsByName('../tab/index')
  182. const activeKey = getActiveKey(elements, value)
  183. if (this.data.activeKey !== activeKey) {
  184. this.setData({ activeKey })
  185. }
  186. this.changeCurrent(activeKey, elements)
  187. },
  188. changeCurrent(activeKey, elements) {
  189. const { scroll, theme, direction, activeLineMode } = this.data
  190. if (elements.length > 0) {
  191. elements.forEach((element) => {
  192. element.changeCurrent({
  193. current: element.data.key === activeKey,
  194. context: {
  195. scroll,
  196. theme,
  197. direction,
  198. activeLineMode,
  199. },
  200. })
  201. if (element.data.key === activeKey) {
  202. this.setNextScroll(element)
  203. }
  204. })
  205. }
  206. if (this.data.keys.length !== elements.length) {
  207. this.setData({
  208. keys: elements.map((element) => element.data),
  209. })
  210. }
  211. },
  212. emitEvent(key) {
  213. this.triggerEvent('change', {
  214. key,
  215. keys: this.data.keys,
  216. })
  217. },
  218. setActiveKey(activeKey) {
  219. if (!this.data.controlled) {
  220. this.updated(activeKey)
  221. }
  222. this.emitEvent(activeKey)
  223. },
  224. /**
  225. * 水平排列方式
  226. */
  227. setStyles(justify) {
  228. if (this.data.direction === 'horizontal') {
  229. const scrollViewStyle = styleToCssString({
  230. 'justify-content': justify,
  231. })
  232. if (this.data.scrollViewStyle !== scrollViewStyle) {
  233. this.setData({
  234. scrollViewStyle,
  235. })
  236. }
  237. }
  238. },
  239. },
  240. ready() {
  241. const { defaultCurrent, current, controlled, justify } = this.data
  242. const activeKey = controlled ? current : defaultCurrent
  243. this.updated(activeKey)
  244. this.setStyles(justify)
  245. },
  246. })