UNPKG

vue-data-ui

Version:

A user-empowering data visualization Vue 3 components library for eloquent data storytelling

468 lines (467 loc) 17.8 kB
import { ref as i, computed as we, onMounted as ke, nextTick as be, onBeforeUnmount as Ce, watch as oe, createElementBlock as x, openBlock as y, Fragment as q, createCommentVNode as Y, normalizeStyle as b, normalizeClass as D, createElementVNode as C, withDirectives as ae, createVNode as M, toDisplayString as re, unref as se, vModelText as ue, withModifiers as ie, renderList as ce } from "vue"; import { l as $e, i as Se, X as Ae } from "./index-q-LPw2IT.js"; import R from "./BaseIcon-CCivwZUq.js"; import { C as Ee } from "./ColorPicker-CWed-s-E.js"; import { _ as Be } from "./_plugin-vue_export-helper-CHgC5LLL.js"; const Te = { class: /* @__PURE__ */ D({ "vue-ui-pen-and-paper-action": !0 }), style: { padding: "0 !important" } }, De = ["disabled"], Ne = ["data-mode", "xmlns", "viewBox"], Me = ["cx", "cy", "r", "fill"], Le = ["d", "stroke", "stroke-width"], Fe = ["x", "y", "fill", "font-size"], Pe = ["x", "dy"], ze = ["d", "stroke", "stroke-width"], Xe = { __name: "NonSvgPenAndPaper", props: { parent: { type: HTMLElement }, backgroundColor: { type: String, default: "#FFFFFF" }, color: { type: String, default: "#2D353C" }, active: { type: Boolean, default: !1 } }, emits: ["close"], setup(u, { emit: ve }) { const $ = u, de = ve, w = i([]), N = i([]), G = i("0 0 0 0"), S = i($.color), L = i(1), m = i("draw"), c = i(null), V = i(!1), p = i(null), F = i({ x: 0, y: 0 }), A = i([""]), P = i({ row: 0, col: 0 }), E = i(16); let pe = 1; function I() { return pe++; } function fe(l) { const e = k.value; if (!e) return { x: 0, y: 0 }; const t = e.createSVGPoint(); let n = l.clientX, o = l.clientY; l.touches && l.touches.length && (n = l.touches[0].clientX, o = l.touches[0].clientY), t.x = n, t.y = o; const a = e.getScreenCTM()?.inverse(); return a ? t.matrixTransform(a) : { x: 0, y: 0 }; } function Q(l) { if (!c.value || m.value !== "text" || V.value) return; const { x: e, y: t } = fe(l); F.value = { x: e, y: t }, A.value = [""], P.value = { row: 0, col: 0 }; const n = document.createElementNS("http://www.w3.org/2000/svg", "text"); n.setAttribute("x", e), n.setAttribute("y", t), n.setAttribute("fill", S.value), n.setAttribute("font-size", E.value), n.setAttribute("font-family", "sans-serif"), n.setAttribute("class", "vue-data-ui-doodle"), n.setAttribute("dominant-baseline", "hanging"), n.setAttribute("pointer-events", "all"); const o = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); o.setAttribute("x", e), o.setAttribute("dy", "0"), o.textContent = "", n.appendChild(o), n.style.pointerEvents = "none", n.style.userSelect = "none", c.value.appendChild(n), p.value = n, V.value = !0, window.addEventListener("keydown", H), window.addEventListener("mousedown", J, !0), O(), j(); } function H(l) { if (!V.value) return; let { row: e, col: t } = P.value, n = A.value.slice(), o = !1; if (l.key === "Enter") { const a = n[e], r = a.slice(0, t), v = a.slice(t); n.splice(e, 1, r, v), e += 1, t = 0, o = !0, l.preventDefault(); } else if (l.key === "Backspace") { if (t > 0) n[e] = n[e].slice(0, t - 1) + n[e].slice(t), t -= 1, o = !0; else if (e > 0) { const a = n[e - 1].length; n[e - 1] += n[e], n.splice(e, 1), e -= 1, t = a, o = !0; } l.preventDefault(); } else if (l.key === "Delete") t < n[e].length ? (n[e] = n[e].slice(0, t) + n[e].slice(t + 1), o = !0) : e < n.length - 1 && (n[e] += n[e + 1], n.splice(e + 1, 1), o = !0), l.preventDefault(); else if (l.key === "ArrowLeft") t > 0 ? t -= 1 : e > 0 && (e -= 1, t = n[e].length), o = !0, l.preventDefault(); else if (l.key === "ArrowRight") t < n[e].length ? t += 1 : e < n.length - 1 && (e += 1, t = 0), o = !0, l.preventDefault(); else if (l.key === "ArrowUp") e > 0 && (e -= 1, t = Math.min(t, n[e].length), o = !0), l.preventDefault(); else if (l.key === "ArrowDown") e < n.length - 1 && (e += 1, t = Math.min(t, n[e].length), o = !0), l.preventDefault(); else if (l.key.length === 1 && !l.ctrlKey && !l.metaKey && !l.altKey) n[e] = n[e].slice(0, t) + l.key + n[e].slice(t), t += 1, o = !0, l.preventDefault(); else if (l.key === "Escape") { Z(!0); return; } else l.key === "Tab" && l.preventDefault(); o && (A.value = n, P.value = { row: e, col: t }, O(), j()); } function O() { const l = p.value, { x: e, y: t } = F.value; for (; l.firstChild; ) l.removeChild(l.firstChild); A.value.forEach((n, o) => { const a = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); a.setAttribute("x", e), a.setAttribute("dy", o === 0 ? "0" : `${E.value * 1.2}`), a.textContent = n.length ? n : "​", l.appendChild(a); }); } function j() { const l = c.value.querySelector(".vue-data-ui-svg-caret"); l && c.value.removeChild(l); const e = p.value; if (!e) return; const { x: t, y: n } = F.value, { row: o, col: a } = P.value, r = E.value, v = e.childNodes[o]; if (!v) return; let d = v.textContent.slice(0, a); d.endsWith(" ") && (d += " "); const s = document.createElementNS("http://www.w3.org/2000/svg", "text"); s.setAttribute("x", t), s.setAttribute("y", n), s.setAttribute("font-size", r), s.setAttribute("font-family", "sans-serif"), s.textContent = d || "", c.value.appendChild(s); const f = s.getBBox(); c.value.removeChild(s); let h = n + o * r * 1.2, T = t + f.width; const g = document.createElementNS("http://www.w3.org/2000/svg", "rect"); g.setAttribute("x", T), g.setAttribute("y", h), g.setAttribute("width", 1), g.setAttribute("height", r), g.setAttribute("fill", S.value), g.setAttribute("class", "vue-data-ui-svg-caret"), c.value.appendChild(g); } function J(l) { if (p.value && !p.value.contains(l.target)) { const e = p.value.children; e.length === 1 && (e[0].textContent === "" || e[0].textContent === "​") && p.value.remove(), Z(!1); } } function Z(l = !1) { window.removeEventListener("keydown", H), window.removeEventListener("mousedown", J, !0); const e = c.value?.querySelector(".vue-data-ui-svg-caret"); e && c.value.removeChild(e); const t = A.value.every( (n) => !n || n === "​" ); l || t || w.value.push({ id: I(), type: "text", x: F.value.x, y: F.value.y, color: S.value, fontSize: E.value, lines: A.value.map((n) => n) }), p.value && c.value && c.value.contains(p.value) && c.value.removeChild(p.value), V.value = !1, p.value = null, A.value = [""], P.value = { row: 0, col: 0 }; } const B = we(() => $e($.color, 0.6)); function W({ width: l, height: e }) { G.value = `0 0 ${l} ${e}`; } const U = i(null); ke(() => { be(() => { if ($.parent) { U.value = new ResizeObserver((t) => { for (const n of t) { const { width: o, height: a } = n.contentRect; W({ width: o, height: a }); } }), U.value.observe($.parent); const { width: l, height: e } = $.parent.getBoundingClientRect(); W({ width: l, height: e }); } }), c.value = k.value.querySelector("g"); }), Ce(() => { U.value && U.value.disconnect(); }), oe( () => $.parent, (l) => { if (!l) return; const { width: e, height: t } = $.parent.getBoundingClientRect(); W({ width: e, height: t }); }, { immediate: !0 } ), oe(m, (l) => { l === "text" ? k.value?.addEventListener("mousedown", Q) : k.value?.removeEventListener("mousedown", Q); }); const z = i(!1), X = i(""), k = i(null); function _(l) { if (m.value !== "draw" || !k.value) return; z.value = !0; const { x: e, y: t } = ne(l); X.value = `M ${e} ${t}`; } function ee(l) { if (!z.value || !k.value) return; const { x: e, y: t } = ne(l); X.value += ` ${e} ${t}`; } function te(l) { const e = l.trim().split(/\s+/); if (e.length < 4) return l; const t = e.slice(1).map(Number); if (t.length % 2 !== 0) return l; const n = he(t), o = [`M ${n[0]} ${n[1]}`]; for (let v = 2; v < n.length - 2; v += 2) { const d = n[v - 2], s = n[v - 1], f = n[v], h = n[v + 1], T = (d + f) / 2, g = (s + h) / 2; o.push(`Q ${d} ${s} ${T} ${g}`); } const a = n[n.length - 2], r = n[n.length - 1]; return o.push(`L ${a} ${r}`), o.join(" "); } function he(l, e = 1) { const t = [...l]; for (let n = 2; n < l.length - 2; n += 2) { const o = l[n], a = l[n + 1], r = l[n - 2], v = l[n - 1], d = l[n + 2], s = l[n + 3]; t[n] = o + e * ((r + d) / 2 - o), t[n + 1] = a + e * ((v + s) / 2 - a); } return t; } function ge(l) { const e = l.trim().split(/\s+/); let t = "", n = "", o = null, a = null; for (let r = 0; r < e.length; r += 1) { const v = e[r]; if (isNaN(v)) { if (n = v, n === "M" || n === "L") o = parseFloat(e[++r]), a = parseFloat(e[++r]), t += `${n}${o} ${a}`; else if (n === "Q") { const d = parseFloat(e[++r]), s = parseFloat(e[++r]), f = parseFloat(e[++r]), h = parseFloat(e[++r]); d === o && s === a ? t += `t${f - o} ${h - a}` : t += `q${d - o} ${s - a} ${f - o} ${h - a}`, o = f, a = h; } } else { const d = parseFloat(v), s = parseFloat(e[++r]); if (n === "L") { const f = d - o, h = s - a; f === 0 ? t += `v${h}` : h === 0 ? t += `h${f}` : t += `l${f} ${h}`, o = d, a = s; } else if (n === "Q") { const f = d, h = s, T = parseFloat(e[++r]), g = parseFloat(e[++r]); f === o && h === a ? t += `t${T - o} ${g - a}` : t += `q${f - o} ${h - a} ${T - o} ${g - a}`, o = T, a = g; } } } return t; } function K() { z.value && (w.value.push({ id: I(), strokeWidth: L.value, path: ge(te(X.value)), color: S.value }), N.value = [], X.value = ""), z.value = !1; } function ne(l) { if (!k.value) return { x: 0, y: 0 }; const e = k.value.getBoundingClientRect(); let t, n; return l.touches && l.touches.length ? (t = l.touches[0].clientX, n = l.touches[0].clientY) : (t = l.clientX, n = l.clientY), { x: t - e.left, y: n - e.top }; } function xe() { if (w.value.length > 0) { const l = w.value.pop(); N.value.push(l); } } function ye() { if (N.value.length > 0) { const l = N.value.pop(); w.value.push(l); } } function me() { w.value = [], N.value = []; } const le = i(null); return (l, e) => (y(), x(q, null, [ u.active ? (y(), x("div", { key: 0, "data-dom-to-png-ignore": "", class: D({ "vue-ui-pen-and-paper-actions": !0, visible: u.active }), style: b({ backgroundColor: u.backgroundColor }) }, [ C("button", { class: "vue-ui-pen-and-paper-action", style: b({ backgroundColor: u.backgroundColor, border: `1px solid ${B.value}` }), onClick: e[0] || (e[0] = (t) => de("close")) }, [ M(R, { name: "close", stroke: u.color }, null, 8, ["stroke"]) ], 4), C("button", Te, [ M(Ee, { value: S.value, "onUpdate:value": e[1] || (e[1] = (t) => S.value = t), backgroundColor: u.backgroundColor, buttonBorderColor: B.value }, null, 8, ["value", "backgroundColor", "buttonBorderColor"]) ]), C("button", { class: D(["vue-ui-pen-and-paper-action", { "vue-ui-pen-and-paper-action-active": m.value === "text" }]), onClick: e[2] || (e[2] = (t) => m.value = m.value === "text" ? "draw" : "text"), style: b({ backgroundColor: u.backgroundColor, border: `1px solid ${B.value}` }) }, [ M(R, { name: m.value === "draw" ? "annotator" : "text", stroke: u.color }, null, 8, ["name", "stroke"]), C("div", { style: b({ position: "absolute", bottom: "-20px", color: B.value, width: "100%", textAlign: "center", fontSize: "12px" }) }, re(se(Se)({ v: m.value === "draw" ? L.value : E.value, s: "px", r: 1 })), 5) ], 6), C("button", { class: D({ "vue-ui-pen-and-paper-action": !0, "vue-ui-pen-and-paper-action-disabled": !w.value.length }), disabled: !w.value.length, style: b({ backgroundColor: u.backgroundColor, border: `1px solid ${B.value}`, marginTop: "20px" }), onClick: xe }, [ M(R, { name: "restart", stroke: u.color }, null, 8, ["stroke"]) ], 14, De), C("button", { class: D({ "vue-ui-pen-and-paper-action": !0, "vue-ui-pen-and-paper-action-disabled": !N.value.length }), style: b({ backgroundColor: u.backgroundColor, border: `1px solid ${B.value}` }), onClick: ye }, [ M(R, { name: "restart", stroke: u.color, style: { transform: "scaleX(-1)" } }, null, 8, ["stroke"]) ], 6), C("button", { class: D([{ "vue-ui-pen-and-paper-action": !0, "vue-ui-pen-and-paper-action-disabled": !w.value.length }, "vue-ui-pen-and-paper-action"]), style: b({ backgroundColor: u.backgroundColor, border: `1px solid ${B.value}` }), onClick: me }, [ M(R, { name: "trash", stroke: u.color }, null, 8, ["stroke"]) ], 6), m.value === "draw" ? ae((y(), x("input", { key: 0, ref_key: "range", ref: le, type: "range", class: "vertical-range", min: 0.5, max: 12, step: 0.1, "onUpdate:modelValue": e[3] || (e[3] = (t) => L.value = t), style: b({ accentColor: u.color }) }, null, 4)), [ [ue, L.value] ]) : Y("", !0), m.value === "text" ? ae((y(), x("input", { key: 1, ref_key: "range", ref: le, type: "range", class: "vertical-range", min: 3, max: 48, step: 0.1, "onUpdate:modelValue": e[4] || (e[4] = (t) => E.value = t), style: b({ accentColor: u.color }) }, null, 4)), [ [ue, E.value] ]) : Y("", !0) ], 6)) : Y("", !0), (y(), x("svg", { "data-mode": m.value, ref_key: "svgElement", ref: k, xmlns: se(Ae), viewBox: G.value, class: D({ "vue-ui-pen-and-paper": !0, inactive: !u.active }), onMousedown: _, onMousemove: ee, onMouseup: K, onMouseleave: K, onTouchstart: ie(_, ["prevent"]), onTouchmove: ie(ee, ["prevent"]), onTouchend: K }, [ C("g", { ref_key: "G", ref: c }, [ (y(!0), x(q, null, ce(w.value, (t) => (y(), x(q, { key: t.id }, [ t.path && t.path.replace("M", "").split(" ").length === 2 ? (y(), x("circle", { key: 0, cx: t.path.replace("M", "").split(" ")[0], cy: t.path.replace("M", "").split(" ")[1], r: t.strokeWidth / 2, fill: t.color }, null, 8, Me)) : t.path ? (y(), x("path", { key: 1, class: "vue-ui-pen-and-paper-path", d: t.path, stroke: t.color, "stroke-width": t.strokeWidth, fill: "none" }, null, 8, Le)) : t.type === "text" ? (y(), x("text", { key: 2, x: t.x, y: t.y, fill: t.color, "font-size": t.fontSize, "font-family": "sans-serif", "dominant-baseline": "hanging", class: "vue-ui-pen-and-paper-text" }, [ (y(!0), x(q, null, ce(t.lines, (n, o) => (y(), x("tspan", { key: o, x: t.x, dy: o === 0 ? "0" : t.fontSize * 1.2 }, re(n.length ? n : "​"), 9, Pe))), 128)) ], 8, Fe)) : Y("", !0) ], 64))), 128)) ], 512), z.value ? (y(), x("path", { key: 0, class: "vue-ui-pen-and-paper-path vue-ui-pen-and-paper-path-drawing", d: te(X.value), stroke: S.value, "stroke-width": L.value * 1.1, fill: "none" }, null, 8, ze)) : Y("", !0) ], 42, Ne)) ], 64)); } }, We = /* @__PURE__ */ Be(Xe, [["__scopeId", "data-v-14478ba4"]]); export { We as default };