@icen.ai/ui 0.5.0 → 0.8.1
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/AGENTS.md +262 -0
- package/README.md +52 -13
- package/dist/base.css +33 -5
- package/dist/behaviors/accordion.d.mts +7 -0
- package/dist/behaviors/accordion.mjs +37 -11
- package/dist/behaviors/ai-chat.d.mts +59 -0
- package/dist/behaviors/ai-chat.mjs +580 -0
- package/dist/behaviors/ai-composer.d.mts +137 -0
- package/dist/behaviors/ai-composer.mjs +2114 -0
- package/dist/behaviors/ai-core.d.mts +156 -0
- package/dist/behaviors/ai-core.mjs +504 -0
- package/dist/behaviors/ai-diff.d.mts +53 -0
- package/dist/behaviors/ai-diff.mjs +413 -0
- package/dist/behaviors/ai-panel.d.mts +101 -0
- package/dist/behaviors/ai-panel.mjs +1087 -0
- package/dist/behaviors/ai-provider.d.mts +198 -0
- package/dist/behaviors/ai-provider.mjs +1061 -0
- package/dist/behaviors/ai-tool.d.mts +26 -0
- package/dist/behaviors/ai-tool.mjs +2173 -0
- package/dist/behaviors/ai-tools.d.mts +84 -0
- package/dist/behaviors/ai-tools.mjs +1762 -0
- package/dist/behaviors/back-top.d.mts +20 -0
- package/dist/behaviors/back-top.mjs +64 -32
- package/dist/behaviors/carousel.d.mts +7 -0
- package/dist/behaviors/carousel.mjs +72 -27
- package/dist/behaviors/charts.d.mts +296 -0
- package/dist/behaviors/charts.mjs +1364 -440
- package/dist/behaviors/command-palette.d.mts +42 -0
- package/dist/behaviors/command-palette.mjs +167 -27
- package/dist/behaviors/context-menu.d.mts +53 -0
- package/dist/behaviors/context-menu.mjs +127 -39
- package/dist/behaviors/controls.d.mts +59 -0
- package/dist/behaviors/controls.mjs +601 -0
- package/dist/behaviors/copy.d.mts +7 -0
- package/dist/behaviors/copy.mjs +41 -15
- package/dist/behaviors/datatable.d.mts +90 -0
- package/dist/behaviors/datatable.mjs +279 -53
- package/dist/behaviors/date-picker.d.mts +4 -0
- package/dist/behaviors/date-picker.mjs +276 -69
- package/dist/behaviors/dropdown.d.mts +8 -0
- package/dist/behaviors/dropdown.mjs +171 -28
- package/dist/behaviors/events.d.mts +330 -0
- package/dist/behaviors/events.mjs +208 -0
- package/dist/behaviors/input.d.mts +4 -0
- package/dist/behaviors/input.mjs +52 -32
- package/dist/behaviors/modal.d.mts +30 -0
- package/dist/behaviors/modal.mjs +87 -16
- package/dist/behaviors/nav.d.mts +7 -0
- package/dist/behaviors/nav.mjs +57 -15
- package/dist/behaviors/notification.d.mts +188 -0
- package/dist/behaviors/notification.mjs +274 -140
- package/dist/behaviors/popover.d.mts +82 -0
- package/dist/behaviors/popover.mjs +92 -5
- package/dist/behaviors/select.d.mts +6 -0
- package/dist/behaviors/select.mjs +287 -45
- package/dist/behaviors/sidebar.d.mts +7 -0
- package/dist/behaviors/sidebar.mjs +44 -13
- package/dist/behaviors/slider.d.mts +7 -0
- package/dist/behaviors/slider.mjs +55 -43
- package/dist/behaviors/split-pane.d.mts +8 -0
- package/dist/behaviors/split-pane.mjs +70 -29
- package/dist/behaviors/tabs.d.mts +9 -0
- package/dist/behaviors/tabs.mjs +102 -26
- package/dist/behaviors/tag-input.d.mts +7 -0
- package/dist/behaviors/tag-input.mjs +66 -17
- package/dist/behaviors/theme.d.mts +36 -0
- package/dist/behaviors/theme.mjs +51 -5
- package/dist/behaviors/toast.d.mts +48 -0
- package/dist/behaviors/toast.mjs +98 -25
- package/dist/behaviors/tree.d.mts +7 -0
- package/dist/behaviors/tree.mjs +39 -15
- package/dist/behaviors/upload.d.mts +4 -0
- package/dist/behaviors/upload.mjs +79 -44
- package/dist/cli.mjs +1 -1
- package/dist/components/ai-chat.css +1037 -0
- package/dist/components/ai-chat.mjs +3 -0
- package/dist/components/ai-composer.mjs +3 -0
- package/dist/components/ai-context.mjs +2 -0
- package/dist/components/ai-diff.css +311 -0
- package/dist/components/ai-diff.mjs +3 -0
- package/dist/components/ai-files.mjs +3 -0
- package/dist/components/ai-message.mjs +3 -0
- package/dist/components/ai-panel.css +682 -0
- package/dist/components/ai-reasoning.mjs +3 -0
- package/dist/components/ai-subagent.mjs +2 -0
- package/dist/components/ai-todo.mjs +2 -0
- package/dist/components/ai-tool-call.mjs +2 -0
- package/dist/components/ai-tool.css +532 -0
- package/dist/components/ai-usage.mjs +3 -0
- package/dist/components/badge.css +5 -9
- package/dist/components/breadcrumb.css +0 -36
- package/dist/components/btn.css +4 -7
- package/dist/components/card.css +3 -6
- package/dist/components/chart-bar.css +10 -0
- package/dist/components/chart-calendar.mjs +4 -0
- package/dist/components/chart-gauge.css +1 -1
- package/dist/components/chart-heatmap.css +40 -0
- package/dist/components/chart-pie.css +0 -6
- package/dist/components/chart-scatter.css +24 -0
- package/dist/components/chart-scatter.mjs +4 -0
- package/dist/components/charts.css +87 -2
- package/dist/components/charts.mjs +1 -0
- package/dist/components/command-palette.css +2 -2
- package/dist/components/content.css +11 -25
- package/dist/components/copy.css +8 -8
- package/dist/components/datatable.css +5 -9
- package/dist/components/date-picker.css +4 -8
- package/dist/components/empty.css +0 -4
- package/dist/components/feedback.css +10 -17
- package/dist/components/form.css +13 -14
- package/dist/components/input.css +2 -2
- package/dist/components/layout.css +36 -36
- package/dist/components/media.css +12 -8
- package/dist/components/menu.css +2 -2
- package/dist/components/modal.css +1 -1
- package/dist/components/nav.css +11 -6
- package/dist/components/notification.css +27 -28
- package/dist/components/pagination.css +4 -2
- package/dist/components/pagination.mjs +1 -0
- package/dist/components/panel.css +16 -16
- package/dist/components/pill.css +15 -15
- package/dist/components/rating.mjs +1 -0
- package/dist/components/segmented.css +14 -6
- package/dist/components/segmented.mjs +1 -0
- package/dist/components/select.css +79 -24
- package/dist/components/sidebar.css +1 -1
- package/dist/components/split-pane.css +0 -1
- package/dist/components/stat.css +14 -18
- package/dist/components/steps.css +15 -5
- package/dist/components/steps.mjs +1 -0
- package/dist/components/switch.css +11 -13
- package/dist/components/switch.mjs +1 -0
- package/dist/components/table.css +46 -47
- package/dist/components/table.mjs +1 -0
- package/dist/components/tabs.css +57 -45
- package/dist/components/tag-input.css +5 -10
- package/dist/components/toast.css +16 -13
- package/dist/components/tooltip.css +2 -2
- package/dist/components/upload.css +0 -15
- package/dist/index.d.mts +39 -0
- package/dist/index.mjs +11243 -3312
- package/dist/registry.json +147 -9
- package/dist/retro-effects.css +23 -32
- package/dist/tokens/colors.css +4 -1
- package/dist/tokens/retro-effects.css +23 -32
- package/dist/tokens/style-profiles.css +24 -2
- package/dist/tokens/typography.css +2 -0
- package/dist/tokens.css +30 -3
- package/dist/ui.css +3420 -645
- package/docs/spec/ai-native.md +662 -0
- package/package.json +15 -5
|
@@ -42,37 +42,52 @@ function syncContainerPosition(el) {
|
|
|
42
42
|
el.dataset.position = cfg.position;
|
|
43
43
|
}
|
|
44
44
|
var DEFAULT_PERSIST_KEY = "icen.ui.notifications";
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (
|
|
45
|
+
var knownPersistKeys = /* @__PURE__ */ new Set();
|
|
46
|
+
function persistKeyFor(p) {
|
|
47
|
+
if (p === true) return DEFAULT_PERSIST_KEY;
|
|
48
|
+
if (typeof p === "string" && p.length > 0) return p;
|
|
48
49
|
return null;
|
|
49
50
|
}
|
|
51
|
+
function recordPersist(record) {
|
|
52
|
+
return record.cfgRef ? record.cfgRef.persist : cfg.persist;
|
|
53
|
+
}
|
|
50
54
|
function persistSave() {
|
|
51
|
-
const key = persistKey();
|
|
52
|
-
if (!key) return;
|
|
53
55
|
try {
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
56
|
+
const groups = /* @__PURE__ */ new Map();
|
|
57
|
+
for (const key of knownPersistKeys) groups.set(key, []);
|
|
58
|
+
for (const r of store.values()) {
|
|
59
|
+
const key = persistKeyFor(recordPersist(r));
|
|
60
|
+
if (!key) continue;
|
|
61
|
+
knownPersistKeys.add(key);
|
|
62
|
+
let bucket = groups.get(key);
|
|
63
|
+
if (!bucket) {
|
|
64
|
+
bucket = [];
|
|
65
|
+
groups.set(key, bucket);
|
|
66
|
+
}
|
|
67
|
+
if (r.opts.duration) continue;
|
|
68
|
+
bucket.push({
|
|
69
|
+
id: r.id,
|
|
70
|
+
title: r.title,
|
|
71
|
+
kind: r.kind,
|
|
72
|
+
description: r.opts.description ?? "",
|
|
73
|
+
tag: r.opts.tag,
|
|
74
|
+
unread: r.opts.unread,
|
|
75
|
+
priority: r.opts.priority,
|
|
76
|
+
progress: r.opts.progress,
|
|
77
|
+
progressLabel: r.opts.progressLabel,
|
|
78
|
+
link: r.opts.link,
|
|
79
|
+
linkLabel: r.opts.linkLabel,
|
|
80
|
+
data: r.opts.data,
|
|
81
|
+
createdAt: r.createdAt
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
for (const [key, items] of groups) {
|
|
85
|
+
localStorage.setItem(key, JSON.stringify(items));
|
|
86
|
+
}
|
|
70
87
|
} catch {
|
|
71
88
|
}
|
|
72
89
|
}
|
|
73
|
-
function persistLoad() {
|
|
74
|
-
const key = persistKey();
|
|
75
|
-
if (!key) return [];
|
|
90
|
+
function persistLoad(key) {
|
|
76
91
|
try {
|
|
77
92
|
const raw = localStorage.getItem(key);
|
|
78
93
|
if (!raw) return [];
|
|
@@ -82,12 +97,49 @@ function persistLoad() {
|
|
|
82
97
|
return [];
|
|
83
98
|
}
|
|
84
99
|
}
|
|
100
|
+
function restorePersisted(container, key, cfgRef) {
|
|
101
|
+
const records = [];
|
|
102
|
+
for (const item of persistLoad(key)) {
|
|
103
|
+
const record = {
|
|
104
|
+
id: item.id,
|
|
105
|
+
title: item.title,
|
|
106
|
+
kind: item.kind,
|
|
107
|
+
opts: {
|
|
108
|
+
id: item.id,
|
|
109
|
+
description: item.description,
|
|
110
|
+
tag: item.tag,
|
|
111
|
+
unread: item.unread,
|
|
112
|
+
priority: item.priority,
|
|
113
|
+
progress: item.progress,
|
|
114
|
+
progressLabel: item.progressLabel,
|
|
115
|
+
link: item.link,
|
|
116
|
+
linkLabel: item.linkLabel,
|
|
117
|
+
data: item.data
|
|
118
|
+
},
|
|
119
|
+
el: null,
|
|
120
|
+
createdAt: item.createdAt ?? Date.now(),
|
|
121
|
+
duration: 0,
|
|
122
|
+
remaining: 0,
|
|
123
|
+
paused: false,
|
|
124
|
+
timer: 0,
|
|
125
|
+
startedAt: 0,
|
|
126
|
+
cfgRef
|
|
127
|
+
};
|
|
128
|
+
const handle = makeHandle(record);
|
|
129
|
+
const el = renderNotification(record, container, () => handle);
|
|
130
|
+
record.el = el;
|
|
131
|
+
handle.el = el;
|
|
132
|
+
pushToContainer(container, el, record.opts.priority ?? "normal");
|
|
133
|
+
records.push(record);
|
|
134
|
+
}
|
|
135
|
+
return records;
|
|
136
|
+
}
|
|
85
137
|
var audioCtx = null;
|
|
86
|
-
function playSound() {
|
|
87
|
-
if (!
|
|
138
|
+
function playSound(sound) {
|
|
139
|
+
if (!sound) return;
|
|
88
140
|
try {
|
|
89
|
-
if (typeof
|
|
90
|
-
const audio = new Audio(
|
|
141
|
+
if (typeof sound === "string") {
|
|
142
|
+
const audio = new Audio(sound);
|
|
91
143
|
void audio.play();
|
|
92
144
|
return;
|
|
93
145
|
}
|
|
@@ -115,6 +167,28 @@ function playSound() {
|
|
|
115
167
|
} catch {
|
|
116
168
|
}
|
|
117
169
|
}
|
|
170
|
+
function stripUnsafeSvg(el) {
|
|
171
|
+
for (const attr of Array.from(el.attributes)) {
|
|
172
|
+
if (attr.name.toLowerCase().startsWith("on")) el.removeAttribute(attr.name);
|
|
173
|
+
}
|
|
174
|
+
for (const child of Array.from(el.children)) {
|
|
175
|
+
const tag = child.tagName.toLowerCase();
|
|
176
|
+
if (tag === "script" || tag === "foreignobject") child.remove();
|
|
177
|
+
else stripUnsafeSvg(child);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function sanitizeSvgIcon(source) {
|
|
181
|
+
if (!source.trimStart().startsWith("<svg")) return null;
|
|
182
|
+
try {
|
|
183
|
+
const doc = new DOMParser().parseFromString(source, "image/svg+xml");
|
|
184
|
+
const svg = doc.documentElement;
|
|
185
|
+
if (!svg || svg.tagName.toLowerCase() !== "svg") return null;
|
|
186
|
+
stripUnsafeSvg(svg);
|
|
187
|
+
return document.importNode(svg, true);
|
|
188
|
+
} catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
118
192
|
function buildIcon(record) {
|
|
119
193
|
const wrap = document.createElement("span");
|
|
120
194
|
wrap.className = "notification-icon";
|
|
@@ -141,15 +215,16 @@ function buildIcon(record) {
|
|
|
141
215
|
img.alt = "";
|
|
142
216
|
wrap.appendChild(img);
|
|
143
217
|
} else {
|
|
144
|
-
|
|
218
|
+
const svg = sanitizeSvgIcon(icon);
|
|
219
|
+
if (svg) wrap.appendChild(svg);
|
|
220
|
+
else wrap.textContent = icon;
|
|
145
221
|
}
|
|
146
222
|
return wrap;
|
|
147
223
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
224
|
+
const fallback = sanitizeSvgIcon(
|
|
225
|
+
record.opts.progress !== void 0 && record.opts.progress >= 0 ? PROGRESS_ICON : KIND_ICON[record.kind]
|
|
226
|
+
);
|
|
227
|
+
if (fallback) wrap.appendChild(fallback);
|
|
153
228
|
return wrap;
|
|
154
229
|
}
|
|
155
230
|
function buildTitle(title) {
|
|
@@ -165,7 +240,7 @@ function buildDescription(text) {
|
|
|
165
240
|
el.textContent = text;
|
|
166
241
|
return el;
|
|
167
242
|
}
|
|
168
|
-
function buildActions(record
|
|
243
|
+
function buildActions(record) {
|
|
169
244
|
const actions = record.opts.actions?.length ? record.opts.actions : record.opts.actionLabel ? [{ label: record.opts.actionLabel, onClick: record.opts.onAction }] : null;
|
|
170
245
|
if (!actions || actions.length === 0) return null;
|
|
171
246
|
const wrap = document.createElement("div");
|
|
@@ -179,13 +254,11 @@ function buildActions(record, getHandle) {
|
|
|
179
254
|
btn.textContent = a.label;
|
|
180
255
|
btn.addEventListener("click", (ev) => {
|
|
181
256
|
ev.stopPropagation();
|
|
182
|
-
const h = getHandle();
|
|
183
257
|
if (a.onClick) {
|
|
184
258
|
const ret = a.onClick();
|
|
185
259
|
if (ret === false) return;
|
|
186
260
|
}
|
|
187
261
|
if (!a.keepOpen) dismissRecord(record.id);
|
|
188
|
-
else void h;
|
|
189
262
|
});
|
|
190
263
|
wrap.appendChild(btn);
|
|
191
264
|
}
|
|
@@ -243,13 +316,39 @@ function buildCountdownBar(record) {
|
|
|
243
316
|
bar.appendChild(fill);
|
|
244
317
|
return bar;
|
|
245
318
|
}
|
|
319
|
+
function syncCountdownBar(record) {
|
|
320
|
+
const el = record.el;
|
|
321
|
+
if (!el) return;
|
|
322
|
+
const existing = el.querySelector(".notification-countdown");
|
|
323
|
+
if (record.duration <= 0 || record.opts.showCountdown === false) {
|
|
324
|
+
existing?.remove();
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
let fill;
|
|
328
|
+
if (existing) {
|
|
329
|
+
fill = existing.querySelector(".notification-countdown-fill");
|
|
330
|
+
} else {
|
|
331
|
+
const bar = buildCountdownBar(record);
|
|
332
|
+
if (!bar) return;
|
|
333
|
+
el.appendChild(bar);
|
|
334
|
+
fill = bar.querySelector(".notification-countdown-fill");
|
|
335
|
+
}
|
|
336
|
+
if (fill) {
|
|
337
|
+
fill.style.animation = "none";
|
|
338
|
+
void fill.offsetWidth;
|
|
339
|
+
fill.style.animation = "";
|
|
340
|
+
fill.style.animationDuration = record.duration + "ms";
|
|
341
|
+
fill.style.animationPlayState = "running";
|
|
342
|
+
}
|
|
343
|
+
}
|
|
246
344
|
function buildClose(record) {
|
|
247
345
|
if (record.opts.closable === false) return null;
|
|
248
346
|
const btn = document.createElement("button");
|
|
249
347
|
btn.type = "button";
|
|
250
348
|
btn.className = "notification-close";
|
|
251
349
|
btn.setAttribute("aria-label", "\u5173\u95ED");
|
|
252
|
-
|
|
350
|
+
const svg = sanitizeSvgIcon(CLOSE_SVG);
|
|
351
|
+
if (svg) btn.appendChild(svg);
|
|
253
352
|
btn.addEventListener("click", (ev) => {
|
|
254
353
|
ev.stopPropagation();
|
|
255
354
|
dismissRecord(record.id);
|
|
@@ -258,7 +357,7 @@ function buildClose(record) {
|
|
|
258
357
|
}
|
|
259
358
|
function renderNotification(record, container, getHandle) {
|
|
260
359
|
const el = document.createElement("div");
|
|
261
|
-
let cls = record.kind === "info" ? "notification" : `notification
|
|
360
|
+
let cls = record.kind === "info" ? "notification" : `notification notification--${record.kind}`;
|
|
262
361
|
if (record.opts.priority === "high") cls += " is-priority";
|
|
263
362
|
if (record.opts.unread) cls += " is-unread";
|
|
264
363
|
if (record.opts.progress !== void 0 && record.opts.progress >= 0) cls += " is-progress";
|
|
@@ -278,7 +377,7 @@ function renderNotification(record, container, getHandle) {
|
|
|
278
377
|
content.className = "notification-content";
|
|
279
378
|
content.appendChild(buildTitle(record.title));
|
|
280
379
|
if (record.opts.description) content.appendChild(buildDescription(record.opts.description));
|
|
281
|
-
const actions = buildActions(record
|
|
380
|
+
const actions = buildActions(record);
|
|
282
381
|
if (actions) content.appendChild(actions);
|
|
283
382
|
const link = buildLink(record);
|
|
284
383
|
if (link) content.appendChild(link);
|
|
@@ -314,9 +413,10 @@ function pushToContainer(container, el, priority) {
|
|
|
314
413
|
container.appendChild(el);
|
|
315
414
|
}
|
|
316
415
|
}
|
|
416
|
+
var LEAVE_FALLBACK_MS = 360;
|
|
317
417
|
function removeOne(el, onClose) {
|
|
318
|
-
if (el.classList.contains("leaving")) return;
|
|
319
|
-
el.classList.add("leaving");
|
|
418
|
+
if (el.classList.contains("is-leaving")) return;
|
|
419
|
+
el.classList.add("is-leaving");
|
|
320
420
|
let done = false;
|
|
321
421
|
const finish = () => {
|
|
322
422
|
if (done) return;
|
|
@@ -325,32 +425,43 @@ function removeOne(el, onClose) {
|
|
|
325
425
|
onClose?.();
|
|
326
426
|
};
|
|
327
427
|
el.addEventListener("transitionend", finish, { once: true });
|
|
328
|
-
window.setTimeout(finish,
|
|
428
|
+
window.setTimeout(finish, LEAVE_FALLBACK_MS);
|
|
329
429
|
}
|
|
330
|
-
function trimStack(container) {
|
|
430
|
+
function trimStack(container, maxStack, remove) {
|
|
331
431
|
for (; ; ) {
|
|
332
432
|
const items = Array.from(container.querySelectorAll(":scope > .notification"));
|
|
333
|
-
if (items.length <=
|
|
433
|
+
if (items.length <= maxStack) return;
|
|
334
434
|
const target = items.find((it) => !it.classList.contains("is-priority")) ?? items[0];
|
|
335
435
|
if (!target) return;
|
|
436
|
+
const rec = target.id ? store.get(target.id) : void 0;
|
|
437
|
+
if (rec?.timer) window.clearTimeout(rec.timer);
|
|
438
|
+
if (target.classList.contains("is-leaving")) {
|
|
439
|
+
target.remove();
|
|
440
|
+
if (target.id) remove(target.id);
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
336
443
|
removeOne(target);
|
|
444
|
+
if (target.id) remove(target.id);
|
|
445
|
+
return;
|
|
337
446
|
}
|
|
338
447
|
}
|
|
339
448
|
function attachTimer(record, handle) {
|
|
340
|
-
if (!record.duration || record.duration <= 0) return;
|
|
341
449
|
const el = record.el;
|
|
342
450
|
if (!el) return;
|
|
343
451
|
const start = () => {
|
|
344
452
|
if (record.timer) window.clearTimeout(record.timer);
|
|
453
|
+
record.timer = 0;
|
|
454
|
+
if (record.remaining <= 0) return;
|
|
455
|
+
record.startedAt = Date.now();
|
|
345
456
|
record.timer = window.setTimeout(() => dismissRecord(record.id), record.remaining);
|
|
346
457
|
record.paused = false;
|
|
347
458
|
};
|
|
348
459
|
const pause = () => {
|
|
349
460
|
if (record.paused) return;
|
|
350
|
-
if (record.timer)
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
461
|
+
if (!record.timer) return;
|
|
462
|
+
window.clearTimeout(record.timer);
|
|
463
|
+
record.timer = 0;
|
|
464
|
+
record.remaining = Math.max(0, record.remaining - (Date.now() - record.startedAt));
|
|
354
465
|
record.paused = true;
|
|
355
466
|
el.classList.add("is-paused");
|
|
356
467
|
const fill = el.querySelector(".notification-countdown-fill");
|
|
@@ -358,19 +469,33 @@ function attachTimer(record, handle) {
|
|
|
358
469
|
};
|
|
359
470
|
const resume = () => {
|
|
360
471
|
if (!record.paused) return;
|
|
472
|
+
record.paused = false;
|
|
361
473
|
el.classList.remove("is-paused");
|
|
362
474
|
const fill = el.querySelector(".notification-countdown-fill");
|
|
363
475
|
if (fill) fill.style.animationPlayState = "running";
|
|
476
|
+
if (record.remaining <= 0) {
|
|
477
|
+
dismissRecord(record.id);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
start();
|
|
481
|
+
};
|
|
482
|
+
record.restartTimer = () => {
|
|
483
|
+
record.paused = false;
|
|
484
|
+
el.classList.remove("is-paused");
|
|
364
485
|
start();
|
|
365
486
|
};
|
|
366
|
-
const pauseOnHover = record.opts.pauseOnHover ?? cfg.pauseOnHover;
|
|
367
|
-
if (pauseOnHover) {
|
|
487
|
+
const pauseOnHover = record.opts.pauseOnHover ?? record.cfgRef?.pauseOnHover ?? cfg.pauseOnHover;
|
|
488
|
+
if (pauseOnHover && !record.hoverBound) {
|
|
489
|
+
record.hoverBound = true;
|
|
368
490
|
el.addEventListener("mouseenter", () => handle.pause());
|
|
369
491
|
el.addEventListener("mouseleave", () => handle.resume());
|
|
370
492
|
}
|
|
371
493
|
handle.pause = pause;
|
|
372
494
|
handle.resume = resume;
|
|
373
|
-
|
|
495
|
+
if (record.duration > 0) {
|
|
496
|
+
record.remaining = record.duration;
|
|
497
|
+
start();
|
|
498
|
+
}
|
|
374
499
|
}
|
|
375
500
|
function makeHandle(record) {
|
|
376
501
|
const handle = {
|
|
@@ -390,37 +515,49 @@ function makeHandle(record) {
|
|
|
390
515
|
};
|
|
391
516
|
return handle;
|
|
392
517
|
}
|
|
393
|
-
function
|
|
394
|
-
if (!isBrowser()) return null;
|
|
395
|
-
const container = ensureContainer();
|
|
518
|
+
function pushRecord(title, kind, opts, container, maxStack, cfgRef, ownerStore) {
|
|
396
519
|
const id = opts.id ?? genId();
|
|
397
|
-
if (store.has(id)) dismissRecord(id);
|
|
398
520
|
const duration = opts.duration ?? 0;
|
|
399
521
|
const record = {
|
|
400
522
|
id,
|
|
401
523
|
title,
|
|
402
524
|
kind,
|
|
403
|
-
|
|
525
|
+
// 默认值放展开前会被显式传入的 { unread: undefined } 覆盖回 undefined,故先展开再兜底
|
|
526
|
+
opts: { ...opts, unread: opts.unread ?? false, id },
|
|
404
527
|
el: null,
|
|
405
528
|
createdAt: Date.now(),
|
|
406
529
|
duration,
|
|
407
530
|
remaining: duration,
|
|
408
531
|
paused: false,
|
|
409
|
-
timer: 0
|
|
532
|
+
timer: 0,
|
|
533
|
+
startedAt: 0,
|
|
534
|
+
cfgRef,
|
|
535
|
+
ownerStore
|
|
410
536
|
};
|
|
411
537
|
const handle = makeHandle(record);
|
|
412
538
|
const el = renderNotification(record, container, () => handle);
|
|
413
539
|
record.el = el;
|
|
414
540
|
handle.el = el;
|
|
415
541
|
pushToContainer(container, el, record.opts.priority ?? "normal");
|
|
542
|
+
ownerStore?.set(id, record);
|
|
416
543
|
store.set(id, record);
|
|
417
544
|
attachTimer(record, handle);
|
|
418
|
-
trimStack(container)
|
|
545
|
+
trimStack(container, maxStack, (removedId) => {
|
|
546
|
+
ownerStore?.delete(removedId);
|
|
547
|
+
store.delete(removedId);
|
|
548
|
+
});
|
|
419
549
|
persistSave();
|
|
420
|
-
playSound();
|
|
550
|
+
playSound(cfgRef?.sound ?? cfg.sound);
|
|
421
551
|
opts.onShow?.(handle);
|
|
422
552
|
return handle;
|
|
423
553
|
}
|
|
554
|
+
function showRecord(title, kind, opts) {
|
|
555
|
+
if (!isBrowser()) return null;
|
|
556
|
+
const container = ensureContainer();
|
|
557
|
+
const id = opts.id ?? genId();
|
|
558
|
+
if (store.has(id)) dismissRecord(id);
|
|
559
|
+
return pushRecord(title, kind, opts, container, cfg.maxStack);
|
|
560
|
+
}
|
|
424
561
|
function dismissRecord(id) {
|
|
425
562
|
const record = store.get(id);
|
|
426
563
|
if (!record) {
|
|
@@ -432,6 +569,7 @@ function dismissRecord(id) {
|
|
|
432
569
|
const handle = makeHandle(record);
|
|
433
570
|
const onClose = record.opts.onClose;
|
|
434
571
|
store.delete(id);
|
|
572
|
+
record.ownerStore?.delete(id);
|
|
435
573
|
if (record.el) removeOne(record.el, () => onClose?.(handle));
|
|
436
574
|
else onClose?.(handle);
|
|
437
575
|
persistSave();
|
|
@@ -457,6 +595,11 @@ function updateRecord(id, patch) {
|
|
|
457
595
|
if (patch.linkLabel !== void 0) record.opts.linkLabel = patch.linkLabel;
|
|
458
596
|
const el = record.el;
|
|
459
597
|
if (!el) return;
|
|
598
|
+
if (patch.duration !== void 0 && el.isConnected) {
|
|
599
|
+
syncCountdownBar(record);
|
|
600
|
+
if (record.restartTimer) record.restartTimer();
|
|
601
|
+
else if (patch.duration > 0) attachTimer(record, makeHandle(record));
|
|
602
|
+
}
|
|
460
603
|
if (patch.title !== void 0) {
|
|
461
604
|
const t = el.querySelector(".notification-title");
|
|
462
605
|
if (t) t.textContent = patch.title;
|
|
@@ -547,7 +690,7 @@ function confirmDialog(title, opts = {}) {
|
|
|
547
690
|
if (!handle) settle(false);
|
|
548
691
|
});
|
|
549
692
|
}
|
|
550
|
-
function createNotificationCenter(parent
|
|
693
|
+
function createNotificationCenter(parent, opts = {}) {
|
|
551
694
|
if (!isBrowser()) {
|
|
552
695
|
return {
|
|
553
696
|
push: () => null,
|
|
@@ -557,13 +700,17 @@ function createNotificationCenter(parent = document.body, opts = {}) {
|
|
|
557
700
|
},
|
|
558
701
|
markAllRead() {
|
|
559
702
|
},
|
|
703
|
+
destroy() {
|
|
704
|
+
},
|
|
560
705
|
get: () => null,
|
|
561
706
|
getAll: () => [],
|
|
562
707
|
config() {
|
|
563
708
|
},
|
|
709
|
+
// SSR 桩:非真实元素,destroy 前不得触碰 el(本分支内其余方法均为 no-op)
|
|
564
710
|
el: {}
|
|
565
711
|
};
|
|
566
712
|
}
|
|
713
|
+
const mount = parent ?? document.body;
|
|
567
714
|
const local = {
|
|
568
715
|
position: opts.position ?? "top-right",
|
|
569
716
|
maxStack: opts.maxStack ?? 5,
|
|
@@ -576,48 +723,29 @@ function createNotificationCenter(parent = document.body, opts = {}) {
|
|
|
576
723
|
container.setAttribute("role", "region");
|
|
577
724
|
container.setAttribute("aria-label", "\u901A\u77E5");
|
|
578
725
|
container.dataset.position = local.position;
|
|
579
|
-
|
|
726
|
+
mount.appendChild(container);
|
|
580
727
|
const localStore = /* @__PURE__ */ new Map();
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
id,
|
|
588
|
-
|
|
589
|
-
kind,
|
|
590
|
-
opts: { unread: opts2.unread ?? false, ...opts2, id },
|
|
591
|
-
el: null,
|
|
592
|
-
createdAt: Date.now(),
|
|
593
|
-
duration,
|
|
594
|
-
remaining: duration,
|
|
595
|
-
paused: false,
|
|
596
|
-
timer: 0
|
|
597
|
-
};
|
|
598
|
-
const handle = makeHandle(record);
|
|
599
|
-
const el = renderNotification(record, container, () => handle);
|
|
600
|
-
record.el = el;
|
|
601
|
-
handle.el = el;
|
|
602
|
-
pushToContainer(container, el, record.opts.priority ?? "normal");
|
|
603
|
-
localStore.set(id, record);
|
|
604
|
-
store.set(id, record);
|
|
605
|
-
attachTimer(record, handle);
|
|
606
|
-
for (; ; ) {
|
|
607
|
-
const items = Array.from(container.querySelectorAll(":scope > .notification"));
|
|
608
|
-
if (items.length <= local.maxStack) break;
|
|
609
|
-
const target = items.find((it) => !it.classList.contains("is-priority")) ?? items[0];
|
|
610
|
-
if (!target) break;
|
|
611
|
-
const tid = target.id;
|
|
612
|
-
removeOne(target);
|
|
613
|
-
const rec = tid ? localStore.get(tid) : null;
|
|
614
|
-
if (rec) {
|
|
615
|
-
localStore.delete(tid);
|
|
616
|
-
store.delete(tid);
|
|
617
|
-
}
|
|
728
|
+
let destroyed = false;
|
|
729
|
+
const pKey = persistKeyFor(local.persist);
|
|
730
|
+
if (pKey) {
|
|
731
|
+
knownPersistKeys.add(pKey);
|
|
732
|
+
for (const r of restorePersisted(container, pKey, local)) {
|
|
733
|
+
r.ownerStore = localStore;
|
|
734
|
+
localStore.set(r.id, r);
|
|
735
|
+
store.set(r.id, r);
|
|
618
736
|
}
|
|
619
|
-
|
|
620
|
-
|
|
737
|
+
}
|
|
738
|
+
function push(input) {
|
|
739
|
+
if (destroyed) return null;
|
|
740
|
+
return pushRecord(
|
|
741
|
+
input.title,
|
|
742
|
+
input.kind ?? "info",
|
|
743
|
+
input.options ?? {},
|
|
744
|
+
container,
|
|
745
|
+
local.maxStack,
|
|
746
|
+
local,
|
|
747
|
+
localStore
|
|
748
|
+
);
|
|
621
749
|
}
|
|
622
750
|
function dismiss(id) {
|
|
623
751
|
if (id) {
|
|
@@ -628,6 +756,7 @@ function createNotificationCenter(parent = document.body, opts = {}) {
|
|
|
628
756
|
localStore.delete(id);
|
|
629
757
|
store.delete(id);
|
|
630
758
|
if (rec.el) removeOne(rec.el, () => rec.opts.onClose?.(handle));
|
|
759
|
+
persistSave();
|
|
631
760
|
return;
|
|
632
761
|
}
|
|
633
762
|
Array.from(localStore.keys()).forEach((k) => dismiss(k));
|
|
@@ -641,11 +770,22 @@ function createNotificationCenter(parent = document.body, opts = {}) {
|
|
|
641
770
|
rec.el?.classList.remove("is-unread");
|
|
642
771
|
});
|
|
643
772
|
}
|
|
773
|
+
function destroy() {
|
|
774
|
+
if (destroyed) return;
|
|
775
|
+
destroyed = true;
|
|
776
|
+
localStore.forEach((rec) => {
|
|
777
|
+
if (rec.timer) window.clearTimeout(rec.timer);
|
|
778
|
+
store.delete(rec.id);
|
|
779
|
+
});
|
|
780
|
+
localStore.clear();
|
|
781
|
+
container.remove();
|
|
782
|
+
}
|
|
644
783
|
return {
|
|
645
784
|
push,
|
|
646
785
|
dismiss,
|
|
647
786
|
clear,
|
|
648
787
|
markAllRead,
|
|
788
|
+
destroy,
|
|
649
789
|
get: (id) => localStore.get(id) ?? null,
|
|
650
790
|
getAll: () => Array.from(localStore.values()),
|
|
651
791
|
config: (next) => {
|
|
@@ -655,7 +795,11 @@ function createNotificationCenter(parent = document.body, opts = {}) {
|
|
|
655
795
|
}
|
|
656
796
|
if (typeof next.maxStack === "number" && next.maxStack > 0) local.maxStack = next.maxStack;
|
|
657
797
|
if (typeof next.pauseOnHover === "boolean") local.pauseOnHover = next.pauseOnHover;
|
|
658
|
-
if (next.persist !== void 0)
|
|
798
|
+
if (next.persist !== void 0) {
|
|
799
|
+
local.persist = next.persist;
|
|
800
|
+
const k = persistKeyFor(next.persist);
|
|
801
|
+
if (k) knownPersistKeys.add(k);
|
|
802
|
+
}
|
|
659
803
|
if (next.sound !== void 0) local.sound = next.sound;
|
|
660
804
|
},
|
|
661
805
|
el: container
|
|
@@ -701,51 +845,41 @@ var notify = Object.assign(
|
|
|
701
845
|
}
|
|
702
846
|
if (typeof opts.maxStack === "number" && opts.maxStack > 0) cfg.maxStack = opts.maxStack;
|
|
703
847
|
if (typeof opts.pauseOnHover === "boolean") cfg.pauseOnHover = opts.pauseOnHover;
|
|
704
|
-
if (opts.persist !== void 0)
|
|
848
|
+
if (opts.persist !== void 0) {
|
|
849
|
+
cfg.persist = opts.persist;
|
|
850
|
+
const k = persistKeyFor(opts.persist);
|
|
851
|
+
if (k) knownPersistKeys.add(k);
|
|
852
|
+
}
|
|
705
853
|
if (opts.sound !== void 0) cfg.sound = opts.sound;
|
|
706
854
|
}
|
|
707
855
|
}
|
|
708
856
|
);
|
|
709
857
|
function initNotification() {
|
|
710
|
-
if (!isBrowser()) return
|
|
711
|
-
|
|
858
|
+
if (!isBrowser()) return () => {
|
|
859
|
+
};
|
|
860
|
+
if (globalInit.done) return () => {
|
|
861
|
+
};
|
|
712
862
|
globalInit.done = true;
|
|
713
|
-
ensureContainer();
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
kind: item.kind,
|
|
722
|
-
opts: {
|
|
723
|
-
id: item.id,
|
|
724
|
-
description: item.description,
|
|
725
|
-
tag: item.tag,
|
|
726
|
-
unread: item.unread,
|
|
727
|
-
priority: item.priority,
|
|
728
|
-
progress: item.progress,
|
|
729
|
-
progressLabel: item.progressLabel,
|
|
730
|
-
link: item.link,
|
|
731
|
-
linkLabel: item.linkLabel,
|
|
732
|
-
data: item.data
|
|
733
|
-
},
|
|
734
|
-
el: null,
|
|
735
|
-
createdAt: item.createdAt ?? Date.now(),
|
|
736
|
-
duration: 0,
|
|
737
|
-
remaining: 0,
|
|
738
|
-
paused: false,
|
|
739
|
-
timer: 0
|
|
740
|
-
};
|
|
741
|
-
const handle = makeHandle(record);
|
|
742
|
-
const el = renderNotification(record, container, () => handle);
|
|
743
|
-
record.el = el;
|
|
744
|
-
handle.el = el;
|
|
745
|
-
pushToContainer(container, el, record.opts.priority ?? "normal");
|
|
746
|
-
store.set(record.id, record);
|
|
863
|
+
const container = ensureContainer();
|
|
864
|
+
const key = persistKeyFor(cfg.persist);
|
|
865
|
+
const restored = [];
|
|
866
|
+
if (key) {
|
|
867
|
+
knownPersistKeys.add(key);
|
|
868
|
+
for (const r of restorePersisted(container, key)) {
|
|
869
|
+
store.set(r.id, r);
|
|
870
|
+
restored.push(r);
|
|
747
871
|
}
|
|
748
872
|
}
|
|
873
|
+
return () => {
|
|
874
|
+
for (const r of restored) {
|
|
875
|
+
if (r.timer) window.clearTimeout(r.timer);
|
|
876
|
+
store.delete(r.id);
|
|
877
|
+
r.el?.remove();
|
|
878
|
+
}
|
|
879
|
+
restored.length = 0;
|
|
880
|
+
if (container.childElementCount === 0) container.remove();
|
|
881
|
+
globalInit.done = false;
|
|
882
|
+
};
|
|
749
883
|
}
|
|
750
884
|
export {
|
|
751
885
|
createNotificationCenter,
|