@leavepulse/ui 0.24.1 → 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.
Files changed (91) hide show
  1. package/dist/assets/LpNumberFlow-rZkwQwlU.css +116 -0
  2. package/dist/assets/LpRollingText-CR1mb0Vt.css +74 -0
  3. package/dist/assets/LpSparkline-0mKTq7pb.css +51 -0
  4. package/dist/assets/LpTooltip-B61SYd4K.css +17 -0
  5. package/dist/assets/LpUptimeBar-B98o9krI.css +18 -0
  6. package/dist/chunks/LpChart-tNXs1ttr.js +671 -0
  7. package/dist/chunks/{LpConfirmDialog-CObRah9c.js → LpConfirmDialog-CjOGcj1e.js} +32 -4
  8. package/dist/chunks/{LpFileTree-H7Ho8uoq.js → LpFileTree-BaV7f0O7.js} +1 -1
  9. package/dist/chunks/{LpFileTreeNode-BEZIvxHi.js → LpFileTreeNode-BzpBa7R7.js} +5 -7
  10. package/dist/chunks/{LpLogViewer-B1aLUVdz.js → LpLogViewer-CcQ3s3Fp.js} +2 -1
  11. package/dist/chunks/{LpMapPicker-DFKrd1Ne.js → LpMapPicker-CXvoTrAe.js} +2 -4
  12. package/dist/chunks/{LpNotificationBell-X1M30e0X.js → LpNotificationBell-Dvu-b6Vn.js} +56 -27
  13. package/dist/chunks/LpNumberFlow-amWYaeuA.js +221 -0
  14. package/dist/chunks/LpPie-BkjWlfSw.js +525 -0
  15. package/dist/chunks/LpProgress-DoIP4jQR.js +198 -0
  16. package/dist/chunks/LpRollingText-CM5gkSeF.js +50 -0
  17. package/dist/chunks/LpSparkline-C6xQ2dwv.js +175 -0
  18. package/dist/chunks/{LpStat-CdbEFOvn.js → LpStat-CDanpzU0.js} +43 -5
  19. package/dist/chunks/{LpThemeSwitcher-BgpJgrnP.js → LpThemeSwitcher-7mAK6bI7.js} +2 -4
  20. package/dist/chunks/LpTooltip-hCLPAqMG.js +131 -0
  21. package/dist/chunks/LpUptimeBar-DcQ8HikG.js +152 -0
  22. package/dist/component-names.d.ts +1 -1
  23. package/dist/components/LpChart.vue.d.ts +88 -0
  24. package/dist/components/LpChart.vue.js +4 -0
  25. package/dist/components/LpConfirmDialog.vue.d.ts +18 -2
  26. package/dist/components/LpConfirmDialog.vue.js +1 -1
  27. package/dist/components/LpFileTree.vue.d.ts +1 -1
  28. package/dist/components/LpFileTree.vue.js +1 -1
  29. package/dist/components/LpFileTreeNode.vue.js +1 -1
  30. package/dist/components/LpLightbox.vue.d.ts +1 -1
  31. package/dist/components/LpLogViewer.vue.d.ts +1 -1
  32. package/dist/components/LpLogViewer.vue.js +1 -1
  33. package/dist/components/LpMapPicker.vue.js +1 -1
  34. package/dist/components/LpNotificationBell.vue.d.ts +12 -0
  35. package/dist/components/LpNotificationBell.vue.js +1 -1
  36. package/dist/components/LpNumberFlow.vue.d.ts +35 -0
  37. package/dist/components/LpNumberFlow.vue.js +4 -0
  38. package/dist/components/LpPie.vue.d.ts +134 -0
  39. package/dist/components/LpPie.vue.js +4 -0
  40. package/dist/components/LpProgress.vue.d.ts +56 -2
  41. package/dist/components/LpProgress.vue.js +1 -1
  42. package/dist/components/LpRollingText.vue.d.ts +33 -0
  43. package/dist/components/LpRollingText.vue.js +4 -0
  44. package/dist/components/LpSparkline.vue.d.ts +48 -0
  45. package/dist/components/LpSparkline.vue.js +4 -0
  46. package/dist/components/LpStat.vue.d.ts +25 -2
  47. package/dist/components/LpStat.vue.js +1 -1
  48. package/dist/components/LpThemeSwitcher.vue.js +1 -1
  49. package/dist/components/LpTooltip.vue.d.ts +24 -3
  50. package/dist/components/LpTooltip.vue.js +1 -1
  51. package/dist/components/LpUptimeBar.vue.js +1 -1
  52. package/dist/components/chart.d.ts +180 -0
  53. package/dist/components/chart.js +353 -0
  54. package/dist/composables/easing.d.ts +46 -0
  55. package/dist/composables/easing.js +23 -0
  56. package/dist/composables/usePillTransition.d.ts +13 -1
  57. package/dist/composables/usePillTransition.js +22 -2
  58. package/dist/composables/useReveal.d.ts +26 -0
  59. package/dist/composables/useReveal.js +45 -0
  60. package/dist/composables/useSettled.js +2 -2
  61. package/dist/composables/useShift.js +2 -4
  62. package/dist/index.d.ts +14 -0
  63. package/dist/index.js +125 -102
  64. package/package.json +2 -1
  65. package/src/component-names.ts +5 -0
  66. package/src/components/LpChart.vue +846 -0
  67. package/src/components/LpConfirmDialog.vue +49 -0
  68. package/src/components/LpFileTreeNode.vue +5 -9
  69. package/src/components/LpLogViewer.vue +2 -3
  70. package/src/components/LpMapPicker.vue +2 -5
  71. package/src/components/LpNotificationBell.vue +76 -15
  72. package/src/components/LpNumberFlow.vue +524 -0
  73. package/src/components/LpPie.vue +778 -0
  74. package/src/components/LpProgress.vue +283 -6
  75. package/src/components/LpRollingText.vue +157 -0
  76. package/src/components/LpSparkline.vue +312 -0
  77. package/src/components/LpStat.vue +52 -4
  78. package/src/components/LpTooltip.vue +185 -3
  79. package/src/components/LpUptimeBar.vue +150 -53
  80. package/src/components/chart.ts +686 -0
  81. package/src/composables/easing.ts +88 -0
  82. package/src/composables/usePillTransition.ts +71 -6
  83. package/src/composables/useReveal.ts +113 -0
  84. package/src/composables/useSettled.ts +2 -4
  85. package/src/composables/useShift.ts +2 -8
  86. package/src/index.ts +19 -0
  87. package/src/theme/useTheme.ts +2 -8
  88. package/src/tokens/tokens.css +36 -0
  89. package/dist/chunks/LpProgress-BhI0IbjC.js +0 -37
  90. package/dist/chunks/LpTooltip-D1hbjQWG.js +0 -63
  91. package/dist/chunks/LpUptimeBar-CB366k0O.js +0 -127
