UNPKG

double-ui-vue

Version:
405 lines (404 loc) 11.9 kB
import { defineComponent, computed, isRef, createVNode, Transition, withDirectives, Fragment, vShow, render, reactive, watch, onBeforeUnmount } from "vue"; var style = ""; const GetScrollbarWidth = () => { const oDiv = document.createElement("div"); oDiv.style.cssText = "position:absolute;top:-1000px;width:100px;height:100px;overflow:hidden;opacity:0;"; const noScroll = document.body.appendChild(oDiv).clientWidth; oDiv.style.overflowY = "scroll"; const scroll = oDiv.clientWidth; document.body.removeChild(oDiv); const bw = noScroll - scroll; return bw < 6 ? 6 : bw; }; const TipModal = defineComponent({ name: "TipModal", props: { tag: { type: Object, default: () => ({}) }, countTag: { type: Object, default: () => ({}) }, position: { type: Boolean, default: false }, tipShow: { type: Boolean, default: false }, dropShow: { type: Boolean, default: false }, content: { type: String, default: "" }, split: { type: Boolean, default: true }, changeTipShow: { type: Function, default: () => { } }, tipHandle: { type: Function, default: () => { } } }, setup(props) { const getScrollWidth = (text) => { const maxWidth = 452; const div = document.createElement("div"); div.innerText = text; div.style.position = "absolute"; div.style.left = "0"; div.style.bottom = "0"; div.style.zIndex = "-100"; div.style.display = "inline-block"; div.style.height = "0"; div.style.opacity = "0"; div.style.overflow = "hidden"; div.style.whiteSpace = "nowrap"; div.style.fontSize = "14px"; document.body.appendChild(div); const { scrollWidth } = div; document.body.removeChild(div); return scrollWidth > maxWidth ? scrollWidth - maxWidth : 0; }; const contentArr = computed(() => { const content = isRef(props.content) ? props.content.value : props.content; if (!content) return []; return content.split("\u3001").map((d, i) => ({ id: `content-${i}`, text: d, scroll: d.length > 32 ? getScrollWidth(d) : 0 })); }); const popoverTipHandle = (str) => { if (!props.split) return; props.tipHandle(str); }; const contentEnter = () => { if (props.split) return; props.changeTipShow(false); }; return () => { const { split, content, tipShow, position } = props; return createVNode(Transition, { "name": position ? "selectDownUpExtend" : "selectDownUpExtendTop" }, { default: () => [withDirectives(createVNode("div", { "class": ["d-popover-tip", split ? "d-popover-tip-split" : "d-popover-tip-words", position ? "d-popover-tip-bottom" : "d-popover-tip-top"], "onWheel": (e) => e.stopPropagation(), "onMouseenter": () => popoverTipHandle("enter"), "onMouseleave": () => popoverTipHandle("leave") }, [createVNode("section", { "class": "d-popover-tip-content", "onMouseenter": contentEnter }, [split ? createVNode(Fragment, null, [contentArr.value.map((item) => createVNode("article", { "class": ["d-popover-tip-content-item", item.scroll && "d-popover-tip-content-item-before"] }, [createVNode("span", { "key": item.id, "class": "d-popover-tip-item-text", "style": { transform: `translateX(${-item.scroll}px)` } }, [item.text])]))]) : createVNode("article", { "class": "d-popover-tip-content-words" }, [isRef(content) ? content.value : content])]), createVNode("section", { "class": "d-popover-tip-triangle" }, null)]), [[vShow, tipShow]])] }); }; } }); const CountWh = (vm) => { const { content, split } = vm.component.props; const contentArr = (isRef(content) ? content.value : content).split("\u3001"); const { body } = document; const element = document.createElement("div"); element.className = "d-popover-tip"; element.style.left = "0"; element.style.bottom = "0"; element.style.opacity = "0"; element.style.zIndex = "-100"; let html = '<section class="d-popover-tip-content">'; if (split) { contentArr.forEach((d) => { html += `<article class="d-popover-tip-content-item"><span class="d-popover-tip-item-text">${d}</span></article>`; }); } else { html += `<article class="d-popover-tip-content-words">${content}</article>`; } html += "</section>"; element.innerHTML = html; body.appendChild(element); const { width, height } = element.getBoundingClientRect(); body.removeChild(element); const scrollWidth = GetScrollbarWidth(); return { width, height, scrollWidth }; }; const createArticle = (str) => { const article = document.createElement("article"); article.className = "d-popover-tip-content-item"; article.style.display = "inline-block"; article.style.height = "0"; article.style.opacity = "0"; article.innerHTML = `<span class="d-popover-tip-item-text">${str}</span>`; document.body.appendChild(article); const w = article.clientWidth + 4; document.body.removeChild(article); return w; }; const countContentWidth = (vm) => { const { component: { props: { content } } } = vm; const contentArr = (isRef(content) ? content.value : content).split("\u3001"); const numArr = []; const len = contentArr.length; let num = 0; for (let i = 0; i < len; i++) { const iw = createArticle(contentArr[i]); num += iw; if (num > 468) { numArr.push(num - iw); if (i === len - 1) numArr.push(num); else num = iw; } else if (num === 468) { return 468; } } return numArr && numArr.length ? Math.max(...numArr) : num; }; const CalcPopoverTipPosition = (triggerDom, targetHeight) => { const { clientHeight } = document.body; const { pageYOffset } = window; const { top, height } = triggerDom.getBoundingClientRect(); let targetOffsetY; let P = true; const triggerPositionTop = top - 8; const triggerPositionBtm = clientHeight - (top + height + 8); if (triggerPositionTop >= targetHeight) { targetOffsetY = triggerPositionTop - targetHeight + pageYOffset; P = false; } else if (triggerPositionBtm >= targetHeight) { targetOffsetY = top + height + 8 + pageYOffset; } else { targetOffsetY = clientHeight - targetHeight + pageYOffset; } return { Y: targetOffsetY, P }; }; const resetPosition = (instance) => { const { vm, tag, countTag } = instance; const isr = isRef(vm.component.props.content); if (!(isr ? vm.component.props.content.value : vm.component.props.content)) { return; } const { innerWidth, pageXOffset } = window; const tagEle = isRef(tag) ? tag.value : tag; const countTagEle = isRef(countTag) ? countTag.value : countTag; const { left: tagLeft, width: tagWidth } = tagEle.getBoundingClientRect(); const { left: cLeft, width: cWidth } = countTagEle.getBoundingClientRect(); if (innerWidth <= cLeft + cWidth / 2 || innerWidth <= cWidth || cLeft < 0) return; const { width, height, scrollWidth } = CountWh(vm); const { Y, P } = CalcPopoverTipPosition(tagEle, height); if (vm.component.props.split) { const maxWidth = countContentWidth(vm) + 13; vm.el.style.maxWidth = `${maxWidth}px`; } vm.component.props.position = P; vm.el.style.top = `${Y}px`; const triangle = vm.el.lastChild; let popoverLeft = 0; let triangleLeft; if (width === innerWidth) { if (pageXOffset > 0) popoverLeft = 0; triangleLeft = cLeft + cWidth / 2 - 6; } else { if (tagLeft === 0) { popoverLeft = 0; } else { const discoverWidth = innerWidth - tagLeft; const centerPlace = cLeft + cWidth / 2; const popoverLeftWidth = width / 2; if (centerPlace < popoverLeftWidth) { popoverLeft = 0; } else if (width <= tagWidth) { if (width <= discoverWidth) popoverLeft = centerPlace - popoverLeftWidth; else popoverLeft = innerWidth - width; } else { if (centerPlace >= popoverLeftWidth) { if (innerWidth - centerPlace <= popoverLeftWidth) { popoverLeft = innerWidth - width - scrollWidth; } else popoverLeft = centerPlace - popoverLeftWidth; } else { popoverLeft = 0; } } } triangleLeft = cLeft - popoverLeft + cWidth / 2 - 6; } vm.el.style.left = `${popoverLeft + pageXOffset}px`; triangle.style.left = `${triangleLeft}px`; instance.setShow(true); }; let dom; const TipModalHandle = (options) => { const { tipShow, props, changeTipShow, tipHandle } = options; const { tag, countTag } = props; if (!dom) { dom = document.createElement("div"); document.body.appendChild(dom); } const vm = createVNode(TipModal, { tipShow, ...props, changeTipShow, tipHandle }); const instance = { vm, tag, countTag, setContent(content) { vm.component.props.content = content; }, setShow: changeTipShow, resetPosition() { resetPosition(instance); }, remove() { if (vm.component.isUnmounted) return; render(null, dom); } }; render(vm, dom); return instance; }; const PopoverTip = defineComponent({ name: "PopoverTip", props: { tag: { type: Object, default: () => ({}) }, countTag: { type: Object, default: () => ({}) }, content: { type: String, default: "" }, split: { type: Boolean, default: true }, dropShow: { type: Boolean, default: false } }, setup(props, { slots }) { let tipModal; let enterTimer; let leaveTimer; const state = reactive({ tipShow: false }); const changeTipShow = (status) => { state.tipShow = status; if (tipModal) tipModal.vm.component.props.tipShow = status; }; PopoverTip.changeTipShow = changeTipShow; const tipHandle = (str) => { if (str === "enter") { clearTimeout(leaveTimer); leaveTimer = null; } else { leaveTimer = window.setTimeout(() => { changeTipShow(false); }, 500); } }; const mouseEnter = () => { if (leaveTimer) { clearTimeout(leaveTimer); leaveTimer = null; } if (!props.content || props.dropShow || state.tipShow) return; enterTimer = window.setTimeout(() => { if (tipModal) { tipModal.resetPosition(); } else { tipModal = TipModalHandle({ tipShow: state.tipShow, props, changeTipShow, tipHandle }); if (tipModal) tipModal.resetPosition(); PopoverTip.tipModal = tipModal; } }, 360); }; const mouseLeave = () => { if (!props.content) return; if (enterTimer) { clearTimeout(enterTimer); enterTimer = null; } leaveTimer = window.setTimeout(() => { if (tipModal) tipModal.setShow(false); }, 500); }; watch(() => props.content, (n, o) => { if (n === o || !tipModal) return; tipModal.vm.component.props.content = n; }); onBeforeUnmount(() => { if (enterTimer) clearTimeout(enterTimer); if (leaveTimer) clearTimeout(leaveTimer); if (tipModal) tipModal.remove(); }); return () => createVNode("span", { "onMouseenter": mouseEnter, "onMouseleave": mouseLeave }, [slots.default()]); } }); export { PopoverTip as default };