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