UNPKG

vue-devui

Version:

DevUI components based on Vite and Vue3

849 lines (848 loc) 26.4 kB
var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); import { toRefs, watch, onMounted, onUnmounted, ref, computed, defineComponent, createVNode, Transition, mergeProps, unref, nextTick, withModifiers, Comment, Text, h, Fragment, inject, withDirectives, cloneVNode, provide, Teleport, vShow, getCurrentInstance, onBeforeUnmount, resolveComponent } from "vue"; import { offset, autoPlacement, arrow, shift, computePosition } from "@floating-ui/dom"; const breadcrumbProps = { source: { type: Array, default: [] }, separatorIcon: { type: String } }; const breadcrumbItemProps = { showMenu: { type: Boolean, default: false }, menuList: { type: Array }, isSearch: { type: Boolean, dafault: false }, to: { type: [String, Object], default: "" }, replace: { type: Boolean, default: false } }; function createBem(namespace, element, modifier) { let cls = namespace; if (element) { cls += `__${element}`; } if (modifier) { cls += `--${modifier}`; } return cls; } function useNamespace(block, needDot = false) { const namespace = needDot ? `.devui-${block}` : `devui-${block}`; const b = () => createBem(namespace); const e = (element) => element ? createBem(namespace, element) : ""; const m = (modifier) => modifier ? createBem(namespace, "", modifier) : ""; const em = (element, modifier) => element && modifier ? createBem(namespace, element, modifier) : ""; return { b, e, m, em }; } var breadcrumbItem = ""; const dropdownProps = { visible: { type: Boolean, default: false }, trigger: { type: String, default: "click" }, closeScope: { type: String, default: "all" }, position: { type: Array, default: ["bottom"] }, align: { type: String, default: null }, offset: { type: [Number, Object], default: 4 }, shiftOffset: { type: Number }, closeOnMouseLeaveMenu: { type: Boolean, default: false }, showAnimation: { type: Boolean, default: true }, overlayClass: { type: String, default: "" }, destroyOnHide: { type: Boolean, default: true } }; const POPPER_TRIGGER_TOKEN = Symbol("popper-trigger"); function getElement(element) { if (element instanceof Element) { return element; } if (element && typeof element === "object" && element.$el instanceof Element) { return element.$el; } return null; } const dropdownMap = /* @__PURE__ */ new Map(); function subscribeEvent(dom, type, callback) { dom == null ? void 0 : dom.addEventListener(type, callback); return () => { dom == null ? void 0 : dom.removeEventListener(type, callback); }; } const useDropdownEvent = ({ id, isOpen, origin, dropdownRef, props, emit }) => { let overlayEnter = false; let originEnter = false; const { trigger, closeScope, closeOnMouseLeaveMenu } = toRefs(props); const toggle = (status) => { isOpen.value = status; emit("toggle", isOpen.value); }; const handleLeave = async (elementType, closeAll) => { await new Promise((resolve) => setTimeout(resolve, 50)); if (elementType === "origin" && overlayEnter || elementType === "dropdown" && originEnter) { return; } if (closeAll) { [...dropdownMap.values()].reverse().forEach((item) => { setTimeout(() => { var _a; (_a = item.toggle) == null ? void 0 : _a.call(item); }, 0); }); } toggle(false); }; watch([trigger, origin, dropdownRef], ([triggerVal, originVal, dropdownEl], ov, onInvalidate) => { const originEl = getElement(originVal); const subscriptions = []; setTimeout(() => { subscriptions.push(subscribeEvent(document, "click", (e) => { e.stopPropagation(); const dropdownValues = [...dropdownMap.values()]; if (!isOpen.value || closeScope.value === "none" || (dropdownEl == null ? void 0 : dropdownEl.contains(e.target)) && closeScope.value === "blank" || dropdownValues.some((item) => { var _a; return (_a = item.toggleEl) == null ? void 0 : _a.contains(e.target); }) && dropdownValues.some((item) => { var _a; return (_a = item.menuEl) == null ? void 0 : _a.contains(e.target); })) { return; } [...dropdownMap.values()].reverse().forEach((item) => { setTimeout(() => { var _a, _b; if (!((_a = item.toggleEl) == null ? void 0 : _a.contains(e.target))) { (_b = item.toggle) == null ? void 0 : _b.call(item); } }, 0); }); overlayEnter = false; })); }, 0); if (triggerVal === "click") { subscriptions.push(subscribeEvent(originEl, "click", () => toggle(!isOpen.value)), subscribeEvent(dropdownEl, "mouseleave", (e) => { var _a; if (closeOnMouseLeaveMenu.value && !((_a = dropdownMap.get(id).child) == null ? void 0 : _a.contains(e.relatedTarget))) { handleLeave("dropdown", true); } })); } else if (triggerVal === "hover") { subscriptions.push(subscribeEvent(originEl, "mouseenter", () => { originEnter = true; toggle(true); }), subscribeEvent(originEl, "mouseleave", () => { originEnter = false; handleLeave("origin"); }), subscribeEvent(dropdownEl, "mouseenter", () => { overlayEnter = true; isOpen.value = true; }), subscribeEvent(dropdownEl, "mouseleave", (e) => { var _a; overlayEnter = false; if (e.relatedTarget && ((originEl == null ? void 0 : originEl.contains(e.relatedTarget)) || ((_a = dropdownMap.get(id).child) == null ? void 0 : _a.contains(e.relatedTarget)))) { return; } handleLeave("dropdown", true); })); } onInvalidate(() => subscriptions.forEach((v) => v())); }); }; function useDropdown(id, visible, isOpen, origin, dropdownRef, popDirection, emit) { const calcPopDirection = (dropdownEl) => { const elementHeight = dropdownEl.offsetHeight; const bottomDistance = window.innerHeight - origin.value.getBoundingClientRect().bottom; const isBottomEnough = bottomDistance >= elementHeight; if (!isBottomEnough) { popDirection.value = "top"; } else { popDirection.value = "bottom"; } }; watch(visible, (newVal, oldVal) => { if (oldVal === void 0) { return; } isOpen.value = newVal; emit("toggle", isOpen.value); }, { immediate: true }); watch([isOpen, dropdownRef], ([isOpenVal, dropdownEl]) => { var _a; if (isOpenVal) { dropdownMap.set(id, __spreadProps(__spreadValues({}, dropdownMap.get(id)), { menuEl: dropdownEl, toggle: () => { isOpen.value = false; emit("toggle", isOpen.value); } })); for (const value of dropdownMap.values()) { if ((_a = value.menuEl) == null ? void 0 : _a.contains(origin.value)) { value.child = dropdownEl; } } } if (dropdownEl) { calcPopDirection(dropdownEl); } }); onMounted(() => { dropdownMap.set(id, { toggleEl: origin.value }); }); onUnmounted(() => { dropdownMap.delete(id); }); } function useOverlayProps(props, currentPosition, isOpen) { const { showAnimation, overlayClass, destroyOnHide } = toRefs(props); const overlayModelValue = ref(false); const overlayShowValue = ref(false); const styles = computed(() => ({ transformOrigin: currentPosition.value === "top" ? "0% 100%" : "0% 0%", zIndex: "var(--devui-z-index-dropdown, 1052)" })); const classes = computed(() => ({ "fade-in-bottom": showAnimation.value && isOpen.value && currentPosition.value === "bottom", "fade-in-top": showAnimation.value && isOpen.value && currentPosition.value === "top", [`${overlayClass.value}`]: true })); const handlePositionChange = (pos) => { currentPosition.value = pos.includes("top") || pos.includes("right-end") || pos.includes("left-end") ? "top" : "bottom"; }; watch(isOpen, (isOpenVal) => { overlayModelValue.value = destroyOnHide.value ? isOpenVal : true; overlayShowValue.value = isOpenVal; }); return { overlayModelValue, overlayShowValue, styles, classes, handlePositionChange }; } const fixedOverlayProps = { modelValue: { type: Boolean, default: false }, lockScroll: { type: Boolean, default: true }, closeOnClickOverlay: { type: Boolean, default: true } }; function lockScroll() { if (document.documentElement.scrollHeight > document.documentElement.clientHeight) { const scrollTop = document.documentElement.scrollTop; const style = document.documentElement.getAttribute("style"); document.documentElement.style.position = "fixed"; document.documentElement.style.top = `-${scrollTop}px`; document.documentElement.style.width = document.documentElement.style.width || "100%"; document.documentElement.style.overflowY = "scroll"; return () => { if (style) { document.documentElement.setAttribute("style", style); } else { document.documentElement.removeAttribute("style"); } document.documentElement.scrollTop = scrollTop; }; } return; } function useFixedOverlay(props, ctx) { let lockScrollCb; const onClick = (event) => { event.preventDefault(); ctx.emit("click", event); if (props.closeOnClickOverlay) { ctx.emit("update:modelValue", false); } }; const removeBodyAdditions = () => { lockScrollCb == null ? void 0 : lockScrollCb(); }; watch(() => props.modelValue, (val) => { if (val) { props.lockScroll && (lockScrollCb = lockScroll()); } else { removeBodyAdditions(); } }); onUnmounted(removeBodyAdditions); return { onClick }; } var fixedOverlay = ""; defineComponent({ name: "DFixedOverlay", inheritAttrs: false, props: fixedOverlayProps, emits: ["update:modelValue", "click"], setup(props, ctx) { const { modelValue } = toRefs(props); const ns2 = useNamespace("fixed-overlay"); const { onClick } = useFixedOverlay(props, ctx); return () => createVNode(Transition, { "name": ns2.m("fade") }, { default: () => { var _a, _b; return [modelValue.value && createVNode("div", mergeProps({ "class": ns2.b() }, ctx.attrs, { "onClick": onClick }), [(_b = (_a = ctx.slots).default) == null ? void 0 : _b.call(_a)])]; } }); } }); const flexibleOverlayProps = { modelValue: { type: Boolean, default: false }, origin: { type: Object, require: true }, position: { type: Array, default: ["bottom"] }, offset: { type: [Number, Object], default: 8 }, shiftOffset: { type: Number }, align: { type: String, default: null }, showArrow: { type: Boolean, default: false }, isArrowCenter: { type: Boolean, default: true }, clickEventBubble: { type: Boolean, default: false } }; function getScrollParent(element) { const overflowRegex = /(auto|scroll|hidden)/; for (let parent = element; parent = parent.parentElement; parent.parentElement !== document.body) { const style = window.getComputedStyle(parent); if (overflowRegex.test(style.overflow + style.overflowX + style.overflowY)) { return parent; } } return window; } function adjustArrowPosition(isArrowCenter, point, placement, originRect) { let { x, y } = point; if (!isArrowCenter) { const { width, height } = originRect; if (x && placement.includes("start")) { x = 12; } if (x && placement.includes("end")) { x = Math.round(width - 24); } if (y && placement.includes("start")) { y = 10; } if (y && placement.includes("end")) { y = height - 14; } } return { x, y }; } function useOverlay(props, emit) { const overlayRef = ref(); const arrowRef = ref(); let originParent = null; const updateArrowPosition = (arrowEl, placement, point, overlayEl) => { const { x, y } = adjustArrowPosition(props.isArrowCenter, point, placement, overlayEl.getBoundingClientRect()); const staticSide = { top: "bottom", right: "left", bottom: "top", left: "right" }[placement.split("-")[0]]; Object.assign(arrowEl.style, { left: x ? `${x}px` : "", top: y ? `${y}px` : "", right: "", bottom: "", [staticSide]: "-4px" }); }; const updatePosition = async () => { const hostEl = props.origin; const overlayEl = unref(overlayRef.value); const arrowEl = unref(arrowRef.value); const middleware = [ offset(props.offset), autoPlacement({ alignment: props.align, allowedPlacements: props.position }) ]; props.showArrow && middleware.push(arrow({ element: arrowEl })); props.shiftOffset !== void 0 && middleware.push(shift()); if (!overlayEl) { return; } const { x, y, placement, middlewareData } = await computePosition(hostEl, overlayEl, { strategy: "fixed", middleware }); let applyX = x; let applyY = y; if (props.shiftOffset !== void 0) { const { x: shiftX, y: shiftY } = middlewareData.shift; shiftX < 0 && (applyX -= props.shiftOffset); shiftX > 0 && (applyX += props.shiftOffset); shiftY < 0 && (applyY -= props.shiftOffset); shiftY > 0 && (applyY += props.shiftOffset); } emit("positionChange", placement); Object.assign(overlayEl.style, { top: `${applyY}px`, left: `${applyX}px` }); props.showArrow && updateArrowPosition(arrowEl, placement, middlewareData.arrow, overlayEl); }; watch(() => props.modelValue, () => { if (props.modelValue && props.origin) { originParent = getScrollParent(props.origin); nextTick(updatePosition); originParent == null ? void 0 : originParent.addEventListener("scroll", updatePosition); originParent !== window && window.addEventListener("scroll", updatePosition); window.addEventListener("resize", updatePosition); } else { originParent == null ? void 0 : originParent.removeEventListener("scroll", updatePosition); originParent !== window && window.removeEventListener("scroll", updatePosition); window.removeEventListener("resize", updatePosition); } }); onUnmounted(() => { originParent == null ? void 0 : originParent.removeEventListener("scroll", updatePosition); originParent !== window && window.removeEventListener("scroll", updatePosition); window.removeEventListener("resize", updatePosition); }); return { arrowRef, overlayRef, updatePosition }; } var flexibleOverlay = ""; const FlexibleOverlay = defineComponent({ name: "DFlexibleOverlay", inheritAttrs: false, props: flexibleOverlayProps, emits: ["update:modelValue", "positionChange"], setup(props, { slots, attrs, emit, expose }) { const ns2 = useNamespace("flexible-overlay"); const { clickEventBubble } = toRefs(props); const { arrowRef, overlayRef, updatePosition } = useOverlay(props, emit); expose({ updatePosition }); return () => { var _a; return props.modelValue && createVNode("div", mergeProps({ "ref": overlayRef, "class": ns2.b() }, attrs, { "onClick": withModifiers(() => ({}), [clickEventBubble.value ? "" : "stop"]), "onPointerup": withModifiers(() => ({}), ["stop"]) }), [(_a = slots.default) == null ? void 0 : _a.call(slots), props.showArrow && createVNode("div", { "ref": arrowRef, "class": ns2.e("arrow") }, null)]); }; } }); const isObject = (val) => val !== null && typeof val === "object"; const ns = useNamespace("popper-trigger"); function wrapContent(content) { return h("span", { class: ns.b() }, content); } function getFirstValidChild(nodes) { for (const child of nodes) { if (isObject(child)) { if (child.type === Comment) { continue; } if (child.type === "svg" || child.type === Text) { return wrapContent(child); } if (child.type === Fragment) { return getFirstValidChild(child.children); } return child; } return wrapContent(child); } return null; } var PopperTrigger = defineComponent({ name: "DPopperTrigger", setup(_, ctx) { const { slots, attrs } = ctx; return () => { var _a; const defaultSlot = (_a = slots.default) == null ? void 0 : _a.call(slots, attrs); const triggerRef = inject(POPPER_TRIGGER_TOKEN); if (!defaultSlot) { return null; } const firstValidChild = getFirstValidChild(defaultSlot); if (!firstValidChild) { return null; } return withDirectives(cloneVNode(firstValidChild, attrs), [[{ mounted(el) { triggerRef.value = el; }, updated(el) { triggerRef.value = el; }, unmounted() { triggerRef.value = null; } }]]); }; } }); var dropdown = ""; let dropdownId = 1; var DDropdown = defineComponent({ name: "DDropdown", inheritAttrs: false, props: dropdownProps, emits: ["toggle"], setup(props, { slots, attrs, emit, expose }) { const { visible, position, align, offset: offset2, destroyOnHide, shiftOffset, showAnimation } = toRefs(props); const origin = ref(); const dropdownRef = ref(); const overlayRef = ref(); const id = `dropdown_${dropdownId++}`; const isOpen = ref(false); const currentPosition = ref("bottom"); const ns2 = useNamespace("dropdown"); provide(POPPER_TRIGGER_TOKEN, origin); useDropdownEvent({ id, isOpen, origin, dropdownRef, props, emit }); useDropdown(id, visible, isOpen, origin, dropdownRef, currentPosition, emit); const { overlayModelValue, overlayShowValue, styles, classes, handlePositionChange } = useOverlayProps(props, currentPosition, isOpen); watch(overlayShowValue, (overlayShowValueVal) => { nextTick(() => { if (!destroyOnHide.value && overlayShowValueVal) { overlayRef.value.updatePosition(); } }); }); expose({ updatePosition: () => overlayRef.value.updatePosition() }); return () => createVNode(Fragment, null, [createVNode(PopperTrigger, null, { default: () => { var _a; return [(_a = slots.default) == null ? void 0 : _a.call(slots)]; } }), createVNode(Teleport, { "to": "body" }, { default: () => [createVNode(Transition, { "name": showAnimation.value ? ns2.m(`fade-${currentPosition.value}`) : "" }, { default: () => [withDirectives(createVNode(FlexibleOverlay, { "modelValue": overlayModelValue.value, "onUpdate:modelValue": ($event) => overlayModelValue.value = $event, "ref": overlayRef, "origin": origin.value, "position": position.value, "align": align.value, "offset": offset2.value, "shiftOffset": shiftOffset == null ? void 0 : shiftOffset.value, "onPositionChange": handlePositionChange, "click-event-bubble": true, "class": classes.value, "style": styles.value }, { default: () => { var _a; return [createVNode("div", mergeProps({ "ref": dropdownRef, "class": ns2.e("menu-wrap") }, attrs), [(_a = slots.menu) == null ? void 0 : _a.call(slots)])]; } }), [[vShow, overlayShowValue.value]])] })] })]); } }); var BreadcrumbItem = defineComponent({ name: "DBreadcrumbItem", components: { DDropdown }, props: breadcrumbItemProps, setup(props, { slots }) { var _a; const separatorIcon = inject("separatorIcon"); const ns2 = useNamespace("breadcrumb"); const linkClass = props.to ? "is-link" : ""; const dropdownTitleClass = props.showMenu && ((_a = props.menuList) == null ? void 0 : _a.length) ? ns2.e("dropdown-title") : ""; const link = ref(null); const instance = getCurrentInstance(); const router = instance == null ? void 0 : instance.appContext.config.globalProperties.$router; const showMenu = ref(props.showMenu); const menuList = ref(props.menuList || []); const handleClickLink = () => { if (!props.to || !router) { return; } props.replace ? router.replace(props.to) : router.push(props.to); }; onMounted(() => { var _a2; (_a2 = link.value) == null ? void 0 : _a2.addEventListener("click", handleClickLink); }); onBeforeUnmount(() => { var _a2; (_a2 = link.value) == null ? void 0 : _a2.removeEventListener("click", handleClickLink); }); const renderBreadcrumbSperator = () => { return createVNode("span", { "class": ns2.e("separator") }, [separatorIcon]); }; const renderBreadcrumbNode = () => { var _a2; if (showMenu.value) { return createVNode("div", { "class": ns2.e("item") }, [createVNode(resolveComponent("d-dropdown"), { "trigger": "hover", "close-scope": "blank" }, { default: () => { var _a3; return [createVNode("span", { "class": [linkClass, dropdownTitleClass] }, [(_a3 = slots == null ? void 0 : slots.default) == null ? void 0 : _a3.call(slots), createVNode("span", { "class": "icon icon-chevron-down" }, null)])]; }, menu: () => createVNode("ul", { "class": ns2.e("item-dropdown") }, [menuList.value.map((item) => { return item.link ? createVNode("a", { "href": item.link, "target": item.target ? item.target : "_self" }, [createVNode("li", { "class": ns2.e("item-dropdown-item") }, [item.title])]) : createVNode("li", { "class": ns2.e("item-dropdown-item") }, [createVNode("span", { "class": linkClass }, [item.title])]); })]) })]); } return createVNode("div", { "class": ns2.e("item") }, [createVNode("span", { "ref": link, "class": linkClass }, [(_a2 = slots == null ? void 0 : slots.default) == null ? void 0 : _a2.call(slots)]), renderBreadcrumbSperator()]); }; return () => { return renderBreadcrumbNode(); }; } }); function getPropsSlot(slots, props, prop = "default") { var _a, _b; return (_b = props[prop]) != null ? _b : (_a = slots[prop]) == null ? void 0 : _a.call(slots); } var breadcrumb = ""; var Breadcrumb = defineComponent({ name: "DBreadcrumb", components: { DBreadcrumbItem: BreadcrumbItem }, props: breadcrumbProps, setup(props, { slots }) { var _a; const separatorIcon = (_a = getPropsSlot(slots, props, "separatorIcon")) != null ? _a : "/"; provide("separatorIcon", separatorIcon); const ns2 = useNamespace("breadcrumb"); const renderBreadcrumbItemRouted = (item) => { return createVNode(resolveComponent("d-breadcrumb-item"), { "to": `path: ${item.link}`, "replace": item.replace }, { default: () => [item.title] }); }; const renderBreadcrumbItemDropdown = (item) => { return createVNode(resolveComponent("d-breadcrumb-item"), { "menuList": item.children, "showMenu": item.showMenu, "to": `path: ${item.link}`, "replace": item.replace }, { default: () => [!item.noNavigation && (!item.linkType || item.linkType === "hrefLink") ? createVNode("a", { "href": item.link, "target": item.target ? item.target : "_self" }, [item.title]) : null, item.noNavigation ? createVNode("span", null, [item.title]) : null] }); }; const renderBreadItemList = (source) => { return source.map((item) => { if (!item.noNavigation && item.linkType === "routerLink") { return renderBreadcrumbItemRouted(item); } if (item.children && item.children.length > 0) { return renderBreadcrumbItemDropdown(item); } return createVNode(resolveComponent("d-breadcrumb-item"), null, { default: () => [!item.noNavigation && (!item.linkType || item.linkType === "hrefLink") ? createVNode("a", { "href": item.link, "target": item.target ? item.target : "_self" }, [item.title]) : null, item.noNavigation ? createVNode("span", null, [item.title]) : null] }); }); }; return () => { var _a2; return createVNode("div", { "class": ns2.b() }, [props.source && props.source.length ? renderBreadItemList(props.source) : (_a2 = slots == null ? void 0 : slots.default) == null ? void 0 : _a2.call(slots)]); }; } }); var index = { title: "Breadcrumb \u9762\u5305\u5C51", category: "\u5BFC\u822A", status: "100%", install(app) { app.component(Breadcrumb.name, Breadcrumb); app.component(BreadcrumbItem.name, BreadcrumbItem); } }; export { Breadcrumb, BreadcrumbItem, breadcrumbItemProps, breadcrumbProps, index as default };