@icen.ai/ui 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/behaviors/back-top.mjs +84 -0
  2. package/dist/behaviors/command-palette.mjs +177 -0
  3. package/dist/behaviors/date-picker.mjs +228 -0
  4. package/dist/behaviors/input.mjs +170 -1
  5. package/dist/behaviors/notification.mjs +754 -0
  6. package/dist/behaviors/select.mjs +151 -22
  7. package/dist/behaviors/split-pane.mjs +102 -0
  8. package/dist/behaviors/upload.mjs +165 -14
  9. package/dist/components/back-top.css +88 -0
  10. package/dist/components/back-top.mjs +3 -0
  11. package/dist/components/badge.css +131 -0
  12. package/dist/components/badge.mjs +2 -0
  13. package/dist/components/breadcrumb.css +57 -0
  14. package/dist/components/card.css +68 -0
  15. package/dist/components/command-palette.css +246 -0
  16. package/dist/components/command-palette.mjs +3 -0
  17. package/dist/components/content.css +214 -0
  18. package/dist/components/copy.css +93 -0
  19. package/dist/components/copy.mjs +3 -0
  20. package/dist/components/date-picker.css +244 -0
  21. package/dist/components/date-picker.mjs +3 -0
  22. package/dist/components/empty.css +73 -3
  23. package/dist/components/feedback.css +239 -1
  24. package/dist/components/form.css +119 -0
  25. package/dist/components/form.mjs +2 -0
  26. package/dist/components/input.css +24 -1
  27. package/dist/components/media.css +144 -1
  28. package/dist/components/nav.css +99 -0
  29. package/dist/components/notification.css +387 -0
  30. package/dist/components/notification.mjs +3 -0
  31. package/dist/components/panel.css +100 -5
  32. package/dist/components/pill.css +70 -0
  33. package/dist/components/scroll-area.css +112 -0
  34. package/dist/components/scroll-area.mjs +2 -0
  35. package/dist/components/segmented.css +75 -0
  36. package/dist/components/select.css +97 -2
  37. package/dist/components/sidebar.css +118 -0
  38. package/dist/components/slider.css +113 -0
  39. package/dist/components/split-pane.css +112 -0
  40. package/dist/components/split-pane.mjs +3 -0
  41. package/dist/components/stat.css +129 -6
  42. package/dist/components/steps.css +97 -0
  43. package/dist/components/switch.css +142 -0
  44. package/dist/components/table.css +86 -3
  45. package/dist/components/tabs.css +122 -1
  46. package/dist/components/tag.css +164 -0
  47. package/dist/components/tag.mjs +2 -0
  48. package/dist/components/toast.css +114 -0
  49. package/dist/components/toolbar.css +101 -0
  50. package/dist/components/toolbar.mjs +2 -0
  51. package/dist/components/upload.css +61 -4
  52. package/dist/index.mjs +1858 -69
  53. package/dist/registry.json +64 -1
  54. package/dist/ui.css +4320 -278
  55. package/package.json +1 -1
