@leavepulse/ui 0.24.1 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/LpNumberFlow-rZkwQwlU.css +116 -0
- package/dist/assets/LpRollingText-CR1mb0Vt.css +74 -0
- package/dist/assets/LpSkeleton-D7xU8IPV.css +29 -0
- package/dist/assets/LpSparkline-0mKTq7pb.css +51 -0
- package/dist/assets/LpTooltip-B61SYd4K.css +17 -0
- package/dist/assets/LpUptimeBar-B98o9krI.css +18 -0
- package/dist/chunks/LpChart-tNXs1ttr.js +671 -0
- package/dist/chunks/{LpConfirmDialog-CObRah9c.js → LpConfirmDialog-CjOGcj1e.js} +32 -4
- package/dist/chunks/{LpFileTree-H7Ho8uoq.js → LpFileTree-BaV7f0O7.js} +1 -1
- package/dist/chunks/{LpFileTreeNode-BEZIvxHi.js → LpFileTreeNode-BzpBa7R7.js} +5 -7
- package/dist/chunks/{LpLogViewer-B1aLUVdz.js → LpLogViewer-CcQ3s3Fp.js} +2 -1
- package/dist/chunks/{LpMapPicker-DFKrd1Ne.js → LpMapPicker-CXvoTrAe.js} +2 -4
- package/dist/chunks/{LpNotificationBell-X1M30e0X.js → LpNotificationBell-Dvu-b6Vn.js} +56 -27
- package/dist/chunks/LpNumberFlow-amWYaeuA.js +221 -0
- package/dist/chunks/LpPie-BkjWlfSw.js +525 -0
- package/dist/chunks/LpProgress-DoIP4jQR.js +198 -0
- package/dist/chunks/LpRollingText-CM5gkSeF.js +50 -0
- package/dist/chunks/LpSkeleton-t5mMYl3a.js +29 -0
- package/dist/chunks/LpSparkline-C6xQ2dwv.js +175 -0
- package/dist/chunks/{LpStat-CdbEFOvn.js → LpStat-CDanpzU0.js} +43 -5
- package/dist/chunks/{LpThemeSwitcher-BgpJgrnP.js → LpThemeSwitcher-7mAK6bI7.js} +2 -4
- package/dist/chunks/LpTooltip-hCLPAqMG.js +131 -0
- package/dist/chunks/LpUptimeBar-DcQ8HikG.js +152 -0
- package/dist/component-names.d.ts +1 -1
- package/dist/components/LpChart.vue.d.ts +88 -0
- package/dist/components/LpChart.vue.js +4 -0
- package/dist/components/LpConfirmDialog.vue.d.ts +18 -2
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpFileTree.vue.d.ts +1 -1
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpLightbox.vue.d.ts +1 -1
- package/dist/components/LpLogViewer.vue.d.ts +1 -1
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpMapPicker.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.d.ts +12 -0
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpNumberFlow.vue.d.ts +35 -0
- package/dist/components/LpNumberFlow.vue.js +4 -0
- package/dist/components/LpPie.vue.d.ts +134 -0
- package/dist/components/LpPie.vue.js +4 -0
- package/dist/components/LpProgress.vue.d.ts +56 -2
- package/dist/components/LpProgress.vue.js +1 -1
- package/dist/components/LpRollingText.vue.d.ts +33 -0
- package/dist/components/LpRollingText.vue.js +4 -0
- package/dist/components/LpSkeleton.vue.d.ts +11 -1
- package/dist/components/LpSkeleton.vue.js +1 -1
- package/dist/components/LpSparkline.vue.d.ts +48 -0
- package/dist/components/LpSparkline.vue.js +4 -0
- package/dist/components/LpStat.vue.d.ts +25 -2
- package/dist/components/LpStat.vue.js +1 -1
- package/dist/components/LpThemeSwitcher.vue.js +1 -1
- package/dist/components/LpTooltip.vue.d.ts +24 -3
- package/dist/components/LpTooltip.vue.js +1 -1
- package/dist/components/LpUptimeBar.vue.js +1 -1
- package/dist/components/chart.d.ts +180 -0
- package/dist/components/chart.js +353 -0
- package/dist/composables/easing.d.ts +46 -0
- package/dist/composables/easing.js +23 -0
- package/dist/composables/usePillTransition.d.ts +13 -1
- package/dist/composables/usePillTransition.js +22 -2
- package/dist/composables/useReveal.d.ts +26 -0
- package/dist/composables/useReveal.js +45 -0
- package/dist/composables/useSettled.js +2 -2
- package/dist/composables/useShift.js +2 -4
- package/dist/index.d.ts +14 -0
- package/dist/index.js +125 -102
- package/package.json +2 -1
- package/src/component-names.ts +5 -0
- package/src/components/LpChart.vue +846 -0
- package/src/components/LpConfirmDialog.vue +49 -0
- package/src/components/LpFileTreeNode.vue +5 -9
- package/src/components/LpLogViewer.vue +2 -3
- package/src/components/LpMapPicker.vue +2 -5
- package/src/components/LpNotificationBell.vue +76 -15
- package/src/components/LpNumberFlow.vue +524 -0
- package/src/components/LpPie.vue +778 -0
- package/src/components/LpProgress.vue +283 -6
- package/src/components/LpRollingText.vue +157 -0
- package/src/components/LpSkeleton.vue +81 -8
- package/src/components/LpSparkline.vue +312 -0
- package/src/components/LpStat.vue +52 -4
- package/src/components/LpTooltip.vue +185 -3
- package/src/components/LpUptimeBar.vue +150 -53
- package/src/components/chart.ts +686 -0
- package/src/composables/easing.ts +88 -0
- package/src/composables/usePillTransition.ts +71 -6
- package/src/composables/useReveal.ts +113 -0
- package/src/composables/useSettled.ts +2 -4
- package/src/composables/useShift.ts +2 -8
- package/src/index.ts +19 -0
- package/src/theme/useTheme.ts +2 -8
- package/src/tokens/tokens.css +36 -0
- package/dist/chunks/LpProgress-BhI0IbjC.js +0 -37
- package/dist/chunks/LpSkeleton-eSFIo4kD.js +0 -22
- package/dist/chunks/LpTooltip-D1hbjQWG.js +0 -63
- package/dist/chunks/LpUptimeBar-CB366k0O.js +0 -127
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
import { defineComponent, computed, ref, watch, onBeforeUnmount, openBlock, createElementBlock, toDisplayString, createCommentVNode, normalizeStyle, createVNode, createElementVNode, createBlock, withCtx, withModifiers, Fragment, renderList, normalizeClass, renderSlot } from "vue";
|
|
2
|
+
import { prefersReducedMotion, easeTravel } from "../composables/easing.js";
|
|
3
|
+
import { useReveal } from "../composables/useReveal.js";
|
|
4
|
+
import { resolveSlices, arcPath, calloutLabels, formatTick } from "../components/chart.js";
|
|
5
|
+
import { _ as _sfc_main$2 } from "./LpContextMenu-D0mzXqA7.js";
|
|
6
|
+
import { _ as _sfc_main$1 } from "./LpIcon-CCnX5_2j.js";
|
|
7
|
+
import { _ as _sfc_main$4 } from "./LpNumberFlow-amWYaeuA.js";
|
|
8
|
+
import { _ as _sfc_main$5 } from "./LpRollingText-CM5gkSeF.js";
|
|
9
|
+
import { _ as _sfc_main$6 } from "./LpShift-DXNsauQZ.js";
|
|
10
|
+
import { _ as _sfc_main$3 } from "./LpTooltip-hCLPAqMG.js";
|
|
11
|
+
const _hoisted_1 = {
|
|
12
|
+
key: 0,
|
|
13
|
+
class: "flex flex-col gap-0.5"
|
|
14
|
+
};
|
|
15
|
+
const _hoisted_2 = {
|
|
16
|
+
key: 0,
|
|
17
|
+
class: "text-sm font-medium text-ink"
|
|
18
|
+
};
|
|
19
|
+
const _hoisted_3 = {
|
|
20
|
+
key: 1,
|
|
21
|
+
class: "text-xs text-muted"
|
|
22
|
+
};
|
|
23
|
+
const _hoisted_4 = { class: "text-xs" };
|
|
24
|
+
const _hoisted_5 = { class: "flex flex-wrap items-start gap-x-6 gap-y-3" };
|
|
25
|
+
const _hoisted_6 = { class: "flex items-center gap-1.5" };
|
|
26
|
+
const _hoisted_7 = ["width", "height"];
|
|
27
|
+
const _hoisted_8 = { key: 0 };
|
|
28
|
+
const _hoisted_9 = ["id"];
|
|
29
|
+
const _hoisted_10 = ["d"];
|
|
30
|
+
const _hoisted_11 = ["clip-path"];
|
|
31
|
+
const _hoisted_12 = ["d", "fill", "transform", "opacity"];
|
|
32
|
+
const _hoisted_13 = ["d", "onPointerenter", "onPointerleave", "onPointerdown", "onContextmenu"];
|
|
33
|
+
const _hoisted_14 = { key: 1 };
|
|
34
|
+
const _hoisted_15 = ["opacity", "onPointerenter", "onPointerleave", "onPointerdown", "onContextmenu"];
|
|
35
|
+
const _hoisted_16 = ["points", "stroke"];
|
|
36
|
+
const _hoisted_17 = ["x", "y", "text-anchor"];
|
|
37
|
+
const _hoisted_18 = {
|
|
38
|
+
key: 0,
|
|
39
|
+
class: "pointer-events-none absolute inset-0 flex flex-col items-center justify-center text-center"
|
|
40
|
+
};
|
|
41
|
+
const _hoisted_19 = ["aria-pressed", "onPointerenter", "onPointerleave", "onPointerdown", "onContextmenu"];
|
|
42
|
+
const _hoisted_20 = { class: "truncate text-muted-strong group-hover:text-ink" };
|
|
43
|
+
const _hoisted_21 = {
|
|
44
|
+
key: 0,
|
|
45
|
+
class: "shrink-0 text-muted"
|
|
46
|
+
};
|
|
47
|
+
const _hoisted_22 = { class: "ml-auto shrink-0 pl-2 font-mono tabular-nums text-muted" };
|
|
48
|
+
const ELBOW = 16;
|
|
49
|
+
const RUN = 14;
|
|
50
|
+
const LABEL_LINE = 17;
|
|
51
|
+
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
52
|
+
__name: "LpPie",
|
|
53
|
+
props: {
|
|
54
|
+
slices: {},
|
|
55
|
+
type: { default: "donut" },
|
|
56
|
+
size: { default: 180 },
|
|
57
|
+
innerRatio: { default: 0.62 },
|
|
58
|
+
title: {},
|
|
59
|
+
subtitle: {},
|
|
60
|
+
maxSlices: { default: 6 },
|
|
61
|
+
otherLabel: { default: "Other" },
|
|
62
|
+
sortBySize: { type: Boolean },
|
|
63
|
+
unit: {},
|
|
64
|
+
formatValue: {},
|
|
65
|
+
labels: { type: Boolean },
|
|
66
|
+
labelWidth: { default: 140 },
|
|
67
|
+
showLegend: { type: Boolean },
|
|
68
|
+
showPercent: { type: Boolean, default: true },
|
|
69
|
+
showTotal: { type: Boolean, default: true },
|
|
70
|
+
padAngle: { default: 1.5 },
|
|
71
|
+
cornerRadius: { default: 4 },
|
|
72
|
+
hoverOffset: { default: 8 },
|
|
73
|
+
animate: { type: Boolean, default: true },
|
|
74
|
+
duration: { default: 900 },
|
|
75
|
+
menuItems: {},
|
|
76
|
+
emptyLabel: { default: "No data" }
|
|
77
|
+
},
|
|
78
|
+
emits: ["sliceClick", "selectionChange", "sliceContextMenu"],
|
|
79
|
+
setup(__props, { emit: __emit }) {
|
|
80
|
+
const props = __props;
|
|
81
|
+
const emit = __emit;
|
|
82
|
+
const resolved = computed(
|
|
83
|
+
() => resolveSlices(
|
|
84
|
+
props.slices,
|
|
85
|
+
props.maxSlices || void 0,
|
|
86
|
+
props.otherLabel,
|
|
87
|
+
!props.sortBySize
|
|
88
|
+
)
|
|
89
|
+
);
|
|
90
|
+
const hasData = computed(() => resolved.value.slices.length > 0);
|
|
91
|
+
const legendShown = computed(() => props.showLegend ?? !props.labels);
|
|
92
|
+
const fmt = (v) => `${props.formatValue ? props.formatValue(v) : formatTick(v)}${props.unit ?? ""}`;
|
|
93
|
+
const pct = (fraction) => {
|
|
94
|
+
const p = fraction * 100;
|
|
95
|
+
if (p > 0 && p < 0.1) return "<0.1%";
|
|
96
|
+
return `${Number(p.toFixed(p < 10 ? 1 : 0))}%`;
|
|
97
|
+
};
|
|
98
|
+
const arcAngles = computed(() => {
|
|
99
|
+
const list = resolved.value.slices;
|
|
100
|
+
const pad = list.length > 1 ? props.padAngle * Math.PI / 180 : 0;
|
|
101
|
+
let angle = -Math.PI / 2;
|
|
102
|
+
return list.map((s, index) => {
|
|
103
|
+
const sweep2 = s.fraction * Math.PI * 2;
|
|
104
|
+
const start = angle;
|
|
105
|
+
angle += sweep2;
|
|
106
|
+
const gap = Math.min(pad, sweep2 * 0.5);
|
|
107
|
+
return { index, slice: s, start: start + gap / 2, end: angle - gap / 2, mid: start + sweep2 / 2 };
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
const canvasWidth = computed(
|
|
111
|
+
() => props.labels ? props.size + (props.labelWidth + ELBOW + RUN) * 2 : props.size
|
|
112
|
+
);
|
|
113
|
+
const canvasHeight = computed(() => {
|
|
114
|
+
if (!props.labels) return props.size;
|
|
115
|
+
const right = arcAngles.value.filter((a) => Math.cos(a.mid) >= 0).length;
|
|
116
|
+
const perSide = Math.max(right, arcAngles.value.length - right) + 2;
|
|
117
|
+
return Math.max(props.size, perSide * LABEL_LINE + 16);
|
|
118
|
+
});
|
|
119
|
+
const cx = computed(() => canvasWidth.value / 2);
|
|
120
|
+
const cy = computed(() => canvasHeight.value / 2);
|
|
121
|
+
const outer = computed(() => props.size / 2 - 2 - props.hoverOffset);
|
|
122
|
+
const inner = computed(
|
|
123
|
+
() => props.type === "donut" ? outer.value * Math.min(0.95, Math.max(0, props.innerRatio)) : 0
|
|
124
|
+
);
|
|
125
|
+
const arcs = computed(
|
|
126
|
+
() => arcAngles.value.map(({ slice, start, end, mid }) => ({
|
|
127
|
+
...slice,
|
|
128
|
+
d: arcPath(cx.value, cy.value, outer.value, inner.value, start, end, props.cornerRadius),
|
|
129
|
+
/*
|
|
130
|
+
* Push along the slice's own bisector, so it moves straight out of the
|
|
131
|
+
* circle rather than sideways.
|
|
132
|
+
*
|
|
133
|
+
* As an SVG `transform` attribute, not a CSS `translate`: Vue binds the
|
|
134
|
+
* latter as an inline style, and on an SVG <path> that produced no style
|
|
135
|
+
* attribute at all — the wedge never moved, though the dimming beside it
|
|
136
|
+
* worked and made it look as if it had.
|
|
137
|
+
*/
|
|
138
|
+
lift: `translate(${(Math.cos(mid) * props.hoverOffset).toFixed(2)} ${(Math.sin(mid) * props.hoverOffset).toFixed(2)})`
|
|
139
|
+
}))
|
|
140
|
+
);
|
|
141
|
+
const { el: revealAnchor, revealed } = useReveal({ immediate: () => !props.animate });
|
|
142
|
+
const sweep = ref(props.animate ? 0 : 1);
|
|
143
|
+
let sweepFrame = 0;
|
|
144
|
+
const stopSweep = () => {
|
|
145
|
+
if (sweepFrame) cancelAnimationFrame(sweepFrame);
|
|
146
|
+
sweepFrame = 0;
|
|
147
|
+
};
|
|
148
|
+
const runSweep = () => {
|
|
149
|
+
stopSweep();
|
|
150
|
+
if (prefersReducedMotion() || props.duration <= 0) {
|
|
151
|
+
sweep.value = 1;
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const started = performance.now();
|
|
155
|
+
const step = (now) => {
|
|
156
|
+
const t = Math.min(1, (now - started) / props.duration);
|
|
157
|
+
sweep.value = easeTravel(t);
|
|
158
|
+
sweepFrame = t < 1 ? requestAnimationFrame(step) : 0;
|
|
159
|
+
};
|
|
160
|
+
sweepFrame = requestAnimationFrame(step);
|
|
161
|
+
};
|
|
162
|
+
watch(
|
|
163
|
+
revealed,
|
|
164
|
+
(visible) => {
|
|
165
|
+
if (visible && props.animate) runSweep();
|
|
166
|
+
},
|
|
167
|
+
{ immediate: true }
|
|
168
|
+
);
|
|
169
|
+
onBeforeUnmount(stopSweep);
|
|
170
|
+
const sweepId = `lp-pie-${Math.random().toString(36).slice(2, 9)}`;
|
|
171
|
+
const sweepPath = computed(() => {
|
|
172
|
+
const start = -Math.PI / 2;
|
|
173
|
+
const radius = outer.value + props.hoverOffset + 4;
|
|
174
|
+
return arcPath(cx.value, cy.value, radius, 0, start, start + Math.PI * 2 * sweep.value);
|
|
175
|
+
});
|
|
176
|
+
const callouts = computed(
|
|
177
|
+
() => props.labels ? calloutLabels(
|
|
178
|
+
arcAngles.value.map((a) => ({ index: a.index, mid: a.mid })),
|
|
179
|
+
cx.value,
|
|
180
|
+
cy.value,
|
|
181
|
+
outer.value,
|
|
182
|
+
ELBOW,
|
|
183
|
+
RUN,
|
|
184
|
+
LABEL_LINE,
|
|
185
|
+
canvasHeight.value
|
|
186
|
+
) : []
|
|
187
|
+
);
|
|
188
|
+
const active = ref(-1);
|
|
189
|
+
const selected = ref(/* @__PURE__ */ new Set());
|
|
190
|
+
const anchor = ref(-1);
|
|
191
|
+
const dragging = ref(false);
|
|
192
|
+
const isSelected = (i) => selected.value.has(i);
|
|
193
|
+
const payloadFor = (i) => {
|
|
194
|
+
const a = arcs.value[i];
|
|
195
|
+
return { index: i, label: a.label, value: a.value, isOther: a.isOther };
|
|
196
|
+
};
|
|
197
|
+
const emitSelection = () => {
|
|
198
|
+
const indices = [...selected.value].sort((x, y) => x - y);
|
|
199
|
+
emit("selectionChange", indices.map(payloadFor));
|
|
200
|
+
};
|
|
201
|
+
const rangeTo = (i) => {
|
|
202
|
+
const from = anchor.value < 0 ? i : anchor.value;
|
|
203
|
+
const [lo, hi] = from <= i ? [from, i] : [i, from];
|
|
204
|
+
const next = new Set(selected.value);
|
|
205
|
+
for (let k = lo; k <= hi; k++) next.add(k);
|
|
206
|
+
return next;
|
|
207
|
+
};
|
|
208
|
+
const onSlicePointerDown = (i, e) => {
|
|
209
|
+
if (e.button === 2) return;
|
|
210
|
+
if (e.shiftKey) {
|
|
211
|
+
selected.value = rangeTo(i);
|
|
212
|
+
} else if (e.ctrlKey || e.metaKey) {
|
|
213
|
+
const next = new Set(selected.value);
|
|
214
|
+
if (next.has(i)) next.delete(i);
|
|
215
|
+
else next.add(i);
|
|
216
|
+
selected.value = next;
|
|
217
|
+
anchor.value = i;
|
|
218
|
+
} else {
|
|
219
|
+
const only = selected.value.size === 1 && selected.value.has(i);
|
|
220
|
+
selected.value = only ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([i]);
|
|
221
|
+
anchor.value = only ? -1 : i;
|
|
222
|
+
if (!only) beginDrag();
|
|
223
|
+
}
|
|
224
|
+
emitSelection();
|
|
225
|
+
emit("sliceClick", payloadFor(i));
|
|
226
|
+
};
|
|
227
|
+
const onSlicePointerEnter = (i) => {
|
|
228
|
+
clearTimeout(leaveTimer);
|
|
229
|
+
active.value = i;
|
|
230
|
+
if (!dragging.value || selected.value.has(i)) return;
|
|
231
|
+
const next = new Set(selected.value);
|
|
232
|
+
next.add(i);
|
|
233
|
+
selected.value = next;
|
|
234
|
+
emitSelection();
|
|
235
|
+
};
|
|
236
|
+
let leaveTimer;
|
|
237
|
+
const onSlicePointerLeave = (i) => {
|
|
238
|
+
if (active.value !== i) return;
|
|
239
|
+
clearTimeout(leaveTimer);
|
|
240
|
+
leaveTimer = setTimeout(() => {
|
|
241
|
+
if (active.value === i) active.value = -1;
|
|
242
|
+
}, 60);
|
|
243
|
+
};
|
|
244
|
+
const leaveChart = (e) => {
|
|
245
|
+
const to = e.relatedTarget;
|
|
246
|
+
if (to instanceof Element && (to.closest("[data-lp-follow]") || to.querySelector("[data-lp-follow]")))
|
|
247
|
+
return;
|
|
248
|
+
clearTimeout(leaveTimer);
|
|
249
|
+
active.value = -1;
|
|
250
|
+
endDrag();
|
|
251
|
+
};
|
|
252
|
+
const endDrag = () => {
|
|
253
|
+
dragging.value = false;
|
|
254
|
+
window.removeEventListener("pointerup", endDrag);
|
|
255
|
+
window.removeEventListener("pointercancel", endDrag);
|
|
256
|
+
};
|
|
257
|
+
const beginDrag = () => {
|
|
258
|
+
dragging.value = true;
|
|
259
|
+
window.addEventListener("pointerup", endDrag);
|
|
260
|
+
window.addEventListener("pointercancel", endDrag);
|
|
261
|
+
};
|
|
262
|
+
onBeforeUnmount(() => {
|
|
263
|
+
endDrag();
|
|
264
|
+
clearTimeout(leaveTimer);
|
|
265
|
+
window.removeEventListener("keydown", onKeydown);
|
|
266
|
+
});
|
|
267
|
+
const clearSelection = () => {
|
|
268
|
+
if (!selected.value.size) return;
|
|
269
|
+
selected.value = /* @__PURE__ */ new Set();
|
|
270
|
+
anchor.value = -1;
|
|
271
|
+
emitSelection();
|
|
272
|
+
};
|
|
273
|
+
const onKeydown = (e) => {
|
|
274
|
+
if (e.key === "Escape") clearSelection();
|
|
275
|
+
};
|
|
276
|
+
watch(
|
|
277
|
+
() => selected.value.size > 0,
|
|
278
|
+
(hasSelection) => {
|
|
279
|
+
if (hasSelection) window.addEventListener("keydown", onKeydown);
|
|
280
|
+
else window.removeEventListener("keydown", onKeydown);
|
|
281
|
+
}
|
|
282
|
+
);
|
|
283
|
+
const menuPoint = ref(
|
|
284
|
+
null
|
|
285
|
+
);
|
|
286
|
+
const menuEntries = computed(
|
|
287
|
+
() => props.menuItems && menuPoint.value ? props.menuItems(menuPoint.value, [...selected.value].sort((x, y) => x - y).map(payloadFor)) : []
|
|
288
|
+
);
|
|
289
|
+
const onSliceContextMenu = (i) => {
|
|
290
|
+
if (!selected.value.has(i)) {
|
|
291
|
+
selected.value = /* @__PURE__ */ new Set([i]);
|
|
292
|
+
anchor.value = i;
|
|
293
|
+
emitSelection();
|
|
294
|
+
}
|
|
295
|
+
menuPoint.value = payloadFor(i);
|
|
296
|
+
emit("sliceContextMenu", menuPoint.value);
|
|
297
|
+
};
|
|
298
|
+
const focused = computed(() => {
|
|
299
|
+
if (active.value >= 0) return active.value;
|
|
300
|
+
return selected.value.size === 1 ? [...selected.value][0] : -1;
|
|
301
|
+
});
|
|
302
|
+
const dimmed = (i) => selected.value.size > 0 && !selected.value.has(i);
|
|
303
|
+
const total = computed(() => resolved.value.total);
|
|
304
|
+
const selectedArcs = computed(
|
|
305
|
+
() => [...selected.value].sort((x, y) => x - y).map((i) => arcs.value[i])
|
|
306
|
+
);
|
|
307
|
+
const hovered = computed(() => active.value >= 0 ? arcs.value[active.value] : null);
|
|
308
|
+
const tipSlice = ref(null);
|
|
309
|
+
watch(hovered, (v) => {
|
|
310
|
+
if (v) tipSlice.value = v;
|
|
311
|
+
});
|
|
312
|
+
const centreValue = computed(() => {
|
|
313
|
+
if (focused.value >= 0) return arcs.value[focused.value].value;
|
|
314
|
+
if (selected.value.size > 1)
|
|
315
|
+
return selectedArcs.value.reduce((sum, a) => sum + a.value, 0);
|
|
316
|
+
return total.value;
|
|
317
|
+
});
|
|
318
|
+
const centreLabel = computed(() => {
|
|
319
|
+
if (focused.value >= 0) return arcs.value[focused.value].label;
|
|
320
|
+
if (selected.value.size > 1) return `${selected.value.size} selected`;
|
|
321
|
+
return "total";
|
|
322
|
+
});
|
|
323
|
+
return (_ctx, _cache) => {
|
|
324
|
+
return openBlock(), createElementBlock("div", {
|
|
325
|
+
ref_key: "revealAnchor",
|
|
326
|
+
ref: revealAnchor,
|
|
327
|
+
class: "flex flex-col gap-3"
|
|
328
|
+
}, [
|
|
329
|
+
__props.title || __props.subtitle ? (openBlock(), createElementBlock("div", _hoisted_1, [
|
|
330
|
+
__props.title ? (openBlock(), createElementBlock("span", _hoisted_2, toDisplayString(__props.title), 1)) : createCommentVNode("", true),
|
|
331
|
+
__props.subtitle ? (openBlock(), createElementBlock("span", _hoisted_3, toDisplayString(__props.subtitle), 1)) : createCommentVNode("", true)
|
|
332
|
+
])) : createCommentVNode("", true),
|
|
333
|
+
!hasData.value ? (openBlock(), createElementBlock("div", {
|
|
334
|
+
key: 1,
|
|
335
|
+
class: "flex flex-col items-center justify-center gap-2 rounded-control border border-dashed border-line text-muted",
|
|
336
|
+
style: normalizeStyle({ width: `${__props.size}px`, height: `${__props.size}px` })
|
|
337
|
+
}, [
|
|
338
|
+
createVNode(_sfc_main$1, {
|
|
339
|
+
name: "lucide:chart-pie",
|
|
340
|
+
size: 18
|
|
341
|
+
}),
|
|
342
|
+
createElementVNode("span", _hoisted_4, toDisplayString(__props.emptyLabel), 1)
|
|
343
|
+
], 4)) : (openBlock(), createBlock(_sfc_main$2, {
|
|
344
|
+
key: 2,
|
|
345
|
+
items: menuEntries.value,
|
|
346
|
+
always: !!__props.menuItems
|
|
347
|
+
}, {
|
|
348
|
+
default: withCtx(() => [
|
|
349
|
+
createElementVNode("div", _hoisted_5, [
|
|
350
|
+
createVNode(_sfc_main$3, {
|
|
351
|
+
delay: 80,
|
|
352
|
+
"follow-cursor": "",
|
|
353
|
+
side: "top",
|
|
354
|
+
disabled: !hovered.value
|
|
355
|
+
}, {
|
|
356
|
+
content: withCtx(() => [
|
|
357
|
+
tipSlice.value ? (openBlock(), createBlock(_sfc_main$6, {
|
|
358
|
+
key: 0,
|
|
359
|
+
axis: "width"
|
|
360
|
+
}, {
|
|
361
|
+
default: withCtx(() => [
|
|
362
|
+
createElementVNode("span", _hoisted_6, [
|
|
363
|
+
createElementVNode("span", {
|
|
364
|
+
class: "size-1.5 shrink-0 rounded-full transition-colors duration-fast",
|
|
365
|
+
style: normalizeStyle({ backgroundColor: tipSlice.value.color })
|
|
366
|
+
}, null, 4),
|
|
367
|
+
createVNode(_sfc_main$5, {
|
|
368
|
+
class: "text-muted-strong",
|
|
369
|
+
value: tipSlice.value.label,
|
|
370
|
+
duration: 200,
|
|
371
|
+
align: "start"
|
|
372
|
+
}, null, 8, ["value"]),
|
|
373
|
+
createVNode(_sfc_main$4, {
|
|
374
|
+
class: "font-mono text-ink",
|
|
375
|
+
value: tipSlice.value.value,
|
|
376
|
+
duration: 240,
|
|
377
|
+
format: fmt,
|
|
378
|
+
"animate-on-mount": false
|
|
379
|
+
}, null, 8, ["value"]),
|
|
380
|
+
createVNode(_sfc_main$5, {
|
|
381
|
+
class: "text-muted",
|
|
382
|
+
value: pct(tipSlice.value.fraction),
|
|
383
|
+
duration: 200,
|
|
384
|
+
align: "end"
|
|
385
|
+
}, null, 8, ["value"])
|
|
386
|
+
])
|
|
387
|
+
]),
|
|
388
|
+
_: 1
|
|
389
|
+
})) : createCommentVNode("", true)
|
|
390
|
+
]),
|
|
391
|
+
default: withCtx(() => [
|
|
392
|
+
createElementVNode("div", {
|
|
393
|
+
class: "relative shrink-0",
|
|
394
|
+
style: normalizeStyle({ width: `${canvasWidth.value}px`, height: `${canvasHeight.value}px` })
|
|
395
|
+
}, [
|
|
396
|
+
(openBlock(), createElementBlock("svg", {
|
|
397
|
+
width: canvasWidth.value,
|
|
398
|
+
height: canvasHeight.value,
|
|
399
|
+
class: "touch-none select-none",
|
|
400
|
+
onPointerleave: leaveChart,
|
|
401
|
+
onPointerup: endDrag,
|
|
402
|
+
onClick: withModifiers(clearSelection, ["self"])
|
|
403
|
+
}, [
|
|
404
|
+
__props.animate ? (openBlock(), createElementBlock("defs", _hoisted_8, [
|
|
405
|
+
createElementVNode("clipPath", {
|
|
406
|
+
id: `${sweepId}-sweep`
|
|
407
|
+
}, [
|
|
408
|
+
createElementVNode("path", { d: sweepPath.value }, null, 8, _hoisted_10)
|
|
409
|
+
], 8, _hoisted_9)
|
|
410
|
+
])) : createCommentVNode("", true),
|
|
411
|
+
createElementVNode("g", {
|
|
412
|
+
"clip-path": __props.animate ? `url(#${sweepId}-sweep)` : void 0
|
|
413
|
+
}, [
|
|
414
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(arcs.value, (a, i) => {
|
|
415
|
+
return openBlock(), createElementBlock("path", {
|
|
416
|
+
key: a.label,
|
|
417
|
+
d: a.d,
|
|
418
|
+
fill: a.color,
|
|
419
|
+
class: "pointer-events-none transition-[transform,opacity] duration-medium ease-[var(--ease-emphasized)] motion-reduce:transition-none",
|
|
420
|
+
transform: active.value === i ? a.lift : void 0,
|
|
421
|
+
opacity: dimmed(i) ? 0.28 : 1
|
|
422
|
+
}, null, 8, _hoisted_12);
|
|
423
|
+
}), 128))
|
|
424
|
+
], 8, _hoisted_11),
|
|
425
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(arcs.value, (a, i) => {
|
|
426
|
+
return openBlock(), createElementBlock("path", {
|
|
427
|
+
key: `hit-${a.label}`,
|
|
428
|
+
d: a.d,
|
|
429
|
+
fill: "transparent",
|
|
430
|
+
class: "cursor-pointer",
|
|
431
|
+
onPointerenter: ($event) => onSlicePointerEnter(i),
|
|
432
|
+
onPointerleave: ($event) => onSlicePointerLeave(i),
|
|
433
|
+
onPointerdown: ($event) => onSlicePointerDown(i, $event),
|
|
434
|
+
onContextmenu: ($event) => onSliceContextMenu(i)
|
|
435
|
+
}, null, 40, _hoisted_13);
|
|
436
|
+
}), 128)),
|
|
437
|
+
__props.labels ? (openBlock(), createElementBlock("g", _hoisted_14, [
|
|
438
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(callouts.value, (c) => {
|
|
439
|
+
return openBlock(), createElementBlock("g", {
|
|
440
|
+
key: `c-${c.index}`,
|
|
441
|
+
class: "cursor-pointer transition-opacity duration-fast",
|
|
442
|
+
opacity: dimmed(c.index) ? 0.35 : 1,
|
|
443
|
+
onPointerenter: ($event) => onSlicePointerEnter(c.index),
|
|
444
|
+
onPointerleave: ($event) => onSlicePointerLeave(c.index),
|
|
445
|
+
onPointerdown: ($event) => onSlicePointerDown(c.index, $event),
|
|
446
|
+
onContextmenu: ($event) => onSliceContextMenu(c.index)
|
|
447
|
+
}, [
|
|
448
|
+
createElementVNode("polyline", {
|
|
449
|
+
points: c.points,
|
|
450
|
+
fill: "none",
|
|
451
|
+
stroke: arcs.value[c.index].color,
|
|
452
|
+
"stroke-width": "1"
|
|
453
|
+
}, null, 8, _hoisted_16),
|
|
454
|
+
createElementVNode("text", {
|
|
455
|
+
x: c.x,
|
|
456
|
+
y: c.y,
|
|
457
|
+
"text-anchor": c.side === "right" ? "start" : "end",
|
|
458
|
+
"dominant-baseline": "middle",
|
|
459
|
+
class: normalizeClass(["text-[11px]", active.value === c.index ? "fill-[var(--color-ink)]" : "fill-[var(--color-muted-strong)]"])
|
|
460
|
+
}, toDisplayString(arcs.value[c.index].label) + ": " + toDisplayString(pct(arcs.value[c.index].fraction)), 11, _hoisted_17)
|
|
461
|
+
], 40, _hoisted_15);
|
|
462
|
+
}), 128))
|
|
463
|
+
])) : createCommentVNode("", true)
|
|
464
|
+
], 40, _hoisted_7)),
|
|
465
|
+
__props.type === "donut" && (__props.showTotal || _ctx.$slots.center) ? (openBlock(), createElementBlock("div", _hoisted_18, [
|
|
466
|
+
renderSlot(_ctx.$slots, "center", {
|
|
467
|
+
total: total.value,
|
|
468
|
+
active: focused.value >= 0 ? arcs.value[focused.value] : null,
|
|
469
|
+
selection: selectedArcs.value
|
|
470
|
+
}, () => [
|
|
471
|
+
createVNode(_sfc_main$4, {
|
|
472
|
+
class: "font-mono text-xl font-medium text-ink",
|
|
473
|
+
value: centreValue.value,
|
|
474
|
+
duration: 360,
|
|
475
|
+
format: fmt,
|
|
476
|
+
"animate-on-mount": false
|
|
477
|
+
}, null, 8, ["value"]),
|
|
478
|
+
createVNode(_sfc_main$5, {
|
|
479
|
+
class: "max-w-[80%] text-[11px] text-muted",
|
|
480
|
+
value: centreLabel.value,
|
|
481
|
+
duration: 260
|
|
482
|
+
}, null, 8, ["value"])
|
|
483
|
+
])
|
|
484
|
+
])) : createCommentVNode("", true)
|
|
485
|
+
], 4)
|
|
486
|
+
]),
|
|
487
|
+
_: 3
|
|
488
|
+
}, 8, ["disabled"]),
|
|
489
|
+
legendShown.value ? (openBlock(), createElementBlock("div", {
|
|
490
|
+
key: 0,
|
|
491
|
+
class: "flex min-w-0 flex-col gap-1.5",
|
|
492
|
+
onPointerup: endDrag
|
|
493
|
+
}, [
|
|
494
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList(arcs.value, (a, i) => {
|
|
495
|
+
return openBlock(), createElementBlock("button", {
|
|
496
|
+
key: a.label,
|
|
497
|
+
type: "button",
|
|
498
|
+
class: normalizeClass(["group flex cursor-pointer items-center gap-2 rounded-md px-1 py-0.5 text-xs outline-none transition-[opacity,background-color] duration-fast focus-visible:ring-2 focus-visible:ring-ring", [dimmed(i) ? "opacity-45" : "", isSelected(i) ? "bg-surface-soft" : ""]]),
|
|
499
|
+
"aria-pressed": isSelected(i),
|
|
500
|
+
onPointerenter: ($event) => onSlicePointerEnter(i),
|
|
501
|
+
onPointerleave: ($event) => onSlicePointerLeave(i),
|
|
502
|
+
onPointerdown: ($event) => onSlicePointerDown(i, $event),
|
|
503
|
+
onContextmenu: ($event) => onSliceContextMenu(i)
|
|
504
|
+
}, [
|
|
505
|
+
createElementVNode("span", {
|
|
506
|
+
class: "size-2 shrink-0 rounded-full transition-transform duration-fast group-hover:scale-125",
|
|
507
|
+
style: normalizeStyle({ backgroundColor: a.color })
|
|
508
|
+
}, null, 4),
|
|
509
|
+
createElementVNode("span", _hoisted_20, toDisplayString(a.label), 1),
|
|
510
|
+
a.isOther ? (openBlock(), createElementBlock("span", _hoisted_21, "(" + toDisplayString(a.count) + ")", 1)) : createCommentVNode("", true),
|
|
511
|
+
createElementVNode("span", _hoisted_22, toDisplayString(__props.showPercent ? pct(a.fraction) : fmt(a.value)), 1)
|
|
512
|
+
], 42, _hoisted_19);
|
|
513
|
+
}), 128))
|
|
514
|
+
], 32)) : createCommentVNode("", true)
|
|
515
|
+
])
|
|
516
|
+
]),
|
|
517
|
+
_: 3
|
|
518
|
+
}, 8, ["items", "always"]))
|
|
519
|
+
], 512);
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
export {
|
|
524
|
+
_sfc_main as _
|
|
525
|
+
};
|