UNPKG

@opensig/opendesign

Version:

669 lines (668 loc) 27 kB
import { defineComponent, computed, ref, getCurrentInstance, toValue, watch, onMounted, provide, createElementBlock, openBlock, normalizeStyle, normalizeClass, unref, createVNode, createElementVNode, withCtx, withDirectives, createBlock, createCommentVNode, resolveDynamicComponent, Fragment, createTextVNode, toDisplayString, renderSlot, renderList, vShow } from "vue"; import { useElementBounding, until, createReusableTemplate, useMutationObserver } from "@vueuse/core"; import { OOption } from "../option/index.mjs"; import { OPopup } from "../popup/index.mjs"; import { IconClose, IconChevronDown, IconAdd } from "../_utils/icons.mjs"; import { isUndefined } from "../_utils/is.mjs"; import { checkElementOverflowHorizontal } from "../_utils/dom.mjs"; import "../hooks/use-theme.mjs"; import { useScreen } from "../hooks/use-screen.mjs"; import { useResponseCssVar } from "../hooks/use-response-css-var.mjs"; import { useSortedTeleportChildren } from "../hooks/useSortedTeleportChildren.mjs"; import { vOnResize } from "../directives/on-resize.mjs"; import { debounceRAF } from "../_utils/helper.mjs"; import { defaultSize } from "../_utils/global.mjs"; import ClientOnly from "../_components/client-only.mjs"; import { mergeClass } from "../_utils/vue-utils.mjs"; import { getRoundClass } from "../_utils/style-class.mjs"; import { useI18n } from "../locale/index.mjs"; import { tabInjectKey } from "./provide.mjs"; import { tabProps } from "./types.mjs"; import _sfc_main$2 from "./OTabPane.vue.mjs"; import _sfc_main$1 from "../option/OOptionList.vue.mjs"; const _hoisted_1 = ["onClick"]; const _hoisted_2 = ["onClick"]; const _hoisted_3 = { key: 0, class: "o-tab-head-prefix" }; const _hoisted_4 = { key: 1, class: "o-tab-head-suffix" }; const _hoisted_5 = { class: "o-tab-body" }; const _sfc_main = /* @__PURE__ */ defineComponent({ __name: "OTab", props: tabProps, emits: ["update:modelValue", "change", "delete", "add"], setup(__props, { emit: __emit }) { const props = __props; const emits = __emit; const round = getRoundClass(props, "tab-btn"); const { lePadV } = useScreen(); const { t } = useI18n(); const moreLabel = computed(() => props.moreLabel || t("common.more")); const activeKey = ref(props.modelValue); const anchorStyle = ref({}); const navsContainerRef = ref(); const { width: navsContainerWidth } = useElementBounding(navsContainerRef); const navGapNumber = useResponseCssVar("--tab-nav-gap", navsContainerRef, { initialValue: "32px", transform(value) { return Number.parseInt(value); } }); const tabNavMeasurementRef = ref(); const { width: tabNavMeasurementWidth } = useElementBounding(tabNavMeasurementRef); const ellipsisRef = ref(); const { width: ellipsisWidth } = useElementBounding(ellipsisRef); const navEllipsisShadowWidthNumber = useResponseCssVar("--tab-nav-ellipsis-shadow-width", navsContainerRef, { initialValue: "8px", transform(value) { return Number.parseInt(value); } }); const navListStyle = computed(() => { if (props.variant === "button" || lePadV.value) { return; } if (props.maxShow && props.maxShow > 0) { return { width: `${navsContainerWidth.value}px` }; } if (ellipsisWidth.value) { return { width: `${navsContainerWidth.value - ellipsisWidth.value - navEllipsisShadowWidthNumber.value}px` }; } return void 0; }); const tabNavRef = ref(); const tabNavLeftOverflown = ref(false); const tabNavRightOverflown = ref(false); const checkOverflow = debounceRAF(() => { if (!lePadV.value || !tabNavRef.value) { return; } const result = checkElementOverflowHorizontal({ element: tabNavRef.value, threshold: 1 }); tabNavLeftOverflown.value = result.isOverflowLeft; tabNavRightOverflown.value = result.isOverflowRight; }); until(tabNavRef).toBeTruthy().then(checkOverflow); const instance = getCurrentInstance(); const { children: sortedChildren, childMap, addChild, OTeleportWrapper } = useSortedTeleportChildren(instance, _sfc_main$2); const uidSet = computed(() => sortedChildren.value.map((c) => c.uid)); const showUids = ref([]); const hiddenUids = ref([]); const paneKeyToUid = computed(() => { const map = /* @__PURE__ */ new Map(); sortedChildren.value.forEach((c) => { map.set(toValue(c.paneKey), c.uid); }); return map; }); watch(uidSet, () => { showUids.value = showUids.value.filter((uid) => uidSet.value.includes(uid)); hiddenUids.value = hiddenUids.value.filter((uid) => uidSet.value.includes(uid)); }); const getChildData = (uid) => childMap[uid]; const pushShowUid = (localShow, uid, widthCount) => { const item = getChildData(uid); const { navElWidth } = item; const gap = widthCount > 0 ? navGapNumber.value : 0; const isWidthWillExceed = widthCount + gap + navElWidth > navsContainerWidth.value; const isShowNumWillExceed = isUndefined(props.maxShow) ? false : localShow.length + 1 > props.maxShow; if (!isWidthWillExceed && !isShowNumWillExceed) { localShow.push(uid); return widthCount + gap + navElWidth; } return widthCount; }; const sortUidList = debounceRAF(() => { if (props.variant === "button" || lePadV.value) { showUids.value = uidSet.value; hiddenUids.value = []; return; } uidSet.value.forEach((uid) => { const item = getChildData(uid); if (item == null ? void 0 : item.navMeasureEl) { item.navElWidth = item.navMeasureEl.clientWidth; } }); let localShow = [...showUids.value]; let widthCount = 0; if (!localShow.length) { uidSet.value.forEach((uid) => { widthCount = pushShowUid(localShow, uid, widthCount); }); } widthCount = localShow.reduce((count, uid) => { const item = getChildData(uid); return count + item.navElWidth + navGapNumber.value; }, -navGapNumber.value); const isTotalWidthExceed = tabNavMeasurementWidth.value > navsContainerWidth.value; const isTotalNumExceed = isUndefined(props.maxShow) ? false : uidSet.value.length > props.maxShow; if (isTotalWidthExceed || isTotalNumExceed) { widthCount += isUndefined(props.maxShow) ? navEllipsisShadowWidthNumber.value : navGapNumber.value; widthCount += ellipsisWidth.value; } const activeUid = activeKey.value != null ? paneKeyToUid.value.get(activeKey.value) : void 0; let activeItemIndex = localShow.findIndex((uid) => uid === activeUid); if (activeItemIndex === -1 && activeUid != null) { const activeItemIndexInTotal = uidSet.value.findIndex((uid) => uid === activeUid); const targetIndex = activeItemIndexInTotal === 0 ? 0 : localShow.findIndex((uid, i) => { if (i === localShow.length - 1) { return true; } const curIndexInTotal = uidSet.value.findIndex((v) => v === uid); const nextIndexInTotal = uidSet.value.findIndex((v) => v === localShow[i + 1]); if (curIndexInTotal < activeItemIndexInTotal && nextIndexInTotal > activeItemIndexInTotal) { return true; } return false; }) + 1; const activeItem = getChildData(activeUid); localShow.splice(targetIndex, 0, activeUid); widthCount += activeItem.navElWidth; widthCount += navGapNumber.value; activeItemIndex = localShow.length - 1; } const getIsShowWidthExceed = () => widthCount > navsContainerWidth.value; const getIsShowNumExceed = () => isUndefined(props.maxShow) ? false : localShow.length > props.maxShow; while ((getIsShowWidthExceed() || getIsShowNumExceed()) && localShow.length > 1) { activeItemIndex = localShow.findIndex((uid) => uid === activeUid); const removedUid = activeItemIndex === localShow.length - 1 ? localShow.shift() : localShow.pop(); const shiftedItem = getChildData(removedUid); widthCount -= shiftedItem.navElWidth; widthCount -= navGapNumber.value; } let localHidden = uidSet.value.filter((uid) => !localShow.includes(uid)); localHidden.forEach((uid) => { widthCount = pushShowUid(localShow, uid, widthCount); }); localHidden = uidSet.value.filter((uid) => !localShow.includes(uid)); localShow.sort((a, b) => { return uidSet.value.findIndex((v) => v === a) - uidSet.value.findIndex((v) => v === b); }); localHidden.sort((a, b) => { return uidSet.value.findIndex((v) => v === a) - uidSet.value.findIndex((v) => v === b); }); showUids.value = localShow; hiddenUids.value = localHidden; }); onMounted(() => { watch( [ uidSet, activeKey, navsContainerWidth, tabNavMeasurementWidth, () => props.maxShow, () => props.variant, ellipsisWidth, navGapNumber, navEllipsisShadowWidthNumber ], () => { sortUidList(); }, { immediate: true, deep: true } ); }); const [DefineTabNavTemplate, ReuseTabNavTemplate] = createReusableTemplate(); const isEllipsisOptionShow = ref(false); const updateAnchor = async () => { if (isUndefined(activeKey.value)) { return; } const activeUid = paneKeyToUid.value.get(activeKey.value); if (activeUid == null) { return; } const activeItem = getChildData(activeUid); await until(() => (activeItem == null ? void 0 : activeItem.navEl) && activeItem.navMeasureEl).toBeTruthy(); const { clientWidth, offsetLeft } = activeItem.navEl; anchorStyle.value = { transform: `translate3d(${offsetLeft}px, 0px, 0px)`, width: `${clientWidth}px` }; }; const scrollActiveIntoView = async () => { var _a; if (!lePadV.value || !navsContainerRef.value) { return; } if (isUndefined(activeKey.value)) { return; } const activeUid = paneKeyToUid.value.get(activeKey.value); if (activeUid == null) { return; } const activeItem = getChildData(activeUid); await until(() => activeItem == null ? void 0 : activeItem.navEl).toBeTruthy(); (_a = activeItem.navEl) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" }); }; onMounted(() => { scrollActiveIntoView(); }); watch( [showUids, activeKey], () => { updateAnchor(); scrollActiveIntoView(); }, { immediate: true, deep: true } ); const setNavEl = (el, uid) => { if (el == null) { return; } const item = getChildData(uid); item.navEl = el; }; const setNavMeasureEl = async (el, uid) => { if (el == null) { return; } const item = getChildData(uid); item.navMeasureEl = el; if (el) { item.navElWidth = el.clientWidth; } }; const updateValue = async (uid) => { const child = childMap[uid]; if (child.props.disabled) { return; } const _value = toValue(child.paneKey); emits("update:modelValue", _value); if (activeKey.value !== _value) { emits("change", _value, activeKey.value); activeKey.value = _value; } isEllipsisOptionShow.value = false; }; watch( () => props.modelValue, (v) => { activeKey.value = v; if (v != null) { const uid = paneKeyToUid.value.get(v); if (uid != null) { updateValue(uid); } } } ); const onDeletePane = (e, uid) => { var _a; e.stopImmediatePropagation(); const child = childMap[uid]; const _value = toValue(child.paneKey); emits("delete", _value); const idx = uidSet.value.indexOf(uid); if (activeKey.value === _value) { const targetUid = uidSet.value[idx > 0 ? idx - 1 : idx + 1]; activeKey.value = toValue((_a = childMap[targetUid]) == null ? void 0 : _a.paneKey); emits("change", activeKey.value, _value); } }; const isAdding = ref(false); const onAddNav = (e) => { emits("add", e); if (!props.addInactive) { isAdding.value = true; } }; provide(tabInjectKey, { lazy: props.lazy, activeValue: activeKey, addChild: (child) => { addChild(child); if (activeKey.value === void 0 || isAdding.value) { updateValue(child.uid); } } }); const onHeadItemResize = debounceRAF((uid) => { const item = getChildData(uid); if (!(item == null ? void 0 : item.navMeasureEl)) { return; } item.navElWidth = item.navMeasureEl.clientWidth; }); const onHeadResize = debounceRAF(() => { checkOverflow(); updateAnchor(); scrollActiveIntoView(); }); useMutationObserver( tabNavRef, (mutations) => { if (mutations[0]) { onHeadResize(); } }, { childList: true } ); watch(lePadV, () => { onHeadResize(); }); return (_ctx, _cache) => { return openBlock(), createElementBlock( "div", { class: normalizeClass(["o-tab", [ `o-tab-${props.variant}`, { "o-tab-button-inverse": props.variant === "button" && props.buttonInverse }, `o-tab-${props.size || unref(defaultSize)}`, unref(round).class.value ]]), style: normalizeStyle(unref(round).style.value) }, [ createVNode(unref(DefineTabNavTemplate), null, { default: withCtx(({ uid, measurement }) => [ withDirectives((openBlock(), createElementBlock("div", { ref: (el) => measurement ? setNavMeasureEl(el, uid) : setNavEl(el, uid), class: normalizeClass([ "o-tab-nav", { "o-tab-nav-active": toValue(unref(childMap)[uid].paneKey) === activeKey.value, "o-tab-nav-disabled": unref(childMap)[uid].props.disabled, "o-tab-nav-closable": unref(childMap)[uid].props.closable } ]), onClick: () => updateValue(uid) }, [ unref(childMap)[uid].navRenderer ? (openBlock(), createBlock(resolveDynamicComponent(unref(childMap)[uid].navRenderer), { key: 0 })) : (openBlock(), createElementBlock( Fragment, { key: 1 }, [ createTextVNode( toDisplayString(unref(childMap)[uid].props.label || unref(childMap)[uid].props.value), 1 /* TEXT */ ) ], 64 /* STABLE_FRAGMENT */ )), unref(childMap)[uid].props.closable ? (openBlock(), createElementBlock("div", { key: 2, class: "o-tab-nav-close", onClick: (e) => onDeletePane(e, uid) }, [ createVNode(unref(IconClose)) ], 8, _hoisted_2)) : createCommentVNode("v-if", true) ], 10, _hoisted_1)), [ [unref(vOnResize), measurement ? () => unref(onHeadItemResize)(uid) : () => { }] ]) ]), _: 1 /* STABLE */ }), createElementVNode( "div", { class: normalizeClass([ "o-tab-head", unref(mergeClass)( { "with-act": !!_ctx.$slots.suffix || !!_ctx.$slots.prefix, "show-line": !!props.line && props.variant !== "button" }, props.headerClass ) ]) }, [ _ctx.$slots.prefix ? (openBlock(), createElementBlock("div", _hoisted_3, [ renderSlot(_ctx.$slots, "prefix") ])) : createCommentVNode("v-if", true), createElementVNode( "div", { class: normalizeClass({ "o-tab-navs": true, "o-tab-navs-overflown-left": tabNavLeftOverflown.value, "o-tab-navs-overflown-right": tabNavRightOverflown.value }) }, [ withDirectives((openBlock(), createElementBlock( "div", { ref_key: "navsContainerRef", ref: navsContainerRef, class: normalizeClass({ "o-tab-navs-container": true, overflown: hiddenUids.value.length, "o-tab-navs-container-mb-overflown": tabNavLeftOverflown.value || tabNavRightOverflown.value }), onScroll: _cache[0] || (_cache[0] = //@ts-ignore (...args) => unref(checkOverflow) && unref(checkOverflow)(...args)) }, [ createCommentVNode(" 渲染一个全宽但是零高度的节点来测量每个节点的宽度,以计算溢出情况 "), withDirectives((openBlock(), createElementBlock("div", { ref_key: "tabNavMeasurementRef", ref: tabNavMeasurementRef, class: "o-tab-nav-list width-measurement" }, [ (openBlock(true), createElementBlock( Fragment, null, renderList(uidSet.value, (uid) => { return openBlock(), createBlock(unref(ReuseTabNavTemplate), { key: unref(childMap)[uid].paneKey, uid, measurement: true }, null, 8, ["uid"]); }), 128 /* KEYED_FRAGMENT */ )) ])), [ [vShow, !unref(lePadV)], [unref(vOnResize), unref(onHeadResize)] ]), withDirectives((openBlock(), createElementBlock( "div", { ref_key: "tabNavRef", ref: tabNavRef, class: "o-tab-nav-list", style: normalizeStyle(navListStyle.value) }, [ (openBlock(true), createElementBlock( Fragment, null, renderList(showUids.value, (uid) => { return openBlock(), createBlock(unref(ReuseTabNavTemplate), { key: unref(childMap)[uid].paneKey, uid }, null, 8, ["uid"]); }), 128 /* KEYED_FRAGMENT */ )), props.variant !== "button" ? withDirectives((openBlock(), createElementBlock( "div", { key: 0, ref_key: "ellipsisRef", ref: ellipsisRef, class: normalizeClass([ "o-tab-nav", { "o-tab-nav-active": props.maxShow && isEllipsisOptionShow.value, "o-tab-nav-ellipsis": unref(isUndefined)(props.maxShow) } ]) }, [ props.maxShow ? (openBlock(), createElementBlock( Fragment, { key: 0 }, [ createTextVNode( toDisplayString(moreLabel.value) + " ", 1 /* TEXT */ ), createVNode(unref(IconChevronDown), { class: normalizeClass({ "o-tab-nav-more-arrow": true, active: isEllipsisOptionShow.value }) }, null, 8, ["class"]) ], 64 /* STABLE_FRAGMENT */ )) : (openBlock(), createElementBlock( Fragment, { key: 1 }, [ createTextVNode("...") ], 64 /* STABLE_FRAGMENT */ )) ], 2 /* CLASS */ )), [ [vShow, hiddenUids.value.length] ]) : createCommentVNode("v-if", true) ], 4 /* STYLE */ )), [ [unref(vOnResize), unref(onHeadResize)] ]), props.variant === "text" ? (openBlock(), createElementBlock( "div", { key: 0, class: "o-tab-nav-anchor", style: normalizeStyle(anchorStyle.value) }, [ renderSlot(_ctx.$slots, "anchor", {}, () => [ _cache[2] || (_cache[2] = createElementVNode( "div", { class: "o-tab-nav-anchor-line" }, null, -1 /* CACHED */ )) ]) ], 4 /* STYLE */ )) : createCommentVNode("v-if", true) ], 34 /* CLASS, NEED_HYDRATION */ )), [ [unref(vOnResize), unref(onHeadResize)] ]), props.addable ? (openBlock(), createElementBlock("div", { key: 0, class: "o-tab-nav-add", onClick: onAddNav }, [ createVNode(unref(IconAdd)) ])) : createCommentVNode("v-if", true) ], 2 /* CLASS */ ), _ctx.$slots.suffix ? (openBlock(), createElementBlock("div", _hoisted_4, [ renderSlot(_ctx.$slots, "suffix") ])) : createCommentVNode("v-if", true) ], 2 /* CLASS */ ), createElementVNode("div", _hoisted_5, [ createVNode(unref(OTeleportWrapper), null, { default: withCtx(() => [ renderSlot(_ctx.$slots, "default") ]), _: 3 /* FORWARDED */ }) ]), createVNode(unref(ClientOnly), null, { default: withCtx(() => [ createVNode(unref(OPopup), { visible: isEllipsisOptionShow.value, "onUpdate:visible": _cache[1] || (_cache[1] = ($event) => isEllipsisOptionShow.value = $event), "wrap-class": "o-options-popup o-tab-more-popup", "body-class": "o-popup-body", position: "bl", wrapper: "body", target: ellipsisRef.value, trigger: ["click-outclick", "hover"], offset: 4 }, { default: withCtx(() => [ createVNode(unref(_sfc_main$1), { "wrap-class": "o-scrollbar-container" }, { default: withCtx(() => [ (openBlock(true), createElementBlock( Fragment, null, renderList(hiddenUids.value, (uid) => { return openBlock(), createBlock(unref(OOption), { key: uid, value: toValue(unref(childMap)[uid].paneKey), onClick: ($event) => updateValue(uid) }, { default: withCtx(() => [ unref(childMap)[uid].navRenderer ? (openBlock(), createBlock(resolveDynamicComponent(unref(childMap)[uid].navRenderer), { key: 0 })) : (openBlock(), createElementBlock( Fragment, { key: 1 }, [ createTextVNode( toDisplayString(unref(childMap)[uid].props.label || unref(childMap)[uid].props.value), 1 /* TEXT */ ) ], 64 /* STABLE_FRAGMENT */ )) ]), _: 2 /* DYNAMIC */ }, 1032, ["value", "onClick"]); }), 128 /* KEYED_FRAGMENT */ )) ]), _: 1 /* STABLE */ }) ]), _: 1 /* STABLE */ }, 8, ["visible", "target"]) ]), _: 1 /* STABLE */ }) ], 6 /* CLASS, STYLE */ ); }; } }); export { _sfc_main as default };