@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
@@ -1,6 +1,8 @@
1
- import { defineComponent, openBlock, createBlock, withCtx, renderSlot, createVNode, createTextVNode, toDisplayString } from "vue";
1
+ import { defineComponent, computed, openBlock, createBlock, createSlots, withCtx, renderSlot, createVNode, createTextVNode, toDisplayString, createElementVNode, normalizeClass } from "vue";
2
2
  import { _ as _sfc_main$2 } from "./LpButton-5VXXCjwF.js";
3
+ import { _ as _sfc_main$3 } from "./LpIcon-CCnX5_2j.js";
3
4
  import { _ as _sfc_main$1 } from "./LpModal-D_o-56To.js";
5
+ const _hoisted_1 = { class: "inline-flex items-center gap-2.5" };
4
6
  const _sfc_main = /* @__PURE__ */ defineComponent({
5
7
  __name: "LpConfirmDialog",
6
8
  props: {
@@ -10,6 +12,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
10
12
  confirmLabel: { default: "Confirm" },
11
13
  cancelLabel: { default: "Cancel" },
12
14
  danger: { type: Boolean },
15
+ icon: {},
13
16
  loading: { type: Boolean },
14
17
  loadingLabel: { default: "Working…" }
15
18
  },
@@ -17,6 +20,10 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
17
20
  setup(__props, { emit: __emit }) {
18
21
  const props = __props;
19
22
  const emit = __emit;
23
+ const marker = computed(() => {
24
+ if (props.icon !== void 0) return props.icon;
25
+ return props.danger ? "lucide:triangle-alert" : "";
26
+ });
20
27
  function confirm() {
21
28
  emit("confirm");
22
29
  if (!props.loading) emit("update:open", false);
@@ -31,7 +38,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
31
38
  title: __props.title,
32
39
  description: __props.description,
33
40
  "onUpdate:open": _cache[0] || (_cache[0] = (v) => _ctx.$emit("update:open", v))
34
- }, {
41
+ }, createSlots({
35
42
  footer: withCtx(() => [
36
43
  createVNode(_sfc_main$2, {
37
44
  variant: "ghost",
@@ -57,8 +64,29 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
57
64
  default: withCtx(() => [
58
65
  renderSlot(_ctx.$slots, "default")
59
66
  ]),
60
- _: 3
61
- }, 8, ["open", "title", "description"]);
67
+ _: 2
68
+ }, [
69
+ marker.value ? {
70
+ name: "title",
71
+ fn: withCtx(() => [
72
+ createElementVNode("span", _hoisted_1, [
73
+ createElementVNode("span", {
74
+ class: normalizeClass([
75
+ "flex size-9 shrink-0 items-center justify-center rounded-full",
76
+ __props.danger ? "border border-danger/30 bg-danger-soft text-danger" : "border border-line bg-surface-soft text-muted-strong"
77
+ ])
78
+ }, [
79
+ createVNode(_sfc_main$3, {
80
+ name: marker.value,
81
+ size: 20
82
+ }, null, 8, ["name"])
83
+ ], 2),
84
+ createTextVNode(" " + toDisplayString(__props.title), 1)
85
+ ])
86
+ ]),
87
+ key: "0"
88
+ } : void 0
89
+ ]), 1032, ["open", "title", "description"]);
62
90
  };
63
91
  }
64
92
  });
@@ -1,7 +1,7 @@
1
1
  import { defineComponent, ref, watch, computed, openBlock, createElementBlock, Fragment, renderList, normalizeStyle, createBlock, createVNode, withCtx, createElementVNode, renderSlot, mergeProps, normalizeProps, guardReactiveProps, createTextVNode, toDisplayString, createCommentVNode } from "vue";
2
2
  import { computeStats, formatSize, sortNodes, subtreeIds, ancestorIds } from "../components/fileTree.js";
3
3
  import { _ as _sfc_main$1 } from "./LpEmptyState-BV3q7Qyi.js";
4
- import { _ as _sfc_main$3 } from "./LpFileTreeNode-BEZIvxHi.js";
4
+ import { _ as _sfc_main$3 } from "./LpFileTreeNode-BzpBa7R7.js";
5
5
  import { _ as _sfc_main$2 } from "./LpScrollArea-D8xyKXcF.js";
