UNPKG

gw-popup

Version:

A configurable popup component for Vue 3

806 lines (805 loc) 25.9 kB
import { useEventListener as w } from "@vueuse/core"; import { defineComponent as S, inject as C, ref as P, openBlock as u, createBlock as $, Teleport as D, createElementBlock as k, normalizeClass as H, createElementVNode as y, normalizeStyle as x, Fragment as q, renderList as B, createCommentVNode as R, renderSlot as j, resolveComponent as A, withCtx as N, toDisplayString as z } from "vue"; import { defineStore as I, createPinia as U } from "pinia"; const _ = S({ name: "PopUp", setup(t) { const i = C("popupManager"); if (!i) throw new Error("PopupManager not provided"); const s = { top: 0, left: 0, width: 0, height: 0 }, a = {}, l = {}, h = { backgroundFill: "transparent", popupPosition: s, showRequested: !1, hideRequested: !1, isDisplayed: !1, isInit: !1, popupId: "", retries: 0, positioner: null, svgParams: null }, n = { current: { left: 0, top: 0 }, original: { left: 0, top: 0 } }, o = { arrowDistance: 2, arrowHeight: 7, arrowOffset: 12, arrowPosition: "center", arrowWidth: 9, class: "", cornerRadius: 3, debugMode: !1, direction: "e", eventOff: "mouseleave", eventOn: "mouseenter", isManual: !1, isModal: !1, isPositioned: !0, isTooltip: !0, logging: !1, paddingX: 6, paddingY: 2, popupOffset: 0, popupPosition: "center", positioner: "", shiftX: 0, shiftY: 0, zIndex: 100 }; h.popupId = t.id; const g = i.mergeConfig( t.mode, o, t.props ); return { data: P(h), shifts: P(n), styles: P({ content: a, element: l }), config: g, closeHandle: () => { }, popupManager: i }; }, props: { id: { type: String, default: null }, mode: { type: String, required: !0 }, props: { type: Object, default: () => { } }, refSource: { type: Object, default: null }, autoOpen: { type: Boolean, default: !1 } }, methods: { getFill(t) { var e, i; return ((e = this.data.svgParams) == null ? void 0 : e.paths[t].fill) === "backgroundColor" ? this.data.backgroundFill : (i = this.data.svgParams) == null ? void 0 : i.paths[t].fill; }, forceHide() { this.data.isDisplayed && !this.config.isModal && (this.styles.element = { top: "-3000px", left: "-3000px" }), this.data.hideRequested = !1, this.data.showRequested = !1, this.data.isDisplayed = !1, this.popupManager.setStatus(this.data.popupId, "isOpen", !1), this.closeHandle(); }, // eslint-disable-next-line @typescript-eslint/no-unused-vars refresh(t) { this.data.isDisplayed && this.data.positioner && this.show({ positioner: this.data.positioner }); }, log(t) { this.config.logging === !0 && console.log(t); }, show(t) { var i, s, a; this.log("show: " + this.data.popupId); let e = null; if (this.config.isPositioned) { const l = t.positioner; typeof l == "string" ? (e = (i = this.$parent) == null ? void 0 : i.$refs[l], typeof e < "u" ? this.data.positioner = e : e = (a = (s = this.$parent) == null ? void 0 : s.$parent) == null ? void 0 : a.$refs[l]) : e = l, e || this.log("Popup.vue could not find positioner for id: " + this.id); let h = e; this.data.showRequested && (this.data.hideRequested = !1), this.data.showRequested = !0; const n = setTimeout(() => { this.data.showRequested = !1, this.data.hideRequested && (this.hide({}), this.log("hide requested")), clearTimeout(n); }, 200); this.data.isDisplayed = !0, this.popupManager.closeOpenTooltips(this.data.popupId); let o; const g = this.$refs.popupContent; let m = g.children; if (typeof m > "u") return; m = g.children; try { o = { positioner: h.getBoundingClientRect(), content: m[0].getBoundingClientRect(), window: this.popupManager.percentHeightWidth(100) }; } catch (f) { this.log( `the content or positioner probably no longer rendered, so close: ` + f ), this.hide({}); return; } if (o.positioner.top > 2e4) { const f = setTimeout(() => { this.data.retries++, this.data.retries > 1 ? this.log("we are hosed on popup retries") : this.show({ positioner: this.data.positioner }), clearTimeout(f); }, 100); } let c = { left: 0, top: 0 }; this.data.retries = 0; const v = g.children[0]; this.data.backgroundFill = window.getComputedStyle(v).backgroundColor; let p = this.config.direction; t.direction !== null && typeof t.direction < "u" && (p = t.direction), this.data.isInit ? c = { left: this.shifts.current.left, top: this.shifts.current.top } : (this.shifts.original.left = 0, this.shifts.original.top = 0, this.shifts.current.left = this.config.shiftX, this.shifts.current.top = this.config.shiftY, this.data.isInit = !0); let b = !1, r = {}, d = { top: 0, left: 0 }, O = 0; do { switch (b = !1, this.log("Direction: " + this.config.direction), p) { case "n": r = { left: this.getPos(o, p), top: o.positioner.top - this.config.arrowDistance - (o.content.height + 2 * this.config.paddingY + this.config.arrowHeight), width: o.content.width + 2 * this.config.paddingX, height: o.content.height + 2 * this.config.paddingY + this.config.arrowHeight }, d.left = this.config.paddingX, d.top = this.config.paddingY; break; case "e": r = { left: o.positioner.right + this.config.arrowDistance, top: this.getPos(o, p), width: o.content.width + this.config.arrowWidth + 2 * this.config.paddingX, height: o.content.height + 2 * this.config.paddingY }, d.left = this.config.arrowHeight + this.config.paddingX, d.top = this.config.paddingY; break; case "s": r = { left: this.getPos(o, p), top: o.positioner.top + o.positioner.height, width: o.content.width + 2 * this.config.paddingX, height: o.content.height + 2 * this.config.paddingY + this.config.arrowHeight }, d.left = this.config.paddingX, d.top = this.config.paddingY + this.config.arrowHeight; break; case "w": r = { left: o.positioner.left - o.content.width - 2 * this.config.paddingX - this.config.arrowDistance - this.config.arrowHeight, top: this.getPos(o, p), width: o.content.width + 2 * this.config.paddingX + this.config.arrowHeight, height: o.content.height + 2 * this.config.paddingY }, d.left = this.config.paddingX, d.top = this.config.paddingY; break; } r.top + r.height > o.window.height && (c.top = c.top - (o.window.height - (r.top + r.height)) + this.popupManager.percentHeightWidth(10).height, b = !0); const f = p === "e" || p === "w"; r.top + r.height < o.positioner.top - (f ? o.positioner.height / 2 : this.popupManager.percentHeightWidth(10).height) && (c = { top: this.shifts.original.top, left: this.shifts.original.left }, b = !0), b && (this.shifts.current.left = c.left, this.shifts.current.top = c.top), O++; } while (b && O < 2); let W = { width: r.width + "px", height: r.height + "px", top: r.top + "px", left: r.left + "px", zIndex: this.config.zIndex }; this.data.popupPosition = r; let T = 0, M = "height"; switch (p) { case "n": case "s": M = "width"; break; } switch (this.config.arrowPosition) { case "min": T = this.config.arrowDistance; break; case "center": T = r[M] / 2; break; case "max": T = r[M] - this.config.arrowDistance; break; } this.data.svgParams = this.popupManager.buildArrowBox({ mode: this.mode, arrowHeight: this.config.arrowHeight, arrowWidth: this.config.arrowWidth, arrowPosition: T, height: r.height, width: r.width, cornerRadius: this.config.cornerRadius, direction: p, refX: o.positioner.x, refY: o.positioner.y, refWidth: o.positioner.width, refHeight: o.positioner.height }), this.styles.element = W, this.styles.content = { left: d.left + "px", top: d.top + "px" }, this.config.isTooltip && (this.closeHandle = w( window, "mouseMove", (f) => { (Math.abs(f.clientX - r.left) > 200 || Math.abs(f.clientY - r.top) > 200) && (this.forceHide(), this.log("force hidden")); } )); } else this.styles.element = {}, this.data.isDisplayed = !0, this.$nextTick(() => { const l = this.$refs.popupContent; let h = l.children; if (typeof h > "u") return; h = l.children; const n = h[0].getBoundingClientRect(); this.styles.content = { width: n.width + 2 * this.config.paddingX + "px", height: n.height + 2 * this.config.paddingY + "px", background: window.getComputedStyle(h[0]).backgroundColor, padding: this.config.paddingY + "px " + (this.config.paddingX + "px") }; }); }, getPos(t, e) { const i = ["n", "s"].indexOf(e) > -1; let s = i ? "width" : "height", a = this.config[i ? "paddingX" : "paddingY"]; const l = t.positioner, h = t.content; let n = l[i ? "x" : "y"]; switch (this.config.popupPosition) { case "max": n = n + l[s] + this.config.popupOffset - h[s] - a; break; case "center": n = n + (l[s] - h[s] - a * 2) / 2; break; case "min": n = n - a - this.config.popupOffset; break; } return n; }, hide(t) { if (this.log("hide: " + this.data.popupId), !this.config.isPositioned) { this.data.isDisplayed = !1; return; } if (!this.data.isDisplayed) return; if (this.data.showRequested) { this.data.hideRequested = !0; return; } const e = setTimeout(() => { if (this.data.showRequested) clearTimeout(e), this.data.hideRequested = !1; else { let i = this.data.popupPosition; i.top = this.config.debugMode ? 300 : -3e3, i.left = this.config.debugMode ? 300 : -3e3; let s = { width: i.width + "px", height: i.height + "px", top: i.top + "px", left: i.left + "px", zIndex: this.config.zIndex }; this.styles.element = s, this.data.isDisplayed = !1, this.popupManager.setStatus(this.data.popupId, "isOpen", !1), this.closeHandle(); } }, 2e3); this.data.hideRequested = !0; }, startUp(t) { var l, h; const e = { show: this.show, hide: this.hide, refresh: this.refresh }; let i = null, s = null; if (!this.config.isManual) { let n = null; if (typeof ((l = this.$parent) == null ? void 0 : l.$refs[this.config.positioner]) < "u" && (s = (h = this.$parent) == null ? void 0 : h.$refs[this.config.positioner], s && (i = s), i !== null && (n = i.getBoundingClientRect())), n === null) { t++, t < 11 ? setTimeout(() => { this.startUp(t); }, 20) : this.log("PopUp.vue could not find ref: " + this.config.positioner); return; } else this.log( "Popup found positioner: " + this.config.positioner + " on try " + t ); } const a = { isTooltip: this.config.isTooltip, isModal: this.config.isModal, isManual: this.config.isManual, positioner: this.config.positioner, eventObject: i, eventOn: this.config.eventOn, eventOff: this.config.eventOff }; this.data.popupId = this.popupManager.registerPopup( this.id, a, e ), this.popupManager.setStatus(this.data.popupId, "isOpen", !1), this.autoOpen && this.show({ positioner: this.config.positioner }); } }, mounted() { this.$nextTick(() => this.startUp(1)); }, beforeUnmount() { this.popupManager.destroyPopup(this.data.popupId), this.closeHandle(); } }), E = (t, e) => { const i = t.__vccOpts || t; for (const [s, a] of e) i[s] = a; return i; }, F = ["id"], V = ["width", "height", "viewBox"], L = ["fill", "d"]; function J(t, e, i, s, a, l) { return u(), $(D, { to: "body" }, [ t.config.isPositioned || t.data.isDisplayed ? (u(), k("div", { key: 0, class: H(["gw-popup", t.config.class]) }, [ y("div", { class: "popupContainer", id: t.data.popupId }, [ y("div", { class: "popupElements", style: x(t.styles.element) }, [ t.data.svgParams !== null ? (u(!0), k(q, { key: 0 }, B(t.data.svgParams.paths, (h, n) => (u(), k("svg", { key: h.class, "xml:space": "preserve", width: t.data.svgParams.width, height: t.data.svgParams.height, viewBox: t.data.svgParams.viewbox }, [ y("path", { class: H(["popupPath", h.class]), fill: t.getFill(n), d: h.path }, null, 10, L) ], 8, V))), 128)) : R("", !0), t.config.isModal && t.data.isDisplayed ? (u(), k("div", { key: 1, class: "modalBackground", onClick: e[0] || (e[0] = (h) => t.forceHide()) })) : R("", !0), y("div", { class: "popupContent", ref: "popupContent", style: x(t.styles.content) }, [ j(t.$slots, "default", { ref: "content" }) ], 4) ], 4) ], 8, F) ], 2)) : R("", !0) ]); } const G = /* @__PURE__ */ E(_, [["render", J]]), Q = S({ name: "ToolTip", components: { PopUp: G }, setup() { const t = { toolTipText: "", ready: !1, tips: [], tipIndex: {} }, e = [], i = C("popupManager"); return { data: P(t), callbacks: P(e), popupManager: i }; }, methods: { registerTooltips(t) { t.forEach((e) => { const i = this.data.tipIndex[e.refName] ? this.data.tipIndex[e.refName] : this.data.tips.length; this.data.tipIndex[e.refName] = i; const s = { refName: e.refName, ref: e.ref, text: e.text, direction: e.direction }; this.data.tips[i] = s; const a = w(s.ref, "mouseenter", () => { this.show(s.refName); }); this.callbacks[i] = a; const l = w(s.ref, "mouseleave", () => { this.hide(); }); this.callbacks[i] = l; }), this.data.ready = !0; }, show(t) { const e = this.data.tips[this.data.tipIndex[t]]; this.data.toolTipText = e.text, this.popupManager.showPopup(this.id, e.ref, e.direction); }, hide() { this.popupManager.hidePopup(this.id); } }, props: { id: { type: String, default: "ToolTip" }, cssClass: { type: String, default: "toolTip" } }, mounted() { this.popupManager.connectToolTip(this.id, this.registerTooltips); }, beforeUnmount() { this.popupManager.disconnectToolTip(this.id), this.callbacks.forEach((t) => { t(); }), this.callbacks = []; } }); function Z(t, e, i, s, a, l) { const h = A("PopUp"); return u(), $(h, { mode: "tooltip", props: { isManual: !0 }, id: t.id }, { default: N(() => [ y("div", { class: H(t.cssClass) }, z(t.data.toolTipText), 3) ]), _: 1 }, 8, ["id"]); } const nt = /* @__PURE__ */ E(Q, [["render", Z]]), X = { log: !1, debugMode: !1, modes: { dialog: { props: { isPositioned: !1, class: "centered", paddingX: 10, paddingY: 10, arrowOffset: 6, logging: !1, isManual: !0, isModal: !0, direction: "e" } }, tooltip: { props: { isManual: !1, direction: "e" }, directionMap: { n: { yReverse: !0, switchXY: !1, svgElements: ["arrowBox"], startOffset: "H" }, e: { yReverse: !1, switchXY: !0, svgElements: ["arrowBox"], startOffset: "0" }, w: { yReverse: !0, switchXY: !0, svgElements: ["arrowBox"], startOffset: "W" }, s: { yReverse: !1, switchXY: !1, svgElements: ["arrowBox"], startOffset: "0" } } }, popup: { props: { isManual: !0, logging: !1, isModal: !0, direction: "e" } }, windowPopup: { props: { isPositioned: !1, class: "centered", isManual: !0, logging: !0, isModal: !0 } } }, svgElements: { arrowBox: { fill: "backgroundColor", pathTemplate: ` M {{T}},{{S}} l {{AW/2}},{{AH}} l {{W-T-R-AW/2}},0 q {{R}},0 {{R}},{{R}} l 0,{{H-R-R-AH}} q 0,{{R}} -{{R}},{{R}} l -{{W-R-R}},0 q -{{R}},0 -{{R}},-{{R}} l 0,-{{H-R-R-AH}} q 0,-{{R}} {{R}},-{{R}} l {{W-T-AW/2-R}},0 z ` } } }; let Y = { ...X }; function K(t) { Y = { ...X, ...t }; } function tt() { return Y; } const et = I("popupManager", { state: () => ({ popupIndex: 0, status: {}, callbacks: {}, callbackHandles: {}, settings: null, toolTips: {}, globalConfig: tt() // Use default config }), actions: { mergeConfig(t, e, i) { try { const s = this.globalConfig.modes[t].props; if (typeof s > "u") return console.error("Popup Service handled non-existent mode key: " + t), e; e = this.mergeTool(e, s), typeof i < "u" && (e = this.mergeTool(e, i)); } catch { console.error( "Unexpected error in PopupManager mergeConfig for: " + t ); } return e; }, mergeTool(t, e) { return Object.keys(e).forEach((s) => { const a = e[s]; typeof a < "u" && this.setPropValue(s, t, a); }), t; }, setPropValue(t, e, i) { e[t] = i; }, connectToolTip(t, e) { this.toolTips[t] = e; }, registerToolTips(t, e, i = 5) { this.toolTips[t] ? this.toolTips[t](e) : i > 0 ? setTimeout(() => this.registerToolTips(t, e, i - 1), 100) : console.error(`Tooltip handler for id '${t}' not found`); }, disconnectToolTip(t) { delete this.toolTips[t]; }, config(t) { this.settings = t, K(t); }, buildArrowBox(t) { const e = { width: t.width + "px", height: t.height + "px", viewbox: `0 0 ${t.width} ${t.height}`, paths: [] }, i = this.globalConfig.modes[t.mode].directionMap; if (!i) return null; const s = i[t.direction]; return s ? (s.svgElements.forEach((a) => { const l = this.globalConfig.svgElements[a], h = s.switchXY ? t.height : t.width, n = s.switchXY ? t.width : t.height, g = { H: t.height, W: t.width, "-H": -t.height, "-W": -t.width, 0: 0 }[s.startOffset] ?? 0, m = s.switchXY ? t.arrowWidth : t.arrowHeight, c = s.switchXY ? t.arrowHeight : t.arrowWidth, v = { T: t.arrowPosition, S: g, W: h, H: n, AW: c, AH: m, R: t.cornerRadius, RefX: t.refX, RefY: t.refY, RefW: t.refWidth, RefH: t.refHeight }, p = this.processSvgTemplate( l.pathTemplate, v, s ); e.paths.push({ class: a, path: p, fill: l.fill }); }), e) : (console.error( "Popup.vue expected to find a direction map item for:" + t.direction ), null); }, processSvgTemplate(t, e, i) { const a = t.replace( /\{\{(.*?)\}\}/g, (l, h) => { try { return String( Function( ...Object.keys(e), `return ${h}` )(...Object.values(e)) ); } catch (n) { return console.error(`Error processing template expression: ${h}`, n), "NaN"; } } ).trim().split(` `).map((l) => l.trim()); return i.yReverse && a.forEach((l, h) => { const n = l.split(/[ ,]/); if (n[0] !== "M") for (let o = 2; o < n.length; o += 2) n[o] = "," + String(-Number(n[o])); a[h] = n.join(" ").split(" ,").join(","); }), i.switchXY && a.forEach((l, h) => { const n = l.split(/[ ,]/); for (let o = 1; o < n.length; o += 2) [n[o], n[o + 1]] = [n[o + 1], "," + n[o]]; a[h] = n.join(" ").split(" ,").join(","); }), a.join(" "); }, percentHeightWidth(t) { return { height: t * window.innerWidth / 100, width: t * window.innerHeight / 100 }; }, log(t, e) { var i; (i = this.settings) != null && i.log && console.log(t + ":" + JSON.stringify(e, null, " ")); }, logSimple(t) { var e; (e = this.settings) != null && e.log && console.log(t); }, attachEvents(t, e) { const i = this.callbackHandles[t].show; i !== null && i(), this.logSimple("attachEvent: show"); const s = w( e.eventObject, e.eventOn, () => { this.callbacks[t].show({ positioner: e.positioner }); const a = this.callbackHandles[t].hide; a !== null && a(), this.logSimple("attachEvent: hide"); const l = w( e.eventObject, e.eventOff, () => { this.callbacks[t].hide({ positioner: "" }); } ); this.callbackHandles[t].hide = l; } ); this.callbackHandles[t].show = s; }, attachEvent(t, e, i, s) { const a = this.callbackHandles[t][s]; a !== null && i && a(), this.logSimple("attachEvent: " + s); const l = w(e, i, () => { this.callbacks[t][s]({}); }); this.callbackHandles[t][s] = l; }, registerPopup(t, e, i) { return t || (t = "popup_" + this.popupIndex++), this.callbacks[t] && (this.logSimple("duplicate popup register, removing prior callback"), this.destroyPopup(t), this.logSimple("Popup " + t + " was re-registered")), this.callbacks[t] = i, this.callbackHandles[t] = { show: null, hide: null, refresh: null }, e.eventObject && !e.isManual && e.positioner && this.attachEvents(t, e), this.attachEvent(t, window, "resize", "refresh"), this.status[t] = { isTooltip: e.isTooltip, isOpen: !1 }, this.logSimple("registered popup: " + t), t; }, destroyPopup(t) { this.logSimple("Destroyed pop up: " + t); try { this.callbackHandles[t].hide !== null && this.callbackHandles[t].hide(), this.callbackHandles[t].show !== null && this.callbackHandles[t].show(), this.callbackHandles[t].refresh !== null && this.callbackHandles[t].refresh(), delete this.callbacks[t], delete this.callbackHandles[t], delete this.status[t], this.popupIndex = 0; } catch { console.error("Error destroying popup " + t); } }, closeOpenTooltips(t) { Object.keys(this.status).forEach((e) => { e !== t && this.status[e].isOpen && this.status[e].isTooltip && (this.logSimple("Tooltip: " + e + " autoclosed"), this.callbacks[e].hide({}), this.logSimple("autoclosed: " + e), this.status[e].isOpen = !1); }); }, showPopup(t, e, i) { setTimeout(() => { this.callbacks[t] ? (this.callbacks[t].show({ positioner: e || "", direction: i }), this.status[t].isOpen = !0) : console.log("showPopup: Popup " + t + " was not found"); }, 10); }, hidePopup(t) { this.callbacks[t] ? (this.callbacks[t].hide({}), this.status[t].isOpen = !1) : this.logSimple("hidePopup: Popup " + t + " was not found"); }, refreshPopup(t) { const e = this.callbacks[t]; e && setTimeout(() => e.refresh({}), 100); }, setStatus(t, e, i) { this.status[t] || (this.status[t] = { isOpen: !1, isTooltip: !0 }, this.logSimple( "PopupManager.setStatus:" + t + "," + e + "," + i )), this.status[t][e] = i; }, reset() { this.callbacks = {}, this.callbackHandles = {}, this.status = {}, this.popupIndex = 0; } } }), at = { install(t) { const e = U(); t.use(e); const i = et(e); t.provide("popupManager", i); } }; export { G as PopUp, at as PopupPlugin, nt as ToolTip, et as usePopupManager };