UNPKG

vue-devui

Version:

DevUI components based on Vite and Vue3

970 lines (969 loc) 26.8 kB
import { defineComponent, toRefs, computed, createVNode, resolveDynamicComponent, mergeProps, watch, onUnmounted, Transition, ref, nextTick, unref, withModifiers, onMounted, watchEffect, onBeforeUnmount, Teleport, h, render } from "vue"; import { offset, flip, arrow, computePosition } from "@floating-ui/dom"; const modalProps = { modelValue: { type: Boolean, default: false }, title: { type: String, default: "" }, lockScroll: { type: Boolean, default: true }, draggable: { type: Boolean, default: true }, closeOnClickOverlay: { type: Boolean, default: true }, beforeClose: { type: Function }, escapable: { type: Boolean, default: true }, showClose: { type: Boolean, default: true }, showAnimation: { type: Boolean, default: true }, showOverlay: { type: Boolean, default: true }, appendToBody: { type: Boolean, default: true }, type: { type: String, default: "" }, keepLast: { type: Boolean, default: false } }; const DEFAULT_PREFIX = "icon"; const iconProps = { name: { type: String, default: "" }, size: { type: [Number, String], default: "inherit" }, color: { type: String, default: "inherit" }, component: { type: Object, default: null }, classPrefix: { type: String, default: DEFAULT_PREFIX }, operable: { type: Boolean, default: false }, disabled: { type: Boolean, default: false }, rotate: { type: [Number, String] } }; const svgIconProps = { name: { type: String, default: "", required: true }, color: { type: String, default: "inherit" }, size: { type: [Number, String], default: "inherit" } }; 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 icon = ""; var svgIcon = defineComponent({ name: "DSvgIcon", props: svgIconProps, setup(props) { const { name, color, size } = toRefs(props); const ns = useNamespace("svg-icon"); const iconName = computed(() => `#icon-${name.value}`); const iconSize = computed(() => { return typeof size.value === "number" ? `${size.value}px` : size.value; }); const styles = { width: iconSize.value, height: iconSize.value }; return () => { return createVNode("svg", { "class": ns.b(), "style": styles }, [createVNode("use", { "xlink:href": iconName.value, "fill": color.value }, null)]); }; } }); function isUrl(value) { return /^((http|https):)?\/\//.test(value); } function useIconDom(props, ctx) { const { component, name, size, color, classPrefix, rotate } = toRefs(props); const ns = useNamespace("icon"); const iconSize = computed(() => { return typeof size.value === "number" ? `${size.value}px` : size.value; }); const IconComponent = component.value ? resolveDynamicComponent(component.value) : resolveDynamicComponent(svgIcon); const imgIconDom = () => { return createVNode("img", mergeProps({ "src": name.value, "alt": name.value.split("/")[name.value.split("/").length - 1], "class": [(rotate == null ? void 0 : rotate.value) === "infinite" && ns.m("spin")], "style": { width: iconSize.value || "", transform: `rotate(${rotate == null ? void 0 : rotate.value}deg)`, verticalAlign: "middle" } }, ctx.attrs), null); }; const svgIconDom = () => { return createVNode(IconComponent, mergeProps({ "name": name.value, "color": color.value, "size": iconSize.value, "class": [(rotate == null ? void 0 : rotate.value) === "infinite" && ns.m("spin")], "style": { transform: `rotate(${rotate == null ? void 0 : rotate.value}deg)` } }, ctx.attrs), null); }; const fontIconDom = () => { const fontIconClass = /^icon-/.test(name.value) ? name.value : `${classPrefix.value}-${name.value}`; return createVNode("i", mergeProps({ "class": [classPrefix.value, fontIconClass, (rotate == null ? void 0 : rotate.value) === "infinite" && ns.m("spin")], "style": { fontSize: iconSize.value, color: color.value, transform: `rotate(${rotate == null ? void 0 : rotate.value}deg)` } }, ctx.attrs), null); }; const iconDom = () => { return component.value ? svgIconDom() : isUrl(name.value) ? imgIconDom() : fontIconDom(); }; return { iconDom }; } var Icon = defineComponent({ name: "DIcon", props: iconProps, emits: ["click"], setup(props, ctx) { const { disabled, operable } = toRefs(props); const { iconDom } = useIconDom(props, ctx); const ns = useNamespace("icon"); const wrapClassed = computed(() => ({ [ns.e("container")]: true, [ns.m("disabled")]: disabled.value, [ns.m("operable")]: operable.value, [ns.m("no-slots")]: !Object.keys(ctx.slots).length })); const onClick = (e) => { if (disabled.value) { return; } ctx.emit("click", e); }; return () => { var _a, _b, _c, _d; return createVNode("div", { "class": wrapClassed.value, "onClick": onClick }, [(_b = (_a = ctx.slots).prefix) == null ? void 0 : _b.call(_a), iconDom(), (_d = (_c = ctx.slots).suffix) == null ? void 0 : _d.call(_c)]); }; } }); var iconGroup = ""; defineComponent({ name: "DIconGroup", setup(_, ctx) { const ns = useNamespace("icon-group"); return () => { var _a, _b; return createVNode("div", { "class": ns.b() }, [(_b = (_a = ctx.slots).default) == null ? void 0 : _b.call(_a)]); }; } }); 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 = ""; const FixedOverlay = defineComponent({ name: "DFixedOverlay", inheritAttrs: false, props: fixedOverlayProps, emits: ["update:modelValue", "click"], setup(props, ctx) { const { modelValue } = toRefs(props); const ns = useNamespace("fixed-overlay"); const { onClick } = useFixedOverlay(props, ctx); return () => createVNode(Transition, { "name": ns.m("fade") }, { default: () => { var _a, _b; return [modelValue.value && createVNode("div", mergeProps({ "class": ns.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 }, fitOriginWidth: { type: Boolean, default: false } }; 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 { fitOriginWidth, position, showArrow } = toRefs(props); const overlayRef = ref(); const arrowRef = ref(); const overlayWidth = ref(0); let originObserver; const styles = computed(() => { if (fitOriginWidth.value) { return { width: overlayWidth.value + "px" }; } else { return {}; } }); 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 [mainPosition, ...fallbackPosition] = position.value; const middleware = [offset(props.offset)]; middleware.push(fallbackPosition.length ? flip({ fallbackPlacements: fallbackPosition }) : flip()); if (showArrow.value) { middleware.push(arrow({ element: arrowRef.value })); } const { x, y, placement, middlewareData } = await computePosition(hostEl, overlayEl, { strategy: "fixed", placement: mainPosition, middleware }); let applyX = x; let applyY = y; emit("positionChange", placement); Object.assign(overlayEl.style, { top: `${applyY}px`, left: `${applyX}px` }); props.showArrow && updateArrowPosition(arrowEl, placement, middlewareData.arrow, overlayEl); }; const scrollCallback = (e) => { var _a, _b; const scrollElement = e.target; if (scrollElement == null ? void 0 : scrollElement.contains((_b = (_a = props.origin) == null ? void 0 : _a.$el) != null ? _b : props.origin)) { updatePosition(); } }; const updateWidth = (originEl) => { overlayWidth.value = originEl.getBoundingClientRect().width; updatePosition(); }; const observeOrigin = () => { var _a, _b; if (fitOriginWidth.value && typeof window !== "undefined") { const originEl = (_b = (_a = props.origin) == null ? void 0 : _a.$el) != null ? _b : props.origin; if (originEl) { originObserver = new window.ResizeObserver(() => updateWidth(originEl)); originObserver.observe(originEl); } } }; const unobserveOrigin = () => { var _a, _b; const originEl = (_b = (_a = props.origin) == null ? void 0 : _a.$el) != null ? _b : props.origin; originEl && (originObserver == null ? void 0 : originObserver.unobserve(originEl)); }; watch( () => props.modelValue, () => { if (props.modelValue && props.origin) { nextTick(updatePosition); window.addEventListener("scroll", scrollCallback, true); window.addEventListener("resize", updatePosition); observeOrigin(); } else { window.removeEventListener("scroll", scrollCallback, true); window.removeEventListener("resize", updatePosition); unobserveOrigin(); } } ); onUnmounted(() => { window.removeEventListener("scroll", scrollCallback, true); window.removeEventListener("resize", updatePosition); unobserveOrigin(); }); return { arrowRef, overlayRef, styles, updatePosition }; } var flexibleOverlay = ""; defineComponent({ name: "DFlexibleOverlay", inheritAttrs: false, props: flexibleOverlayProps, emits: ["update:modelValue", "positionChange"], setup(props, { slots, attrs, emit, expose }) { const ns = useNamespace("flexible-overlay"); const { clickEventBubble } = toRefs(props); const { arrowRef, overlayRef, styles, updatePosition } = useOverlay(props, emit); expose({ updatePosition }); return () => { var _a; return props.modelValue && createVNode("div", mergeProps({ "ref": overlayRef, "class": ns.b(), "style": styles.value }, 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": ns.e("arrow") }, null)]); }; } }); const inBrowser = typeof window !== "undefined"; function useModal(props, emit) { function close() { emit("update:modelValue", false); emit("close"); } function execClose() { props.beforeClose ? props.beforeClose(close) : close(); } function onKeydown(event) { if (event.code === "Escape") { execClose(); } } onMounted(() => { if (props.escapable) { window.addEventListener("keydown", onKeydown); } }); onUnmounted(() => { if (props.escapable) { window.addEventListener("keydown", onKeydown); } }); return { execClose }; } function useModalRender(props) { let lockScrollCb; const removeBodyAdditions = () => { lockScrollCb == null ? void 0 : lockScrollCb(); }; watch( () => props.modelValue, (val) => { if (val) { props.lockScroll && (lockScrollCb = lockScroll()); } else { removeBodyAdditions(); } }, { immediate: true } ); onUnmounted(removeBodyAdditions); } function addUnit(value, defaultUnit = "px") { if (!value) { return ""; } if (typeof value === "string") { return value; } else if (typeof value === "number") { return `${value}${defaultUnit}`; } else { return ""; } } const useDraggable = (targetRef, dragRef, draggable) => { const modalPosition = ref("translate(-50%, -50%)"); let transform = { offsetX: 0, offsetY: 0 }; const onMousedown = (e) => { const downX = e.clientX; const downY = e.clientY; const { offsetX, offsetY } = transform; const targetRect = targetRef.value.getBoundingClientRect(); const targetLeft = targetRect.left; const targetTop = targetRect.top; const targetWidth = targetRect.width; const targetHeight = targetRect.height; const clientWidth = document.documentElement.clientWidth; const clientHeight = document.documentElement.clientHeight; const minLeft = -targetLeft + offsetX; const minTop = -targetTop + offsetY; const maxLeft = clientWidth - targetLeft - targetWidth + offsetX; const maxTop = clientHeight - targetTop - targetHeight + offsetY; const onMousemove = (ev) => { const moveX = Math.min(Math.max(offsetX + ev.clientX - downX, minLeft), maxLeft); const moveY = Math.min(Math.max(offsetY + ev.clientY - downY, minTop), maxTop); transform = { offsetX: moveX, offsetY: moveY }; modalPosition.value = `translate(calc(-50% + ${addUnit(moveX)}), calc(-50% + ${addUnit(moveY)}))`; }; const onMouseup = () => { document.removeEventListener("mousemove", onMousemove); document.removeEventListener("mouseup", onMouseup); }; document.addEventListener("mousemove", onMousemove); document.addEventListener("mouseup", onMouseup); }; const onDraggable = () => { if (dragRef.value && targetRef.value) { dragRef.value.addEventListener("mousedown", onMousedown); } }; const offDraggable = () => { if (dragRef.value && targetRef.value) { dragRef.value.removeEventListener("mousedown", onMousedown); } }; onMounted(() => { watchEffect(() => { if (draggable.value) { onDraggable(); } else { offDraggable(); } }); }); onBeforeUnmount(() => { offDraggable(); }); const clearPosition = () => { transform = { offsetX: 0, offsetY: 0 }; modalPosition.value = "translate(-50%, -50%)"; }; return { clearPosition, modalPosition }; }; var ModalHeader = defineComponent({ name: "DModalHeader", setup(props, { slots }) { const ns = useNamespace("modal"); return () => { var _a; return createVNode("div", { "class": ns.e("header") }, [(_a = slots.default) == null ? void 0 : _a.call(slots)]); }; } }); var ModalBody = defineComponent({ name: "DModalBody", setup(props, { slots }) { const ns = useNamespace("modal"); return () => { var _a; return createVNode("div", { "class": ns.e("body") }, [(_a = slots.default) == null ? void 0 : _a.call(slots)]); }; } }); function CloseIcon() { return createVNode("svg", { "width": "18px", "height": "18px", "viewBox": "0 0 16 16", "version": "1.1", "xmlns": "http://www.w3.org/2000/svg" }, [createVNode("g", { "id": "modal-close", "stroke": "none", "stroke-width": "1", "fill": "none", "fill-rule": "evenodd" }, [createVNode("g", { "transform": "translate(3.000000, 3.000000)", "fill": "#71757F", "fill-rule": "nonzero", "id": "modal-close-road" }, [createVNode("path", { "d": `M-0.353553391,-0.353553391 C-0.179987039,-0.527119742 0.0894373624,-0.546404893 0.284305503,-0.411408841 L0.353553391,-0.353553391 L10.3535534,9.64644661 C10.5488155,9.84170876 10.5488155,10.1582912 10.3535534,10.3535534 C10.179987,10.5271197 9.91056264,10.5464049 9.7156945,10.4114088 L9.64644661,10.3535534 L-0.353553391,0.353553391 C-0.548815536,0.158291245 -0.548815536,-0.158291245 -0.353553391,-0.353553391 Z` }, null), createVNode("path", { "d": `M9.64644661,-0.353553391 C9.84170876,-0.548815536 10.1582912,-0.548815536 10.3535534,-0.353553391 C10.5271197,-0.179987039 10.5464049,0.0894373624 10.4114088,0.284305503 L10.3535534,0.353553391 L0.353553391,10.3535534 C0.158291245,10.5488155 -0.158291245,10.5488155 -0.353553391,10.3535534 C-0.527119742,10.179987 -0.546404893,9.91056264 -0.411408841,9.7156945 L-0.353553391,9.64644661 L9.64644661,-0.353553391 Z` }, null)])])]); } var modal = ""; var Modal = defineComponent({ name: "DModal", inheritAttrs: false, props: modalProps, emits: ["update:modelValue", "close"], setup(props, { slots, attrs, emit }) { const ns = useNamespace("modal"); const { modelValue, title, showClose, showOverlay, appendToBody, closeOnClickOverlay, keepLast } = toRefs(props); const { execClose } = useModal(props, emit); useModalRender(props); const dialogRef = ref(); const headerRef = ref(); const draggable = computed(() => props.draggable); const { clearPosition, modalPosition } = useDraggable(dialogRef, headerRef, draggable); watch(modelValue, (val) => { if (val && !keepLast.value) { clearPosition(); nextTick(() => { const autofocus = document == null ? void 0 : document.querySelector("[autofocus]"); if (autofocus) { autofocus.focus(); } }); } }); const renderType = () => { const typeList = [{ type: "success", text: "\u6210\u529F", icon: "right-o", color: "var(--devui-success)" }, { type: "failed", text: "\u9519\u8BEF", icon: "error-o", color: "var(--devui-danger)" }, { type: "warning", text: "\u8B66\u544A", icon: "warning-o", color: "var(--devui-warning)" }, { type: "info", text: "\u4FE1\u606F", icon: "info-o", color: "var(--devui-info)" }]; const item = typeList.find((i) => i.type === props.type); return createVNode("div", { "style": { cursor: props.draggable ? "move" : "default" }, "ref": headerRef }, [createVNode(ModalHeader, null, { default: () => [createVNode("div", { "class": "type-content" }, [createVNode("div", { "class": "type-content-icon" }, [createVNode(Icon, { "name": item == null ? void 0 : item.icon, "color": item == null ? void 0 : item.color }, null)]), createVNode("div", { "class": "type-content-text" }, [item == null ? void 0 : item.text])])] })]); }; return () => createVNode(Teleport, { "to": "body", "disabled": !appendToBody.value }, { default: () => [showOverlay.value && createVNode(FixedOverlay, mergeProps({ "modelValue": modelValue.value }, { "onUpdate:modelValue": execClose }, { "class": ns.e("overlay"), "lock-scroll": false, "close-on-click-overlay": closeOnClickOverlay.value, "style": { zIndex: "calc(var(--devui-z-index-modal, 1050) - 1)" } }), null), createVNode(Transition, { "name": props.showAnimation ? ns.m("wipe") : "" }, { default: () => { var _a; return [modelValue.value && createVNode("div", mergeProps({ "ref": dialogRef, "class": ns.b() }, attrs, { "onClick": (e) => e.stopPropagation(), "style": { transform: modalPosition.value } }), [showClose.value && createVNode("div", { "onClick": execClose, "class": "btn-close" }, [createVNode(Icon, { "operable": true, "component": CloseIcon() }, null)]), props.type ? renderType() : createVNode("div", { "style": { cursor: props.draggable ? "move" : "default" }, "ref": headerRef }, [slots.header ? slots.header() : title.value && createVNode(ModalHeader, null, { default: () => [title.value] })]), createVNode(ModalBody, null, { default: () => { var _a2; return [(_a2 = slots.default) == null ? void 0 : _a2.call(slots)]; } }), (_a = slots.footer) == null ? void 0 : _a.call(slots)])]; } })] }); } }); var ModalFooter = defineComponent({ name: "DModalFooter", setup(props, { slots }) { const ns = useNamespace("modal"); return () => { var _a; return createVNode("div", { "class": ns.e("footer") }, [(_a = slots.default) == null ? void 0 : _a.call(slots)]); }; } }); class CommonModalService { constructor(anchorContainer) { this.anchorContainer = anchorContainer; } renderModal(anchor, props, children) { const vnode = h(this.component(), props, children); render(vnode, anchor); return vnode; } renderNull(anchor) { setTimeout(() => { render(null, anchor); }, 500); } } let vm; class ModalService extends CommonModalService { component() { return Modal; } open(props = {}) { const anchor = document.createElement("div"); this.anchorContainer.appendChild(anchor); const { header, content, footer, ...resProps } = props; const renderOrigin = (propsValue, onUpdateModelValue) => { return this.renderModal( anchor, { ...propsValue, modelValue: true, "onUpdate:modelValue": onUpdateModelValue }, { header, default: content, footer } ); }; const hide = () => { var _a, _b, _c; const innerNeedHideOrNot = (value) => { if (!value) { hide(); } }; renderOrigin(resProps, (value) => { if (!value) { this.renderModal(anchor, { ...resProps, modelValue: false }); this.renderNull(anchor); } else { renderOrigin(resProps, innerNeedHideOrNot); } }); (_c = (_b = (_a = vm == null ? void 0 : vm.component) == null ? void 0 : _a.exposed) == null ? void 0 : _b.handleVisibleChange) == null ? void 0 : _c.call(_b, false); }; const needHideOrNot = (value) => { if (!value) { hide(); } }; this.renderModal(anchor, { modelValue: false }); vm = renderOrigin(resProps, needHideOrNot); return { hide }; } } ModalService.token = "MODAL_SERVICE_TOKEN"; var index = { title: "Modal \u5F39\u7A97", category: "\u53CD\u9988", status: "100%", install(app) { app.component(Modal.name, Modal); app.component(ModalHeader.name, ModalHeader); app.component(ModalBody.name, ModalBody); app.component(ModalFooter.name, ModalFooter); if (!inBrowser) { return; } let anchorsContainer = document.getElementById("d-modal-anchors-container"); if (!anchorsContainer) { anchorsContainer = document.createElement("div"); anchorsContainer.setAttribute("id", "d-modal-anchors-container"); document.body.appendChild(anchorsContainer); } app.provide(ModalService.token, new ModalService(anchorsContainer)); } }; export { Modal, ModalBody, ModalFooter, ModalHeader, index as default, modalProps };