vue-data-ui
Version:
A user-empowering data visualization Vue 3 components library for eloquent data storytelling
403 lines (402 loc) • 18.7 kB
JavaScript
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
};