index.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. import { VantComponent } from '../common/component';
  2. import { touch } from '../mixins/touch';
  3. import { isDef, addUnit } from '../common/utils';
  4. VantComponent({
  5. mixins: [touch],
  6. classes: ['nav-class', 'tab-class', 'tab-active-class', 'line-class'],
  7. relation: {
  8. name: 'tab',
  9. type: 'descendant',
  10. current: 'tabs',
  11. linked(target) {
  12. target.index = this.children.length - 1;
  13. this.updateTabs();
  14. },
  15. unlinked() {
  16. this.children = this.children.map((child, index) => {
  17. child.index = index;
  18. return child;
  19. });
  20. this.updateTabs();
  21. },
  22. },
  23. props: {
  24. sticky: Boolean,
  25. border: Boolean,
  26. swipeable: Boolean,
  27. titleActiveColor: String,
  28. titleInactiveColor: String,
  29. color: {
  30. type: String,
  31. observer: 'setLine',
  32. },
  33. animated: {
  34. type: Boolean,
  35. observer() {
  36. this.children.forEach((child, index) =>
  37. child.updateRender(index === this.data.currentIndex, this)
  38. );
  39. },
  40. },
  41. lineWidth: {
  42. type: [String, Number],
  43. value: 40,
  44. observer: 'setLine',
  45. },
  46. lineHeight: {
  47. type: [String, Number],
  48. value: -1,
  49. observer: 'setLine',
  50. },
  51. active: {
  52. type: [String, Number],
  53. value: 0,
  54. observer(name) {
  55. if (name !== this.getCurrentName()) {
  56. this.setCurrentIndexByName(name);
  57. }
  58. },
  59. },
  60. type: {
  61. type: String,
  62. value: 'line',
  63. },
  64. ellipsis: {
  65. type: Boolean,
  66. value: true,
  67. },
  68. duration: {
  69. type: Number,
  70. value: 0.3,
  71. },
  72. zIndex: {
  73. type: Number,
  74. value: 1,
  75. },
  76. swipeThreshold: {
  77. type: Number,
  78. value: 5,
  79. observer(value) {
  80. this.setData({
  81. scrollable: this.children.length > value || !this.data.ellipsis,
  82. });
  83. },
  84. },
  85. offsetTop: {
  86. type: Number,
  87. value: 0,
  88. },
  89. lazyRender: {
  90. type: Boolean,
  91. value: true,
  92. },
  93. },
  94. data: {
  95. tabs: [],
  96. lineStyle: '',
  97. scrollLeft: 0,
  98. scrollable: false,
  99. trackStyle: '',
  100. currentIndex: null,
  101. container: null,
  102. },
  103. mounted() {
  104. wx.nextTick(() => {
  105. this.setLine(true);
  106. this.scrollIntoView();
  107. });
  108. },
  109. methods: {
  110. updateContainer() {
  111. this.setData({
  112. container: () => this.createSelectorQuery().select('.van-tabs'),
  113. });
  114. },
  115. updateTabs() {
  116. const { children = [], data } = this;
  117. this.setData({
  118. tabs: children.map((child) => child.data),
  119. scrollable:
  120. this.children.length > data.swipeThreshold || !data.ellipsis,
  121. });
  122. this.setCurrentIndexByName(this.getCurrentName() || data.active);
  123. },
  124. trigger(eventName, child) {
  125. const { currentIndex } = this.data;
  126. const currentChild = child || this.children[currentIndex];
  127. if (!isDef(currentChild)) {
  128. return;
  129. }
  130. this.$emit(eventName, {
  131. index: currentChild.index,
  132. name: currentChild.getComputedName(),
  133. title: currentChild.data.title,
  134. });
  135. },
  136. onTap(event) {
  137. const { index } = event.currentTarget.dataset;
  138. const child = this.children[index];
  139. if (child.data.disabled) {
  140. this.trigger('disabled', child);
  141. } else {
  142. this.setCurrentIndex(index);
  143. wx.nextTick(() => {
  144. this.trigger('click');
  145. });
  146. }
  147. },
  148. // correct the index of active tab
  149. setCurrentIndexByName(name) {
  150. const { children = [] } = this;
  151. const matched = children.filter(
  152. (child) => child.getComputedName() === name
  153. );
  154. if (matched.length) {
  155. this.setCurrentIndex(matched[0].index);
  156. }
  157. },
  158. setCurrentIndex(currentIndex) {
  159. const { data, children = [] } = this;
  160. if (
  161. !isDef(currentIndex) ||
  162. currentIndex >= children.length ||
  163. currentIndex < 0
  164. ) {
  165. return;
  166. }
  167. children.forEach((item, index) => {
  168. const active = index === currentIndex;
  169. if (active !== item.data.active || !item.inited) {
  170. item.updateRender(active, this);
  171. }
  172. });
  173. if (currentIndex === data.currentIndex) {
  174. return;
  175. }
  176. const shouldEmitChange = data.currentIndex !== null;
  177. this.setData({ currentIndex });
  178. wx.nextTick(() => {
  179. this.setLine();
  180. this.scrollIntoView();
  181. this.updateContainer();
  182. this.trigger('input');
  183. if (shouldEmitChange) {
  184. this.trigger('change');
  185. }
  186. });
  187. },
  188. getCurrentName() {
  189. const activeTab = this.children[this.data.currentIndex];
  190. if (activeTab) {
  191. return activeTab.getComputedName();
  192. }
  193. },
  194. setLine(skipTransition) {
  195. if (this.data.type !== 'line') {
  196. return;
  197. }
  198. const {
  199. color,
  200. duration,
  201. currentIndex,
  202. lineWidth,
  203. lineHeight,
  204. } = this.data;
  205. this.getRect('.van-tab', true).then((rects = []) => {
  206. const rect = rects[currentIndex];
  207. if (rect == null) {
  208. return;
  209. }
  210. const height =
  211. lineHeight !== -1
  212. ? `height: ${addUnit(lineHeight)}; border-radius: ${addUnit(
  213. lineHeight
  214. )};`
  215. : '';
  216. let left = rects
  217. .slice(0, currentIndex)
  218. .reduce((prev, curr) => prev + curr.width, 0);
  219. left += (rect.width - lineWidth) / 2;
  220. const transition = skipTransition
  221. ? ''
  222. : `transition-duration: ${duration}s; -webkit-transition-duration: ${duration}s;`;
  223. this.setData({
  224. lineStyle: `
  225. ${height}
  226. width: ${addUnit(lineWidth)};
  227. background-color: ${color};
  228. -webkit-transform: translateX(${left}px);
  229. transform: translateX(${left}px);
  230. ${transition}
  231. `,
  232. });
  233. });
  234. },
  235. // scroll active tab into view
  236. scrollIntoView() {
  237. const { currentIndex, scrollable } = this.data;
  238. if (!scrollable) {
  239. return;
  240. }
  241. Promise.all([
  242. this.getRect('.van-tab', true),
  243. this.getRect('.van-tabs__nav'),
  244. ]).then(([tabRects, navRect]) => {
  245. const tabRect = tabRects[currentIndex];
  246. const offsetLeft = tabRects
  247. .slice(0, currentIndex)
  248. .reduce((prev, curr) => prev + curr.width, 0);
  249. this.setData({
  250. scrollLeft: offsetLeft - (navRect.width - tabRect.width) / 2,
  251. });
  252. });
  253. },
  254. onTouchScroll(event) {
  255. this.$emit('scroll', event.detail);
  256. },
  257. onTouchStart(event) {
  258. if (!this.data.swipeable) return;
  259. this.touchStart(event);
  260. },
  261. onTouchMove(event) {
  262. if (!this.data.swipeable) return;
  263. this.touchMove(event);
  264. },
  265. // watch swipe touch end
  266. onTouchEnd() {
  267. if (!this.data.swipeable) return;
  268. const { direction, deltaX, offsetX } = this;
  269. const minSwipeDistance = 50;
  270. if (direction === 'horizontal' && offsetX >= minSwipeDistance) {
  271. const index = this.getAvaiableTab(deltaX);
  272. if (index !== -1) {
  273. this.setCurrentIndex(index);
  274. }
  275. }
  276. },
  277. getAvaiableTab(direction) {
  278. const { tabs, currentIndex } = this.data;
  279. const step = direction > 0 ? -1 : 1;
  280. for (
  281. let i = step;
  282. currentIndex + i < tabs.length && currentIndex + i >= 0;
  283. i += step
  284. ) {
  285. const index = currentIndex + i;
  286. if (
  287. index >= 0 &&
  288. index < tabs.length &&
  289. tabs[index] &&
  290. !tabs[index].disabled
  291. ) {
  292. return index;
  293. }
  294. }
  295. return -1;
  296. },
  297. },
  298. });