tabs.mjs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import { EVENT_CODE } from "../../../constants/aria.mjs";
  2. import { UPDATE_MODEL_EVENT } from "../../../constants/event.mjs";
  3. import { getEventCode } from "../../../utils/dom/event.mjs";
  4. import { isNumber, isString, isUndefined as isUndefined$1 } from "../../../utils/types.mjs";
  5. import { buildProps, definePropType } from "../../../utils/vue/props/runtime.mjs";
  6. import { useNamespace } from "../../../hooks/use-namespace/index.mjs";
  7. import { useOrderedChildren } from "../../../hooks/use-ordered-children/index.mjs";
  8. import { ElIcon } from "../../icon/index.mjs";
  9. import { tabsRootContextKey } from "./constants.mjs";
  10. import TabNav from "./tab-nav.mjs";
  11. import { omit } from "lodash-unified";
  12. import { Plus } from "@element-plus/icons-vue";
  13. import { computed, createVNode, defineComponent, getCurrentInstance, nextTick, provide, ref, renderSlot, watch } from "vue";
  14. //#region ../../packages/components/tabs/src/tabs.tsx
  15. const tabsProps = buildProps({
  16. type: {
  17. type: String,
  18. values: [
  19. "card",
  20. "border-card",
  21. ""
  22. ],
  23. default: ""
  24. },
  25. closable: Boolean,
  26. addable: Boolean,
  27. modelValue: { type: [String, Number] },
  28. defaultValue: { type: [String, Number] },
  29. editable: Boolean,
  30. tabPosition: {
  31. type: String,
  32. values: [
  33. "top",
  34. "right",
  35. "bottom",
  36. "left"
  37. ],
  38. default: "top"
  39. },
  40. beforeLeave: {
  41. type: definePropType(Function),
  42. default: () => true
  43. },
  44. stretch: Boolean,
  45. tabindex: {
  46. type: [String, Number],
  47. default: 0
  48. }
  49. });
  50. const isPaneName = (value) => isString(value) || isNumber(value);
  51. const tabsEmits = {
  52. [UPDATE_MODEL_EVENT]: (name) => isPaneName(name),
  53. tabClick: (pane, ev) => ev instanceof Event,
  54. tabChange: (name) => isPaneName(name),
  55. edit: (paneName, action) => ["remove", "add"].includes(action),
  56. tabRemove: (name) => isPaneName(name),
  57. tabAdd: () => true
  58. };
  59. const Tabs = /* @__PURE__ */ defineComponent({
  60. name: "ElTabs",
  61. props: tabsProps,
  62. emits: tabsEmits,
  63. setup(props, { emit, slots, expose }) {
  64. const ns = useNamespace("tabs");
  65. const isVertical = computed(() => ["left", "right"].includes(props.tabPosition));
  66. const { children: panes, addChild: registerPane, removeChild: unregisterPane, ChildrenSorter: PanesSorter } = useOrderedChildren(getCurrentInstance(), "ElTabPane");
  67. const nav$ = ref();
  68. const currentName = ref((isUndefined$1(props.modelValue) ? props.defaultValue : props.modelValue) ?? "0");
  69. const setCurrentName = async (value, trigger = false) => {
  70. if (currentName.value === value || isUndefined$1(value)) return;
  71. try {
  72. let canLeave;
  73. if (props.beforeLeave) {
  74. const result = props.beforeLeave(value, currentName.value);
  75. canLeave = result instanceof Promise ? await result : result;
  76. } else canLeave = true;
  77. if (canLeave !== false) {
  78. const isFocusInsidePane = panes.value.find((item) => item.paneName === currentName.value)?.isFocusInsidePane();
  79. currentName.value = value;
  80. if (trigger) {
  81. emit(UPDATE_MODEL_EVENT, value);
  82. emit("tabChange", value);
  83. }
  84. nav$.value?.removeFocus?.();
  85. if (isFocusInsidePane) nav$.value?.focusActiveTab();
  86. }
  87. } catch {}
  88. };
  89. const handleTabClick = (tab, tabName, event) => {
  90. if (tab.props.disabled) return;
  91. emit("tabClick", tab, event);
  92. setCurrentName(tabName, true);
  93. };
  94. const handleTabRemove = (pane, ev) => {
  95. if (pane.props.disabled || isUndefined$1(pane.props.name)) return;
  96. ev.stopPropagation();
  97. emit("edit", pane.props.name, "remove");
  98. emit("tabRemove", pane.props.name);
  99. };
  100. const handleTabAdd = () => {
  101. emit("edit", void 0, "add");
  102. emit("tabAdd");
  103. };
  104. const handleKeydown = (event) => {
  105. const code = getEventCode(event);
  106. if ([EVENT_CODE.enter, EVENT_CODE.numpadEnter].includes(code)) handleTabAdd();
  107. };
  108. const swapChildren = (vnode) => {
  109. const actualFirstChild = vnode.el.firstChild;
  110. const firstChild = ["bottom", "right"].includes(props.tabPosition) ? vnode.children[0].el : vnode.children[1].el;
  111. if (actualFirstChild !== firstChild) actualFirstChild.before(firstChild);
  112. };
  113. watch(() => props.modelValue, (modelValue) => setCurrentName(modelValue));
  114. watch(currentName, async () => {
  115. await nextTick();
  116. nav$.value?.scrollToActiveTab();
  117. });
  118. provide(tabsRootContextKey, {
  119. props,
  120. currentName,
  121. registerPane,
  122. unregisterPane,
  123. nav$
  124. });
  125. expose({
  126. currentName,
  127. get tabNavRef() {
  128. return omit(nav$.value, ["scheduleRender"]);
  129. }
  130. });
  131. return () => {
  132. const addSlot = slots["add-icon"];
  133. const newButton = props.editable || props.addable ? createVNode("div", {
  134. "class": [ns.e("new-tab"), isVertical.value && ns.e("new-tab-vertical")],
  135. "tabindex": props.tabindex,
  136. "onClick": handleTabAdd,
  137. "onKeydown": handleKeydown
  138. }, [addSlot ? renderSlot(slots, "add-icon") : createVNode(ElIcon, { "class": ns.is("icon-plus") }, { default: () => [createVNode(Plus, null, null)] })]) : null;
  139. const tabNav = () => createVNode(TabNav, {
  140. "ref": nav$,
  141. "currentName": currentName.value,
  142. "editable": props.editable,
  143. "type": props.type,
  144. "panes": panes.value,
  145. "stretch": props.stretch,
  146. "onTabClick": handleTabClick,
  147. "onTabRemove": handleTabRemove
  148. }, null);
  149. const header = createVNode("div", { "class": [
  150. ns.e("header"),
  151. isVertical.value && ns.e("header-vertical"),
  152. ns.is(props.tabPosition)
  153. ] }, [createVNode(PanesSorter, null, {
  154. default: tabNav,
  155. $stable: true
  156. }), newButton]);
  157. const panels = createVNode("div", { "class": ns.e("content") }, [renderSlot(slots, "default")]);
  158. return createVNode("div", {
  159. "class": [
  160. ns.b(),
  161. ns.m(props.tabPosition),
  162. {
  163. [ns.m("card")]: props.type === "card",
  164. [ns.m("border-card")]: props.type === "border-card"
  165. }
  166. ],
  167. "onVnodeMounted": swapChildren,
  168. "onVnodeUpdated": swapChildren
  169. }, [panels, header]);
  170. };
  171. }
  172. });
  173. //#endregion
  174. export { Tabs as default, tabsEmits, tabsProps };
  175. //# sourceMappingURL=tabs.mjs.map