@leavepulse/ui 0.24.0 → 0.25.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/LpSparkline-0mKTq7pb.css +51 -0
- package/dist/assets/LpTooltip-B61SYd4K.css +17 -0
- package/dist/assets/LpUptimeBar-B98o9krI.css +18 -0
- package/dist/chunks/{LpAppShell-CaQYQKZV.js → LpAppShell-B5k0J3Cf.js} +1 -1
- package/dist/chunks/LpChart-tNXs1ttr.js +671 -0
- package/dist/chunks/{LpCodeBlock-CSiOSPs8.js → LpCodeBlock-rS_6mO3D.js} +1 -1
- package/dist/chunks/{LpConfirmDialog-CObRah9c.js → LpConfirmDialog-CjOGcj1e.js} +32 -4
- package/dist/chunks/{LpDisclosure-DP6yfgfM.js → LpDisclosure-BnwP4Q-b.js} +1 -1
- package/dist/chunks/{LpFileTree-nU1wRRxZ.js → LpFileTree-BaV7f0O7.js} +1 -1
- package/dist/chunks/{LpFileTreeNode-D7WgUPcJ.js → LpFileTreeNode-BzpBa7R7.js} +6 -8
- 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/{LpSidebar-D3GGLSE7.js → LpSidebar-7Vpy2IJc.js} +1 -1
- package/dist/chunks/{LpSidebarNav-X14xMdPa.js → LpSidebarNav-CFX5NxGs.js} +1 -1
- 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/LpAppShell.vue.js +1 -1
- package/dist/components/LpChart.vue.d.ts +88 -0
- package/dist/components/LpChart.vue.js +4 -0
- package/dist/components/LpCodeBlock.vue.js +1 -1
- package/dist/components/LpConfirmDialog.vue.d.ts +18 -2
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpDisclosure.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/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.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 +126 -103
- package/package.json +2 -1
- package/src/component-names.ts +5 -0
- package/src/components/LpChart.vue +846 -0
- package/src/components/LpCodeBlock.vue +1 -1
- package/src/components/LpConfirmDialog.vue +49 -0
- package/src/components/LpDisclosure.vue +1 -1
- package/src/components/LpFileTreeNode.vue +9 -10
- 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/LpSidebarNav.vue +1 -1
- 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/LpTooltip-D1hbjQWG.js +0 -63
- package/dist/chunks/LpUptimeBar-CB366k0O.js +0 -127
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
const plotWidth = (b) => Math.max(0, b.width - b.left - b.right);
|
|
2
|
+
const plotHeight = (b) => Math.max(0, b.height - b.top - b.bottom);
|
|
3
|
+
const CHART_PALETTE = [
|
|
4
|
+
"var(--color-brand)",
|
|
5
|
+
"var(--color-action)",
|
|
6
|
+
"var(--color-accent)",
|
|
7
|
+
"var(--color-danger)",
|
|
8
|
+
"var(--color-muted-strong)"
|
|
9
|
+
];
|
|
10
|
+
const valueOf = (d) => d == null ? null : typeof d === "number" ? d : d.y;
|
|
11
|
+
const normalise = (series) => series.map((s, i) => ({
|
|
12
|
+
name: s.name,
|
|
13
|
+
color: s.color ?? CHART_PALETTE[i % CHART_PALETTE.length],
|
|
14
|
+
dashed: s.dashed ?? false,
|
|
15
|
+
axis: s.axis ?? "left",
|
|
16
|
+
values: s.data.map(valueOf)
|
|
17
|
+
}));
|
|
18
|
+
const resolveLabels = (series, labels, count) => Array.from({ length: count }, (_, i) => {
|
|
19
|
+
for (const s of series) {
|
|
20
|
+
const d = s.data[i];
|
|
21
|
+
if (d && typeof d !== "number" && d.x != null) return d.x;
|
|
22
|
+
}
|
|
23
|
+
return labels?.[i] ?? String(i + 1);
|
|
24
|
+
});
|
|
25
|
+
const pointCount = (series) => series.reduce((n, s) => Math.max(n, s.values.length), 0);
|
|
26
|
+
const niceScale = (min, max, ticks) => {
|
|
27
|
+
if (!Number.isFinite(min) || !Number.isFinite(max)) return { min: 0, max: 1, step: 1 };
|
|
28
|
+
if (min === max) {
|
|
29
|
+
const pad = Math.abs(min) || 1;
|
|
30
|
+
min -= pad / 2;
|
|
31
|
+
max += pad / 2;
|
|
32
|
+
}
|
|
33
|
+
const raw = (max - min) / Math.max(1, ticks);
|
|
34
|
+
const mag = 10 ** Math.floor(Math.log10(raw));
|
|
35
|
+
const norm = raw / mag;
|
|
36
|
+
const step = (norm >= 5 ? 10 : norm >= 2 ? 5 : norm >= 1 ? 2 : 1) * mag;
|
|
37
|
+
return { min: Math.floor(min / step) * step, max: Math.ceil(max / step) * step, step };
|
|
38
|
+
};
|
|
39
|
+
const axisTicks = (min, max, step) => {
|
|
40
|
+
const out = [];
|
|
41
|
+
if (!(step > 0)) return [min, max];
|
|
42
|
+
for (let v = min; v <= max + step / 2; v += step) out.push(Number(v.toFixed(10)));
|
|
43
|
+
return out;
|
|
44
|
+
};
|
|
45
|
+
const extent = (series, includeZero) => {
|
|
46
|
+
let min = Infinity;
|
|
47
|
+
let max = -Infinity;
|
|
48
|
+
for (const s of series)
|
|
49
|
+
for (const v of s.values) {
|
|
50
|
+
if (v == null) continue;
|
|
51
|
+
if (v < min) min = v;
|
|
52
|
+
if (v > max) max = v;
|
|
53
|
+
}
|
|
54
|
+
if (min === Infinity) return { min: 0, max: 1 };
|
|
55
|
+
if (includeZero) {
|
|
56
|
+
min = Math.min(min, 0);
|
|
57
|
+
max = Math.max(max, 0);
|
|
58
|
+
}
|
|
59
|
+
return { min, max };
|
|
60
|
+
};
|
|
61
|
+
const xAt = (i, count, box, banded) => {
|
|
62
|
+
const w = plotWidth(box);
|
|
63
|
+
if (banded) return box.left + w / Math.max(1, count) * (i + 0.5);
|
|
64
|
+
if (count <= 1) return box.left + w / 2;
|
|
65
|
+
return box.left + w / (count - 1) * i;
|
|
66
|
+
};
|
|
67
|
+
const yAt = (v, min, max, box) => {
|
|
68
|
+
const h = plotHeight(box);
|
|
69
|
+
const span = max - min || 1;
|
|
70
|
+
return box.top + h - (v - min) / span * h;
|
|
71
|
+
};
|
|
72
|
+
const runs = (values, count) => {
|
|
73
|
+
const out = [];
|
|
74
|
+
let cur = [];
|
|
75
|
+
for (let i = 0; i < count; i++) {
|
|
76
|
+
if (values[i] == null) {
|
|
77
|
+
if (cur.length) out.push(cur);
|
|
78
|
+
cur = [];
|
|
79
|
+
} else cur.push(i);
|
|
80
|
+
}
|
|
81
|
+
if (cur.length) out.push(cur);
|
|
82
|
+
return out;
|
|
83
|
+
};
|
|
84
|
+
const smoothSegment = (pts, tension) => {
|
|
85
|
+
let d = `M${pts[0].x},${pts[0].y}`;
|
|
86
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
87
|
+
const p0 = pts[i - 1] ?? pts[i];
|
|
88
|
+
const p1 = pts[i];
|
|
89
|
+
const p2 = pts[i + 1];
|
|
90
|
+
const p3 = pts[i + 2] ?? p2;
|
|
91
|
+
const c1x = p1.x + (p2.x - p0.x) / 6 * tension;
|
|
92
|
+
const c2x = p2.x - (p3.x - p1.x) / 6 * tension;
|
|
93
|
+
let c1y = p1.y + (p2.y - p0.y) / 6 * tension;
|
|
94
|
+
let c2y = p2.y - (p3.y - p1.y) / 6 * tension;
|
|
95
|
+
const lo = Math.min(p1.y, p2.y);
|
|
96
|
+
const hi = Math.max(p1.y, p2.y);
|
|
97
|
+
c1y = Math.min(hi, Math.max(lo, c1y));
|
|
98
|
+
c2y = Math.min(hi, Math.max(lo, c2y));
|
|
99
|
+
d += `C${c1x},${c1y} ${c2x},${c2y} ${p2.x},${p2.y}`;
|
|
100
|
+
}
|
|
101
|
+
return d;
|
|
102
|
+
};
|
|
103
|
+
const segmentPath = (idx, values, s, smooth) => {
|
|
104
|
+
const pts = idx.map((i) => ({
|
|
105
|
+
x: xAt(i, s.count, s.box, s.banded),
|
|
106
|
+
y: yAt(values[i], s.min, s.max, s.box)
|
|
107
|
+
}));
|
|
108
|
+
if (pts.length === 1) return `M${pts[0].x},${pts[0].y}L${pts[0].x},${pts[0].y}`;
|
|
109
|
+
if (smooth > 0) return smoothSegment(pts, smooth);
|
|
110
|
+
return pts.map((p, i) => `${i ? "L" : "M"}${p.x},${p.y}`).join("");
|
|
111
|
+
};
|
|
112
|
+
const linePath = (values, s, smooth) => runs(values, s.count).map((idx) => segmentPath(idx, values, s, smooth)).join(" ");
|
|
113
|
+
const areaPath = (values, s, smooth) => {
|
|
114
|
+
const base = yAt(Math.max(s.min, Math.min(s.max, 0)), s.min, s.max, s.box);
|
|
115
|
+
return runs(values, s.count).map((idx) => {
|
|
116
|
+
const d = segmentPath(idx, values, s, smooth);
|
|
117
|
+
const x0 = xAt(idx[0], s.count, s.box, s.banded);
|
|
118
|
+
const x1 = xAt(idx[idx.length - 1], s.count, s.box, s.banded);
|
|
119
|
+
return `${d}L${x1},${base}L${x0},${base}Z`;
|
|
120
|
+
}).join(" ");
|
|
121
|
+
};
|
|
122
|
+
const nearestIndex = (px, s) => {
|
|
123
|
+
if (s.count === 0) return -1;
|
|
124
|
+
let best = 0;
|
|
125
|
+
let bestDist = Infinity;
|
|
126
|
+
for (let i = 0; i < s.count; i++) {
|
|
127
|
+
const d = Math.abs(xAt(i, s.count, s.box, s.banded) - px);
|
|
128
|
+
if (d < bestDist) {
|
|
129
|
+
bestDist = d;
|
|
130
|
+
best = i;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return best;
|
|
134
|
+
};
|
|
135
|
+
const barPath = (x, y, w, h, r, round) => {
|
|
136
|
+
const k = Math.max(0, Math.min(r, w / 2, h / 2));
|
|
137
|
+
const rt = round.top ? k : 0;
|
|
138
|
+
const rb = round.bottom ? k : 0;
|
|
139
|
+
return [
|
|
140
|
+
`M${x},${y + rt}`,
|
|
141
|
+
rt ? `A${rt},${rt} 0 0 1 ${x + rt},${y}` : `L${x},${y}`,
|
|
142
|
+
`L${x + w - rt},${y}`,
|
|
143
|
+
rt ? `A${rt},${rt} 0 0 1 ${x + w},${y + rt}` : "",
|
|
144
|
+
`L${x + w},${y + h - rb}`,
|
|
145
|
+
rb ? `A${rb},${rb} 0 0 1 ${x + w - rb},${y + h}` : `L${x + w},${y + h}`,
|
|
146
|
+
`L${x + rb},${y + h}`,
|
|
147
|
+
rb ? `A${rb},${rb} 0 0 1 ${x},${y + h - rb}` : "",
|
|
148
|
+
"Z"
|
|
149
|
+
].filter(Boolean).join("");
|
|
150
|
+
};
|
|
151
|
+
const resolveSlices = (slices, max, otherLabel, preserveOrder = true) => {
|
|
152
|
+
const usable = slices.filter((s) => Number.isFinite(s.value) && s.value > 0);
|
|
153
|
+
const total = usable.reduce((sum, s) => sum + s.value, 0);
|
|
154
|
+
if (!total) return { slices: [], total: 0 };
|
|
155
|
+
const coloured = usable.map((s) => ({
|
|
156
|
+
...s,
|
|
157
|
+
color: s.color ?? CHART_PALETTE[slices.indexOf(s) % CHART_PALETTE.length]
|
|
158
|
+
}));
|
|
159
|
+
const ranked = [...coloured].sort((a, b) => b.value - a.value);
|
|
160
|
+
const collapse = max != null && max > 0 && ranked.length > max;
|
|
161
|
+
const keptRanked = collapse ? ranked.slice(0, max - 1) : ranked;
|
|
162
|
+
const tail = collapse ? ranked.slice(max - 1) : [];
|
|
163
|
+
const kept = preserveOrder ? coloured.filter((s) => keptRanked.includes(s)) : keptRanked;
|
|
164
|
+
const out = kept.map((s) => ({
|
|
165
|
+
label: s.label,
|
|
166
|
+
value: s.value,
|
|
167
|
+
color: s.color,
|
|
168
|
+
fraction: s.value / total,
|
|
169
|
+
isOther: false,
|
|
170
|
+
count: 1
|
|
171
|
+
}));
|
|
172
|
+
if (tail.length) {
|
|
173
|
+
const value = tail.reduce((sum, s) => sum + s.value, 0);
|
|
174
|
+
out.push({
|
|
175
|
+
label: otherLabel,
|
|
176
|
+
value,
|
|
177
|
+
color: "var(--color-line-strong)",
|
|
178
|
+
fraction: value / total,
|
|
179
|
+
isOther: true,
|
|
180
|
+
count: tail.length
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return { slices: out, total };
|
|
184
|
+
};
|
|
185
|
+
const arcPath = (cx, cy, outer, inner, startAngle, endAngle, corner = 0) => {
|
|
186
|
+
const full = endAngle - startAngle >= Math.PI * 2 - 1e-9;
|
|
187
|
+
if (full) {
|
|
188
|
+
const mid = startAngle + Math.PI;
|
|
189
|
+
return arcPath(cx, cy, outer, inner, startAngle, mid) + arcPath(cx, cy, outer, inner, mid, startAngle + Math.PI * 2);
|
|
190
|
+
}
|
|
191
|
+
const at = (r, a) => `${cx + r * Math.cos(a)},${cy + r * Math.sin(a)}`;
|
|
192
|
+
const large = endAngle - startAngle > Math.PI ? 1 : 0;
|
|
193
|
+
if (corner <= 0) {
|
|
194
|
+
if (inner <= 0) {
|
|
195
|
+
return [
|
|
196
|
+
`M${cx},${cy}`,
|
|
197
|
+
`L${at(outer, startAngle)}`,
|
|
198
|
+
`A${outer},${outer} 0 ${large} 1 ${at(outer, endAngle)}`,
|
|
199
|
+
"Z"
|
|
200
|
+
].join("");
|
|
201
|
+
}
|
|
202
|
+
return [
|
|
203
|
+
`M${at(outer, startAngle)}`,
|
|
204
|
+
`A${outer},${outer} 0 ${large} 1 ${at(outer, endAngle)}`,
|
|
205
|
+
`L${at(inner, endAngle)}`,
|
|
206
|
+
`A${inner},${inner} 0 ${large} 0 ${at(inner, startAngle)}`,
|
|
207
|
+
"Z"
|
|
208
|
+
].join("");
|
|
209
|
+
}
|
|
210
|
+
const band = inner > 0 ? outer - inner : outer;
|
|
211
|
+
const sweep = endAngle - startAngle;
|
|
212
|
+
const rOuter = Math.min(corner, band / 2, sweep * outer / 4);
|
|
213
|
+
const rInner = inner > 0 ? Math.min(corner, band / 2, sweep * inner / 4) : 0;
|
|
214
|
+
const aOuter = rOuter / outer;
|
|
215
|
+
const aInner = inner > 0 ? rInner / inner : 0;
|
|
216
|
+
const outerIn = outer - rOuter;
|
|
217
|
+
const innerOut = inner + rInner;
|
|
218
|
+
const oStart = startAngle + aOuter;
|
|
219
|
+
const oEnd = endAngle - aOuter;
|
|
220
|
+
const iStart = startAngle + aInner;
|
|
221
|
+
const iEnd = endAngle - aInner;
|
|
222
|
+
if (inner <= 0) {
|
|
223
|
+
const tip = Math.min(corner, sweep * outer / 6);
|
|
224
|
+
if (tip <= 0.25) {
|
|
225
|
+
return [
|
|
226
|
+
`M${cx},${cy}`,
|
|
227
|
+
`L${at(outerIn, startAngle)}`,
|
|
228
|
+
`A${rOuter},${rOuter} 0 0 1 ${at(outer, oStart)}`,
|
|
229
|
+
`A${outer},${outer} 0 ${oEnd - oStart > Math.PI ? 1 : 0} 1 ${at(outer, oEnd)}`,
|
|
230
|
+
`A${rOuter},${rOuter} 0 0 1 ${at(outerIn, endAngle)}`,
|
|
231
|
+
"Z"
|
|
232
|
+
].join("");
|
|
233
|
+
}
|
|
234
|
+
return [
|
|
235
|
+
`M${at(tip, startAngle)}`,
|
|
236
|
+
`L${at(outerIn, startAngle)}`,
|
|
237
|
+
`A${rOuter},${rOuter} 0 0 1 ${at(outer, oStart)}`,
|
|
238
|
+
`A${outer},${outer} 0 ${oEnd - oStart > Math.PI ? 1 : 0} 1 ${at(outer, oEnd)}`,
|
|
239
|
+
`A${rOuter},${rOuter} 0 0 1 ${at(outerIn, endAngle)}`,
|
|
240
|
+
`L${at(tip, endAngle)}`,
|
|
241
|
+
`A${tip},${tip} 0 0 0 ${at(tip, startAngle)}`,
|
|
242
|
+
"Z"
|
|
243
|
+
].join("");
|
|
244
|
+
}
|
|
245
|
+
return [
|
|
246
|
+
`M${at(outerIn, startAngle)}`,
|
|
247
|
+
`A${rOuter},${rOuter} 0 0 1 ${at(outer, oStart)}`,
|
|
248
|
+
`A${outer},${outer} 0 ${oEnd - oStart > Math.PI ? 1 : 0} 1 ${at(outer, oEnd)}`,
|
|
249
|
+
`A${rOuter},${rOuter} 0 0 1 ${at(outerIn, endAngle)}`,
|
|
250
|
+
`L${at(innerOut, endAngle)}`,
|
|
251
|
+
`A${rInner},${rInner} 0 0 1 ${at(inner, iEnd)}`,
|
|
252
|
+
`A${inner},${inner} 0 ${iEnd - iStart > Math.PI ? 1 : 0} 0 ${at(inner, iStart)}`,
|
|
253
|
+
`A${rInner},${rInner} 0 0 1 ${at(innerOut, startAngle)}`,
|
|
254
|
+
"Z"
|
|
255
|
+
].join("");
|
|
256
|
+
};
|
|
257
|
+
const calloutLabels = (angles, cx, cy, radius, elbow, run, spacing, height) => {
|
|
258
|
+
const sides = {
|
|
259
|
+
left: [],
|
|
260
|
+
right: []
|
|
261
|
+
};
|
|
262
|
+
const fits = Math.max(1, Math.floor(height / spacing));
|
|
263
|
+
const ordered = [...angles].sort(
|
|
264
|
+
(p, q) => Math.abs(Math.cos(q.mid)) - Math.abs(Math.cos(p.mid))
|
|
265
|
+
);
|
|
266
|
+
for (const a of ordered) {
|
|
267
|
+
const natural = Math.cos(a.mid) >= 0 ? "right" : "left";
|
|
268
|
+
const other = natural === "right" ? "left" : "right";
|
|
269
|
+
const side = sides[natural].length >= fits && sides[other].length < fits ? other : natural;
|
|
270
|
+
sides[side].push({ ...a, y: cy + Math.sin(a.mid) * (radius + elbow) });
|
|
271
|
+
}
|
|
272
|
+
const out = [];
|
|
273
|
+
for (const side of ["left", "right"]) {
|
|
274
|
+
const rank = (mid) => {
|
|
275
|
+
let a = mid;
|
|
276
|
+
while (a < -Math.PI / 2) a += Math.PI * 2;
|
|
277
|
+
while (a >= 3 * Math.PI / 2) a -= Math.PI * 2;
|
|
278
|
+
return side === "right" ? a : -a;
|
|
279
|
+
};
|
|
280
|
+
const list = sides[side].sort((p, q) => rank(p.mid) - rank(q.mid));
|
|
281
|
+
const top = spacing / 2;
|
|
282
|
+
const bottom = height - spacing / 2;
|
|
283
|
+
for (let pass = 0; pass < 24; pass++) {
|
|
284
|
+
let moved = false;
|
|
285
|
+
for (let i = 0; i < list.length - 1; i++) {
|
|
286
|
+
const gap = list[i + 1].y - list[i].y;
|
|
287
|
+
if (gap >= spacing) continue;
|
|
288
|
+
const push = (spacing - gap) / 2;
|
|
289
|
+
list[i].y -= push;
|
|
290
|
+
list[i + 1].y += push;
|
|
291
|
+
moved = true;
|
|
292
|
+
}
|
|
293
|
+
list[0].y = Math.max(list[0].y, top);
|
|
294
|
+
list[list.length - 1].y = Math.min(list[list.length - 1].y, bottom);
|
|
295
|
+
if (!moved) break;
|
|
296
|
+
}
|
|
297
|
+
let cursor = top;
|
|
298
|
+
for (const item of list) {
|
|
299
|
+
item.y = Math.max(item.y, cursor);
|
|
300
|
+
cursor = item.y + spacing;
|
|
301
|
+
}
|
|
302
|
+
let limit = bottom;
|
|
303
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
304
|
+
list[i].y = Math.min(list[i].y, limit);
|
|
305
|
+
limit = list[i].y - spacing;
|
|
306
|
+
}
|
|
307
|
+
list.forEach((item, rank2) => {
|
|
308
|
+
const dir = side === "right" ? 1 : -1;
|
|
309
|
+
const stagger = elbow + rank2 * 1.6;
|
|
310
|
+
const startX = cx + Math.cos(item.mid) * radius;
|
|
311
|
+
const startY = cy + Math.sin(item.mid) * radius;
|
|
312
|
+
const outX = cx + Math.cos(item.mid) * (radius + stagger);
|
|
313
|
+
const outY = cy + Math.sin(item.mid) * (radius + stagger);
|
|
314
|
+
const endX = cx + dir * (radius + elbow + run);
|
|
315
|
+
out.push({
|
|
316
|
+
index: item.index,
|
|
317
|
+
side,
|
|
318
|
+
points: `${startX},${startY} ${outX},${outY} ${endX},${item.y}`,
|
|
319
|
+
x: endX + dir * 4,
|
|
320
|
+
y: item.y
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
return out;
|
|
325
|
+
};
|
|
326
|
+
const formatTick = (v) => {
|
|
327
|
+
const abs = Math.abs(v);
|
|
328
|
+
if (abs >= 1e9) return `${Number((v / 1e9).toFixed(1))}B`;
|
|
329
|
+
if (abs >= 1e6) return `${Number((v / 1e6).toFixed(1))}M`;
|
|
330
|
+
if (abs >= 1e3) return `${Number((v / 1e3).toFixed(1))}k`;
|
|
331
|
+
return String(Number(v.toFixed(2)));
|
|
332
|
+
};
|
|
333
|
+
export {
|
|
334
|
+
CHART_PALETTE,
|
|
335
|
+
arcPath,
|
|
336
|
+
areaPath,
|
|
337
|
+
axisTicks,
|
|
338
|
+
barPath,
|
|
339
|
+
calloutLabels,
|
|
340
|
+
extent,
|
|
341
|
+
formatTick,
|
|
342
|
+
linePath,
|
|
343
|
+
nearestIndex,
|
|
344
|
+
niceScale,
|
|
345
|
+
normalise,
|
|
346
|
+
plotHeight,
|
|
347
|
+
plotWidth,
|
|
348
|
+
pointCount,
|
|
349
|
+
resolveLabels,
|
|
350
|
+
resolveSlices,
|
|
351
|
+
xAt,
|
|
352
|
+
yAt
|
|
353
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Does the OS ask for less motion?
|
|
3
|
+
*
|
|
4
|
+
* Written out in eight places before this, in two different shapes — some
|
|
5
|
+
* guarded against SSR, some not, which is a crash in a Nuxt app rather than a
|
|
6
|
+
* style difference. Anything that animates asks here.
|
|
7
|
+
*
|
|
8
|
+
* A plain function, not a reactive composable: it is read at the moment an
|
|
9
|
+
* animation starts, and a component that also needs to RE-RENDER when the
|
|
10
|
+
* setting changes mid-session should use motion-v's `useReducedMotion`.
|
|
11
|
+
*/
|
|
12
|
+
export declare const prefersReducedMotion: () => boolean;
|
|
13
|
+
/**
|
|
14
|
+
* A motion token's value, for an API that cannot take a `var()`.
|
|
15
|
+
*
|
|
16
|
+
* The Web Animations API and SVG's SMIL both want a literal easing string, so
|
|
17
|
+
* three call sites had `cubic-bezier(0.2, 0, 0, 1)` typed out — the value of
|
|
18
|
+
* `--ease-emphasized`, copied. A theme that retuned the token moved everything
|
|
19
|
+
* except those, and nothing said they were meant to match.
|
|
20
|
+
*
|
|
21
|
+
* Reading the custom property keeps them themeable. The fallback covers SSR and
|
|
22
|
+
* the frame before tokens are applied.
|
|
23
|
+
*/
|
|
24
|
+
export declare const easingToken: (name: "ease-emphasized" | "ease-travel" | "ease-settle", fallback?: string) => string;
|
|
25
|
+
/**
|
|
26
|
+
* Comes to rest — `--ease-settle`'s shape without the overshoot.
|
|
27
|
+
*
|
|
28
|
+
* Gently decelerating: the value keeps moving visibly for most of the travel
|
|
29
|
+
* instead of arriving early and then creeping. Use it wherever a figure counts
|
|
30
|
+
* up to something, so the digits keep turning across the whole duration.
|
|
31
|
+
*/
|
|
32
|
+
export declare const easeOut: (t: number) => number;
|
|
33
|
+
/**
|
|
34
|
+
* Travels a distance the eye follows — the JS form of `--ease-travel`.
|
|
35
|
+
*
|
|
36
|
+
* Nearly linear, and deliberately so: an ease-out spends about half its
|
|
37
|
+
* duration covering the last tenth of the path, so a stroke or sweep animated
|
|
38
|
+
* with one flashes past and then visibly creeps. Use this wherever the journey
|
|
39
|
+
* is the content — a line drawing itself, a wipe crossing a plot, a circle
|
|
40
|
+
* sweeping open.
|
|
41
|
+
*
|
|
42
|
+
* The ease is applied only near the ends, and the finish is NOT flattened onto
|
|
43
|
+
* the target: the motion is still moving when it lands, which is what keeps
|
|
44
|
+
* the tail short.
|
|
45
|
+
*/
|
|
46
|
+
export declare const easeTravel: (t: number) => number;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const prefersReducedMotion = () => typeof window !== "undefined" && (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false);
|
|
2
|
+
const easingToken = (name, fallback = "cubic-bezier(0.2, 0, 0, 1)") => {
|
|
3
|
+
if (typeof window === "undefined" || !window.getComputedStyle) return fallback;
|
|
4
|
+
const value = getComputedStyle(document.documentElement).getPropertyValue(`--${name}`).trim();
|
|
5
|
+
return value || fallback;
|
|
6
|
+
};
|
|
7
|
+
const easeOut = (t) => 1 - (1 - t) ** 2;
|
|
8
|
+
const easeTravel = (t) => {
|
|
9
|
+
const EDGE = 0.2;
|
|
10
|
+
if (t < EDGE) return t * t / (2 * EDGE) + t * 0.1;
|
|
11
|
+
if (t > 1 - EDGE) {
|
|
12
|
+
const r = 1 - t;
|
|
13
|
+
return 1 - (r * r / (2 * EDGE) + r * 0.1);
|
|
14
|
+
}
|
|
15
|
+
const head = EDGE * EDGE / (2 * EDGE) + EDGE * 0.1;
|
|
16
|
+
return head + (t - EDGE) / (1 - 2 * EDGE) * (1 - 2 * head);
|
|
17
|
+
};
|
|
18
|
+
export {
|
|
19
|
+
easeOut,
|
|
20
|
+
easeTravel,
|
|
21
|
+
easingToken,
|
|
22
|
+
prefersReducedMotion
|
|
23
|
+
};
|
|
@@ -6,4 +6,16 @@ export type PillTransition = {
|
|
|
6
6
|
stiffness: number;
|
|
7
7
|
damping: number;
|
|
8
8
|
};
|
|
9
|
-
export
|
|
9
|
+
export type PillFeel = "pill" | "float";
|
|
10
|
+
export declare function usePillTransition(feel?: PillFeel): ComputedRef<PillTransition>;
|
|
11
|
+
export interface SpringState {
|
|
12
|
+
/** Current position; seed it with the start value. */
|
|
13
|
+
value: number;
|
|
14
|
+
/** Current velocity; seed with 0. */
|
|
15
|
+
velocity: number;
|
|
16
|
+
}
|
|
17
|
+
export declare function springStep(state: SpringState, target: number, feel?: PillFeel,
|
|
18
|
+
/** Frame time in seconds; clamped so a backgrounded tab can't explode it. */
|
|
19
|
+
dt?: number): SpringState;
|
|
20
|
+
/** True once a spring is close enough to its target to stop animating. */
|
|
21
|
+
export declare const springSettled: (state: SpringState, target: number) => boolean;
|
|
@@ -1,11 +1,31 @@
|
|
|
1
1
|
import { useReducedMotion } from "motion-v";
|
|
2
2
|
import { computed } from "vue";
|
|
3
|
-
|
|
3
|
+
const SPRING = {
|
|
4
|
+
pill: { type: "spring", stiffness: 520, damping: 40 },
|
|
5
|
+
float: { type: "spring", stiffness: 210, damping: 26 }
|
|
6
|
+
};
|
|
7
|
+
function usePillTransition(feel = "pill") {
|
|
4
8
|
const reduceMotion = useReducedMotion();
|
|
5
9
|
return computed(
|
|
6
|
-
() => reduceMotion.value ? { duration: 0 } :
|
|
10
|
+
() => reduceMotion.value ? { duration: 0 } : SPRING[feel]
|
|
7
11
|
);
|
|
8
12
|
}
|
|
13
|
+
const STEP_SPRING = {
|
|
14
|
+
// Critically damped: reaches the target quickly, never past it.
|
|
15
|
+
pill: { stiffness: 420, damping: 41 },
|
|
16
|
+
float: { stiffness: 260, damping: 32 }
|
|
17
|
+
};
|
|
18
|
+
function springStep(state, target, feel = "float", dt = 1 / 60) {
|
|
19
|
+
const { stiffness, damping } = STEP_SPRING[feel];
|
|
20
|
+
const step = Math.min(dt, 1 / 30);
|
|
21
|
+
const force = (target - state.value) * stiffness;
|
|
22
|
+
const drag = state.velocity * damping;
|
|
23
|
+
const velocity = state.velocity + (force - drag) * step;
|
|
24
|
+
return { value: state.value + velocity * step, velocity };
|
|
25
|
+
}
|
|
26
|
+
const springSettled = (state, target) => Math.abs(target - state.value) < 0.3 && Math.abs(state.velocity) < 0.3;
|
|
9
27
|
export {
|
|
28
|
+
springSettled,
|
|
29
|
+
springStep,
|
|
10
30
|
usePillTransition
|
|
11
31
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { MaybeRefOrGetter, Ref } from 'vue';
|
|
2
|
+
export interface UseRevealOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Skip the wait and reveal at once. For a caller that has switched its
|
|
5
|
+
* entrance off — there is nothing to gate, and holding content back for an
|
|
6
|
+
* observer that no longer guards anything only delays the first paint.
|
|
7
|
+
*/
|
|
8
|
+
immediate?: MaybeRefOrGetter<boolean | undefined>;
|
|
9
|
+
/**
|
|
10
|
+
* Fraction of the element that must be on screen. A sliver of a tall chart
|
|
11
|
+
* scrolling into view is not yet an arrival; a quarter of it is.
|
|
12
|
+
*/
|
|
13
|
+
threshold?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Grow the viewport for the test, so an element just past the fold starts
|
|
16
|
+
* drawing as it is scrolled toward rather than after it lands.
|
|
17
|
+
*/
|
|
18
|
+
rootMargin?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface UseReveal {
|
|
21
|
+
/** Attach to the element whose appearance starts the animation. */
|
|
22
|
+
el: Ref<Element | null>;
|
|
23
|
+
/** False until that element has been seen; true forever after. */
|
|
24
|
+
revealed: Ref<boolean>;
|
|
25
|
+
}
|
|
26
|
+
export declare function useReveal(options?: UseRevealOptions): UseReveal;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ref, watch, toValue, onBeforeUnmount } from "vue";
|
|
2
|
+
import { prefersReducedMotion } from "./easing.js";
|
|
3
|
+
function useReveal(options = {}) {
|
|
4
|
+
const { threshold = 0.25, rootMargin = "0px 0px -10% 0px" } = options;
|
|
5
|
+
const el = ref(null);
|
|
6
|
+
const revealed = ref(typeof window === "undefined");
|
|
7
|
+
let observer = null;
|
|
8
|
+
function stop() {
|
|
9
|
+
observer?.disconnect();
|
|
10
|
+
observer = null;
|
|
11
|
+
}
|
|
12
|
+
function reveal() {
|
|
13
|
+
revealed.value = true;
|
|
14
|
+
stop();
|
|
15
|
+
}
|
|
16
|
+
watch(
|
|
17
|
+
[el, () => toValue(options.immediate)],
|
|
18
|
+
([node, immediate]) => {
|
|
19
|
+
if (revealed.value) return;
|
|
20
|
+
if (immediate || prefersReducedMotion()) {
|
|
21
|
+
reveal();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
stop();
|
|
25
|
+
if (!node) return;
|
|
26
|
+
if (typeof IntersectionObserver === "undefined") {
|
|
27
|
+
reveal();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
observer = new IntersectionObserver(
|
|
31
|
+
(entries) => {
|
|
32
|
+
if (entries.some((entry) => entry.isIntersecting)) reveal();
|
|
33
|
+
},
|
|
34
|
+
{ threshold, rootMargin }
|
|
35
|
+
);
|
|
36
|
+
observer.observe(node);
|
|
37
|
+
},
|
|
38
|
+
{ immediate: true, flush: "post" }
|
|
39
|
+
);
|
|
40
|
+
onBeforeUnmount(stop);
|
|
41
|
+
return { el, revealed };
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
useReveal
|
|
45
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ref, watch, toValue, onScopeDispose } from "vue";
|
|
2
|
+
import { prefersReducedMotion } from "./easing.js";
|
|
2
3
|
const SETTLE_MS = 260;
|
|
3
4
|
function useSettled(open, options = {}) {
|
|
4
5
|
const settled = ref(false);
|
|
@@ -18,8 +19,7 @@ function useSettled(open, options = {}) {
|
|
|
18
19
|
return;
|
|
19
20
|
}
|
|
20
21
|
settled.value = false;
|
|
21
|
-
const
|
|
22
|
-
const wait = reduced ? 0 : toValue(options.delay) ?? SETTLE_MS;
|
|
22
|
+
const wait = prefersReducedMotion() ? 0 : toValue(options.delay) ?? SETTLE_MS;
|
|
23
23
|
timer = setTimeout(() => {
|
|
24
24
|
settled.value = true;
|
|
25
25
|
timer = void 0;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ref, watch, toValue, onBeforeUnmount } from "vue";
|
|
2
|
+
import { prefersReducedMotion } from "./easing.js";
|
|
2
3
|
const TWEEN_MS = 160;
|
|
3
4
|
function useShift(options = {}) {
|
|
4
5
|
const el = ref(null);
|
|
@@ -14,9 +15,6 @@ function useShift(options = {}) {
|
|
|
14
15
|
const axisOf = () => toValue(options.axis) ?? "height";
|
|
15
16
|
const wantsHeight = () => axisOf() !== "width";
|
|
16
17
|
const wantsWidth = () => axisOf() !== "height";
|
|
17
|
-
function reducedMotion() {
|
|
18
|
-
return typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
19
|
-
}
|
|
20
18
|
function retune() {
|
|
21
19
|
const node = el.value;
|
|
22
20
|
if (!node) return;
|
|
@@ -48,7 +46,7 @@ function useShift(options = {}) {
|
|
|
48
46
|
inFlight = true;
|
|
49
47
|
resizing.value = true;
|
|
50
48
|
clearTimeout(settleTimer);
|
|
51
|
-
const wait =
|
|
49
|
+
const wait = prefersReducedMotion() ? 0 : toValue(options.duration) ?? TWEEN_MS;
|
|
52
50
|
settleTimer = setTimeout(() => {
|
|
53
51
|
inFlight = false;
|
|
54
52
|
resizing.value = false;
|
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,10 @@ export type { Crumb } from './components/LpBreadcrumbs.vue';
|
|
|
14
14
|
export { default as LpButton } from './components/LpButton.vue';
|
|
15
15
|
export { default as LpCalendar } from './components/LpCalendar.vue';
|
|
16
16
|
export { default as LpCard } from './components/LpCard.vue';
|
|
17
|
+
export { default as LpChart } from './components/LpChart.vue';
|
|
18
|
+
export type { ChartPointEvent } from './components/LpChart.vue';
|
|
19
|
+
export { CHART_PALETTE, axisTicks, formatTick, niceScale, } from './components/chart';
|
|
20
|
+
export type { ChartAxis, ChartPoint, ChartSeries } from './components/chart';
|
|
17
21
|
export { default as LpCheckbox } from './components/LpCheckbox.vue';
|
|
18
22
|
export { default as LpCodeBlock } from './components/LpCodeBlock.vue';
|
|
19
23
|
export type { CodeLang } from './components/codeHighlight';
|
|
@@ -48,9 +52,14 @@ export { default as LpModal } from './components/LpModal.vue';
|
|
|
48
52
|
export { default as LpNotificationBell } from './components/LpNotificationBell.vue';
|
|
49
53
|
export type { NotificationItem, NotificationLabels, } from './components/LpNotificationBell.vue';
|
|
50
54
|
export { default as LpNumberField } from './components/LpNumberField.vue';
|
|
55
|
+
export { default as LpNumberFlow } from './components/LpNumberFlow.vue';
|
|
51
56
|
export { default as LpOtpInput } from './components/LpOtpInput.vue';
|
|
52
57
|
export { default as LpPagination } from './components/LpPagination.vue';
|
|
53
58
|
export { default as LpPasswordInput } from './components/LpPasswordInput.vue';
|
|
59
|
+
export { default as LpPie } from './components/LpPie.vue';
|
|
60
|
+
export type { SliceEvent } from './components/LpPie.vue';
|
|
61
|
+
export { resolveSlices } from './components/chart';
|
|
62
|
+
export type { PieSlice, ResolvedSlice } from './components/chart';
|
|
54
63
|
export { default as LpPhoneInput } from './components/LpPhoneInput.vue';
|
|
55
64
|
export type { PhoneDetail } from './components/LpPhoneInput.vue';
|
|
56
65
|
export { flagEmoji, loadCountries, matchCountryByValue } from './components/countries';
|
|
@@ -60,6 +69,7 @@ export { default as LpProgress } from './components/LpProgress.vue';
|
|
|
60
69
|
export { default as LpRadio } from './components/LpRadio.vue';
|
|
61
70
|
export { default as LpRadioGroup } from './components/LpRadioGroup.vue';
|
|
62
71
|
export type { RadioOption } from './components/LpRadioGroup.vue';
|
|
72
|
+
export { default as LpRollingText } from './components/LpRollingText.vue';
|
|
63
73
|
export { default as LpScrollArea } from './components/LpScrollArea.vue';
|
|
64
74
|
export { default as LpSegmented } from './components/LpSegmented.vue';
|
|
65
75
|
export type { SegmentedOption } from './components/LpSegmented.vue';
|
|
@@ -74,6 +84,7 @@ export { default as LpSidebar } from './components/LpSidebar.vue';
|
|
|
74
84
|
export type { SidebarItem, SidebarSection } from './components/LpSidebar.vue';
|
|
75
85
|
export { default as LpSkeleton } from './components/LpSkeleton.vue';
|
|
76
86
|
export { default as LpSlider } from './components/LpSlider.vue';
|
|
87
|
+
export { default as LpSparkline } from './components/LpSparkline.vue';
|
|
77
88
|
export { default as LpStat } from './components/LpStat.vue';
|
|
78
89
|
export { default as LpStepper } from './components/LpStepper.vue';
|
|
79
90
|
export type { Step } from './components/LpStepper.vue';
|
|
@@ -114,6 +125,9 @@ export { useHotkeys, isAppleKeyboard, appleModifierLabel } from './composables/u
|
|
|
114
125
|
export type { HotkeyBinding } from './composables/useHotkeys';
|
|
115
126
|
export { useInputFilter } from './composables/useInputFilter';
|
|
116
127
|
export type { UseInputFilterOptions } from './composables/useInputFilter';
|
|
128
|
+
export { easeOut, easeTravel, easingToken, prefersReducedMotion } from './composables/easing';
|
|
129
|
+
export { useReveal } from './composables/useReveal';
|
|
130
|
+
export type { UseReveal, UseRevealOptions } from './composables/useReveal';
|
|
117
131
|
export { useRovingFocus } from './composables/useRovingFocus';
|
|
118
132
|
export type { RovingItem, UseRovingFocusOptions } from './composables/useRovingFocus';
|
|
119
133
|
export { useSettled } from './composables/useSettled';
|