@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.
- 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/LpChart-tNXs1ttr.js +671 -0
- package/dist/chunks/{LpConfirmDialog-CObRah9c.js → LpConfirmDialog-CjOGcj1e.js} +32 -4
- package/dist/chunks/{LpFileTree-H7Ho8uoq.js → LpFileTree-BaV7f0O7.js} +1 -1
- package/dist/chunks/{LpFileTreeNode-BEZIvxHi.js → LpFileTreeNode-BzpBa7R7.js} +5 -7
- package/dist/chunks/{LpLogViewer-B1aLUVdz.js → LpLogViewer-CcQ3s3Fp.js} +2 -1
- package/dist/chunks/{LpMapPicker-DFKrd1Ne.js → LpMapPicker-CXvoTrAe.js} +2 -4
- package/dist/chunks/{LpNotificationBell-X1M30e0X.js → LpNotificationBell-Dvu-b6Vn.js} +56 -27
- package/dist/chunks/LpNumberFlow-amWYaeuA.js +221 -0
- package/dist/chunks/LpPie-BkjWlfSw.js +525 -0
- package/dist/chunks/LpProgress-DoIP4jQR.js +198 -0
- package/dist/chunks/LpRollingText-CM5gkSeF.js +50 -0
- package/dist/chunks/LpSparkline-C6xQ2dwv.js +175 -0
- package/dist/chunks/{LpStat-CdbEFOvn.js → LpStat-CDanpzU0.js} +43 -5
- package/dist/chunks/{LpThemeSwitcher-BgpJgrnP.js → LpThemeSwitcher-7mAK6bI7.js} +2 -4
- package/dist/chunks/LpTooltip-hCLPAqMG.js +131 -0
- package/dist/chunks/LpUptimeBar-DcQ8HikG.js +152 -0
- package/dist/component-names.d.ts +1 -1
- package/dist/components/LpChart.vue.d.ts +88 -0
- package/dist/components/LpChart.vue.js +4 -0
- package/dist/components/LpConfirmDialog.vue.d.ts +18 -2
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpFileTree.vue.d.ts +1 -1
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpLightbox.vue.d.ts +1 -1
- package/dist/components/LpLogViewer.vue.d.ts +1 -1
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpMapPicker.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.d.ts +12 -0
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpNumberFlow.vue.d.ts +35 -0
- package/dist/components/LpNumberFlow.vue.js +4 -0
- package/dist/components/LpPie.vue.d.ts +134 -0
- package/dist/components/LpPie.vue.js +4 -0
- package/dist/components/LpProgress.vue.d.ts +56 -2
- package/dist/components/LpProgress.vue.js +1 -1
- package/dist/components/LpRollingText.vue.d.ts +33 -0
- package/dist/components/LpRollingText.vue.js +4 -0
- package/dist/components/LpSparkline.vue.d.ts +48 -0
- package/dist/components/LpSparkline.vue.js +4 -0
- package/dist/components/LpStat.vue.d.ts +25 -2
- package/dist/components/LpStat.vue.js +1 -1
- package/dist/components/LpThemeSwitcher.vue.js +1 -1
- package/dist/components/LpTooltip.vue.d.ts +24 -3
- package/dist/components/LpTooltip.vue.js +1 -1
- package/dist/components/LpUptimeBar.vue.js +1 -1
- package/dist/components/chart.d.ts +180 -0
- package/dist/components/chart.js +353 -0
- package/dist/composables/easing.d.ts +46 -0
- package/dist/composables/easing.js +23 -0
- package/dist/composables/usePillTransition.d.ts +13 -1
- package/dist/composables/usePillTransition.js +22 -2
- package/dist/composables/useReveal.d.ts +26 -0
- package/dist/composables/useReveal.js +45 -0
- package/dist/composables/useSettled.js +2 -2
- package/dist/composables/useShift.js +2 -4
- package/dist/index.d.ts +14 -0
- package/dist/index.js +125 -102
- package/package.json +2 -1
- package/src/component-names.ts +5 -0
- package/src/components/LpChart.vue +846 -0
- package/src/components/LpConfirmDialog.vue +49 -0
- package/src/components/LpFileTreeNode.vue +5 -9
- package/src/components/LpLogViewer.vue +2 -3
- package/src/components/LpMapPicker.vue +2 -5
- package/src/components/LpNotificationBell.vue +76 -15
- package/src/components/LpNumberFlow.vue +524 -0
- package/src/components/LpPie.vue +778 -0
- package/src/components/LpProgress.vue +283 -6
- package/src/components/LpRollingText.vue +157 -0
- package/src/components/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
|
@@ -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
|
-
_:
|
|
61
|
-
},
|
|
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-
|
|
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 (
|
|
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: "
|
|
91
|
+
{ duration: 220, easing: easingToken("ease-emphasized") }
|
|
94
92
|
).onfinish = () => done();
|
|
95
93
|
}
|
|
96
94
|
function onBranchLeave(el, done) {
|
|
97
|
-
if (
|
|
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: "
|
|
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 =
|
|
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 (
|
|
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
|
|
17
|
-
const
|
|
18
|
-
const
|
|
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
|
|
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
|
|
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
|
|
69
|
-
|
|
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[
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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",
|
|
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-
|
|
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" : "
|
|
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",
|
|
221
|
-
createElementVNode("span",
|
|
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",
|
|
254
|
+
!item.read ? (openBlock(), createElementBlock("span", _hoisted_7)) : createCommentVNode("", true)
|
|
226
255
|
]),
|
|
227
|
-
item.body ? (openBlock(), createElementBlock("span",
|
|
228
|
-
item.createdAt ? (openBlock(), createElementBlock("span",
|
|
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
|
+
};
|