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