@@ -0,0 +1,198 @@
1
+ import { defineComponent, ref, onBeforeUnmount, watch, computed, openBlock, createBlock, unref, normalizeStyle, withCtx, createElementBlock, createElementVNode, Fragment, createTextVNode, toDisplayString, createCommentVNode, createVNode, normalizeClass } from "vue";
2
+ import { ProgressRoot, ProgressIndicator } from "reka-ui";
3
+ import { prefersReducedMotion, easeOut } from "../composables/easing.js";
4
+ import { useReveal } from "../composables/useReveal.js";
5
+ import { _ as _sfc_main$1 } from "./LpNumberFlow-amWYaeuA.js";
6
+ const _hoisted_1 = ["width", "height"];
7
+ const _hoisted_2 = ["cx", "cy", "r", "stroke-width"];
8
+ const _hoisted_3 = ["cx", "cy", "r", "stroke", "stroke-width", "stroke-dasharray"];
9
+ const _hoisted_4 = {
10
+ key: 0,
11
+ class: "absolute font-mono text-[11px] font-medium text-ink"
12
+ };
13
+ const _hoisted_5 = {
14
+ key: 1,
15
+ class: "flex w-full flex-col gap-1"
16
+ };
17
+ const _hoisted_6 = { class: "flex items-baseline justify-between gap-2 text-xs" };
18
+ const _hoisted_7 = { class: "text-muted" };
19
+ const _hoisted_8 = { class: "font-mono tabular-nums text-muted-strong" };
20
+ const _sfc_main = /* @__PURE__ */ defineComponent({
21
+ __name: "LpProgress",
22
+ props: {
23
+ value: { default: 0 },
24
+ max: { default: 100 },
25
+ tone: { default: "brand" },
26
+ warnAt: { default: 0.8 },
27
+ dangerAt: { default: 0.9 },
28
+ title: {},
29
+ caption: { type: [Boolean, String] },
30
+ variant: { default: "bar" },
31
+ size: { default: 44 },
32
+ thickness: { default: 4 },
33
+ label: { type: [Boolean, String] },
34
+ animateOnMount: { type: Boolean, default: true },
35
+ duration: { default: 900 }
36
+ },
37
+ setup(__props) {
38
+ const props = __props;
39
+ const eased = ref(props.animateOnMount ? 0 : 1);
40
+ let raf = 0;
41
+ let from = props.animateOnMount ? 0 : props.value;
42
+ const runSweep = (ms) => {
43
+ if (raf) cancelAnimationFrame(raf);
44
+ if (prefersReducedMotion() || ms <= 0) {
45
+ eased.value = 1;
46
+ return;
47
+ }
48
+ const t0 = performance.now();
49
+ const step = (now) => {
50
+ const t = Math.min(1, (now - t0) / ms);
51
+ eased.value = easeOut(t);
52
+ raf = t < 1 ? requestAnimationFrame(step) : 0;
53
+ };
54
+ raf = requestAnimationFrame(step);
55
+ };
56
+ onBeforeUnmount(() => {
57
+ if (raf) cancelAnimationFrame(raf);
58
+ });
59
+ const { el: revealAnchor, revealed } = useReveal({ immediate: () => !props.animateOnMount });
60
+ const bindReveal = (instance) => {
61
+ revealAnchor.value = instance?.$el ?? null;
62
+ };
63
+ watch(revealed, (visible) => {
64
+ if (visible && props.animateOnMount) runSweep(props.duration);
65
+ }, { immediate: true });
66
+ watch(
67
+ () => [props.value, props.max],
68
+ () => {
69
+ from = shown.value;
70
+ runSweep(props.duration);
71
+ }
72
+ );
73
+ const shown = computed(() => from + (props.value - from) * eased.value);
74
+ const pct = computed(
75
+ () => Math.max(0, Math.min(100, shown.value / props.max * 100))
76
+ );
77
+ const paintedTone = computed(() => {
78
+ if (props.tone !== "auto") return props.tone;
79
+ const fraction = props.max > 0 ? props.value / props.max : 0;
80
+ if (fraction >= props.dangerAt) return "danger";
81
+ if (fraction >= props.warnAt) return "accent";
82
+ return "action";
83
+ });
84
+ const captionText = computed(() => {
85
+ if (props.caption === void 0 || props.caption === false) return "";
86
+ return typeof props.caption === "string" ? props.caption : `${Math.round(pct.value)}%`;
87
+ });
88
+ const bar = {
89
+ brand: "bg-brand",
90
+ action: "bg-action",
91
+ danger: "bg-danger",
92
+ accent: "bg-accent"
93
+ };
94
+ const stroke = {
95
+ brand: "var(--color-brand)",
96
+ action: "var(--color-action)",
97
+ danger: "var(--color-danger)",
98
+ accent: "var(--color-accent)"
99
+ };
100
+ const radius = computed(() => (props.size - props.thickness) / 2);
101
+ const circumference = computed(() => 2 * Math.PI * radius.value);
102
+ const dash = computed(() => {
103
+ const full = circumference.value;
104
+ const raw = pct.value / 100 * full;
105
+ if (pct.value >= 100) return full;
106
+ return Math.min(raw, full - props.thickness * 1.5);
107
+ });
108
+ const staticCaption = computed(
109
+ () => typeof props.label === "string" ? props.label : null
110
+ );
111
+ return (_ctx, _cache) => {
112
+ return __props.variant === "ring" ? (openBlock(), createBlock(unref(ProgressRoot), {
113
+ key: 0,
114
+ ref: bindReveal,
115
+ "model-value": __props.value,
116
+ max: __props.max,
117
+ class: "relative inline-flex shrink-0 items-center justify-center",
118
+ style: normalizeStyle({ width: `${__props.size}px`, height: `${__props.size}px` })
119
+ }, {
120
+ default: withCtx(() => [
121
+ (openBlock(), createElementBlock("svg", {
122
+ width: __props.size,
123
+ height: __props.size,
124
+ class: "-rotate-90"
125
+ }, [
126
+ createElementVNode("circle", {
127
+ cx: __props.size / 2,
128
+ cy: __props.size / 2,
129
+ r: radius.value,
130
+ fill: "none",
131
+ stroke: "var(--color-surface-soft)",
132
+ "stroke-width": __props.thickness
133
+ }, null, 8, _hoisted_2),
134
+ createElementVNode("circle", {
135
+ cx: __props.size / 2,
136
+ cy: __props.size / 2,
137
+ r: radius.value,
138
+ fill: "none",
139
+ stroke: stroke[paintedTone.value],
140
+ "stroke-width": __props.thickness,
141
+ "stroke-linecap": "round",
142
+ "stroke-dasharray": `${dash.value} ${circumference.value - dash.value}`
143
+ }, null, 8, _hoisted_3)
144
+ ], 8, _hoisted_1)),
145
+ __props.label !== false && __props.label !== void 0 ? (openBlock(), createElementBlock("span", _hoisted_4, [
146
+ staticCaption.value ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
147
+ createTextVNode(toDisplayString(staticCaption.value), 1)
148
+ ], 64)) : (openBlock(), createBlock(_sfc_main$1, {
149
+ key: 1,
150
+ value: pct.value,
151
+ suffix: "%",
152
+ duration: 0,
153
+ "animate-on-mount": false,
154
+ style: { "--lp-nf-spin": "90ms" }
155
+ }, null, 8, ["value"]))
156
+ ])) : createCommentVNode("", true)
157
+ ]),
158
+ _: 1
159
+ }, 8, ["model-value", "max", "style"])) : __props.title || captionText.value ? (openBlock(), createElementBlock("div", _hoisted_5, [
160
+ createElementVNode("div", _hoisted_6, [
161
+ createElementVNode("span", _hoisted_7, toDisplayString(__props.title), 1),
162
+ createElementVNode("span", _hoisted_8, toDisplayString(captionText.value), 1)
163
+ ]),
164
+ createVNode(unref(ProgressRoot), {
165
+ ref: bindReveal,
166
+ "model-value": __props.value,
167
+ max: __props.max,
168
+ class: "h-1.5 w-full overflow-hidden rounded-pill bg-surface-soft"
169
+ }, {
170
+ default: withCtx(() => [
171
+ createVNode(unref(ProgressIndicator), {
172
+ class: normalizeClass(["h-full rounded-pill transition-colors duration-medium", bar[paintedTone.value]]),
173
+ style: normalizeStyle({ width: `${pct.value}%` })
174
+ }, null, 8, ["class", "style"])
175
+ ]),
176
+ _: 1
177
+ }, 8, ["model-value", "max"])
178
+ ])) : (openBlock(), createBlock(unref(ProgressRoot), {
179
+ key: 2,
180
+ ref: bindReveal,
181
+ "model-value": __props.value,
182
+ max: __props.max,
183
+ class: "h-1.5 w-full overflow-hidden rounded-pill bg-surface-soft"
184
+ }, {
185
+ default: withCtx(() => [
186
+ createVNode(unref(ProgressIndicator), {
187
+ class: normalizeClass(["h-full rounded-pill transition-colors duration-medium", bar[paintedTone.value]]),
188
+ style: normalizeStyle({ width: `${pct.value}%` })
189
+ }, null, 8, ["class", "style"])
190
+ ]),
191
+ _: 1
192
+ }, 8, ["model-value", "max"]));
193
+ };
194
+ }
195
+ });
196
+ export {
197
+ _sfc_main as _
198
+ };
@@ -0,0 +1,50 @@
1
+ import { defineComponent, computed, openBlock, createElementBlock, normalizeStyle, normalizeClass, createVNode, Transition, withCtx, renderSlot, createTextVNode, toDisplayString } from "vue";
2
+ import '../assets/LpRollingText-CR1mb0Vt.css';const _sfc_main = /* @__PURE__ */ defineComponent({
3
+ __name: "LpRollingText",
4
+ props: {
5
+ value: {},
6
+ mode: { default: "slide" },
7
+ direction: { default: "up" },
8
+ duration: { default: 320 },
9
+ align: { default: "center" }
10
+ },
11
+ setup(__props) {
12
+ const props = __props;
13
+ const key = computed(() => String(props.value));
14
+ const name = computed(
15
+ () => props.mode === "flip" ? "lp-roll-flip" : `lp-roll-${props.direction}`
16
+ );
17
+ const justify = computed(
18
+ () => ({ start: "justify-start", center: "justify-center", end: "justify-end" })[props.align]
19
+ );
20
+ const style = computed(() => ({
21
+ "--lp-roll-duration": `${props.duration}ms`,
22
+ // The leaving copy is taken out of flow so both can occupy the same line;
23
+ // without this the box would grow to hold two lines mid-transition.
24
+ "--lp-roll-perspective": props.mode === "flip" ? "300px" : "none"
25
+ }));
26
+ return (_ctx, _cache) => {
27
+ return openBlock(), createElementBlock("span", {
28
+ class: normalizeClass(["lp-roll relative inline-flex overflow-hidden align-bottom", justify.value]),
29
+ style: normalizeStyle(style.value)
30
+ }, [
31
+ createVNode(Transition, { name: name.value }, {
32
+ default: withCtx(() => [
33
+ (openBlock(), createElementBlock("span", {
34
+ key: key.value,
35
+ class: "lp-roll__item"
36
+ }, [
37
+ renderSlot(_ctx.$slots, "default", { value: __props.value }, () => [
38
+ createTextVNode(toDisplayString(__props.value), 1)
39
+ ])
40
+ ]))
41
+ ]),
42
+ _: 3
43
+ }, 8, ["name"])
44
+ ], 6);
45
+ };
46
+ }
47
+ });
48
+ export {
49
+ _sfc_main as _
50
+ };
@@ -0,0 +1,175 @@
1
+ import { defineComponent, computed, openBlock, createElementBlock, createElementVNode, createCommentVNode, normalizeStyle, normalizeClass } from "vue";
2
+ import { useReveal } from "../composables/useReveal.js";
3
+ import { normalise, extent, linePath, areaPath } from "../components/chart.js";
4
+ import '../assets/LpSparkline-0mKTq7pb.css';const _hoisted_1 = ["width", "height", "viewBox", "aria-label"];
5
+ const _hoisted_2 = { key: 0 };
6
+ const _hoisted_3 = ["stop-color"];
7
+ const _hoisted_4 = ["stop-color"];
8
+ const _hoisted_5 = ["d", "fill"];
9
+ const _hoisted_6 = ["d", "stroke"];
10
+ const _hoisted_7 = ["cx", "cy", "fill"];
11
+ const _hoisted_8 = ["aria-label"];
12
+ const PAD = 2;
13
+ const _sfc_main = /* @__PURE__ */ defineComponent({
14
+ __name: "LpSparkline",
15
+ props: {
16
+ data: {},
17
+ width: { default: 84 },
18
+ height: { default: 24 },
19
+ color: {},
20
+ tone: { default: "brand" },
21
+ area: { type: Boolean },
22
+ smooth: { default: 0.9 },
23
+ showLast: { type: Boolean },
24
+ min: {},
25
+ max: {},
26
+ ariaLabel: {},
27
+ animate: { type: Boolean, default: true },
28
+ duration: { default: 900 }
29
+ },
30
+ setup(__props) {
31
+ const props = __props;
32
+ const TONE = {
33
+ brand: "var(--color-brand)",
34
+ action: "var(--color-action)",
35
+ danger: "var(--color-danger)",
36
+ accent: "var(--color-accent)",
37
+ muted: "var(--color-muted)"
38
+ };
39
+ const stroke = computed(() => props.color ?? TONE[props.tone]);
40
+ const series = computed(() => normalise([{ name: "s", data: props.data }]));
41
+ const box = computed(() => ({
42
+ width: props.width,
43
+ height: props.height,
44
+ left: PAD,
45
+ right: PAD,
46
+ top: PAD,
47
+ bottom: PAD
48
+ }));
49
+ const bounds = computed(() => {
50
+ const auto = extent(series.value, false);
51
+ return { min: props.min ?? auto.min, max: props.max ?? auto.max };
52
+ });
53
+ const geo = computed(() => ({
54
+ min: bounds.value.min,
55
+ max: bounds.value.max,
56
+ box: box.value,
57
+ count: props.data.length,
58
+ banded: false
59
+ }));
60
+ const d = computed(() => linePath(series.value[0].values, geo.value, props.smooth));
61
+ const fill = computed(
62
+ () => props.area ? areaPath(series.value[0].values, geo.value, props.smooth) : ""
63
+ );
64
+ const lastPoint = computed(() => {
65
+ const values = series.value[0].values;
66
+ for (let i = values.length - 1; i >= 0; i--) {
67
+ const v = values[i];
68
+ if (v == null) continue;
69
+ const span = bounds.value.max - bounds.value.min || 1;
70
+ const w = props.width - PAD * 2;
71
+ const h = props.height - PAD * 2;
72
+ return {
73
+ x: props.data.length <= 1 ? PAD + w / 2 : PAD + w / (props.data.length - 1) * i,
74
+ y: PAD + h - (v - bounds.value.min) / span * h,
75
+ v
76
+ };
77
+ }
78
+ return null;
79
+ });
80
+ const hasData = computed(() => series.value[0].values.some((v) => v != null));
81
+ const label = computed(() => {
82
+ if (props.ariaLabel) return props.ariaLabel;
83
+ const values = series.value[0].values.filter((v) => v != null);
84
+ if (!values.length) return "No data";
85
+ const first = values[0];
86
+ const last = values[values.length - 1];
87
+ const dir = last > first ? "trending up" : last < first ? "trending down" : "flat";
88
+ return `${dir}, latest ${last}`;
89
+ });
90
+ const pathLength = computed(() => {
91
+ const n = Math.max(1, props.data.length - 1);
92
+ const segment = Math.hypot((props.width - PAD * 2) / n, props.height - PAD * 2);
93
+ return Math.ceil(segment * n + 4);
94
+ });
95
+ const { el, revealed } = useReveal({ immediate: () => !props.animate });
96
+ const revealStyle = computed(
97
+ () => props.animate ? {
98
+ "--lp-spark-len": `${pathLength.value}`,
99
+ "--lp-spark-dur": `${props.duration}ms`
100
+ } : void 0
101
+ );
102
+ const revealClass = computed(() => props.animate && !revealed.value ? "lp-spark--waiting" : "");
103
+ const gradientId = `lp-spark-${Math.random().toString(36).slice(2, 9)}`;
104
+ return (_ctx, _cache) => {
105
+ return hasData.value ? (openBlock(), createElementBlock("svg", {
106
+ key: 0,
107
+ ref_key: "el",
108
+ ref: el,
109
+ width: __props.width,
110
+ height: __props.height,
111
+ viewBox: `0 0 ${__props.width} ${__props.height}`,
112
+ role: "img",
113
+ "aria-label": label.value,
114
+ class: "shrink-0 overflow-visible"
115
+ }, [
116
+ __props.area ? (openBlock(), createElementBlock("defs", _hoisted_2, [
117
+ createElementVNode("linearGradient", {
118
+ id: gradientId,
119
+ x1: "0",
120
+ y1: "0",
121
+ x2: "0",
122
+ y2: "1"
123
+ }, [
124
+ createElementVNode("stop", {
125
+ offset: "0%",
126
+ "stop-color": stroke.value,
127
+ "stop-opacity": "0.3"
128
+ }, null, 8, _hoisted_3),
129
+ createElementVNode("stop", {
130
+ offset: "100%",
131
+ "stop-color": stroke.value,
132
+ "stop-opacity": "0.02"
133
+ }, null, 8, _hoisted_4)
134
+ ])
135
+ ])) : createCommentVNode("", true),
136
+ __props.area ? (openBlock(), createElementBlock("path", {
137
+ key: 1,
138
+ d: fill.value,
139
+ fill: `url(#${gradientId})`,
140
+ class: normalizeClass(["lp-spark__area", revealClass.value]),
141
+ style: normalizeStyle(revealStyle.value)
142
+ }, null, 14, _hoisted_5)) : createCommentVNode("", true),
143
+ createElementVNode("path", {
144
+ d: d.value,
145
+ fill: "none",
146
+ stroke: stroke.value,
147
+ "stroke-width": "1.5",
148
+ "stroke-linecap": "round",
149
+ "stroke-linejoin": "round",
150
+ "vector-effect": "non-scaling-stroke",
151
+ class: normalizeClass(["lp-spark__line", revealClass.value]),
152
+ style: normalizeStyle(revealStyle.value)
153
+ }, null, 14, _hoisted_6),
154
+ __props.showLast && lastPoint.value ? (openBlock(), createElementBlock("circle", {
155
+ key: 2,
156
+ cx: lastPoint.value.x,
157
+ cy: lastPoint.value.y,
158
+ r: "2",
159
+ fill: stroke.value,
160
+ class: normalizeClass(["lp-spark__dot", revealClass.value]),
161
+ style: normalizeStyle(revealStyle.value)
162
+ }, null, 14, _hoisted_7)) : createCommentVNode("", true)
163
+ ], 8, _hoisted_1)) : (openBlock(), createElementBlock("span", {
164
+ key: 1,
165
+ class: "inline-block shrink-0 rounded-pill bg-surface-soft",
166
+ style: normalizeStyle({ width: `${__props.width}px`, height: "2px" }),
167
+ role: "img",
168
+ "aria-label": label.value
169
+ }, null, 12, _hoisted_8));
170
+ };
171
+ }
172
+ });
173
+ export {
174
+ _sfc_main as _
175
+ };
@@ -1,5 +1,6 @@
1
- import { defineComponent, computed, openBlock, createElementBlock, createElementVNode, createCommentVNode, createBlock, toDisplayString, normalizeClass, createVNode, createTextVNode } from "vue";
1
+ import { defineComponent, computed, openBlock, createElementBlock, createElementVNode, createCommentVNode, createBlock, toDisplayString, Fragment, createTextVNode, normalizeClass, createVNode, renderSlot } from "vue";
2
2
  import { _ as _sfc_main$1 } from "./LpIcon-CCnX5_2j.js";
