0f4007a6b167b55d711d13e434d6544968dd5d137447664e2d96b58aebeefce4163f2ba6636eb92efb66035751a12bcdfabc40237651a2ffd9e2c6d613e451 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', { value: true });
  3. var vue = require('vue');
  4. var core = require('@vueuse/core');
  5. var index$1 = require('../../icon/index.js');
  6. var iconsVue = require('@element-plus/icons-vue');
  7. var useWheel = require('../../virtual-list/src/hooks/use-wheel.js');
  8. var lodashUnified = require('lodash-unified');
  9. var tabBar = require('./tab-bar2.js');
  10. var constants = require('./constants.js');
  11. var runtime = require('../../../utils/vue/props/runtime.js');
  12. var typescript = require('../../../utils/typescript.js');
  13. var error = require('../../../utils/error.js');
  14. var index = require('../../../hooks/use-namespace/index.js');
  15. var aria = require('../../../constants/aria.js');
  16. var strings = require('../../../utils/strings.js');
  17. const tabNavProps = runtime.buildProps({
  18. panes: {
  19. type: runtime.definePropType(Array),
  20. default: () => typescript.mutable([])
  21. },
  22. currentName: {
  23. type: [String, Number],
  24. default: ""
  25. },
  26. editable: Boolean,
  27. type: {
  28. type: String,
  29. values: ["card", "border-card", ""],
  30. default: ""
  31. },
  32. stretch: Boolean
  33. });
  34. const tabNavEmits = {
  35. tabClick: (tab, tabName, ev) => ev instanceof Event,
  36. tabRemove: (tab, ev) => ev instanceof Event
  37. };
  38. const COMPONENT_NAME = "ElTabNav";
  39. const TabNav = vue.defineComponent({
  40. name: COMPONENT_NAME,
  41. props: tabNavProps,
  42. emits: tabNavEmits,
  43. setup(props, {
  44. expose,
  45. emit
  46. }) {
  47. const rootTabs = vue.inject(constants.tabsRootContextKey);
  48. if (!rootTabs)
  49. error.throwError(COMPONENT_NAME, `<el-tabs><tab-nav /></el-tabs>`);
  50. const ns = index.useNamespace("tabs");
  51. const visibility = core.useDocumentVisibility();
  52. const focused = core.useWindowFocus();
  53. const navScroll$ = vue.ref();
  54. const nav$ = vue.ref();
  55. const el$ = vue.ref();
  56. const tabRefsMap = vue.ref({});
  57. const tabBarRef = vue.ref();
  58. const scrollable = vue.ref(false);
  59. const navOffset = vue.ref(0);
  60. const isFocus = vue.ref(false);
  61. const focusable = vue.ref(true);
  62. const tracker = vue.shallowRef();
  63. const isHorizontal = vue.computed(() => ["top", "bottom"].includes(rootTabs.props.tabPosition));
  64. const sizeName = vue.computed(() => isHorizontal.value ? "width" : "height");
  65. const navStyle = vue.computed(() => {
  66. const dir = sizeName.value === "width" ? "X" : "Y";
  67. return {
  68. transform: `translate${dir}(-${navOffset.value}px)`
  69. };
  70. });
  71. const {
  72. width: navContainerWidth,
  73. height: navContainerHeight
  74. } = core.useElementSize(navScroll$);
  75. const {
  76. width: navWidth,
  77. height: navHeight
  78. } = core.useElementSize(nav$, {
  79. width: 0,
  80. height: 0
  81. }, {
  82. box: "border-box"
  83. });
  84. const navContainerSize = vue.computed(() => isHorizontal.value ? navContainerWidth.value : navContainerHeight.value);
  85. const navSize = vue.computed(() => isHorizontal.value ? navWidth.value : navHeight.value);
  86. const {
  87. onWheel
  88. } = useWheel["default"]({
  89. atStartEdge: vue.computed(() => navOffset.value <= 0),
  90. atEndEdge: vue.computed(() => navSize.value - navOffset.value <= navContainerSize.value),
  91. layout: vue.computed(() => isHorizontal.value ? "horizontal" : "vertical")
  92. }, (offset) => {
  93. navOffset.value = lodashUnified.clamp(navOffset.value + offset, 0, navSize.value - navContainerSize.value);
  94. });
  95. const scrollPrev = () => {
  96. if (!navScroll$.value)
  97. return;
  98. const containerSize = navScroll$.value[`offset${strings.capitalize(sizeName.value)}`];
  99. const currentOffset = navOffset.value;
  100. if (!currentOffset)
  101. return;
  102. const newOffset = currentOffset > containerSize ? currentOffset - containerSize : 0;
  103. navOffset.value = newOffset;
  104. };
  105. const scrollNext = () => {
  106. if (!navScroll$.value || !nav$.value)
  107. return;
  108. const navSize2 = nav$.value[`offset${strings.capitalize(sizeName.value)}`];
  109. const containerSize = navScroll$.value[`offset${strings.capitalize(sizeName.value)}`];
  110. const currentOffset = navOffset.value;
  111. if (navSize2 - currentOffset <= containerSize)
  112. return;
  113. const newOffset = navSize2 - currentOffset > containerSize * 2 ? currentOffset + containerSize : navSize2 - containerSize;
  114. navOffset.value = newOffset;
  115. };
  116. const scrollToActiveTab = async () => {
  117. const nav = nav$.value;
  118. if (!scrollable.value || !el$.value || !navScroll$.value || !nav)
  119. return;
  120. await vue.nextTick();
  121. const activeTab = tabRefsMap.value[props.currentName];
  122. if (!activeTab)
  123. return;
  124. const navScroll = navScroll$.value;
  125. const activeTabBounding = activeTab.getBoundingClientRect();
  126. const navScrollBounding = navScroll.getBoundingClientRect();
  127. const maxOffset = isHorizontal.value ? nav.offsetWidth - navScrollBounding.width : nav.offsetHeight - navScrollBounding.height;
  128. const currentOffset = navOffset.value;
  129. let newOffset = currentOffset;
  130. if (isHorizontal.value) {
  131. if (activeTabBounding.left < navScrollBounding.left) {
  132. newOffset = currentOffset - (navScrollBounding.left - activeTabBounding.left);
  133. }
  134. if (activeTabBounding.right > navScrollBounding.right) {
  135. newOffset = currentOffset + activeTabBounding.right - navScrollBounding.right;
  136. }
  137. } else {
  138. if (activeTabBounding.top < navScrollBounding.top) {
  139. newOffset = currentOffset - (navScrollBounding.top - activeTabBounding.top);
  140. }
  141. if (activeTabBounding.bottom > navScrollBounding.bottom) {
  142. newOffset = currentOffset + (activeTabBounding.bottom - navScrollBounding.bottom);
  143. }
  144. }
  145. newOffset = Math.max(newOffset, 0);
  146. navOffset.value = Math.min(newOffset, maxOffset);
  147. };
  148. const update = () => {
  149. var _a;
  150. if (!nav$.value || !navScroll$.value)
  151. return;
  152. props.stretch && ((_a = tabBarRef.value) == null ? void 0 : _a.update());
  153. const navSize2 = nav$.value[`offset${strings.capitalize(sizeName.value)}`];
  154. const containerSize = navScroll$.value[`offset${strings.capitalize(sizeName.value)}`];
  155. const currentOffset = navOffset.value;
  156. if (containerSize < navSize2) {
  157. scrollable.value = scrollable.value || {};
  158. scrollable.value.prev = currentOffset;
  159. scrollable.value.next = currentOffset + containerSize < navSize2;
  160. if (navSize2 - currentOffset < containerSize) {
  161. navOffset.value = navSize2 - containerSize;
  162. }
  163. } else {
  164. scrollable.value = false;
  165. if (currentOffset > 0) {
  166. navOffset.value = 0;
  167. }
  168. }
  169. };
  170. const changeTab = (event) => {
  171. let step = 0;
  172. switch (event.code) {
  173. case aria.EVENT_CODE.left:
  174. case aria.EVENT_CODE.up:
  175. step = -1;
  176. break;
  177. case aria.EVENT_CODE.right:
  178. case aria.EVENT_CODE.down:
  179. step = 1;
  180. break;
  181. default:
  182. return;
  183. }
  184. const tabList = Array.from(event.currentTarget.querySelectorAll("[role=tab]:not(.is-disabled)"));
  185. const currentIndex = tabList.indexOf(event.target);
  186. let nextIndex = currentIndex + step;
  187. if (nextIndex < 0) {
  188. nextIndex = tabList.length - 1;
  189. } else if (nextIndex >= tabList.length) {
  190. nextIndex = 0;
  191. }
  192. tabList[nextIndex].focus({
  193. preventScroll: true
  194. });
  195. tabList[nextIndex].click();
  196. setFocus();
  197. };
  198. const setFocus = () => {
  199. if (focusable.value)
  200. isFocus.value = true;
  201. };
  202. const removeFocus = () => isFocus.value = false;
  203. const setRefs = (el, key) => {
  204. tabRefsMap.value[key] = el;
  205. };
  206. const focusActiveTab = async () => {
  207. await vue.nextTick();
  208. const activeTab = tabRefsMap.value[props.currentName];
  209. activeTab == null ? void 0 : activeTab.focus({
  210. preventScroll: true
  211. });
  212. };
  213. vue.watch(visibility, (visibility2) => {
  214. if (visibility2 === "hidden") {
  215. focusable.value = false;
  216. } else if (visibility2 === "visible") {
  217. setTimeout(() => focusable.value = true, 50);
  218. }
  219. });
  220. vue.watch(focused, (focused2) => {
  221. if (focused2) {
  222. setTimeout(() => focusable.value = true, 50);
  223. } else {
  224. focusable.value = false;
  225. }
  226. });
  227. core.useResizeObserver(el$, update);
  228. vue.onMounted(() => setTimeout(() => scrollToActiveTab(), 0));
  229. vue.onUpdated(() => update());
  230. expose({
  231. scrollToActiveTab,
  232. removeFocus,
  233. focusActiveTab,
  234. tabListRef: nav$,
  235. tabBarRef,
  236. scheduleRender: () => vue.triggerRef(tracker)
  237. });
  238. return () => {
  239. const scrollBtn = scrollable.value ? [vue.createVNode("span", {
  240. "class": [ns.e("nav-prev"), ns.is("disabled", !scrollable.value.prev)],
  241. "onClick": scrollPrev
  242. }, [vue.createVNode(index$1.ElIcon, null, {
  243. default: () => [vue.createVNode(iconsVue.ArrowLeft, null, null)]
  244. })]), vue.createVNode("span", {
  245. "class": [ns.e("nav-next"), ns.is("disabled", !scrollable.value.next)],
  246. "onClick": scrollNext
  247. }, [vue.createVNode(index$1.ElIcon, null, {
  248. default: () => [vue.createVNode(iconsVue.ArrowRight, null, null)]
  249. })])] : null;
  250. const tabs = props.panes.map((pane, index) => {
  251. var _a, _b, _c, _d;
  252. const uid = pane.uid;
  253. const disabled = pane.props.disabled;
  254. const tabName = (_b = (_a = pane.props.name) != null ? _a : pane.index) != null ? _b : `${index}`;
  255. const closable = !disabled && (pane.isClosable || pane.props.closable !== false && props.editable);
  256. pane.index = `${index}`;
  257. const btnClose = closable ? vue.createVNode(index$1.ElIcon, {
  258. "class": "is-icon-close",
  259. "onClick": (ev) => emit("tabRemove", pane, ev)
  260. }, {
  261. default: () => [vue.createVNode(iconsVue.Close, null, null)]
  262. }) : null;
  263. const tabLabelContent = ((_d = (_c = pane.slots).label) == null ? void 0 : _d.call(_c)) || pane.props.label;
  264. const tabindex = !disabled && pane.active ? 0 : -1;
  265. return vue.createVNode("div", {
  266. "ref": (el) => setRefs(el, tabName),
  267. "class": [ns.e("item"), ns.is(rootTabs.props.tabPosition), ns.is("active", pane.active), ns.is("disabled", disabled), ns.is("closable", closable), ns.is("focus", isFocus.value)],
  268. "id": `tab-${tabName}`,
  269. "key": `tab-${uid}`,
  270. "aria-controls": `pane-${tabName}`,
  271. "role": "tab",
  272. "aria-selected": pane.active,
  273. "tabindex": tabindex,
  274. "onFocus": () => setFocus(),
  275. "onBlur": () => removeFocus(),
  276. "onClick": (ev) => {
  277. removeFocus();
  278. emit("tabClick", pane, tabName, ev);
  279. },
  280. "onKeydown": (ev) => {
  281. if (closable && (ev.code === aria.EVENT_CODE.delete || ev.code === aria.EVENT_CODE.backspace)) {
  282. emit("tabRemove", pane, ev);
  283. }
  284. }
  285. }, [...[tabLabelContent, btnClose]]);
  286. });
  287. tracker.value;
  288. return vue.createVNode("div", {
  289. "ref": el$,
  290. "class": [ns.e("nav-wrap"), ns.is("scrollable", !!scrollable.value), ns.is(rootTabs.props.tabPosition)]
  291. }, [scrollBtn, vue.createVNode("div", {
  292. "class": ns.e("nav-scroll"),
  293. "ref": navScroll$
  294. }, [props.panes.length > 0 ? vue.createVNode("div", {
  295. "class": [ns.e("nav"), ns.is(rootTabs.props.tabPosition), ns.is("stretch", props.stretch && ["top", "bottom"].includes(rootTabs.props.tabPosition))],
  296. "ref": nav$,
  297. "style": navStyle.value,
  298. "role": "tablist",
  299. "onKeydown": changeTab,
  300. "onWheel": onWheel
  301. }, [...[!props.type ? vue.createVNode(tabBar["default"], {
  302. "ref": tabBarRef,
  303. "tabs": [...props.panes],
  304. "tabRefs": tabRefsMap.value
  305. }, null) : null, tabs]]) : null])]);
  306. };
  307. }
  308. });
  309. exports["default"] = TabNav;
  310. exports.tabNavEmits = tabNavEmits;
  311. exports.tabNavProps = tabNavProps;
  312. //# sourceMappingURL=tab-nav.js.map