@icen.ai/ui 0.5.0 → 0.8.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/AGENTS.md +256 -0
- package/README.md +47 -13
- package/dist/base.css +31 -4
- package/dist/behaviors/accordion.d.mts +4 -0
- package/dist/behaviors/accordion.mjs +4 -3
- package/dist/behaviors/ai-chat.d.mts +57 -0
- package/dist/behaviors/ai-chat.mjs +557 -0
- package/dist/behaviors/ai-composer.d.mts +112 -0
- package/dist/behaviors/ai-composer.mjs +2032 -0
- package/dist/behaviors/ai-core.d.mts +151 -0
- package/dist/behaviors/ai-core.mjs +491 -0
- package/dist/behaviors/ai-diff.d.mts +53 -0
- package/dist/behaviors/ai-diff.mjs +282 -0
- package/dist/behaviors/ai-panel.d.mts +98 -0
- package/dist/behaviors/ai-panel.mjs +1012 -0
- package/dist/behaviors/ai-provider.d.mts +198 -0
- package/dist/behaviors/ai-provider.mjs +1055 -0
- package/dist/behaviors/ai-tool.d.mts +18 -0
- package/dist/behaviors/ai-tool.mjs +2125 -0
- package/dist/behaviors/ai-tools.d.mts +81 -0
- package/dist/behaviors/ai-tools.mjs +1658 -0
- package/dist/behaviors/back-top.d.mts +20 -0
- package/dist/behaviors/back-top.mjs +49 -32
- package/dist/behaviors/carousel.d.mts +4 -0
- package/dist/behaviors/carousel.mjs +34 -16
- package/dist/behaviors/charts.d.mts +250 -0
- package/dist/behaviors/charts.mjs +1301 -448
- package/dist/behaviors/command-palette.d.mts +8 -0
- package/dist/behaviors/command-palette.mjs +24 -12
- package/dist/behaviors/context-menu.d.mts +36 -0
- package/dist/behaviors/context-menu.mjs +6 -2
- package/dist/behaviors/copy.d.mts +4 -0
- package/dist/behaviors/copy.mjs +7 -6
- package/dist/behaviors/datatable.d.mts +78 -0
- package/dist/behaviors/datatable.mjs +54 -11
- package/dist/behaviors/date-picker.d.mts +4 -0
- package/dist/behaviors/date-picker.mjs +104 -36
- package/dist/behaviors/dropdown.d.mts +4 -0
- package/dist/behaviors/dropdown.mjs +7 -3
- package/dist/behaviors/input.d.mts +4 -0
- package/dist/behaviors/input.mjs +29 -22
- package/dist/behaviors/modal.d.mts +11 -0
- package/dist/behaviors/modal.mjs +11 -6
- package/dist/behaviors/nav.d.mts +7 -0
- package/dist/behaviors/nav.mjs +25 -9
- package/dist/behaviors/notification.d.mts +172 -0
- package/dist/behaviors/notification.mjs +243 -89
- package/dist/behaviors/popover.d.mts +46 -0
- package/dist/behaviors/popover.mjs +2 -1
- package/dist/behaviors/select.d.mts +4 -0
- package/dist/behaviors/select.mjs +131 -26
- package/dist/behaviors/sidebar.d.mts +4 -0
- package/dist/behaviors/sidebar.mjs +4 -3
- package/dist/behaviors/slider.d.mts +4 -0
- package/dist/behaviors/slider.mjs +4 -3
- package/dist/behaviors/split-pane.d.mts +4 -0
- package/dist/behaviors/split-pane.mjs +13 -13
- package/dist/behaviors/tabs.d.mts +8 -0
- package/dist/behaviors/tabs.mjs +44 -19
- package/dist/behaviors/tag-input.d.mts +4 -0
- package/dist/behaviors/tag-input.mjs +12 -3
- package/dist/behaviors/theme.d.mts +25 -0
- package/dist/behaviors/theme.mjs +3 -1
- package/dist/behaviors/toast.d.mts +26 -0
- package/dist/behaviors/toast.mjs +24 -18
- package/dist/behaviors/tree.d.mts +4 -0
- package/dist/behaviors/tree.mjs +4 -3
- package/dist/behaviors/upload.d.mts +4 -0
- package/dist/behaviors/upload.mjs +63 -26
- 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 +4 -8
- package/dist/components/breadcrumb.css +1 -5
- 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 +5 -0
- package/dist/components/chart-gauge.css +1 -1
- package/dist/components/chart-heatmap.css +40 -0
- package/dist/components/chart-scatter.css +24 -0
- package/dist/components/chart-scatter.mjs +4 -0
- package/dist/components/charts.css +86 -1
- package/dist/components/charts.mjs +1 -0
- package/dist/components/command-palette.css +2 -2
- package/dist/components/content.css +8 -9
- package/dist/components/copy.css +8 -8
- package/dist/components/datatable.css +4 -8
- package/dist/components/date-picker.css +3 -7
- 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 +28 -28
- package/dist/components/pagination.css +1 -1
- package/dist/components/panel.css +16 -16
- package/dist/components/pill.css +15 -15
- package/dist/components/segmented.css +5 -5
- package/dist/components/segmented.mjs +1 -0
- package/dist/components/select.css +12 -20
- 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 +2 -2
- package/dist/components/switch.css +8 -12
- package/dist/components/table.css +46 -47
- package/dist/components/tabs.css +35 -46
- package/dist/components/tag-input.css +4 -4
- package/dist/components/toast.css +16 -13
- package/dist/components/tooltip.css +2 -2
- package/dist/index.d.mts +37 -0
- package/dist/index.mjs +7890 -1199
- package/dist/registry.json +113 -4
- package/dist/retro-effects.css +21 -17
- package/dist/tokens/colors.css +4 -1
- package/dist/tokens/retro-effects.css +21 -17
- package/dist/tokens/style-profiles.css +21 -0
- package/dist/tokens.css +25 -1
- package/dist/ui.css +3168 -433
- package/docs/spec/ai-native.md +659 -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) {
|
|
@@ -243,13 +318,39 @@ function buildCountdownBar(record) {
|
|
|
243
318
|
bar.appendChild(fill);
|
|
244
319
|
return bar;
|
|
245
320
|
}
|
|
321
|
+
function syncCountdownBar(record) {
|
|
322
|
+
const el = record.el;
|
|
323
|
+
if (!el) return;
|
|
324
|
+
const existing = el.querySelector(".notification-countdown");
|
|
325
|
+
if (record.duration <= 0 || record.opts.showCountdown === false) {
|
|
326
|
+
existing?.remove();
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
let fill;
|
|
330
|
+
if (existing) {
|
|
331
|
+
fill = existing.querySelector(".notification-countdown-fill");
|
|
332
|
+
} else {
|
|
333
|
+
const bar = buildCountdownBar(record);
|
|
334
|
+
if (!bar) return;
|
|
335
|
+
el.appendChild(bar);
|
|
336
|
+
fill = bar.querySelector(".notification-countdown-fill");
|
|
337
|
+
}
|
|
338
|
+
if (fill) {
|
|
339
|
+
fill.style.animation = "none";
|
|
340
|
+
void fill.offsetWidth;
|
|
341
|
+
fill.style.animation = "";
|
|
342
|
+
fill.style.animationDuration = record.duration + "ms";
|
|
343
|
+
fill.style.animationPlayState = "running";
|
|
344
|
+
}
|
|
345
|
+
}
|
|
246
346
|
function buildClose(record) {
|
|
247
347
|
if (record.opts.closable === false) return null;
|
|
248
348
|
const btn = document.createElement("button");
|
|
249
349
|
btn.type = "button";
|
|
250
350
|
btn.className = "notification-close";
|
|
251
351
|
btn.setAttribute("aria-label", "\u5173\u95ED");
|
|
252
|
-
|
|
352
|
+
const svg = sanitizeSvgIcon(CLOSE_SVG);
|
|
353
|
+
if (svg) btn.appendChild(svg);
|
|
253
354
|
btn.addEventListener("click", (ev) => {
|
|
254
355
|
ev.stopPropagation();
|
|
255
356
|
dismissRecord(record.id);
|
|
@@ -258,7 +359,7 @@ function buildClose(record) {
|
|
|
258
359
|
}
|
|
259
360
|
function renderNotification(record, container, getHandle) {
|
|
260
361
|
const el = document.createElement("div");
|
|
261
|
-
let cls = record.kind === "info" ? "notification" : `notification
|
|
362
|
+
let cls = record.kind === "info" ? "notification" : `notification notification--${record.kind}`;
|
|
262
363
|
if (record.opts.priority === "high") cls += " is-priority";
|
|
263
364
|
if (record.opts.unread) cls += " is-unread";
|
|
264
365
|
if (record.opts.progress !== void 0 && record.opts.progress >= 0) cls += " is-progress";
|
|
@@ -315,8 +416,8 @@ function pushToContainer(container, el, priority) {
|
|
|
315
416
|
}
|
|
316
417
|
}
|
|
317
418
|
function removeOne(el, onClose) {
|
|
318
|
-
if (el.classList.contains("leaving")) return;
|
|
319
|
-
el.classList.add("leaving");
|
|
419
|
+
if (el.classList.contains("is-leaving")) return;
|
|
420
|
+
el.classList.add("is-leaving");
|
|
320
421
|
let done = false;
|
|
321
422
|
const finish = () => {
|
|
322
423
|
if (done) return;
|
|
@@ -333,24 +434,35 @@ function trimStack(container) {
|
|
|
333
434
|
if (items.length <= cfg.maxStack) return;
|
|
334
435
|
const target = items.find((it) => !it.classList.contains("is-priority")) ?? items[0];
|
|
335
436
|
if (!target) return;
|
|
437
|
+
const rec = target.id ? store.get(target.id) : void 0;
|
|
438
|
+
if (rec?.timer) window.clearTimeout(rec.timer);
|
|
439
|
+
if (target.classList.contains("is-leaving")) {
|
|
440
|
+
target.remove();
|
|
441
|
+
if (target.id) store.delete(target.id);
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
336
444
|
removeOne(target);
|
|
445
|
+
if (target.id) store.delete(target.id);
|
|
446
|
+
return;
|
|
337
447
|
}
|
|
338
448
|
}
|
|
339
449
|
function attachTimer(record, handle) {
|
|
340
|
-
if (!record.duration || record.duration <= 0) return;
|
|
341
450
|
const el = record.el;
|
|
342
451
|
if (!el) return;
|
|
343
452
|
const start = () => {
|
|
344
453
|
if (record.timer) window.clearTimeout(record.timer);
|
|
454
|
+
record.timer = 0;
|
|
455
|
+
if (record.remaining <= 0) return;
|
|
456
|
+
record.startedAt = Date.now();
|
|
345
457
|
record.timer = window.setTimeout(() => dismissRecord(record.id), record.remaining);
|
|
346
458
|
record.paused = false;
|
|
347
459
|
};
|
|
348
460
|
const pause = () => {
|
|
349
461
|
if (record.paused) return;
|
|
350
|
-
if (record.timer)
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
462
|
+
if (!record.timer) return;
|
|
463
|
+
window.clearTimeout(record.timer);
|
|
464
|
+
record.timer = 0;
|
|
465
|
+
record.remaining = Math.max(0, record.remaining - (Date.now() - record.startedAt));
|
|
354
466
|
record.paused = true;
|
|
355
467
|
el.classList.add("is-paused");
|
|
356
468
|
const fill = el.querySelector(".notification-countdown-fill");
|
|
@@ -358,19 +470,33 @@ function attachTimer(record, handle) {
|
|
|
358
470
|
};
|
|
359
471
|
const resume = () => {
|
|
360
472
|
if (!record.paused) return;
|
|
473
|
+
record.paused = false;
|
|
361
474
|
el.classList.remove("is-paused");
|
|
362
475
|
const fill = el.querySelector(".notification-countdown-fill");
|
|
363
476
|
if (fill) fill.style.animationPlayState = "running";
|
|
477
|
+
if (record.remaining <= 0) {
|
|
478
|
+
dismissRecord(record.id);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
start();
|
|
482
|
+
};
|
|
483
|
+
record.restartTimer = () => {
|
|
484
|
+
record.paused = false;
|
|
485
|
+
el.classList.remove("is-paused");
|
|
364
486
|
start();
|
|
365
487
|
};
|
|
366
|
-
const pauseOnHover = record.opts.pauseOnHover ?? cfg.pauseOnHover;
|
|
367
|
-
if (pauseOnHover) {
|
|
488
|
+
const pauseOnHover = record.opts.pauseOnHover ?? record.cfgRef?.pauseOnHover ?? cfg.pauseOnHover;
|
|
489
|
+
if (pauseOnHover && !record.hoverBound) {
|
|
490
|
+
record.hoverBound = true;
|
|
368
491
|
el.addEventListener("mouseenter", () => handle.pause());
|
|
369
492
|
el.addEventListener("mouseleave", () => handle.resume());
|
|
370
493
|
}
|
|
371
494
|
handle.pause = pause;
|
|
372
495
|
handle.resume = resume;
|
|
373
|
-
|
|
496
|
+
if (record.duration > 0) {
|
|
497
|
+
record.remaining = record.duration;
|
|
498
|
+
start();
|
|
499
|
+
}
|
|
374
500
|
}
|
|
375
501
|
function makeHandle(record) {
|
|
376
502
|
const handle = {
|
|
@@ -406,7 +532,8 @@ function showRecord(title, kind, opts) {
|
|
|
406
532
|
duration,
|
|
407
533
|
remaining: duration,
|
|
408
534
|
paused: false,
|
|
409
|
-
timer: 0
|
|
535
|
+
timer: 0,
|
|
536
|
+
startedAt: 0
|
|
410
537
|
};
|
|
411
538
|
const handle = makeHandle(record);
|
|
412
539
|
const el = renderNotification(record, container, () => handle);
|
|
@@ -417,7 +544,7 @@ function showRecord(title, kind, opts) {
|
|
|
417
544
|
attachTimer(record, handle);
|
|
418
545
|
trimStack(container);
|
|
419
546
|
persistSave();
|
|
420
|
-
playSound();
|
|
547
|
+
playSound(record.cfgRef?.sound ?? cfg.sound);
|
|
421
548
|
opts.onShow?.(handle);
|
|
422
549
|
return handle;
|
|
423
550
|
}
|
|
@@ -432,6 +559,7 @@ function dismissRecord(id) {
|
|
|
432
559
|
const handle = makeHandle(record);
|
|
433
560
|
const onClose = record.opts.onClose;
|
|
434
561
|
store.delete(id);
|
|
562
|
+
record.ownerStore?.delete(id);
|
|
435
563
|
if (record.el) removeOne(record.el, () => onClose?.(handle));
|
|
436
564
|
else onClose?.(handle);
|
|
437
565
|
persistSave();
|
|
@@ -457,6 +585,11 @@ function updateRecord(id, patch) {
|
|
|
457
585
|
if (patch.linkLabel !== void 0) record.opts.linkLabel = patch.linkLabel;
|
|
458
586
|
const el = record.el;
|
|
459
587
|
if (!el) return;
|
|
588
|
+
if (patch.duration !== void 0 && el.isConnected) {
|
|
589
|
+
syncCountdownBar(record);
|
|
590
|
+
if (record.restartTimer) record.restartTimer();
|
|
591
|
+
else if (patch.duration > 0) attachTimer(record, makeHandle(record));
|
|
592
|
+
}
|
|
460
593
|
if (patch.title !== void 0) {
|
|
461
594
|
const t = el.querySelector(".notification-title");
|
|
462
595
|
if (t) t.textContent = patch.title;
|
|
@@ -547,7 +680,7 @@ function confirmDialog(title, opts = {}) {
|
|
|
547
680
|
if (!handle) settle(false);
|
|
548
681
|
});
|
|
549
682
|
}
|
|
550
|
-
function createNotificationCenter(parent
|
|
683
|
+
function createNotificationCenter(parent, opts = {}) {
|
|
551
684
|
if (!isBrowser()) {
|
|
552
685
|
return {
|
|
553
686
|
push: () => null,
|
|
@@ -557,6 +690,8 @@ function createNotificationCenter(parent = document.body, opts = {}) {
|
|
|
557
690
|
},
|
|
558
691
|
markAllRead() {
|
|
559
692
|
},
|
|
693
|
+
destroy() {
|
|
694
|
+
},
|
|
560
695
|
get: () => null,
|
|
561
696
|
getAll: () => [],
|
|
562
697
|
config() {
|
|
@@ -564,6 +699,7 @@ function createNotificationCenter(parent = document.body, opts = {}) {
|
|
|
564
699
|
el: {}
|
|
565
700
|
};
|
|
566
701
|
}
|
|
702
|
+
const mount = parent ?? document.body;
|
|
567
703
|
const local = {
|
|
568
704
|
position: opts.position ?? "top-right",
|
|
569
705
|
maxStack: opts.maxStack ?? 5,
|
|
@@ -576,9 +712,20 @@ function createNotificationCenter(parent = document.body, opts = {}) {
|
|
|
576
712
|
container.setAttribute("role", "region");
|
|
577
713
|
container.setAttribute("aria-label", "\u901A\u77E5");
|
|
578
714
|
container.dataset.position = local.position;
|
|
579
|
-
|
|
715
|
+
mount.appendChild(container);
|
|
580
716
|
const localStore = /* @__PURE__ */ new Map();
|
|
717
|
+
let destroyed = false;
|
|
718
|
+
const pKey = persistKeyFor(local.persist);
|
|
719
|
+
if (pKey) {
|
|
720
|
+
knownPersistKeys.add(pKey);
|
|
721
|
+
for (const r of restorePersisted(container, pKey, local)) {
|
|
722
|
+
r.ownerStore = localStore;
|
|
723
|
+
localStore.set(r.id, r);
|
|
724
|
+
store.set(r.id, r);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
581
727
|
function push(input) {
|
|
728
|
+
if (destroyed) return null;
|
|
582
729
|
const kind = input.kind ?? "info";
|
|
583
730
|
const opts2 = input.options ?? {};
|
|
584
731
|
const id = opts2.id ?? genId();
|
|
@@ -593,7 +740,10 @@ function createNotificationCenter(parent = document.body, opts = {}) {
|
|
|
593
740
|
duration,
|
|
594
741
|
remaining: duration,
|
|
595
742
|
paused: false,
|
|
596
|
-
timer: 0
|
|
743
|
+
timer: 0,
|
|
744
|
+
startedAt: 0,
|
|
745
|
+
cfgRef: local,
|
|
746
|
+
ownerStore: localStore
|
|
597
747
|
};
|
|
598
748
|
const handle = makeHandle(record);
|
|
599
749
|
const el = renderNotification(record, container, () => handle);
|
|
@@ -609,13 +759,25 @@ function createNotificationCenter(parent = document.body, opts = {}) {
|
|
|
609
759
|
const target = items.find((it) => !it.classList.contains("is-priority")) ?? items[0];
|
|
610
760
|
if (!target) break;
|
|
611
761
|
const tid = target.id;
|
|
762
|
+
const rec = tid ? localStore.get(tid) : void 0;
|
|
763
|
+
if (rec?.timer) window.clearTimeout(rec.timer);
|
|
764
|
+
if (target.classList.contains("is-leaving")) {
|
|
765
|
+
target.remove();
|
|
766
|
+
if (tid) {
|
|
767
|
+
localStore.delete(tid);
|
|
768
|
+
store.delete(tid);
|
|
769
|
+
}
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
612
772
|
removeOne(target);
|
|
613
|
-
|
|
614
|
-
if (rec) {
|
|
773
|
+
if (tid) {
|
|
615
774
|
localStore.delete(tid);
|
|
616
775
|
store.delete(tid);
|
|
617
776
|
}
|
|
777
|
+
break;
|
|
618
778
|
}
|
|
779
|
+
persistSave();
|
|
780
|
+
playSound(local.sound);
|
|
619
781
|
opts2.onShow?.(handle);
|
|
620
782
|
return handle;
|
|
621
783
|
}
|
|
@@ -628,6 +790,7 @@ function createNotificationCenter(parent = document.body, opts = {}) {
|
|
|
628
790
|
localStore.delete(id);
|
|
629
791
|
store.delete(id);
|
|
630
792
|
if (rec.el) removeOne(rec.el, () => rec.opts.onClose?.(handle));
|
|
793
|
+
persistSave();
|
|
631
794
|
return;
|
|
632
795
|
}
|
|
633
796
|
Array.from(localStore.keys()).forEach((k) => dismiss(k));
|
|
@@ -641,11 +804,22 @@ function createNotificationCenter(parent = document.body, opts = {}) {
|
|
|
641
804
|
rec.el?.classList.remove("is-unread");
|
|
642
805
|
});
|
|
643
806
|
}
|
|
807
|
+
function destroy() {
|
|
808
|
+
if (destroyed) return;
|
|
809
|
+
destroyed = true;
|
|
810
|
+
localStore.forEach((rec) => {
|
|
811
|
+
if (rec.timer) window.clearTimeout(rec.timer);
|
|
812
|
+
store.delete(rec.id);
|
|
813
|
+
});
|
|
814
|
+
localStore.clear();
|
|
815
|
+
container.remove();
|
|
816
|
+
}
|
|
644
817
|
return {
|
|
645
818
|
push,
|
|
646
819
|
dismiss,
|
|
647
820
|
clear,
|
|
648
821
|
markAllRead,
|
|
822
|
+
destroy,
|
|
649
823
|
get: (id) => localStore.get(id) ?? null,
|
|
650
824
|
getAll: () => Array.from(localStore.values()),
|
|
651
825
|
config: (next) => {
|
|
@@ -655,7 +829,11 @@ function createNotificationCenter(parent = document.body, opts = {}) {
|
|
|
655
829
|
}
|
|
656
830
|
if (typeof next.maxStack === "number" && next.maxStack > 0) local.maxStack = next.maxStack;
|
|
657
831
|
if (typeof next.pauseOnHover === "boolean") local.pauseOnHover = next.pauseOnHover;
|
|
658
|
-
if (next.persist !== void 0)
|
|
832
|
+
if (next.persist !== void 0) {
|
|
833
|
+
local.persist = next.persist;
|
|
834
|
+
const k = persistKeyFor(next.persist);
|
|
835
|
+
if (k) knownPersistKeys.add(k);
|
|
836
|
+
}
|
|
659
837
|
if (next.sound !== void 0) local.sound = next.sound;
|
|
660
838
|
},
|
|
661
839
|
el: container
|
|
@@ -701,7 +879,11 @@ var notify = Object.assign(
|
|
|
701
879
|
}
|
|
702
880
|
if (typeof opts.maxStack === "number" && opts.maxStack > 0) cfg.maxStack = opts.maxStack;
|
|
703
881
|
if (typeof opts.pauseOnHover === "boolean") cfg.pauseOnHover = opts.pauseOnHover;
|
|
704
|
-
if (opts.persist !== void 0)
|
|
882
|
+
if (opts.persist !== void 0) {
|
|
883
|
+
cfg.persist = opts.persist;
|
|
884
|
+
const k = persistKeyFor(opts.persist);
|
|
885
|
+
if (k) knownPersistKeys.add(k);
|
|
886
|
+
}
|
|
705
887
|
if (opts.sound !== void 0) cfg.sound = opts.sound;
|
|
706
888
|
}
|
|
707
889
|
}
|
|
@@ -710,40 +892,12 @@ function initNotification() {
|
|
|
710
892
|
if (!isBrowser()) return;
|
|
711
893
|
if (globalInit.done) return;
|
|
712
894
|
globalInit.done = true;
|
|
713
|
-
ensureContainer();
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
for (const
|
|
718
|
-
|
|
719
|
-
id: item.id,
|
|
720
|
-
title: item.title,
|
|
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);
|
|
895
|
+
const container = ensureContainer();
|
|
896
|
+
const key = persistKeyFor(cfg.persist);
|
|
897
|
+
if (key) {
|
|
898
|
+
knownPersistKeys.add(key);
|
|
899
|
+
for (const r of restorePersisted(container, key)) {
|
|
900
|
+
store.set(r.id, r);
|
|
747
901
|
}
|
|
748
902
|
}
|
|
749
903
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
interface PopoverRect {
|
|
2
|
+
left: number;
|
|
3
|
+
top: number;
|
|
4
|
+
right: number;
|
|
5
|
+
bottom: number;
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
}
|
|
9
|
+
interface PopoverViewport {
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
}
|
|
13
|
+
type PopoverSide = 'top' | 'bottom';
|
|
14
|
+
type PopoverAlign = 'start' | 'center' | 'end' | 'stretch';
|
|
15
|
+
interface PopoverLayoutOptions {
|
|
16
|
+
side?: PopoverSide;
|
|
17
|
+
align?: PopoverAlign;
|
|
18
|
+
offset?: number;
|
|
19
|
+
margin?: number;
|
|
20
|
+
minWidth?: number;
|
|
21
|
+
maxWidth?: number;
|
|
22
|
+
minHeight?: number;
|
|
23
|
+
maxHeight?: number;
|
|
24
|
+
/** 内容预期高度:避免用被裁剪的实测高度参与选侧 */
|
|
25
|
+
contentHeightHint?: number;
|
|
26
|
+
}
|
|
27
|
+
interface PopoverLayout {
|
|
28
|
+
side: PopoverSide;
|
|
29
|
+
left: number;
|
|
30
|
+
top: number;
|
|
31
|
+
width: number;
|
|
32
|
+
maxHeight: number;
|
|
33
|
+
}
|
|
34
|
+
interface OpenPopoverOptions extends PopoverLayoutOptions {
|
|
35
|
+
anchor: Element | PopoverRect;
|
|
36
|
+
/** 关闭(含外点/Esc 触发的关闭)后的回调 */
|
|
37
|
+
onClose?: () => void;
|
|
38
|
+
}
|
|
39
|
+
/** 视口智能布局:返回 {side, left, top, width, maxHeight}。 */
|
|
40
|
+
declare function computePopoverLayout(anchor: PopoverRect, viewport: PopoverViewport, opts?: PopoverLayoutOptions): PopoverLayout;
|
|
41
|
+
/** 打开浮层:panel 移入 body 并按 anchor 布局;重复调用同一 panel 为先关后开。 */
|
|
42
|
+
declare function openPopover(panel: HTMLElement, options: OpenPopoverOptions): void;
|
|
43
|
+
/** 关闭浮层:移除监听并把 panel 还原到打开前的位置与 hidden 状态。 */
|
|
44
|
+
declare function closePopover(panel: HTMLElement): void;
|
|
45
|
+
|
|
46
|
+
export { type OpenPopoverOptions, type PopoverAlign, type PopoverLayout, type PopoverLayoutOptions, type PopoverRect, type PopoverSide, type PopoverViewport, closePopover, computePopoverLayout, openPopover };
|
|
@@ -139,7 +139,8 @@ function closePopover(panel) {
|
|
|
139
139
|
if (!state) return;
|
|
140
140
|
openPanels.delete(panel);
|
|
141
141
|
panel.style.visibility = state.prevVisibility;
|
|
142
|
-
panel.hidden
|
|
142
|
+
if (typeof state.wasHidden === "string") panel.setAttribute("hidden", state.wasHidden);
|
|
143
|
+
else panel.hidden = state.wasHidden;
|
|
143
144
|
panel.style.left = "";
|
|
144
145
|
panel.style.top = "";
|
|
145
146
|
panel.style.width = "";
|