@@ -0,0 +1,754 @@
1
+ // src/behaviors/notification.ts
2
+ var KIND_ICON = {
3
+ success: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><path d="m9 11 3 3L22 4"/></svg>',
4
+ warning: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>',
5
+ error: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>',
6
+ info: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>'
7
+ };
8
+ var CLOSE_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>';
9
+ var PROGRESS_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/></svg>';
10
+ var cfg = {
11
+ position: "top-right",
12
+ maxStack: 5,
13
+ pauseOnHover: true,
14
+ persist: false,
15
+ sound: false
16
+ };
17
+ var store = /* @__PURE__ */ new Map();
18
+ var globalInit = { done: false };
19
+ function isBrowser() {
20
+ return typeof document !== "undefined" && typeof window !== "undefined";
21
+ }
22
+ function genId() {
23
+ return "notif-" + Math.random().toString(36).slice(2, 10);
24
+ }
25
+ function ensureContainer() {
26
+ const sel = `.notification-container[data-notif-default]`;
27
+ const existing = document.body.querySelector(sel);
28
+ if (existing) {
29
+ syncContainerPosition(existing);
30
+ return existing;
31
+ }
32
+ const el = document.createElement("div");
33
+ el.className = "notification-container";
34
+ el.setAttribute("role", "region");
35
+ el.setAttribute("aria-label", "\u901A\u77E5");
36
+ el.dataset.notifDefault = "";
37
+ syncContainerPosition(el);
38
+ document.body.appendChild(el);
39
+ return el;
40
+ }
41
+ function syncContainerPosition(el) {
42
+ el.dataset.position = cfg.position;
43
+ }
44
+ var DEFAULT_PERSIST_KEY = "icen.ui.notifications";
45
+ function persistKey() {
46
+ if (cfg.persist === true) return DEFAULT_PERSIST_KEY;
47
+ if (typeof cfg.persist === "string") return cfg.persist;
48
+ return null;
49
+ }
50
+ function persistSave() {
51
+ const key = persistKey();
52
+ if (!key) return;
53
+ try {
54
+ const items = Array.from(store.values()).filter((r) => !r.opts.duration || r.opts.duration === 0).map((r) => ({
55
+ id: r.id,
56
+ title: r.title,
57
+ kind: r.kind,
58
+ description: r.opts.description ?? "",
59
+ tag: r.opts.tag,
60
+ unread: r.opts.unread,
61
+ priority: r.opts.priority,
62
+ progress: r.opts.progress,
63
+ progressLabel: r.opts.progressLabel,
64
+ link: r.opts.link,
65
+ linkLabel: r.opts.linkLabel,
66
+ data: r.opts.data,
67
+ createdAt: r.createdAt
68
+ }));
69
+ localStorage.setItem(key, JSON.stringify(items));
70
+ } catch {
71
+ }
72
+ }
73
+ function persistLoad() {
74
+ const key = persistKey();
75
+ if (!key) return [];
76
+ try {
77
+ const raw = localStorage.getItem(key);
78
+ if (!raw) return [];
79
+ const arr = JSON.parse(raw);
80
+ return Array.isArray(arr) ? arr : [];
81
+ } catch {
82
+ return [];
83
+ }
84
+ }
85
+ var audioCtx = null;
86
+ function playSound() {
87
+ if (!cfg.sound) return;
88
+ try {
89
+ if (typeof cfg.sound === "string") {
90
+ const audio = new Audio(cfg.sound);
91
+ void audio.play();
92
+ return;
93
+ }
94
+ if (!audioCtx) {
95
+ const Ctor = window.AudioContext ?? window.webkitAudioContext;
96
+ if (!Ctor) return;
97
+ audioCtx = new Ctor();
98
+ }
99
+ if (audioCtx.state === "suspended") void audioCtx.resume();
100
+ const now = audioCtx.currentTime;
101
+ [880, 1320].forEach((freq, i) => {
102
+ const osc = audioCtx.createOscillator();
103
+ const gain = audioCtx.createGain();
104
+ osc.connect(gain);
105
+ gain.connect(audioCtx.destination);
106
+ osc.frequency.value = freq;
107
+ osc.type = "sine";
108
+ const t = now + i * 0.08;
109
+ gain.gain.setValueAtTime(0, t);
110
+ gain.gain.linearRampToValueAtTime(0.08, t + 0.01);
111
+ gain.gain.exponentialRampToValueAtTime(1e-3, t + 0.12);
112
+ osc.start(t);
113
+ osc.stop(t + 0.13);
114
+ });
115
+ } catch {
116
+ }
117
+ }
118
+ function buildIcon(record) {
119
+ const wrap = document.createElement("span");
120
+ wrap.className = "notification-icon";
121
+ wrap.setAttribute("aria-hidden", "true");
122
+ const { icon, avatar } = record.opts;
123
+ if (avatar) {
124
+ const img = document.createElement("img");
125
+ img.className = "notification-avatar";
126
+ img.src = avatar;
127
+ img.alt = "";
128
+ img.loading = "lazy";
129
+ wrap.appendChild(img);
130
+ return wrap;
131
+ }
132
+ if (icon === false) {
133
+ wrap.style.display = "none";
134
+ return wrap;
135
+ }
136
+ if (typeof icon === "string" && icon.length > 0) {
137
+ if (/^https?:\/\/|^data:image\//.test(icon) || icon.endsWith(".png") || icon.endsWith(".jpg") || icon.endsWith(".svg") || icon.endsWith(".webp")) {
138
+ const img = document.createElement("img");
139
+ img.className = "notification-avatar";
140
+ img.src = icon;
141
+ img.alt = "";
142
+ wrap.appendChild(img);
143
+ } else {
144
+ wrap.innerHTML = icon;
145
+ }
146
+ return wrap;
147
+ }
148
+ if (record.opts.progress !== void 0 && record.opts.progress >= 0) {
149
+ wrap.innerHTML = PROGRESS_ICON;
150
+ } else {
151
+ wrap.innerHTML = KIND_ICON[record.kind];
152
+ }
153
+ return wrap;
154
+ }
155
+ function buildTitle(title) {
156
+ const el = document.createElement("p");
157
+ el.className = "notification-title";
158
+ el.textContent = title;
159
+ return el;
160
+ }
161
+ function buildDescription(text) {
162
+ const el = document.createElement("p");
163
+ el.className = "notification-message";
164
+ el.style.whiteSpace = "pre-wrap";
165
+ el.textContent = text;
166
+ return el;
167
+ }
168
+ function buildActions(record, getHandle) {
169
+ const actions = record.opts.actions?.length ? record.opts.actions : record.opts.actionLabel ? [{ label: record.opts.actionLabel, onClick: record.opts.onAction }] : null;
170
+ if (!actions || actions.length === 0) return null;
171
+ const wrap = document.createElement("div");
172
+ wrap.className = "notification-actions";
173
+ for (const a of actions) {
174
+ const btn = document.createElement("button");
175
+ btn.type = "button";
176
+ btn.className = "notification-action";
177
+ if (a.type === "primary") btn.classList.add("notification-action--primary");
178
+ else if (a.type === "danger") btn.classList.add("notification-action--danger");
179
+ btn.textContent = a.label;
180
+ btn.addEventListener("click", (ev) => {
181
+ ev.stopPropagation();
182
+ const h = getHandle();
183
+ if (a.onClick) {
184
+ const ret = a.onClick();
185
+ if (ret === false) return;
186
+ }
187
+ if (!a.keepOpen) dismissRecord(record.id);
188
+ else void h;
189
+ });
190
+ wrap.appendChild(btn);
191
+ }
192
+ return wrap;
193
+ }
194
+ function buildLink(record) {
195
+ const { link, linkLabel } = record.opts;
196
+ if (!link) return null;
197
+ const wrap = document.createElement("div");
198
+ wrap.className = "notification-link-wrap";
199
+ const a = document.createElement("a");
200
+ a.className = "notification-link";
201
+ a.href = link;
202
+ a.textContent = linkLabel ?? "\u67E5\u770B\u8BE6\u60C5";
203
+ a.addEventListener("click", (ev) => ev.stopPropagation());
204
+ wrap.appendChild(a);
205
+ return wrap;
206
+ }
207
+ function buildProgressBar(record) {
208
+ const { progress, progressLabel } = record.opts;
209
+ if (progress === void 0 || progress < 0) return null;
210
+ const wrap = document.createElement("div");
211
+ wrap.className = "notification-progress";
212
+ const track = document.createElement("div");
213
+ track.className = "notification-progress-track";
214
+ const fill = document.createElement("div");
215
+ fill.className = "notification-progress-bar";
216
+ const pct = Math.min(100, Math.max(0, progress * 100));
217
+ fill.style.width = pct + "%";
218
+ track.appendChild(fill);
219
+ wrap.appendChild(track);
220
+ if (progressLabel) {
221
+ const lbl = document.createElement("span");
222
+ lbl.className = "notification-progress-label";
223
+ lbl.textContent = progressLabel;
224
+ wrap.appendChild(lbl);
225
+ } else if (progress > 0) {
226
+ const lbl = document.createElement("span");
227
+ lbl.className = "notification-progress-label";
228
+ lbl.textContent = Math.round(pct) + "%";
229
+ wrap.appendChild(lbl);
230
+ }
231
+ return wrap;
232
+ }
233
+ function buildCountdownBar(record) {
234
+ if (!record.duration || record.duration <= 0) return null;
235
+ if (record.opts.showCountdown === false) return null;
236
+ const bar = document.createElement("div");
237
+ bar.className = "notification-countdown";
238
+ bar.setAttribute("aria-hidden", "true");
239
+ const fill = document.createElement("div");
240
+ fill.className = "notification-countdown-fill";
241
+ fill.style.animationDuration = record.duration + "ms";
242
+ fill.style.animationPlayState = "running";
243
+ bar.appendChild(fill);
244
+ return bar;
245
+ }
246
+ function buildClose(record) {
247
+ if (record.opts.closable === false) return null;
248
+ const btn = document.createElement("button");
249
+ btn.type = "button";
250
+ btn.className = "notification-close";
251
+ btn.setAttribute("aria-label", "\u5173\u95ED");
252
+ btn.innerHTML = CLOSE_SVG;
253
+ btn.addEventListener("click", (ev) => {
254
+ ev.stopPropagation();
255
+ dismissRecord(record.id);
256
+ });
257
+ return btn;
258
+ }
259
+ function renderNotification(record, container, getHandle) {
260
+ const el = document.createElement("div");
261
+ let cls = record.kind === "info" ? "notification" : `notification notif--${record.kind}`;
262
+ if (record.opts.priority === "high") cls += " is-priority";
263
+ if (record.opts.unread) cls += " is-unread";
264
+ if (record.opts.progress !== void 0 && record.opts.progress >= 0) cls += " is-progress";
265
+ if (record.opts.avatar) cls += " has-avatar";
266
+ if (record.opts.onClick) {
267
+ cls += " is-clickable";
268
+ el.setAttribute("role", "button");
269
+ el.tabIndex = 0;
270
+ } else {
271
+ el.setAttribute("role", "alert");
272
+ }
273
+ el.className = cls;
274
+ el.id = record.id;
275
+ const icon = buildIcon(record);
276
+ el.appendChild(icon);
277
+ const content = document.createElement("div");
278
+ content.className = "notification-content";
279
+ content.appendChild(buildTitle(record.title));
280
+ if (record.opts.description) content.appendChild(buildDescription(record.opts.description));
281
+ const actions = buildActions(record, getHandle);
282
+ if (actions) content.appendChild(actions);
283
+ const link = buildLink(record);
284
+ if (link) content.appendChild(link);
285
+ const progress = buildProgressBar(record);
286
+ if (progress) content.appendChild(progress);
287
+ el.appendChild(content);
288
+ const close = buildClose(record);
289
+ if (close) el.appendChild(close);
290
+ const countdown = buildCountdownBar(record);
291
+ if (countdown) el.appendChild(countdown);
292
+ if (record.opts.onClick) {
293
+ const onClick = record.opts.onClick;
294
+ const handleClick = () => onClick(getHandle());
295
+ el.addEventListener("click", handleClick);
296
+ el.addEventListener("keydown", (ev) => {
297
+ if (ev.key === "Enter" || ev.key === " ") {
298
+ ev.preventDefault();
299
+ handleClick();
300
+ }
301
+ });
302
+ }
303
+ record.el = el;
304
+ return el;
305
+ }
306
+ function pushToContainer(container, el, priority) {
307
+ if (priority === "high") {
308
+ const firstNormal = Array.from(container.children).find(
309
+ (c) => c instanceof HTMLElement && !c.classList.contains("is-priority")
310
+ );
311
+ if (firstNormal) container.insertBefore(el, firstNormal);
312
+ else container.appendChild(el);
313
+ } else {
314
+ container.appendChild(el);
315
+ }
316
+ }
317
+ function removeOne(el, onClose) {
318
+ if (el.classList.contains("leaving")) return;
319
+ el.classList.add("leaving");
320
+ let done = false;
321
+ const finish = () => {
322
+ if (done) return;
323
+ done = true;
324
+ el.remove();
325
+ onClose?.();
326
+ };
327
+ el.addEventListener("transitionend", finish, { once: true });
328
+ window.setTimeout(finish, 360);
329
+ }
330
+ function trimStack(container) {
331
+ for (; ; ) {
332
+ const items = Array.from(container.querySelectorAll(":scope > .notification"));
333
+ if (items.length <= cfg.maxStack) return;
334
+ const target = items.find((it) => !it.classList.contains("is-priority")) ?? items[0];
335
+ if (!target) return;
336
+ removeOne(target);
337
+ }
338
+ }
339
+ function attachTimer(record, handle) {
340
+ if (!record.duration || record.duration <= 0) return;
341
+ const el = record.el;
342
+ if (!el) return;
343
+ const start = () => {
344
+ if (record.timer) window.clearTimeout(record.timer);
345
+ record.timer = window.setTimeout(() => dismissRecord(record.id), record.remaining);
346
+ record.paused = false;
347
+ };
348
+ const pause = () => {
349
+ if (record.paused) return;
350
+ if (record.timer) {
351
+ window.clearTimeout(record.timer);
352
+ record.timer = 0;
353
+ }
354
+ record.paused = true;
355
+ el.classList.add("is-paused");
356
+ const fill = el.querySelector(".notification-countdown-fill");
357
+ if (fill) fill.style.animationPlayState = "paused";
358
+ };
359
+ const resume = () => {
360
+ if (!record.paused) return;
361
+ el.classList.remove("is-paused");
362
+ const fill = el.querySelector(".notification-countdown-fill");
363
+ if (fill) fill.style.animationPlayState = "running";
364
+ start();
365
+ };
366
+ const pauseOnHover = record.opts.pauseOnHover ?? cfg.pauseOnHover;
367
+ if (pauseOnHover) {
368
+ el.addEventListener("mouseenter", () => handle.pause());
369
+ el.addEventListener("mouseleave", () => handle.resume());
370
+ }
371
+ handle.pause = pause;
372
+ handle.resume = resume;
373
+ start();
374
+ }
375
+ function makeHandle(record) {
376
+ const handle = {
377
+ id: record.id,
378
+ el: record.el ?? document.createElement("div"),
379
+ dismiss: () => dismissRecord(record.id),
380
+ update: (patch) => updateRecord(record.id, patch),
381
+ setProgress: (value, label) => updateRecord(record.id, {
382
+ progress: Math.min(1, Math.max(0, value)),
383
+ progressLabel: label
384
+ }),
385
+ markRead: () => updateRecord(record.id, { unread: false }),
386
+ pause: () => {
387
+ },
388
+ resume: () => {
389
+ }
390
+ };
391
+ return handle;
392
+ }
393
+ function showRecord(title, kind, opts) {
394
+ if (!isBrowser()) return null;
395
+ const container = ensureContainer();
396
+ const id = opts.id ?? genId();
397
+ if (store.has(id)) dismissRecord(id);
398
+ const duration = opts.duration ?? 0;
399
+ const record = {
400
+ id,
401
+ title,
402
+ kind,
403
+ opts: { unread: opts.unread ?? false, ...opts, id },
404
+ el: null,
405
+ createdAt: Date.now(),
406
+ duration,
407
+ remaining: duration,
408
+ paused: false,
409
+ timer: 0
410
+ };
411
+ const handle = makeHandle(record);
412
+ const el = renderNotification(record, container, () => handle);
413
+ record.el = el;
414
+ handle.el = el;
415
+ pushToContainer(container, el, record.opts.priority ?? "normal");
416
+ store.set(id, record);
417
+ attachTimer(record, handle);
418
+ trimStack(container);
419
+ persistSave();
420
+ playSound();
421
+ opts.onShow?.(handle);
422
+ return handle;
423
+ }
424
+ function dismissRecord(id) {
425
+ const record = store.get(id);
426
+ if (!record) {
427
+ const el = document.getElementById(id);
428
+ if (el && el.classList.contains("notification")) removeOne(el);
429
+ return;
430
+ }
431
+ if (record.timer) window.clearTimeout(record.timer);
432
+ const handle = makeHandle(record);
433
+ const onClose = record.opts.onClose;
434
+ store.delete(id);
435
+ if (record.el) removeOne(record.el, () => onClose?.(handle));
436
+ else onClose?.(handle);
437
+ persistSave();
438
+ }
439
+ function updateRecord(id, patch) {
440
+ const record = store.get(id);
441
+ if (!record) return;
442
+ if (patch.title !== void 0) record.title = patch.title;
443
+ if (patch.description !== void 0) record.opts.description = patch.description;
444
+ if (patch.duration !== void 0) {
445
+ record.duration = patch.duration;
446
+ record.remaining = patch.duration;
447
+ }
448
+ if (patch.unread !== void 0) record.opts.unread = patch.unread;
449
+ if (patch.progress !== void 0) record.opts.progress = patch.progress;
450
+ if (patch.progressLabel !== void 0) record.opts.progressLabel = patch.progressLabel;
451
+ if (patch.actions !== void 0) record.opts.actions = patch.actions;
452
+ if (patch.tag !== void 0) record.opts.tag = patch.tag;
453
+ if (patch.priority !== void 0) record.opts.priority = patch.priority;
454
+ if (patch.icon !== void 0) record.opts.icon = patch.icon;
455
+ if (patch.avatar !== void 0) record.opts.avatar = patch.avatar;
456
+ if (patch.link !== void 0) record.opts.link = patch.link;
457
+ if (patch.linkLabel !== void 0) record.opts.linkLabel = patch.linkLabel;
458
+ const el = record.el;
459
+ if (!el) return;
460
+ if (patch.title !== void 0) {
461
+ const t = el.querySelector(".notification-title");
462
+ if (t) t.textContent = patch.title;
463
+ }
464
+ if (patch.description !== void 0) {
465
+ let m = el.querySelector(".notification-message");
466
+ if (patch.description) {
467
+ if (!m) {
468
+ m = document.createElement("p");
469
+ m.className = "notification-message";
470
+ m.style.whiteSpace = "pre-wrap";
471
+ const content = el.querySelector(".notification-content");
472
+ const titleEl = content?.querySelector(".notification-title");
473
+ if (titleEl && titleEl.nextSibling) content?.insertBefore(m, titleEl.nextSibling);
474
+ else content?.appendChild(m);
475
+ }
476
+ m.textContent = patch.description;
477
+ } else if (m) {
478
+ m.remove();
479
+ }
480
+ }
481
+ if (patch.unread !== void 0) {
482
+ el.classList.toggle("is-unread", patch.unread);
483
+ }
484
+ if (patch.priority !== void 0) {
485
+ el.classList.toggle("is-priority", patch.priority === "high");
486
+ }
487
+ if (patch.progress !== void 0 || patch.progressLabel !== void 0) {
488
+ el.classList.add("is-progress");
489
+ let progWrap = el.querySelector(".notification-progress");
490
+ const pct = Math.min(100, Math.max(0, (record.opts.progress ?? 0) * 100));
491
+ if (!progWrap) {
492
+ progWrap = document.createElement("div");
493
+ progWrap.className = "notification-progress";
494
+ const track = document.createElement("div");
495
+ track.className = "notification-progress-track";
496
+ const fill2 = document.createElement("div");
497
+ fill2.className = "notification-progress-bar";
498
+ track.appendChild(fill2);
499
+ progWrap.appendChild(track);
500
+ el.querySelector(".notification-content")?.appendChild(progWrap);
501
+ }
502
+ const fill = progWrap.querySelector(".notification-progress-bar");
503
+ if (fill) fill.style.width = pct + "%";
504
+ let lbl = progWrap.querySelector(".notification-progress-label");
505
+ const labelText = record.opts.progressLabel ?? Math.round(pct) + "%";
506
+ if (!lbl) {
507
+ lbl = document.createElement("span");
508
+ lbl.className = "notification-progress-label";
509
+ progWrap.appendChild(lbl);
510
+ }
511
+ lbl.textContent = labelText;
512
+ }
513
+ persistSave();
514
+ }
515
+ function confirmDialog(title, opts = {}) {
516
+ return new Promise((resolve) => {
517
+ if (!isBrowser()) {
518
+ resolve(false);
519
+ return;
520
+ }
521
+ let resolved = false;
522
+ const settle = (val) => {
523
+ if (resolved) return;
524
+ resolved = true;
525
+ resolve(val);
526
+ };
527
+ const confirmLabel = opts.confirmLabel ?? "\u786E\u8BA4";
528
+ const cancelLabel = opts.cancelLabel ?? "\u53D6\u6D88";
529
+ const actions = [
530
+ {
531
+ label: cancelLabel,
532
+ type: "ghost",
533
+ onClick: () => settle(false)
534
+ },
535
+ {
536
+ label: confirmLabel,
537
+ type: "primary",
538
+ onClick: () => settle(true)
539
+ }
540
+ ];
541
+ const handle = showRecord(title, "info", {
542
+ ...opts,
543
+ actions,
544
+ closable: false,
545
+ onClose: () => settle(false)
546
+ });
547
+ if (!handle) settle(false);
548
+ });
549
+ }
550
+ function createNotificationCenter(parent = document.body, opts = {}) {
551
+ if (!isBrowser()) {
552
+ return {
553
+ push: () => null,
554
+ dismiss() {
555
+ },
556
+ clear() {
557
+ },
558
+ markAllRead() {
559
+ },
560
+ get: () => null,
561
+ getAll: () => [],
562
+ config() {
563
+ },
564
+ el: {}
565
+ };
566
+ }
567
+ const local = {
568
+ position: opts.position ?? "top-right",
569
+ maxStack: opts.maxStack ?? 5,
570
+ pauseOnHover: opts.pauseOnHover ?? true,
571
+ persist: opts.persist ?? false,
572
+ sound: opts.sound ?? false
573
+ };
574
+ const container = document.createElement("div");
575
+ container.className = "notification-container";
576
+ container.setAttribute("role", "region");
577
+ container.setAttribute("aria-label", "\u901A\u77E5");
578
+ container.dataset.position = local.position;
579
+ parent.appendChild(container);
580
+ const localStore = /* @__PURE__ */ new Map();
581
+ function push(input) {
582
+ const kind = input.kind ?? "info";
583
+ const opts2 = input.options ?? {};
584
+ const id = opts2.id ?? genId();
585
+ const duration = opts2.duration ?? 0;
586
+ const record = {
587
+ id,
588
+ title: input.title,
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
+ }
618
+ }
619
+ opts2.onShow?.(handle);
620
+ return handle;
621
+ }
622
+ function dismiss(id) {
623
+ if (id) {
624
+ const rec = localStore.get(id);
625
+ if (!rec) return;
626
+ if (rec.timer) window.clearTimeout(rec.timer);
627
+ const handle = makeHandle(rec);
628
+ localStore.delete(id);
629
+ store.delete(id);
630
+ if (rec.el) removeOne(rec.el, () => rec.opts.onClose?.(handle));
631
+ return;
632
+ }
633
+ Array.from(localStore.keys()).forEach((k) => dismiss(k));
634
+ }
635
+ function clear() {
636
+ dismiss();
637
+ }
638
+ function markAllRead() {
639
+ localStore.forEach((rec) => {
640
+ rec.opts.unread = false;
641
+ rec.el?.classList.remove("is-unread");
642
+ });
643
+ }
644
+ return {
645
+ push,
646
+ dismiss,
647
+ clear,
648
+ markAllRead,
649
+ get: (id) => localStore.get(id) ?? null,
650
+ getAll: () => Array.from(localStore.values()),
651
+ config: (next) => {
652
+ if (next.position) {
653
+ local.position = next.position;
654
+ container.dataset.position = next.position;
655
+ }
656
+ if (typeof next.maxStack === "number" && next.maxStack > 0) local.maxStack = next.maxStack;
657
+ if (typeof next.pauseOnHover === "boolean") local.pauseOnHover = next.pauseOnHover;
658
+ if (next.persist !== void 0) local.persist = next.persist;
659
+ if (next.sound !== void 0) local.sound = next.sound;
660
+ },
661
+ el: container
662
+ };
663
+ }
664
+ var notify = Object.assign(
665
+ (title, opts) => showRecord(title, "info", opts ?? {}),
666
+ {
667
+ success: (title, opts) => showRecord(title, "success", opts ?? {}),
668
+ warn: (title, opts) => showRecord(title, "warning", opts ?? {}),
669
+ error: (title, opts) => showRecord(title, "error", opts ?? {}),
670
+ info: (title, opts) => showRecord(title, "info", opts ?? {}),
671
+ progress: (title, opts) => showRecord(title, "info", { progress: opts?.progress ?? 0, ...opts }),
672
+ confirm: (title, opts) => confirmDialog(title, opts),
673
+ dismiss: (id) => {
674
+ if (!isBrowser()) return;
675
+ if (id) {
676
+ dismissRecord(id);
677
+ return;
678
+ }
679
+ Array.from(store.keys()).forEach((k) => dismissRecord(k));
680
+ },
681
+ clear: () => {
682
+ if (!isBrowser()) return;
683
+ Array.from(store.keys()).forEach((k) => dismissRecord(k));
684
+ },
685
+ markRead: (id) => {
686
+ if (!isBrowser()) return;
687
+ if (id) {
688
+ updateRecord(id, { unread: false });
689
+ return;
690
+ }
691
+ store.forEach((_, k) => updateRecord(k, { unread: false }));
692
+ },
693
+ update: (id, patch) => updateRecord(id, patch),
694
+ get: (id) => store.get(id) ?? null,
695
+ getAll: () => Array.from(store.values()),
696
+ config: (opts) => {
697
+ if (opts.position) {
698
+ cfg.position = opts.position;
699
+ const c = document.body.querySelector(".notification-container[data-notif-default]");
700
+ if (c) c.dataset.position = opts.position;
701
+ }
702
+ if (typeof opts.maxStack === "number" && opts.maxStack > 0) cfg.maxStack = opts.maxStack;
703
+ if (typeof opts.pauseOnHover === "boolean") cfg.pauseOnHover = opts.pauseOnHover;
704
+ if (opts.persist !== void 0) cfg.persist = opts.persist;
705
+ if (opts.sound !== void 0) cfg.sound = opts.sound;
706
+ }
707
+ }
708
+ );
709
+ function initNotification() {
710
+ if (!isBrowser()) return;
711
+ if (globalInit.done) return;
712
+ globalInit.done = true;
713
+ ensureContainer();
714
+ if (cfg.persist) {
715
+ const items = persistLoad();
716
+ const container = ensureContainer();
717
+ for (const item of items) {
718
+ const record = {
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);
747
+ }
748
+ }
749
+ }
750
+ export {
751
+ createNotificationCenter,
752
+ initNotification,
753
+ notify
754
+ };