6
6
  const _hoisted_1 = {
7
7
  key: 0,
@@ -1,4 +1,5 @@
1
1
  import { defineComponent, computed, resolveComponent, openBlock, createElementBlock, createVNode, withCtx, createElementVNode, withModifiers, normalizeStyle, normalizeClass, createBlock, createCommentVNode, unref, renderSlot, createTextVNode, toDisplayString, Transition, Fragment, renderList, mergeProps } from "vue";
2
+ import { prefersReducedMotion, easingToken } from "../composables/easing.js";
2
3
  import { sortNodes, formatSize, formatModified, fileIcon } from "../components/fileTree.js";
3
4
  import { _ as _sfc_main$3 } from "./LpCheckbox-BO0zuuPh.js";
4
5
  import { _ as _sfc_main$1 } from "./LpContextMenu-D0mzXqA7.js";
@@ -79,29 +80,26 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
79
80
  function rowDelay(index) {
80
81
  return `${Math.min(index * 18, 180)}ms`;
81
82
  }
82
- function reducedMotion() {
83
- return window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
84
- }
85
83
  function onBranchEnter(el, done) {
86
- if (reducedMotion()) return done();
84
+ if (prefersReducedMotion()) return done();
87
85
  const height = el.scrollHeight;
88
86
  el.animate(
89
87
  [
90
88
  { height: "0px", opacity: 0 },
91
89
  { height: `${height}px`, opacity: 1 }
92
90
  ],
93
- { duration: 220, easing: "cubic-bezier(0.2, 0, 0, 1)" }
91
+ { duration: 220, easing: easingToken("ease-emphasized") }
94
92
  ).onfinish = () => done();
95
93
  }
96
94
  function onBranchLeave(el, done) {
97
- if (reducedMotion()) return done();
95
+ if (prefersReducedMotion()) return done();
98
96
  const height = el.scrollHeight;
99
97
  el.animate(
100
98
  [
101
99
  { height: `${height}px`, opacity: 1 },
102
100
  { height: "0px", opacity: 0 }
103
101
  ],
104
- { duration: 180, easing: "cubic-bezier(0.2, 0, 0, 1)" }
102
+ { duration: 180, easing: easingToken("ease-emphasized") }
105
103
  ).onfinish = () => done();
106
104
  }
107
105
  function onActivate() {
@@ -1,4 +1,5 @@
1
1
  import { defineComponent, computed, ref, watch, nextTick, onMounted, onBeforeUnmount, openBlock, createElementBlock, createVNode, normalizeStyle, withCtx, createElementVNode, toDisplayString, createCommentVNode, Fragment, createBlock, resolveDynamicComponent, TransitionGroup, mergeProps, renderList, normalizeClass, createTextVNode, Transition } from "vue";
2
+ import { prefersReducedMotion } from "../composables/easing.js";
2
3
  import { _ as _sfc_main$3 } from "./LpContextMenu-D0mzXqA7.js";
3
4
  import { _ as _sfc_main$2 } from "./LpIcon-CCnX5_2j.js";
4
5
  import { _ as _sfc_main$1 } from "./LpScrollArea-D8xyKXcF.js";
@@ -295,7 +296,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
295
296
  function rideToBottom(smooth = true, force = false) {
296
297
  const el = viewport();
297
298
  if (!el) return;
298
- const reduce = typeof window !== "undefined" && window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
299
+ const reduce = prefersReducedMotion();
299
300
  const far = !force && el.scrollHeight - el.scrollTop - el.clientHeight > el.clientHeight;
300
301
  el.scrollTo({ top: el.scrollHeight, behavior: smooth && !reduce && !far ? "smooth" : "auto" });
301
302
  }
@@ -1,4 +1,5 @@
1
1
  import { defineComponent, computed, ref, watch, onMounted, onBeforeUnmount, openBlock, createElementBlock, normalizeStyle, createElementVNode, normalizeClass, Fragment, renderList, createVNode, createCommentVNode, createBlock, toDisplayString } from "vue";
2
+ import { prefersReducedMotion } from "../composables/easing.js";
2
3
  import { OSM_TILES, project, tileUrl, unproject } from "../components/address.js";
3
4
  import { _ as _sfc_main$1 } from "./LpIcon-CCnX5_2j.js";
4
5
  const _hoisted_1 = ["tabindex", "aria-label"];
@@ -211,7 +212,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
211
212
  glide();
212
213
  }
213
214
  function glide() {
214
- if (reducedMotion() || Math.hypot(velocity.x, velocity.y) < 0.05) {
215
+ if (prefersReducedMotion() || Math.hypot(velocity.x, velocity.y) < 0.05) {
215
216
  velocity = { x: 0, y: 0 };
216
217
  return;
217
218
  }
@@ -230,9 +231,6 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
230
231
  };
231
232
  glideFrame = requestAnimationFrame(step);
232
233
  }
233
- function reducedMotion() {
234
- return typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
235
- }
236
234
  function panBy(dx, dy) {
237
235
  mapCenter.value = unproject(
238
236
  origin.value.x + dx + viewWidth.value / 2,
@@ -1,4 +1,4 @@
1
- import { defineComponent, computed, useModel, openBlock, createBlock, withCtx, createElementVNode, toDisplayString, createElementBlock, createCommentVNode, createVNode, Fragment, renderList, resolveDynamicComponent, normalizeClass, createTextVNode, mergeModels } from "vue";
1
+ import { defineComponent, computed, useModel, getCurrentInstance, openBlock, createBlock, withCtx, createElementVNode, toDisplayString, createElementBlock, createCommentVNode, createVNode, Fragment, renderList, resolveDynamicComponent, normalizeClass, createTextVNode, mergeModels } from "vue";
2
2
  import { _ as _sfc_main$8 } from "./LpBadge-BPodVcJa.js";
3
3
  import { _ as _sfc_main$7 } from "./LpButton-5VXXCjwF.js";
4
4
  import { _ as _sfc_main$5 } from "./LpContextMenu-D0mzXqA7.js";
@@ -9,21 +9,22 @@ import { _ as _sfc_main$4 } from "./LpScrollArea-D8xyKXcF.js";
9
9
  import { _ as _sfc_main$2 } from "./LpShift-DXNsauQZ.js";
10
10
  const _hoisted_1 = { class: "flex items-center justify-between gap-2 px-3 py-2.5" };
11
11
  const _hoisted_2 = { class: "text-sm font-semibold" };
12
- const _hoisted_3 = {
12
+ const _hoisted_3 = { class: "flex items-center gap-0.5" };
13
+ const _hoisted_4 = {
13
14
  key: 0,
14
15
  class: "space-y-1.5 px-2 pb-2"
15
16
  };
16
- const _hoisted_4 = { class: "min-w-0 flex-1" };
17
- const _hoisted_5 = { class: "flex items-center gap-1.5" };
18
- const _hoisted_6 = {
17
+ const _hoisted_5 = { class: "min-w-0 flex-1" };
18
+ const _hoisted_6 = { class: "flex items-center gap-1.5" };
19
+ const _hoisted_7 = {
19
20
  key: 0,
20
21
  class: "size-1.5 shrink-0 rounded-full bg-brand"
21
22
  };
22
- const _hoisted_7 = {
23
+ const _hoisted_8 = {
23
24
  key: 0,
24
25
  class: "mt-0.5 line-clamp-2 block text-xs text-muted"
25
26
  };
26
- const _hoisted_8 = {
27
+ const _hoisted_9 = {
27
28
  key: 1,
28
29
  class: "mt-0.5 block text-[11px] text-muted/70"
29
30
  };
@@ -43,12 +44,13 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
43
44
  "open": { type: Boolean, ...{ default: false } },
44
45
  "openModifiers": {}
45
46
  }),
46
- emits: /* @__PURE__ */ mergeModels(["markRead", "markUnread", "markAllRead", "dismiss", "select"], ["update:open"]),
47
+ emits: /* @__PURE__ */ mergeModels(["markRead", "markUnread", "markAllRead", "clear", "dismiss", "select"], ["update:open"]),
47
48
  setup(__props, { emit: __emit }) {
48
49
  const props = __props;
49
50
  const l = computed(() => ({
50
51
  title: props.title ?? props.labels?.title ?? "Notifications",
51
52
  markAllRead: props.labels?.markAllRead ?? "Mark all read",
53
+ clear: props.labels?.clear ?? "Clear",
52
54
  markAsRead: props.labels?.markAsRead ?? "Mark as read",
53
55
  markAsUnread: props.labels?.markAsUnread ?? "Mark as unread",
54
56
  open: props.labels?.open ?? "Open",
@@ -65,16 +67,35 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
65
67
  () => unread.value > props.maxBadge ? `${props.maxBadge}+` : String(unread.value)
66
68
  );
67
69
  const hasUnread = computed(() => unread.value > 0);
68
- const contextMenu = computed(
69
- () => props.menuItems ?? [
70
+ const TONE_CLASS = {
71
+ brand: "bg-brand-soft text-brand",
72
+ action: "bg-action-soft text-action",
73
+ danger: "bg-danger-soft text-danger",
74
+ accent: "bg-accent-soft text-accent",
75
+ muted: "bg-surface-soft text-muted-strong"
76
+ };
77
+ const instance = getCurrentInstance();
78
+ const handlesClear = computed(() => Boolean(instance?.vnode.props?.onClear));
79
+ const contextMenu = computed(() => {
80
+ if (props.menuItems) return props.menuItems;
81
+ const items = [
70
82
  {
71
83
  label: l.value.markAllRead,
72
84
  icon: "lucide:check-check",
73
85
  disabled: !hasUnread.value,
74
86
  onSelect: () => emit("markAllRead")
75
87
  }
76
- ]
77
- );
88
+ ];
89
+ if (handlesClear.value) {
90
+ items.push({
91
+ label: l.value.clear,
92
+ icon: "lucide:trash-2",
93
+ disabled: !props.items.length,
94
+ onSelect: () => emit("clear")
95
+ });
96
+ }
97
+ return items;
98
+ });
78
99
  function onSelect(item) {
79
100
  if (!item.read) emit("markRead", item.id);
80
101
  emit("select", item);
@@ -128,7 +149,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
128
149
  return (_ctx, _cache) => {
129
150
  return openBlock(), createBlock(_sfc_main$1, {
130
151
  open: open.value,
131
- "onUpdate:open": _cache[2] || (_cache[2] = ($event) => open.value = $event),
152
+ "onUpdate:open": _cache[3] || (_cache[3] = ($event) => open.value = $event),
132
153
  side: "bottom",
133
154
  align: "end",
134
155
  "side-offset": 8,
@@ -169,16 +190,24 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
169
190
  default: withCtx(() => [
170
191
  createElementVNode("header", _hoisted_1, [
171
192
  createElementVNode("span", _hoisted_2, toDisplayString(l.value.title), 1),
172
- hasUnread.value ? (openBlock(), createElementBlock("button", {
173
- key: 0,
174
- type: "button",
175
- class: "rounded-md px-1.5 py-0.5 text-xs font-medium text-brand outline-none transition-colors hover:bg-brand-soft focus-visible:bg-brand-soft",
176
- onClick: _cache[1] || (_cache[1] = ($event) => emit("markAllRead"))
177
- }, toDisplayString(l.value.markAllRead), 1)) : createCommentVNode("", true)
193
+ createElementVNode("span", _hoisted_3, [
194
+ __props.items.length && handlesClear.value ? (openBlock(), createElementBlock("button", {
195
+ key: 0,
196
+ type: "button",
197
+ class: "rounded-md px-1.5 py-0.5 text-xs font-medium text-muted outline-none transition-colors hover:bg-surface-soft hover:text-ink focus-visible:bg-surface-soft",
198
+ onClick: _cache[1] || (_cache[1] = ($event) => emit("clear"))
199
+ }, toDisplayString(l.value.clear), 1)) : createCommentVNode("", true),
200
+ hasUnread.value ? (openBlock(), createElementBlock("button", {
201
+ key: 1,
202
+ type: "button",
203
+ class: "rounded-md px-1.5 py-0.5 text-xs font-medium text-brand outline-none transition-colors hover:bg-brand-soft focus-visible:bg-brand-soft",
204
+ onClick: _cache[2] || (_cache[2] = ($event) => emit("markAllRead"))
205
+ }, toDisplayString(l.value.markAllRead), 1)) : createCommentVNode("", true)
206
+ ])
178
207
  ]),
179
208
  createVNode(_sfc_main$2, { axis: "height" }, {
180
209
  default: withCtx(() => [
181
- __props.loading ? (openBlock(), createElementBlock("div", _hoisted_3, [
210
+ __props.loading ? (openBlock(), createElementBlock("div", _hoisted_4, [
182
211
  (openBlock(), createElementBlock(Fragment, null, renderList(4, (i) => {
183
212
  return createElementVNode("div", {
184
213
  key: i,
@@ -205,27 +234,27 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
205
234
  (openBlock(), createBlock(resolveDynamicComponent(item.link ? "a" : "button"), {
206
235
  href: item.link || void 0,
207
236
  type: item.link ? void 0 : "button",
208
- class: normalizeClass(["group/notif flex w-full items-start gap-2.5 rounded-control px-2 py-2 text-left outline-none transition-colors hover:bg-white/[0.05] focus-visible:bg-white/[0.05]", item.read ? "" : "bg-brand-soft/35"]),
237
+ class: normalizeClass(["group/notif flex w-full items-start gap-2.5 rounded-control px-2 py-2 text-left outline-none transition-colors hover:bg-surface-soft focus-visible:bg-surface-soft", item.read ? "" : "bg-brand-soft/35"]),
209
238
  onClick: ($event) => onSelect(item)
210
239
  }, {
211
240
  default: withCtx(() => [
212
241
  createElementVNode("span", {
213
- class: normalizeClass(["grid size-7 shrink-0 place-items-center rounded-control", item.read ? "bg-surface-soft text-muted" : "bg-brand-soft text-brand"])
242
+ class: normalizeClass(["grid size-7 shrink-0 place-items-center rounded-control", item.read ? "bg-surface-soft text-muted" : TONE_CLASS[item.tone ?? "brand"]])
214
243
  }, [
215
244
  createVNode(_sfc_main$6, {
216
245
  name: item.icon || "lucide:bell",
217
246
  size: 15
218
247
  }, null, 8, ["name"])
219
248
  ], 2),
220
- createElementVNode("span", _hoisted_4, [
221
- createElementVNode("span", _hoisted_5, [
249
+ createElementVNode("span", _hoisted_5, [
250
+ createElementVNode("span", _hoisted_6, [
222
251
  createElementVNode("span", {
223
252
  class: normalizeClass(["truncate text-sm font-medium", item.read ? "text-muted-strong" : "text-ink"])
224
253
  }, toDisplayString(item.title), 3),
225
- !item.read ? (openBlock(), createElementBlock("span", _hoisted_6)) : createCommentVNode("", true)
254
+ !item.read ? (openBlock(), createElementBlock("span", _hoisted_7)) : createCommentVNode("", true)
226
255
  ]),
227
- item.body ? (openBlock(), createElementBlock("span", _hoisted_7, toDisplayString(item.body), 1)) : createCommentVNode("", true),
228
- item.createdAt ? (openBlock(), createElementBlock("span", _hoisted_8, toDisplayString(timeAgo(item.createdAt)), 1)) : createCommentVNode("", true)
256
+ item.body ? (openBlock(), createElementBlock("span", _hoisted_8, toDisplayString(item.body), 1)) : createCommentVNode("", true),
257
+ item.createdAt ? (openBlock(), createElementBlock("span", _hoisted_9, toDisplayString(timeAgo(item.createdAt)), 1)) : createCommentVNode("", true)
229
258
  ])
230
259
  ]),
231
260
  _: 2
@@ -0,0 +1,221 @@
1
+ import { defineComponent, ref, watch, onBeforeUnmount, computed, openBlock, createElementBlock, toDisplayString, createCommentVNode, createElementVNode, createVNode, TransitionGroup, withCtx, Fragment, renderList, normalizeClass, normalizeStyle, createTextVNode } from "vue";
2
+ import { prefersReducedMotion, easeOut } from "../composables/easing.js";
3
+ import { useReveal } from "../composables/useReveal.js";
4
+ import '../assets/LpNumberFlow-rZkwQwlU.css';const _hoisted_1 = ["aria-label"];
5
+ const _hoisted_2 = {
6
+ key: 0,
7
+ "aria-hidden": "true"
8
+ };
9
+ const _hoisted_3 = {
10
+ class: "lp-nf__track inline-flex items-baseline",
11
+ "aria-hidden": "true"
12
+ };
13
+ const _hoisted_4 = {
14
+ key: 1,
15
+ "aria-hidden": "true"
16
+ };
17
+ const _sfc_main = /* @__PURE__ */ defineComponent({
18
+ __name: "LpNumberFlow",
19
+ props: {
20
+ value: {},
21
+ duration: { default: 700 },
22
+ decimals: { default: 0 },
23
+ compact: { type: Boolean },
24
+ grouped: { type: Boolean },
25
+ prefix: {},
26
+ suffix: {},
27
+ format: {},
28
+ animateOnMount: { type: Boolean, default: true },
29
+ locale: {},
30
+ trend: { default: "auto" }
31
+ },
32
+ setup(__props) {
33
+ const props = __props;
34
+ const displayed = ref(props.animateOnMount ? 0 : props.value);
35
+ let frame = 0;
36
+ const padTo = ref(0);
37
+ const settled = ref(true);
38
+ const integerDigits = (v) => {
39
+ const abs = Math.floor(Math.abs(v));
40
+ return abs === 0 ? 1 : Math.floor(Math.log10(abs)) + 1;
41
+ };
42
+ const stop = () => {
43
+ if (frame) cancelAnimationFrame(frame);
44
+ frame = 0;
45
+ };
46
+ const decimalsOf = (v) => {
47
+ if (Number.isInteger(v)) return 0;
48
+ const s = String(v);
49
+ const dot = s.indexOf(".");
50
+ if (dot === -1) return 0;
51
+ return Math.min(6, s.length - dot - 1);
52
+ };
53
+ const journeyDecimals = ref(0);
54
+ let lastTarget = props.animateOnMount ? 0 : props.value;
55
+ const round = (v) => {
56
+ const p = journeyDecimals.value;
57
+ if (p <= 0) return Math.round(v);
58
+ const f = 10 ** p;
59
+ return Math.round(v * f) / f;
60
+ };
61
+ const travel = (to) => {
62
+ stop();
63
+ const from = displayed.value;
64
+ if (from === to) return;
65
+ if (prefersReducedMotion() || props.duration <= 0) {
66
+ displayed.value = to;
67
+ return;
68
+ }
69
+ padTo.value = props.compact ? 0 : Math.max(padTo.value, integerDigits(from), integerDigits(to));
70
+ journeyDecimals.value = Math.max(
71
+ props.decimals,
72
+ decimalsOf(lastTarget),
73
+ decimalsOf(to)
74
+ );
75
+ lastTarget = to;
76
+ settled.value = false;
77
+ const started = performance.now();
78
+ const step = (now) => {
79
+ const t = Math.min(1, (now - started) / props.duration);
80
+ displayed.value = from + (to - from) * easeOut(t);
81
+ if (t < 1) frame = requestAnimationFrame(step);
82
+ else {
83
+ displayed.value = to;
84
+ settled.value = true;
85
+ padTo.value = 0;
86
+ frame = 0;
87
+ }
88
+ };
89
+ frame = requestAnimationFrame(step);
90
+ };
91
+ const { el: revealAnchor, revealed } = useReveal({ immediate: () => !props.animateOnMount });
92
+ watch(revealed, (visible) => {
93
+ if (visible && props.animateOnMount) travel(props.value);
94
+ }, { immediate: true });
95
+ watch(() => props.value, travel);
96
+ onBeforeUnmount(stop);
97
+ const UNITS = [
98
+ { limit: 1e9, suffix: "B" },
99
+ { limit: 1e6, suffix: "M" },
100
+ { limit: 1e3, suffix: "k" }
101
+ ];
102
+ const scaleFor = (v) => {
103
+ const abs = Math.abs(v);
104
+ const unit = UNITS.find((u) => abs >= u.limit);
105
+ if (!unit) return { divisor: 1, suffix: "", decimals: 0 };
106
+ const scaled = v / unit.limit;
107
+ return {
108
+ divisor: unit.limit,
109
+ suffix: unit.suffix,
110
+ decimals: Number.isInteger(scaled) ? 0 : 1
111
+ };
112
+ };
113
+ const formatted = computed(() => {
114
+ const v = round(displayed.value);
115
+ if (props.format) return props.format(v);
116
+ if (props.compact) {
117
+ const { divisor, suffix } = scaleFor(v);
118
+ const places = divisor === 1 ? 0 : 1;
119
+ return `${(v / divisor).toFixed(places)}${suffix}`;
120
+ }
121
+ const pad = settled.value ? 1 : Math.max(1, padTo.value);
122
+ if (props.grouped)
123
+ return v.toLocaleString(props.locale, {
124
+ minimumFractionDigits: props.decimals,
125
+ maximumFractionDigits: props.decimals,
126
+ minimumIntegerDigits: pad
127
+ });
128
+ const text = v.toFixed(props.decimals);
129
+ if (pad <= 1) return text;
130
+ const neg = text.startsWith("-");
131
+ const body = neg ? text.slice(1) : text;
132
+ const dot = body.indexOf(".");
133
+ const int = dot === -1 ? body : body.slice(0, dot);
134
+ const rest = dot === -1 ? "" : body.slice(dot);
135
+ return `${neg ? "-" : ""}${int.padStart(pad, "0")}${rest}`;
136
+ });
137
+ const cells = computed(() => {
138
+ const chars = [...formatted.value];
139
+ const dot = chars.indexOf(".");
140
+ const intEnd = dot === -1 ? chars.length : dot;
141
+ const isDigit = (c) => {
142
+ const code = c.charCodeAt(0);
143
+ return code >= 48 && code <= 57;
144
+ };
145
+ let suffixStart = chars.length;
146
+ while (suffixStart > 0 && !isDigit(chars[suffixStart - 1])) suffixStart--;
147
+ const keys = new Array(chars.length);
148
+ let place = 0;
149
+ for (let i = intEnd - 1; i >= 0; i--) {
150
+ keys[i] = isDigit(chars[i]) ? `i${place++}` : `s${place}-${chars[i]}`;
151
+ }
152
+ place = 0;
153
+ for (let i = intEnd; i < chars.length; i++) {
154
+ keys[i] = isDigit(chars[i]) ? `f${place++}` : `t${place}-${chars[i]}`;
155
+ }
156
+ for (let i = suffixStart; i < chars.length; i++) keys[i] = `u${i - suffixStart}`;
157
+ let leading = !settled.value;
158
+ return chars.map((char, i) => {
159
+ const digit = isDigit(char) ? char.charCodeAt(0) - 48 : null;
160
+ const isPad = leading && digit === 0 && i < intEnd - 1;
161
+ if (digit !== null && digit !== 0) leading = false;
162
+ return { key: keys[i], digit, char, isPad };
163
+ });
164
+ });
165
+ const DIGITS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
166
+ const STRIP = [...DIGITS, ...DIGITS];
167
+ const offsetFor = (digit) => {
168
+ if (props.trend === "up") return digit;
169
+ if (props.trend === "down") return digit + 10;
170
+ return digit;
171
+ };
172
+ const label = computed(
173
+ () => `${props.prefix ?? ""}${props.value}${props.suffix ?? ""}`
174
+ );
175
+ return (_ctx, _cache) => {
176
+ return openBlock(), createElementBlock("span", {
177
+ ref_key: "revealAnchor",
178
+ ref: revealAnchor,
179
+ class: "lp-nf inline-flex items-baseline tabular-nums",
180
+ "aria-label": label.value,
181
+ role: "text"
182
+ }, [
183
+ __props.prefix ? (openBlock(), createElementBlock("span", _hoisted_2, toDisplayString(__props.prefix), 1)) : createCommentVNode("", true),
184
+ createElementVNode("span", _hoisted_3, [
185
+ createVNode(TransitionGroup, { name: "lp-nf-cell" }, {
186
+ default: withCtx(() => [
187
+ (openBlock(true), createElementBlock(Fragment, null, renderList(cells.value, (cell) => {
188
+ return openBlock(), createElementBlock("span", {
189
+ key: cell.key,
190
+ class: normalizeClass(["lp-nf__cell", cell.isPad ? "lp-nf__cell--pad" : ""])
191
+ }, [
192
+ cell.digit !== null ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
193
+ createElementVNode("span", {
194
+ class: "lp-nf__strip",
195
+ style: normalizeStyle({ transform: `translateY(${-offsetFor(cell.digit) * 5}%)` })
196
+ }, [
197
+ (openBlock(), createElementBlock(Fragment, null, renderList(STRIP, (d, i) => {
198
+ return createElementVNode("span", {
199
+ key: i,
200
+ class: "lp-nf__digit"
201
+ }, toDisplayString(d), 1);
202
+ }), 64))
203
+ ], 4),
204
+ _cache[0] || (_cache[0] = createElementVNode("span", { class: "lp-nf__ghost" }, "0", -1))
205
+ ], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [
206
+ createTextVNode(toDisplayString(cell.char), 1)
207
+ ], 64))
208
+ ], 2);
209
+ }), 128))
210
+ ]),
211
+ _: 1
212
+ })
213
+ ]),
214
+ __props.suffix ? (openBlock(), createElementBlock("span", _hoisted_4, toDisplayString(__props.suffix), 1)) : createCommentVNode("", true)
215
+ ], 8, _hoisted_1);
216
+ };
217
+ }
218
+ });
219
+ export {
220
+ _sfc_main as _
221
+ };