UNPKG

vue-data-ui

Version:

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

403 lines (402 loc) 18.7 kB
import { ref as v, computed as xe, watch as J, nextTick as ie, onMounted as ye, onBeforeUnmount as Ce, createElementBlock as Z, createCommentVNode as _, openBlock as ee, normalizeStyle as x, createElementVNode as S, withDirectives as ve, renderSlot as L, createVNode as R, withCtx as ke, normalizeProps as M, guardReactiveProps as F, normalizeClass as W, toDisplayString as Se, unref as Ee, vModelText as ce } from "vue"; import Q from "./BaseIcon-4i3dd7Ty.js"; import { C as Be } from "./ColorPicker-B9oF4-O6.js"; import { q as $e, d as Le } from "./lib-BwysEpWI.js"; const Re = { class: "vue-ui-pen-and-paper-action", style: { padding: "0 !important" } }, Ne = ["disabled"], Fe = { __name: "PenAndPaper", props: { svgRef: { type: [Object, null, void 0], required: !0 }, color: { type: String, default: "#2D353C" }, backgroundColor: { type: String, default: "#FFFFFF" }, active: { type: Boolean, default: !1 }, scale: { type: Number, default: 1 } }, emits: ["close"], setup(d, { emit: de }) { const s = d, fe = de, b = v([]), y = v([]), E = v(s.color), N = v(2), I = v(!1), V = v(""), o = v(null), w = v(null), K = v(null), m = v("draw"), U = v(!1), f = v(null), O = v({ x: 0, y: 0 }), D = v([""]), T = v({ row: 0, col: 0 }), B = v(16), P = v(!1), te = v("url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAABg2lDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw0AcxV9TpSIVh2YQcchQnSyIijhKFYtgobQVWnUwufQLmjQkKS6OgmvBwY/FqoOLs64OroIg+AHi6OSk6CIl/i8ptIjx4Lgf7+497t4BQrPKNKtnAtB020wn4lIuvyqFXhGGiAhCiMnMMpKZxSx8x9c9Any9i/Es/3N/jgG1YDEgIBHPMcO0iTeIZzZtg/M+scjKskp8Tjxu0gWJH7muePzGueSywDNFM5ueJxaJpVIXK13MyqZGPE0cVTWd8oWcxyrnLc5atc7a9+QvDBf0lQzXaY4ggSUkkYIEBXVUUIWNGK06KRbStB/38Q+7/hS5FHJVwMixgBo0yK4f/A9+d2sVpya9pHAc6H1xnI9RILQLtBqO833sOK0TIPgMXOkdf60JzH6S3uho0SNgcBu4uO5oyh5wuQMMPRmyKbtSkKZQLALvZ/RNeSByC/Sveb2193H6AGSpq+Ub4OAQGCtR9rrPu/u6e/v3TLu/H5C7crM1WjgWAAAABmJLR0QAqwB5AHWF+8OUAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAB3RJTUUH5gwUExIUagzGcQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAABfSURBVBjTldAxDoNQDIPhL0+q1L33P1AvAhN7xfK6WAgoLfSfrNiykpQtE+7RLzx2vgF9D3o8lWDmn1QVVMP0LZQGmNtqp1/cmou0XHdG/+sYeGZwFBqPCub8rkcvvAGvsi1VYarR8wAAAABJRU5ErkJggg==') 5 5, auto"); function ne(n) { if (!o.value || m.value !== "text" || U.value) return; const { x: e, y: l } = H(n); O.value = { x: e, y: l }, D.value = [""], T.value = { row: 0, col: 0 }, P.value = !1; const t = document.createElementNS("http://www.w3.org/2000/svg", "text"); t.setAttribute("x", e), t.setAttribute("y", l), t.setAttribute("fill", E.value), t.setAttribute("font-size", B.value * s.scale), t.setAttribute("font-family", "sans-serif"), t.setAttribute("class", "vue-data-ui-doodle"), t.setAttribute("dominant-baseline", "hanging"), t.setAttribute("pointer-events", "all"); const a = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); a.setAttribute("x", e), a.setAttribute("dy", "0"), a.textContent = "", t.appendChild(a), t.style.pointerEvents = "none", t.style.userSelect = "none", o.value.appendChild(t), f.value = t, U.value = !0, window.addEventListener("keydown", le), window.addEventListener("mousedown", se, !0), ae(), oe(); } function le(n) { if (!U.value) return; let { row: e, col: l } = T.value, t = D.value.slice(), a = !1; if (n.key === "Enter") { const u = t[e], r = u.slice(0, l), c = u.slice(l); t.splice(e, 1, r, c), e += 1, l = 0, a = !0, n.preventDefault(); } else if (n.key === "Backspace") { if (l > 0) t[e] = t[e].slice(0, l - 1) + t[e].slice(l), l -= 1, a = !0; else if (e > 0) { const u = t[e - 1].length; t[e - 1] += t[e], t.splice(e, 1), e -= 1, l = u, a = !0; } n.preventDefault(); } else if (n.key === "Delete") l < t[e].length ? (t[e] = t[e].slice(0, l) + t[e].slice(l + 1), a = !0) : e < t.length - 1 && (t[e] += t[e + 1], t.splice(e + 1, 1), a = !0), n.preventDefault(); else if (n.key === "ArrowLeft") l > 0 ? l -= 1 : e > 0 && (e -= 1, l = t[e].length), a = !0, n.preventDefault(); else if (n.key === "ArrowRight") l < t[e].length ? l += 1 : e < t.length - 1 && (e += 1, l = 0), a = !0, n.preventDefault(); else if (n.key === "ArrowUp") e > 0 && (e -= 1, l = Math.min(l, t[e].length), a = !0), n.preventDefault(); else if (n.key === "ArrowDown") e < t.length - 1 && (e += 1, l = Math.min(l, t[e].length), a = !0), n.preventDefault(); else if (n.key.length === 1 && !n.ctrlKey && !n.metaKey && !n.altKey) t[e] = t[e].slice(0, l) + n.key + t[e].slice(l), l += 1, a = !0, n.preventDefault(); else if (n.key === "Escape") { j(!0); return; } else n.key === "Tab" && n.preventDefault(); a && (D.value = t, T.value = { row: e, col: l }, t.some((r) => r.length > 0) && !P.value && f.value && (b.value.push(f.value), y.value = [], P.value = !0), ae(), oe()); } function ae() { const n = f.value, { x: e } = O.value; for (; n.firstChild; ) n.removeChild(n.firstChild); D.value.forEach((l, t) => { const a = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); a.setAttribute("x", e), a.setAttribute("dy", t === 0 ? "0" : `${B.value * 1.2 * s.scale}`), a.textContent = l.length ? l : "​", n.appendChild(a); }); } const G = v(null); function q() { G.value !== null && (clearInterval(G.value), G.value = null); } function ge(n) { q(); let e = !0; n.style.opacity = "1", G.value = setInterval(() => { if (!o.value || !n || !o.value.contains(n)) { q(); return; } e = !e, n.style.opacity = e ? "1" : "0"; }, 500); } function oe() { const n = o.value?.querySelector(".vue-data-ui-svg-caret"); n && o.value && o.value.removeChild(n); const e = f.value; if (!e || !o.value) return; const { x: l, y: t } = O.value, { row: a, col: u } = T.value, r = B.value * s.scale, c = e.childNodes[a]; if (!c) return; let g = c.textContent.slice(0, u); g.endsWith(" ") && (g += " "); const i = document.createElementNS("http://www.w3.org/2000/svg", "text"); i.setAttribute("x", l), i.setAttribute("y", t), i.setAttribute("font-size", r), i.setAttribute("font-family", "sans-serif"), i.textContent = g || "", o.value.appendChild(i); const h = i.getBBox(); o.value.removeChild(i); const A = t + a * r * 1.2, k = l + h.width, p = document.createElementNS("http://www.w3.org/2000/svg", "rect"); p.setAttribute("x", k), p.setAttribute("y", A), p.setAttribute("rx", 1), p.setAttribute("width", 2), p.setAttribute("height", r), p.setAttribute("fill", E.value), p.setAttribute("class", "vue-data-ui-svg-caret"), o.value.appendChild(p), ge(p); } function se(n) { if (f.value && !f.value.contains(n.target)) { const e = f.value.children; e.length === 1 && (e[0].textContent === "" || e[0].textContent === "​") && f.value.remove(), j(!1); } } function j(n = !1) { window.removeEventListener("keydown", le), window.removeEventListener("mousedown", se, !0), q(); const e = o.value?.querySelector(".vue-data-ui-svg-caret"); e && o.value && o.value.removeChild(e); const l = f.value?.children; let t = !1; if (l && l.length === 1) { const a = l[0].textContent; t = !a || a === "​"; } (n || t) && f.value && o.value && o.value.contains(f.value) && o.value.removeChild(f.value), U.value = !1, f.value = null, D.value = [""], T.value = { row: 0, col: 0 }, P.value = !1; } const C = xe(() => $e(s.color, 0.6)); function ue() { if (!o.value) return; const n = o.value.querySelector(".vue-data-ui-mask"); if (n && o.value.removeChild(n), s.active) { const e = document.createElementNS("http://www.w3.org/2000/svg", "rect"); e.setAttribute("class", "vue-data-ui-mask"), e.setAttribute("width", "100%"), e.setAttribute("height", "100%"), e.setAttribute("fill", "transparent"), e.setAttribute("pointer-events", "all"), o.value.insertBefore(e, o.value.firstChild); } } function H(n) { const e = s.svgRef; if (!e) return { x: 0, y: 0 }; const l = e.createSVGPoint(); l.x = n.clientX, l.y = n.clientY; const t = e.getScreenCTM()?.inverse(); return t ? l.matrixTransform(t) : { x: 0, y: 0 }; } function pe(n) { const e = n.trim().split(/\s+/); if (e.length < 4) return n; const l = e.slice(1).map(Number); if (l.length % 2 !== 0) return n; const t = me(l), a = [`M ${t[0]} ${t[1]}`]; for (let c = 2; c < t.length - 2; c += 2) { const g = t[c - 2], i = t[c - 1], h = t[c], A = t[c + 1], k = (g + h) / 2, p = (i + A) / 2; a.push(`Q ${g} ${i} ${k} ${p}`); } const u = t[t.length - 2], r = t[t.length - 1]; return a.push(`L ${u} ${r}`), a.join(" "); } function me(n, e = 1) { const l = [...n]; for (let t = 2; t < n.length - 2; t += 2) { const a = n[t], u = n[t + 1], r = n[t - 2], c = n[t - 1], g = n[t + 2], i = n[t + 3]; l[t] = a + e * ((r + g) / 2 - a), l[t + 1] = u + e * ((c + i) / 2 - u); } return l; } function he(n) { const e = n.trim().split(/\s+/); let l = "", t = "", a = null, u = null; for (let r = 0; r < e.length; r += 1) { const c = e[r]; if (isNaN(c)) { if (t = c, t === "M" || t === "L") a = parseFloat(e[++r]), u = parseFloat(e[++r]), l += `${t}${a} ${u}`; else if (t === "Q") { const g = parseFloat(e[++r]), i = parseFloat(e[++r]), h = parseFloat(e[++r]), A = parseFloat(e[++r]); g === a && i === u ? l += `t${h - a} ${A - u}` : l += `q${g - a} ${i - u} ${h - a} ${A - u}`, a = h, u = A; } } else { const g = parseFloat(c), i = parseFloat(e[++r]); if (t === "L") { const h = g - a, A = i - u; h === 0 ? l += `v${A}` : A === 0 ? l += `h${h}` : l += `l${h} ${A}`, a = g, u = i; } else if (t === "Q") { const h = g, A = i, k = parseFloat(e[++r]), p = parseFloat(e[++r]); h === a && A === u ? l += `t${k - a} ${p - u}` : l += `q${h - a} ${A - u} ${k - a} ${p - u}`, a = k, u = p; } } } return l; } function X(n) { if (m.value !== "draw" || !s.active || !o.value) return; I.value = !0; const { x: e, y: l } = H(n); K.value = { x: e, y: l }, V.value = `M ${e} ${l}`, w.value = document.createElementNS("http://www.w3.org/2000/svg", "path"), w.value.setAttribute("stroke", E.value), w.value.setAttribute("stroke-width", N.value * s.scale), w.value.setAttribute("fill", "none"), w.value.setAttribute("stroke-linecap", "round"), w.value.setAttribute("stroke-linejoin", "round"), w.value.setAttribute("class", "vue-data-ui-doodle"), o.value.appendChild(w.value); } function Y(n) { if (!I.value || !o.value || !w.value) return; const { x: e, y: l } = H(n); V.value += ` ${e} ${l}`, w.value.setAttribute("d", V.value); } function $(n) { if (I.value && o.value && w.value) { const { x: e, y: l } = H(n); if (K.value && K.value.x === e && K.value.y === l) { const t = document.createElementNS("http://www.w3.org/2000/svg", "circle"); t.setAttribute("cx", e), t.setAttribute("cy", l), t.setAttribute("r", N.value * s.scale / 2), t.setAttribute("fill", E.value), t.setAttribute("class", "vue-data-ui-doodle"), o.value.appendChild(t), b.value.push(t); } else { const t = w.value; t.setAttribute("d", he(pe(V.value))), b.value.push(t); } y.value = [], w.value = ""; } I.value = !1; } function Ae() { if (b.value.length > 0) { const n = b.value.pop(); y.value.push(n), n === f.value ? j(!0) : o.value && o.value.contains(n) && o.value.removeChild(n); } } function we() { if (y.value.length > 0) { const n = y.value.pop(); b.value.push(n), o.value && o.value.appendChild(n); } } function be() { o.value && (o.value.innerHTML = ""), b.value = [], y.value = [], P.value = !1, ue(); } J(m, () => { s.active && (z(), re(), m.value === "text" ? o.value.style.cursor = "text" : o.value.style.cursor = te.value); }); function re() { !s.svgRef || !s.active || (m.value === "draw" ? (s.svgRef.addEventListener("mousedown", X), s.svgRef.addEventListener("mousemove", Y), s.svgRef.addEventListener("mouseup", $), s.svgRef.addEventListener("mouseleave", $), s.svgRef.addEventListener("touchstart", X, { passive: !1 }), s.svgRef.addEventListener("touchmove", Y, { passive: !1 }), s.svgRef.addEventListener("touchend", $)) : m.value === "text" && s.svgRef.addEventListener("mousedown", ne), o.value && (o.value.style.pointerEvents = "auto")); } function z() { s.svgRef && (s.svgRef.removeEventListener("mousedown", X), s.svgRef.removeEventListener("mousemove", Y), s.svgRef.removeEventListener("mouseup", $), s.svgRef.removeEventListener("mouseleave", $), s.svgRef.removeEventListener("touchstart", X), s.svgRef.removeEventListener("touchmove", Y), s.svgRef.removeEventListener("touchend", $), s.svgRef.removeEventListener("mousedown", ne), o.value && (o.value.style.pointerEvents = "none")); } return J(() => s.active, (n) => { n ? re() : z(); }), J(() => s.active, () => { ie(() => { ue(); }); }), ye(() => { ie(() => { s.svgRef && (o.value = document.createElementNS("http://www.w3.org/2000/svg", "g"), o.value.setAttribute("class", "vue-data-ui-doodles"), o.value.style.cursor = te.value, s.svgRef.appendChild(o.value), z()); }); }), Ce(() => { q(), o.value && s.svgRef && (o.value.remove(), z()); }), (n, e) => d.active ? (ee(), Z("div", { key: 0, "data-dom-to-png-ignore": "", class: "vue-ui-pen-and-paper-actions", style: x({ backgroundColor: d.backgroundColor }) }, [ S("button", { class: "vue-ui-pen-and-paper-action", onClick: e[0] || (e[0] = (l) => fe("close")), style: x({ backgroundColor: d.backgroundColor, border: `1px solid ${C.value}` }) }, [ L(n.$slots, "annotator-action-close", {}, () => [ R(Q, { name: "close", stroke: d.color }, null, 8, ["stroke"]) ]) ], 4), S("button", Re, [ R(Be, { value: E.value, "onUpdate:value": e[1] || (e[1] = (l) => E.value = l), backgroundColor: d.backgroundColor, buttonBorderColor: C.value }, { "annotator-action-color": ke(({ color: l }) => [ L(n.$slots, "annotator-action-color", M(F({ color: l }))) ]), _: 3 }, 8, ["value", "backgroundColor", "buttonBorderColor"]) ]), S("button", { class: W(["vue-ui-pen-and-paper-action", { "vue-ui-pen-and-paper-action-active": m.value === "text" }]), onClick: e[2] || (e[2] = (l) => m.value = m.value === "text" ? "draw" : "text"), style: x({ backgroundColor: d.backgroundColor, border: `1px solid ${C.value}` }) }, [ L(n.$slots, "annotator-action-draw", M(F({ mode: m.value })), () => [ R(Q, { name: m.value === "draw" ? "annotator" : "text", stroke: d.color }, null, 8, ["name", "stroke"]) ]), S("div", { style: x({ position: "absolute", bottom: "-20px", color: C.value, width: "100%", textAlign: "center", fontSize: "12px", fontVariantNumeric: "tabular-nums" }) }, Se(Ee(Le)({ v: m.value === "draw" ? N.value : B.value, s: "px", r: 1 })), 5) ], 6), S("button", { class: W(["vue-ui-pen-and-paper-action", { "vue-ui-pen-and-paper-action-disabled": !b.value.length }]), disabled: !b.value.length, onClick: Ae, style: x({ backgroundColor: d.backgroundColor, border: `1px solid ${C.value}`, marginTop: "20px" }) }, [ L(n.$slots, "annotator-action-undo", M(F({ disabled: !b.value.length })), () => [ R(Q, { name: "restart", stroke: d.color }, null, 8, ["stroke"]) ]) ], 14, Ne), S("button", { class: W(["vue-ui-pen-and-paper-action", { "vue-ui-pen-and-paper-action-disabled": !y.value.length }]), onClick: we, style: x({ backgroundColor: d.backgroundColor, border: `1px solid ${C.value}` }) }, [ L(n.$slots, "annotator-action-redo", M(F({ disabled: !y.value.length })), () => [ R(Q, { name: "restart", stroke: d.color, style: { transform: "scaleX(-1)" } }, null, 8, ["stroke"]) ]) ], 6), S("button", { class: W(["vue-ui-pen-and-paper-action", { "vue-ui-pen-and-paper-action-disabled": !b.value.length }]), onClick: be, style: x({ backgroundColor: d.backgroundColor, border: `1px solid ${C.value}` }) }, [ L(n.$slots, "annotator-action-delete", M(F({ disabled: !b.value.length })), () => [ R(Q, { name: "trash", stroke: d.color }, null, 8, ["stroke"]) ]) ], 6), m.value === "draw" ? ve((ee(), Z("input", { key: 0, ref: "range", type: "range", class: "vertical-range", min: 0.5, max: 12, step: 0.1, "onUpdate:modelValue": e[3] || (e[3] = (l) => N.value = l), style: x({ accentColor: d.color }) }, null, 4)), [ [ce, N.value] ]) : _("", !0), m.value === "text" ? ve((ee(), Z("input", { key: 1, ref: "range", type: "range", class: "vertical-range", min: 3, max: 48, step: 0.1, "onUpdate:modelValue": e[4] || (e[4] = (l) => B.value = l), style: x({ accentColor: d.color }) }, null, 4)), [ [ce, B.value] ]) : _("", !0) ], 4)) : _("", !0); } }; export { Fe as default };