3
+ import { _ as _sfc_main$2 } from "./LpNumberFlow-amWYaeuA.js";
3
4
  const _hoisted_1 = { class: "flex flex-col gap-2" };
4
5
  const _hoisted_2 = { class: "flex items-center gap-2" };
5
6
  const _hoisted_3 = {
@@ -10,6 +11,10 @@ const _hoisted_4 = { class: "text-[11px] font-semibold uppercase tracking-[0.08e
10
11
  const _hoisted_5 = { class: "flex items-baseline gap-2" };
11
12
  const _hoisted_6 = { class: "font-mono text-3xl font-medium tracking-tight text-ink" };
12
13
  const _hoisted_7 = {
14
+ key: 1,
15
+ class: "ml-3 self-center"
16
+ };
17
+ const _hoisted_8 = {
13
18
  key: 0,
14
19
  class: "text-sm text-muted"
15
20
  };
@@ -18,6 +23,11 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
18
23
  props: {
19
24
  label: {},
20
25
  value: {},
26
+ compact: { type: Boolean },
27
+ grouped: { type: Boolean },
28
+ decimals: { default: 0 },
29
+ prefix: {},
30
+ valueSuffix: {},
21
31
  hint: {},
22
32
  icon: {},
23
33
  online: { type: Boolean },
@@ -53,6 +63,11 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
53
63
  return props.delta;
54
64
  });
55
65
  const showDelta = computed(() => props.delta != null);
66
+ const deltaDecimals = computed(() => {
67
+ if (typeof props.delta !== "number") return 0;
68
+ const [, fraction] = String(props.delta).split(".");
69
+ return Math.min(fraction?.length ?? 0, 4);
70
+ });
56
71
  return (_ctx, _cache) => {
57
72
  return openBlock(), createElementBlock("div", _hoisted_1, [
58
73
  createElementVNode("div", _hoisted_2, [
@@ -66,7 +81,19 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
66
81
  createElementVNode("span", _hoisted_4, toDisplayString(__props.label), 1)
67
82
  ]),
68
83
  createElementVNode("div", _hoisted_5, [
69
- createElementVNode("span", _hoisted_6, toDisplayString(__props.value), 1),
84
+ createElementVNode("span", _hoisted_6, [
85
+ typeof __props.value === "number" ? (openBlock(), createBlock(_sfc_main$2, {
86
+ key: 0,
87
+ value: __props.value,
88
+ compact: __props.compact,
89
+ grouped: __props.grouped,
90
+ decimals: __props.decimals,
91
+ prefix: __props.prefix,
92
+ suffix: __props.valueSuffix
93
+ }, null, 8, ["value", "compact", "grouped", "decimals", "prefix", "suffix"])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
94
+ createTextVNode(toDisplayString(__props.value), 1)
95
+ ], 64))
96
+ ]),
70
97
  showDelta.value ? (openBlock(), createElementBlock("span", {
71
98
  key: 0,
72
99
  class: normalizeClass(["inline-flex items-center gap-0.5 text-sm font-medium tabular-nums", trendColor.value])
@@ -75,10 +102,21 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
75
102
  name: trendIcon.value,
76
103
  size: 14
77
104
  }, null, 8, ["name"]),
78
- createTextVNode(" " + toDisplayString(deltaText.value), 1)
79
- ], 2)) : createCommentVNode("", true)
105
+ typeof __props.delta === "number" ? (openBlock(), createBlock(_sfc_main$2, {
106
+ key: 0,
107
+ value: __props.delta,
108
+ decimals: deltaDecimals.value,
109
+ prefix: __props.delta > 0 ? "+" : "",
110
+ suffix: __props.deltaSuffix
111
+ }, null, 8, ["value", "decimals", "prefix", "suffix"])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
112
+ createTextVNode(toDisplayString(deltaText.value), 1)
113
+ ], 64))
114
+ ], 2)) : createCommentVNode("", true),
115
+ _ctx.$slots.trend ? (openBlock(), createElementBlock("span", _hoisted_7, [
116
+ renderSlot(_ctx.$slots, "trend")
117
+ ])) : createCommentVNode("", true)
80
118
  ]),
