Tabs.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. var __create = Object.create;
  2. var __defProp = Object.defineProperty;
  3. var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
  4. var __getOwnPropNames = Object.getOwnPropertyNames;
  5. var __getProtoOf = Object.getPrototypeOf;
  6. var __hasOwnProp = Object.prototype.hasOwnProperty;
  7. var __export = (target, all) => {
  8. for (var name2 in all)
  9. __defProp(target, name2, { get: all[name2], enumerable: true });
  10. };
  11. var __copyProps = (to, from, except, desc) => {
  12. if (from && typeof from === "object" || typeof from === "function") {
  13. for (let key of __getOwnPropNames(from))
  14. if (!__hasOwnProp.call(to, key) && key !== except)
  15. __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  16. }
  17. return to;
  18. };
  19. var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  20. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  21. mod
  22. ));
  23. var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
  24. var stdin_exports = {};
  25. __export(stdin_exports, {
  26. TABS_KEY: () => TABS_KEY,
  27. default: () => stdin_default
  28. });
  29. module.exports = __toCommonJS(stdin_exports);
  30. var import_vue = require("vue");
  31. var import_vue2 = require("vue");
  32. var import_utils = require("../utils");
  33. var import_utils2 = require("./utils");
  34. var import_use = require("@vant/use");
  35. var import_use_id = require("../composables/use-id");
  36. var import_use_route = require("../composables/use-route");
  37. var import_use_refs = require("../composables/use-refs");
  38. var import_use_expose = require("../composables/use-expose");
  39. var import_on_popup_reopen = require("../composables/on-popup-reopen");
  40. var import_sticky = require("../sticky");
  41. var import_TabsTitle = __toESM(require("./TabsTitle"));
  42. var import_TabsContent = __toESM(require("./TabsContent"));
  43. const [name, bem] = (0, import_utils.createNamespace)("tabs");
  44. const tabsProps = {
  45. type: (0, import_utils.makeStringProp)("line"),
  46. color: String,
  47. border: Boolean,
  48. sticky: Boolean,
  49. shrink: Boolean,
  50. active: (0, import_utils.makeNumericProp)(0),
  51. duration: (0, import_utils.makeNumericProp)(0.3),
  52. animated: Boolean,
  53. ellipsis: import_utils.truthProp,
  54. swipeable: Boolean,
  55. scrollspy: Boolean,
  56. offsetTop: (0, import_utils.makeNumericProp)(0),
  57. background: String,
  58. lazyRender: import_utils.truthProp,
  59. lineWidth: import_utils.numericProp,
  60. lineHeight: import_utils.numericProp,
  61. beforeChange: Function,
  62. swipeThreshold: (0, import_utils.makeNumericProp)(5),
  63. titleActiveColor: String,
  64. titleInactiveColor: String
  65. };
  66. const TABS_KEY = Symbol(name);
  67. var stdin_default = (0, import_vue2.defineComponent)({
  68. name,
  69. props: tabsProps,
  70. emits: ["click", "change", "scroll", "disabled", "rendered", "click-tab", "update:active"],
  71. setup(props, {
  72. emit,
  73. slots
  74. }) {
  75. var _a, _b;
  76. if (process.env.NODE_ENV !== "production") {
  77. const props2 = (_b = (_a = (0, import_vue2.getCurrentInstance)()) == null ? void 0 : _a.vnode) == null ? void 0 : _b.props;
  78. if (props2 && "onClick" in props2) {
  79. console.warn('[Vant] Tabs: "click" event is deprecated, using "click-tab" instead.');
  80. }
  81. if (props2 && "onDisabled" in props2) {
  82. console.warn('[Vant] Tabs: "disabled" event is deprecated, using "click-tab" instead.');
  83. }
  84. }
  85. let tabHeight;
  86. let lockScroll;
  87. let stickyFixed;
  88. const root = (0, import_vue2.ref)();
  89. const navRef = (0, import_vue2.ref)();
  90. const wrapRef = (0, import_vue2.ref)();
  91. const contentRef = (0, import_vue2.ref)();
  92. const id = (0, import_use_id.useId)();
  93. const scroller = (0, import_use.useScrollParent)(root);
  94. const [titleRefs, setTitleRefs] = (0, import_use_refs.useRefs)();
  95. const {
  96. children,
  97. linkChildren
  98. } = (0, import_use.useChildren)(TABS_KEY);
  99. const state = (0, import_vue2.reactive)({
  100. inited: false,
  101. position: "",
  102. lineStyle: {},
  103. currentIndex: -1
  104. });
  105. const scrollable = (0, import_vue2.computed)(() => children.length > props.swipeThreshold || !props.ellipsis || props.shrink);
  106. const navStyle = (0, import_vue2.computed)(() => ({
  107. borderColor: props.color,
  108. background: props.background
  109. }));
  110. const getTabName = (tab, index) => {
  111. var _a2;
  112. return (_a2 = tab.name) != null ? _a2 : index;
  113. };
  114. const currentName = (0, import_vue2.computed)(() => {
  115. const activeTab = children[state.currentIndex];
  116. if (activeTab) {
  117. return getTabName(activeTab, state.currentIndex);
  118. }
  119. });
  120. const offsetTopPx = (0, import_vue2.computed)(() => (0, import_utils.unitToPx)(props.offsetTop));
  121. const scrollOffset = (0, import_vue2.computed)(() => {
  122. if (props.sticky) {
  123. return offsetTopPx.value + tabHeight;
  124. }
  125. return 0;
  126. });
  127. const scrollIntoView = (immediate) => {
  128. const nav = navRef.value;
  129. const titles = titleRefs.value;
  130. if (!scrollable.value || !nav || !titles || !titles[state.currentIndex]) {
  131. return;
  132. }
  133. const title = titles[state.currentIndex].$el;
  134. const to = title.offsetLeft - (nav.offsetWidth - title.offsetWidth) / 2;
  135. (0, import_utils2.scrollLeftTo)(nav, to, immediate ? 0 : +props.duration);
  136. };
  137. const setLine = () => {
  138. const shouldAnimate = state.inited;
  139. (0, import_vue2.nextTick)(() => {
  140. const titles = titleRefs.value;
  141. if (!titles || !titles[state.currentIndex] || props.type !== "line" || (0, import_utils.isHidden)(root.value)) {
  142. return;
  143. }
  144. const title = titles[state.currentIndex].$el;
  145. const {
  146. lineWidth,
  147. lineHeight
  148. } = props;
  149. const left = title.offsetLeft + title.offsetWidth / 2;
  150. const lineStyle = {
  151. width: (0, import_utils.addUnit)(lineWidth),
  152. backgroundColor: props.color,
  153. transform: `translateX(${left}px) translateX(-50%)`
  154. };
  155. if (shouldAnimate) {
  156. lineStyle.transitionDuration = `${props.duration}s`;
  157. }
  158. if ((0, import_utils.isDef)(lineHeight)) {
  159. const height = (0, import_utils.addUnit)(lineHeight);
  160. lineStyle.height = height;
  161. lineStyle.borderRadius = height;
  162. }
  163. state.lineStyle = lineStyle;
  164. });
  165. };
  166. const findAvailableTab = (index) => {
  167. const diff = index < state.currentIndex ? -1 : 1;
  168. while (index >= 0 && index < children.length) {
  169. if (!children[index].disabled) {
  170. return index;
  171. }
  172. index += diff;
  173. }
  174. };
  175. const setCurrentIndex = (currentIndex, skipScrollIntoView) => {
  176. const newIndex = findAvailableTab(currentIndex);
  177. if (!(0, import_utils.isDef)(newIndex)) {
  178. return;
  179. }
  180. const newTab = children[newIndex];
  181. const newName = getTabName(newTab, newIndex);
  182. const shouldEmitChange = state.currentIndex !== null;
  183. if (state.currentIndex !== newIndex) {
  184. state.currentIndex = newIndex;
  185. if (!skipScrollIntoView) {
  186. scrollIntoView();
  187. }
  188. setLine();
  189. }
  190. if (newName !== props.active) {
  191. emit("update:active", newName);
  192. if (shouldEmitChange) {
  193. emit("change", newName, newTab.title);
  194. }
  195. }
  196. if (stickyFixed && !props.scrollspy) {
  197. (0, import_utils.setRootScrollTop)(Math.ceil((0, import_utils.getElementTop)(root.value) - offsetTopPx.value));
  198. }
  199. };
  200. const setCurrentIndexByName = (name2, skipScrollIntoView) => {
  201. const matched = children.find((tab, index2) => getTabName(tab, index2) === name2);
  202. const index = matched ? children.indexOf(matched) : 0;
  203. setCurrentIndex(index, skipScrollIntoView);
  204. };
  205. const scrollToCurrentContent = (immediate = false) => {
  206. if (props.scrollspy) {
  207. const target = children[state.currentIndex].$el;
  208. if (target && scroller.value) {
  209. const to = (0, import_utils.getElementTop)(target, scroller.value) - scrollOffset.value;
  210. lockScroll = true;
  211. (0, import_utils2.scrollTopTo)(scroller.value, to, immediate ? 0 : +props.duration, () => {
  212. lockScroll = false;
  213. });
  214. }
  215. }
  216. };
  217. const onClickTab = (item, index, event) => {
  218. const {
  219. title,
  220. disabled
  221. } = children[index];
  222. const name2 = getTabName(children[index], index);
  223. if (disabled) {
  224. emit("disabled", name2, title);
  225. } else {
  226. (0, import_utils.callInterceptor)(props.beforeChange, {
  227. args: [name2],
  228. done: () => {
  229. setCurrentIndex(index);
  230. scrollToCurrentContent();
  231. }
  232. });
  233. emit("click", name2, title);
  234. (0, import_use_route.route)(item);
  235. }
  236. emit("click-tab", {
  237. name: name2,
  238. title,
  239. event,
  240. disabled
  241. });
  242. };
  243. const onStickyScroll = (params) => {
  244. stickyFixed = params.isFixed;
  245. emit("scroll", params);
  246. };
  247. const scrollTo = (name2) => {
  248. (0, import_vue2.nextTick)(() => {
  249. setCurrentIndexByName(name2);
  250. scrollToCurrentContent(true);
  251. });
  252. };
  253. const getCurrentIndexOnScroll = () => {
  254. for (let index = 0; index < children.length; index++) {
  255. const {
  256. top
  257. } = (0, import_use.useRect)(children[index].$el);
  258. if (top > scrollOffset.value) {
  259. return index === 0 ? 0 : index - 1;
  260. }
  261. }
  262. return children.length - 1;
  263. };
  264. const onScroll = () => {
  265. if (props.scrollspy && !lockScroll) {
  266. const index = getCurrentIndexOnScroll();
  267. setCurrentIndex(index);
  268. }
  269. };
  270. const renderNav = () => children.map((item, index) => (0, import_vue.createVNode)(import_TabsTitle.default, (0, import_vue.mergeProps)({
  271. "key": item.id,
  272. "id": `${id}-${index}`,
  273. "ref": setTitleRefs(index),
  274. "type": props.type,
  275. "color": props.color,
  276. "style": item.titleStyle,
  277. "class": item.titleClass,
  278. "shrink": props.shrink,
  279. "isActive": index === state.currentIndex,
  280. "controls": item.id,
  281. "scrollable": scrollable.value,
  282. "activeColor": props.titleActiveColor,
  283. "inactiveColor": props.titleInactiveColor,
  284. "onClick": (event) => onClickTab(item, index, event)
  285. }, (0, import_utils.pick)(item, ["dot", "badge", "title", "disabled", "showZeroBadge"])), {
  286. title: item.$slots.title
  287. }));
  288. const renderLine = () => {
  289. if (props.type === "line" && children.length) {
  290. return (0, import_vue.createVNode)("div", {
  291. "class": bem("line"),
  292. "style": state.lineStyle
  293. }, null);
  294. }
  295. };
  296. const renderHeader = () => {
  297. var _a2, _b2, _c;
  298. const {
  299. type,
  300. border,
  301. sticky
  302. } = props;
  303. const Header = [(0, import_vue.createVNode)("div", {
  304. "ref": sticky ? void 0 : wrapRef,
  305. "class": [bem("wrap"), {
  306. [import_utils.BORDER_TOP_BOTTOM]: type === "line" && border
  307. }]
  308. }, [(0, import_vue.createVNode)("div", {
  309. "ref": navRef,
  310. "role": "tablist",
  311. "class": bem("nav", [type, {
  312. shrink: props.shrink,
  313. complete: scrollable.value
  314. }]),
  315. "style": navStyle.value,
  316. "aria-orientation": "horizontal"
  317. }, [(_a2 = slots["nav-left"]) == null ? void 0 : _a2.call(slots), renderNav(), renderLine(), (_b2 = slots["nav-right"]) == null ? void 0 : _b2.call(slots)])]), (_c = slots["nav-bottom"]) == null ? void 0 : _c.call(slots)];
  318. if (sticky) {
  319. return (0, import_vue.createVNode)("div", {
  320. "ref": wrapRef
  321. }, [Header]);
  322. }
  323. return Header;
  324. };
  325. (0, import_vue2.watch)([() => props.color, import_utils.windowWidth], setLine);
  326. (0, import_vue2.watch)(() => props.active, (value) => {
  327. if (value !== currentName.value) {
  328. setCurrentIndexByName(value);
  329. }
  330. });
  331. (0, import_vue2.watch)(() => children.length, () => {
  332. if (state.inited) {
  333. setCurrentIndexByName(props.active);
  334. setLine();
  335. (0, import_vue2.nextTick)(() => {
  336. scrollIntoView(true);
  337. });
  338. }
  339. });
  340. const init = () => {
  341. setCurrentIndexByName(props.active, true);
  342. (0, import_vue2.nextTick)(() => {
  343. state.inited = true;
  344. if (wrapRef.value) {
  345. tabHeight = (0, import_use.useRect)(wrapRef.value).height;
  346. }
  347. scrollIntoView(true);
  348. });
  349. };
  350. const onRendered = (name2, title) => emit("rendered", name2, title);
  351. const resize = () => {
  352. setLine();
  353. (0, import_vue2.nextTick)(() => {
  354. var _a2, _b2;
  355. return (_b2 = (_a2 = contentRef.value) == null ? void 0 : _a2.swipeRef.value) == null ? void 0 : _b2.resize();
  356. });
  357. };
  358. (0, import_use_expose.useExpose)({
  359. resize,
  360. scrollTo
  361. });
  362. (0, import_vue2.onActivated)(setLine);
  363. (0, import_on_popup_reopen.onPopupReopen)(setLine);
  364. (0, import_use.onMountedOrActivated)(init);
  365. (0, import_use.useEventListener)("scroll", onScroll, {
  366. target: scroller,
  367. passive: true
  368. });
  369. linkChildren({
  370. id,
  371. props,
  372. setLine,
  373. onRendered,
  374. currentName,
  375. scrollIntoView
  376. });
  377. return () => (0, import_vue.createVNode)("div", {
  378. "ref": root,
  379. "class": bem([props.type])
  380. }, [props.sticky ? (0, import_vue.createVNode)(import_sticky.Sticky, {
  381. "container": root.value,
  382. "offsetTop": offsetTopPx.value,
  383. "onScroll": onStickyScroll
  384. }, {
  385. default: () => [renderHeader()]
  386. }) : renderHeader(), (0, import_vue.createVNode)(import_TabsContent.default, {
  387. "ref": contentRef,
  388. "count": children.length,
  389. "inited": state.inited,
  390. "animated": props.animated,
  391. "duration": props.duration,
  392. "swipeable": props.swipeable,
  393. "lazyRender": props.lazyRender,
  394. "currentIndex": state.currentIndex,
  395. "onChange": setCurrentIndex
  396. }, {
  397. default: () => {
  398. var _a2;
  399. return [(_a2 = slots.default) == null ? void 0 : _a2.call(slots)];
  400. }
  401. })]);
  402. }
  403. });