81
- __props.hint ? (openBlock(), createElementBlock("span", _hoisted_7, toDisplayString(__props.hint), 1)) : createCommentVNode("", true)
119
+ __props.hint ? (openBlock(), createElementBlock("span", _hoisted_8, toDisplayString(__props.hint), 1)) : createCommentVNode("", true)
82
120
  ]);
83
121
  };
84
122
  }
@@ -1,4 +1,5 @@
1
1
  import { computed, readonly, shallowRef, defineComponent, onMounted, openBlock, createBlock, withCtx, createElementVNode, normalizeClass, createElementBlock, normalizeStyle, createVNode, toDisplayString, createCommentVNode } from "vue";
2
+ import { prefersReducedMotion, easingToken } from "../composables/easing.js";
2
3
  import { _ as _sfc_main$1 } from "./LpContextMenu-D0mzXqA7.js";
3
4
  import { _ as _sfc_main$2 } from "./LpIcon-CCnX5_2j.js";
4
5
  const sharedShape = { radiusControl: 8, radiusCard: 14, radiusPill: 9999 };
@@ -383,9 +384,6 @@ function applyTheme(theme, root = document.documentElement) {
383
384
  root.style.colorScheme = theme.mode;
384
385
  detectBackdropFilter(root);
385
386
  }
386
- function prefersReducedMotion() {
387
- return typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true;
388
- }
389
387
  function applyThemeWithTransition(theme, origin, root = document.documentElement, durationMs = 480) {
390
388
  const doc = document;
391
389
  if (typeof doc.startViewTransition !== "function" || prefersReducedMotion()) {
@@ -406,7 +404,7 @@ function applyThemeWithTransition(theme, origin, root = document.documentElement
406
404
  },
407
405
  {
408
406
  duration: durationMs,
409
- easing: "cubic-bezier(0.2, 0, 0, 1)",
407
+ easing: easingToken("ease-emphasized"),
410
408
  // Hold the fully-shrunk circle after the keyframes end. Without it the
411
409
  // old snapshot snaps back to its default (un-clipped) state for the one
412
410
  // frame between animation-end and the browser tearing down the
@@ -0,0 +1,131 @@
1
+ import { defineComponent, ref, watch, onBeforeUnmount, openBlock, createBlock, unref, withCtx, createVNode, renderSlot, normalizeStyle, normalizeClass, createTextVNode, toDisplayString } from "vue";
2
+ import { TooltipProvider, TooltipRoot, TooltipTrigger, TooltipPortal, TooltipContent } from "reka-ui";
3
+ import { springStep, springSettled } from "../composables/usePillTransition.js";
4
+ import { PANEL_SURFACE, TOOLTIP_ANIM } from "../components/dropdown.js";
5
+ import '../assets/LpTooltip-B61SYd4K.css';const _sfc_main = /* @__PURE__ */ defineComponent({
6
+ __name: "LpTooltip",
7
+ props: {
8
+ content: {},
9
+ sideOffset: {},
10
+ side: { default: "top" },
11
+ label: { type: Boolean },
12
+ delay: { default: 200 },
13
+ showOnPointerFocus: { type: Boolean },
14
+ followCursor: { type: Boolean },
15
+ disabled: { type: Boolean }
16
+ },
17
+ setup(__props) {
18
+ const props = __props;
19
+ const target = { x: 0, y: 0 };
20
+ const offset = ref({ x: 0, y: 0 });
21
+ let sx = { value: 0, velocity: 0 };
22
+ let sy = { value: 0, velocity: 0 };
23
+ let frame = 0;
24
+ let lastFrame = 0;
25
+ const settle = (now) => {
26
+ const dt = lastFrame ? (now - lastFrame) / 1e3 : 1 / 60;
27
+ lastFrame = now;
28
+ frame = 0;
29
+ sx = springStep(sx, target.x, "float", dt);
30
+ sy = springStep(sy, target.y, "float", dt);
31
+ if (springSettled(sx, target.x) && springSettled(sy, target.y)) {
32
+ sx = { value: target.x, velocity: 0 };
33
+ sy = { value: target.y, velocity: 0 };
34
+ offset.value = { x: target.x, y: target.y };
35
+ lastFrame = 0;
36
+ return;
37
+ }
38
+ offset.value = { x: sx.value, y: sy.value };
39
+ frame = requestAnimationFrame(settle);
40
+ };
41
+ const onPointerMove = (e) => {
42
+ if (!props.followCursor) return;
43
+ const el = e.currentTarget;
44
+ if (!el) return;
45
+ const rect = el.getBoundingClientRect();
46
+ const halfX = props.side === "left" || props.side === "right" ? rect.width / 2 : 0;
47
+ const dirX = props.side === "right" ? -1 : 1;
48
+ target.x = e.clientX - (rect.left + rect.width / 2) + halfX * dirX;
49
+ const half = props.side === "top" || props.side === "bottom" ? rect.height / 2 : 0;
50
+ const dir = props.side === "bottom" ? -1 : 1;
51
+ target.y = e.clientY - (rect.top + rect.height / 2) + half * dir;
52
+ if (!frame) frame = requestAnimationFrame(settle);
53
+ };
54
+ const open = ref(false);
55
+ const wanted = ref(false);
56
+ const onOpenChange = (next) => {
57
+ wanted.value = next;
58
+ open.value = next && !props.disabled;
59
+ if (!open.value || !props.followCursor) return;
60
+ offset.value = { x: target.x, y: target.y };
61
+ sx = { value: target.x, velocity: 0 };
62
+ sy = { value: target.y, velocity: 0 };
63
+ lastFrame = 0;
64
+ };
65
+ watch(
66
+ () => props.disabled,
67
+ (off) => {
68
+ if (off) open.value = false;
69
+ else if (wanted.value) open.value = true;
70
+ }
71
+ );
72
+ onBeforeUnmount(() => {
73
+ if (frame) cancelAnimationFrame(frame);
74
+ });
75
+ return (_ctx, _cache) => {
76
+ return openBlock(), createBlock(unref(TooltipProvider), { "delay-duration": __props.delay }, {
77
+ default: withCtx(() => [
78
+ createVNode(unref(TooltipRoot), {
79
+ open: open.value,
80
+ "ignore-non-keyboard-focus": !__props.showOnPointerFocus,
81
+ "disable-hoverable-content": __props.followCursor,
82
+ "disable-closing-trigger": __props.followCursor,
83
+ "onUpdate:open": onOpenChange
84
+ }, {
85
+ default: withCtx(() => [
86
+ createVNode(unref(TooltipTrigger), {
87
+ "as-child": "",
88
+ "aria-label": __props.label ? __props.content : void 0,
89
+ onPointermove: onPointerMove
90
+ }, {
91
+ default: withCtx(() => [
92
+ renderSlot(_ctx.$slots, "default")
93
+ ]),
94
+ _: 3
95
+ }, 8, ["aria-label"]),
96
+ createVNode(unref(TooltipPortal), null, {
97
+ default: withCtx(() => [
98
+ createVNode(unref(TooltipContent), {
99
+ side: __props.side,
100
+ "side-offset": __props.sideOffset ?? 6,
101
+ "collision-padding": 8,
102
+ "data-lp-follow": __props.followCursor ? "" : void 0,
103
+ class: normalizeClass([
104
+ unref(PANEL_SURFACE),
105
+ unref(TOOLTIP_ANIM),
106
+ "z-(--z-tooltip) max-w-[min(20rem,90vw)] px-2.5 py-1.5 text-xs text-ink"
107
+ ]),
108
+ style: normalizeStyle(__props.followCursor ? { translate: `${offset.value.x}px ${offset.value.y}px` } : void 0)
109
+ }, {
110
+ default: withCtx(() => [
111
+ renderSlot(_ctx.$slots, "content", {}, () => [
112
+ createTextVNode(toDisplayString(__props.content), 1)
113
+ ])
114
+ ]),
115
+ _: 3
116
+ }, 8, ["side", "side-offset", "data-lp-follow", "class", "style"])
117
+ ]),
118
+ _: 3
119
+ })
120
+ ]),
121
+ _: 3
122
+ }, 8, ["open", "ignore-non-keyboard-focus", "disable-hoverable-content", "disable-closing-trigger"])
123
+ ]),
124
+ _: 3
125
+ }, 8, ["delay-duration"]);
126
+ };
127
+ }
128
+ });
129
+ export {
130
+ _sfc_main as _
131
+ };