@icen.ai/ui 0.4.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.
Files changed (155) hide show
  1. package/AGENTS.md +256 -0
  2. package/README.md +47 -13
  3. package/dist/base.css +31 -4
  4. package/dist/behaviors/accordion.d.mts +4 -0
  5. package/dist/behaviors/accordion.mjs +4 -3
  6. package/dist/behaviors/ai-chat.d.mts +57 -0
  7. package/dist/behaviors/ai-chat.mjs +557 -0
  8. package/dist/behaviors/ai-composer.d.mts +112 -0
  9. package/dist/behaviors/ai-composer.mjs +2032 -0
  10. package/dist/behaviors/ai-core.d.mts +151 -0
  11. package/dist/behaviors/ai-core.mjs +491 -0
  12. package/dist/behaviors/ai-diff.d.mts +53 -0
  13. package/dist/behaviors/ai-diff.mjs +282 -0
  14. package/dist/behaviors/ai-panel.d.mts +98 -0
  15. package/dist/behaviors/ai-panel.mjs +1012 -0
  16. package/dist/behaviors/ai-provider.d.mts +198 -0
  17. package/dist/behaviors/ai-provider.mjs +1055 -0
  18. package/dist/behaviors/ai-tool.d.mts +18 -0
  19. package/dist/behaviors/ai-tool.mjs +2125 -0
  20. package/dist/behaviors/ai-tools.d.mts +81 -0
  21. package/dist/behaviors/ai-tools.mjs +1658 -0
  22. package/dist/behaviors/back-top.d.mts +20 -0
  23. package/dist/behaviors/back-top.mjs +101 -0
  24. package/dist/behaviors/carousel.d.mts +4 -0
  25. package/dist/behaviors/carousel.mjs +34 -16
  26. package/dist/behaviors/charts.d.mts +250 -0
  27. package/dist/behaviors/charts.mjs +1301 -448
  28. package/dist/behaviors/command-palette.d.mts +8 -0
  29. package/dist/behaviors/command-palette.mjs +189 -0
  30. package/dist/behaviors/context-menu.d.mts +36 -0
  31. package/dist/behaviors/context-menu.mjs +6 -2
  32. package/dist/behaviors/copy.d.mts +4 -0
  33. package/dist/behaviors/copy.mjs +7 -6
  34. package/dist/behaviors/datatable.d.mts +78 -0
  35. package/dist/behaviors/datatable.mjs +54 -11
  36. package/dist/behaviors/date-picker.d.mts +4 -0
  37. package/dist/behaviors/date-picker.mjs +296 -0
  38. package/dist/behaviors/dropdown.d.mts +4 -0
  39. package/dist/behaviors/dropdown.mjs +7 -3
  40. package/dist/behaviors/input.d.mts +4 -0
  41. package/dist/behaviors/input.mjs +184 -8
  42. package/dist/behaviors/modal.d.mts +11 -0
  43. package/dist/behaviors/modal.mjs +11 -6
  44. package/dist/behaviors/nav.d.mts +7 -0
  45. package/dist/behaviors/nav.mjs +25 -9
  46. package/dist/behaviors/notification.d.mts +172 -0
  47. package/dist/behaviors/notification.mjs +908 -0
  48. package/dist/behaviors/popover.d.mts +46 -0
  49. package/dist/behaviors/popover.mjs +2 -1
  50. package/dist/behaviors/select.d.mts +4 -0
  51. package/dist/behaviors/select.mjs +268 -34
  52. package/dist/behaviors/sidebar.d.mts +4 -0
  53. package/dist/behaviors/sidebar.mjs +4 -3
  54. package/dist/behaviors/slider.d.mts +4 -0
  55. package/dist/behaviors/slider.mjs +4 -3
  56. package/dist/behaviors/split-pane.d.mts +4 -0
  57. package/dist/behaviors/split-pane.mjs +102 -0
  58. package/dist/behaviors/tabs.d.mts +8 -0
  59. package/dist/behaviors/tabs.mjs +44 -19
  60. package/dist/behaviors/tag-input.d.mts +4 -0
  61. package/dist/behaviors/tag-input.mjs +12 -3
  62. package/dist/behaviors/theme.d.mts +25 -0
  63. package/dist/behaviors/theme.mjs +3 -1
  64. package/dist/behaviors/toast.d.mts +26 -0
  65. package/dist/behaviors/toast.mjs +24 -18
  66. package/dist/behaviors/tree.d.mts +4 -0
  67. package/dist/behaviors/tree.mjs +4 -3
  68. package/dist/behaviors/upload.d.mts +4 -0
  69. package/dist/behaviors/upload.mjs +205 -17
  70. package/dist/components/ai-chat.css +1037 -0
  71. package/dist/components/ai-chat.mjs +3 -0
  72. package/dist/components/ai-composer.mjs +3 -0
  73. package/dist/components/ai-context.mjs +2 -0
  74. package/dist/components/ai-diff.css +311 -0
  75. package/dist/components/ai-diff.mjs +3 -0
  76. package/dist/components/ai-files.mjs +3 -0
  77. package/dist/components/ai-message.mjs +3 -0
  78. package/dist/components/ai-panel.css +682 -0
  79. package/dist/components/ai-reasoning.mjs +3 -0
  80. package/dist/components/ai-subagent.mjs +2 -0
  81. package/dist/components/ai-todo.mjs +2 -0
  82. package/dist/components/ai-tool-call.mjs +2 -0
  83. package/dist/components/ai-tool.css +532 -0
  84. package/dist/components/ai-usage.mjs +3 -0
  85. package/dist/components/back-top.css +88 -0
  86. package/dist/components/back-top.mjs +3 -0
  87. package/dist/components/badge.css +127 -0
  88. package/dist/components/badge.mjs +2 -0
  89. package/dist/components/breadcrumb.css +53 -0
  90. package/dist/components/btn.css +4 -7
  91. package/dist/components/card.css +66 -1
  92. package/dist/components/chart-bar.css +10 -0
  93. package/dist/components/chart-calendar.mjs +5 -0
  94. package/dist/components/chart-gauge.css +1 -1
  95. package/dist/components/chart-heatmap.css +40 -0
  96. package/dist/components/chart-scatter.css +24 -0
  97. package/dist/components/chart-scatter.mjs +4 -0
  98. package/dist/components/charts.css +86 -1
  99. package/dist/components/charts.mjs +1 -0
  100. package/dist/components/command-palette.css +246 -0
  101. package/dist/components/command-palette.mjs +3 -0
  102. package/dist/components/content.css +221 -8
  103. package/dist/components/copy.css +93 -0
  104. package/dist/components/copy.mjs +3 -0
  105. package/dist/components/datatable.css +4 -8
  106. package/dist/components/date-picker.css +240 -0
  107. package/dist/components/date-picker.mjs +3 -0
  108. package/dist/components/empty.css +69 -3
  109. package/dist/components/feedback.css +241 -10
  110. package/dist/components/form.css +129 -11
  111. package/dist/components/form.mjs +2 -0
  112. package/dist/components/input.css +25 -2
  113. package/dist/components/layout.css +36 -36
  114. package/dist/components/media.css +153 -6
  115. package/dist/components/menu.css +2 -2
  116. package/dist/components/modal.css +1 -1
  117. package/dist/components/nav.css +106 -2
  118. package/dist/components/notification.css +387 -0
  119. package/dist/components/notification.mjs +3 -0
  120. package/dist/components/pagination.css +1 -1
  121. package/dist/components/panel.css +104 -9
  122. package/dist/components/pill.css +82 -12
  123. package/dist/components/scroll-area.css +112 -0
  124. package/dist/components/scroll-area.mjs +2 -0
  125. package/dist/components/segmented.css +80 -5
  126. package/dist/components/segmented.mjs +1 -0
  127. package/dist/components/select.css +104 -17
  128. package/dist/components/sidebar.css +119 -1
  129. package/dist/components/slider.css +113 -0
  130. package/dist/components/split-pane.css +111 -0
  131. package/dist/components/split-pane.mjs +3 -0
  132. package/dist/components/stat.css +131 -12
  133. package/dist/components/steps.css +98 -1
  134. package/dist/components/switch.css +145 -7
  135. package/dist/components/table.css +111 -29
  136. package/dist/components/tabs.css +132 -22
  137. package/dist/components/tag-input.css +4 -4
  138. package/dist/components/tag.css +164 -0
  139. package/dist/components/tag.mjs +2 -0
  140. package/dist/components/toast.css +122 -5
  141. package/dist/components/toolbar.css +101 -0
  142. package/dist/components/toolbar.mjs +2 -0
  143. package/dist/components/tooltip.css +2 -2
  144. package/dist/components/upload.css +61 -4
  145. package/dist/index.d.mts +37 -0
  146. package/dist/index.mjs +9409 -929
  147. package/dist/registry.json +176 -4
  148. package/dist/retro-effects.css +21 -17
  149. package/dist/tokens/colors.css +4 -1
  150. package/dist/tokens/retro-effects.css +21 -17
  151. package/dist/tokens/style-profiles.css +21 -0
  152. package/dist/tokens.css +25 -1
  153. package/dist/ui.css +9616 -2839
  154. package/docs/spec/ai-native.md +659 -0
  155. package/package.json +15 -5
@@ -0,0 +1,908 @@
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
+ 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;
49
+ return null;
50
+ }
51
+ function recordPersist(record) {
52
+ return record.cfgRef ? record.cfgRef.persist : cfg.persist;
53
+ }
54
+ function persistSave() {
55
+ try {
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
+ }
87
+ } catch {
88
+ }
89
+ }
90
+ function persistLoad(key) {
91
+ try {
92
+ const raw = localStorage.getItem(key);
93
+ if (!raw) return [];
94
+ const arr = JSON.parse(raw);
95
+ return Array.isArray(arr) ? arr : [];
96
+ } catch {
97
+ return [];
98
+ }
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
+ }
137
+ var audioCtx = null;
138
+ function playSound(sound) {
139
+ if (!sound) return;
140
+ try {
141
+ if (typeof sound === "string") {
142
+ const audio = new Audio(sound);
143
+ void audio.play();
144
+ return;
145
+ }
146
+ if (!audioCtx) {
147
+ const Ctor = window.AudioContext ?? window.webkitAudioContext;
148
+ if (!Ctor) return;
149
+ audioCtx = new Ctor();
150
+ }
151
+ if (audioCtx.state === "suspended") void audioCtx.resume();
152
+ const now = audioCtx.currentTime;
153
+ [880, 1320].forEach((freq, i) => {
154
+ const osc = audioCtx.createOscillator();
155
+ const gain = audioCtx.createGain();
156
+ osc.connect(gain);
157
+ gain.connect(audioCtx.destination);
158
+ osc.frequency.value = freq;
159
+ osc.type = "sine";
160
+ const t = now + i * 0.08;
161
+ gain.gain.setValueAtTime(0, t);
162
+ gain.gain.linearRampToValueAtTime(0.08, t + 0.01);
163
+ gain.gain.exponentialRampToValueAtTime(1e-3, t + 0.12);
164
+ osc.start(t);
165
+ osc.stop(t + 0.13);
166
+ });
167
+ } catch {
168
+ }
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
+ }
192
+ function buildIcon(record) {
193
+ const wrap = document.createElement("span");
194
+ wrap.className = "notification-icon";
195
+ wrap.setAttribute("aria-hidden", "true");
196
+ const { icon, avatar } = record.opts;
197
+ if (avatar) {
198
+ const img = document.createElement("img");
199
+ img.className = "notification-avatar";
200
+ img.src = avatar;
201
+ img.alt = "";
202
+ img.loading = "lazy";
203
+ wrap.appendChild(img);
204
+ return wrap;
205
+ }
206
+ if (icon === false) {
207
+ wrap.style.display = "none";
208
+ return wrap;
209
+ }
210
+ if (typeof icon === "string" && icon.length > 0) {
211
+ if (/^https?:\/\/|^data:image\//.test(icon) || icon.endsWith(".png") || icon.endsWith(".jpg") || icon.endsWith(".svg") || icon.endsWith(".webp")) {
212
+ const img = document.createElement("img");
213
+ img.className = "notification-avatar";
214
+ img.src = icon;
215
+ img.alt = "";
216
+ wrap.appendChild(img);
217
+ } else {
218
+ const svg = sanitizeSvgIcon(icon);
219
+ if (svg) wrap.appendChild(svg);
220
+ else wrap.textContent = icon;
221
+ }
222
+ return wrap;
223
+ }
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);
228
+ return wrap;
229
+ }
230
+ function buildTitle(title) {
231
+ const el = document.createElement("p");
232
+ el.className = "notification-title";
233
+ el.textContent = title;
234
+ return el;
235
+ }
236
+ function buildDescription(text) {
237
+ const el = document.createElement("p");
238
+ el.className = "notification-message";
239
+ el.style.whiteSpace = "pre-wrap";
240
+ el.textContent = text;
241
+ return el;
242
+ }
243
+ function buildActions(record, getHandle) {
244
+ const actions = record.opts.actions?.length ? record.opts.actions : record.opts.actionLabel ? [{ label: record.opts.actionLabel, onClick: record.opts.onAction }] : null;
245
+ if (!actions || actions.length === 0) return null;
246
+ const wrap = document.createElement("div");
247
+ wrap.className = "notification-actions";
248
+ for (const a of actions) {
249
+ const btn = document.createElement("button");
250
+ btn.type = "button";
251
+ btn.className = "notification-action";
252
+ if (a.type === "primary") btn.classList.add("notification-action--primary");
253
+ else if (a.type === "danger") btn.classList.add("notification-action--danger");
254
+ btn.textContent = a.label;
255
+ btn.addEventListener("click", (ev) => {
256
+ ev.stopPropagation();
257
+ const h = getHandle();
258
+ if (a.onClick) {
259
+ const ret = a.onClick();
260
+ if (ret === false) return;
261
+ }
262
+ if (!a.keepOpen) dismissRecord(record.id);
263
+ else void h;
264
+ });
265
+ wrap.appendChild(btn);
266
+ }
267
+ return wrap;
268
+ }
269
+ function buildLink(record) {
270
+ const { link, linkLabel } = record.opts;
271
+ if (!link) return null;
272
+ const wrap = document.createElement("div");
273
+ wrap.className = "notification-link-wrap";
274
+ const a = document.createElement("a");
275
+ a.className = "notification-link";
276
+ a.href = link;
277
+ a.textContent = linkLabel ?? "\u67E5\u770B\u8BE6\u60C5";
278
+ a.addEventListener("click", (ev) => ev.stopPropagation());
279
+ wrap.appendChild(a);
280
+ return wrap;
281
+ }
282
+ function buildProgressBar(record) {
283
+ const { progress, progressLabel } = record.opts;
284
+ if (progress === void 0 || progress < 0) return null;
285
+ const wrap = document.createElement("div");
286
+ wrap.className = "notification-progress";
287
+ const track = document.createElement("div");
288
+ track.className = "notification-progress-track";
289
+ const fill = document.createElement("div");
290
+ fill.className = "notification-progress-bar";
291
+ const pct = Math.min(100, Math.max(0, progress * 100));
292
+ fill.style.width = pct + "%";
293
+ track.appendChild(fill);
294
+ wrap.appendChild(track);
295
+ if (progressLabel) {
296
+ const lbl = document.createElement("span");
297
+ lbl.className = "notification-progress-label";
298
+ lbl.textContent = progressLabel;
299
+ wrap.appendChild(lbl);
300
+ } else if (progress > 0) {
301
+ const lbl = document.createElement("span");
302
+ lbl.className = "notification-progress-label";
303
+ lbl.textContent = Math.round(pct) + "%";
304
+ wrap.appendChild(lbl);
305
+ }
306
+ return wrap;
307
+ }
308
+ function buildCountdownBar(record) {
309
+ if (!record.duration || record.duration <= 0) return null;
310
+ if (record.opts.showCountdown === false) return null;
311
+ const bar = document.createElement("div");
312
+ bar.className = "notification-countdown";
313
+ bar.setAttribute("aria-hidden", "true");
314
+ const fill = document.createElement("div");
315
+ fill.className = "notification-countdown-fill";
316
+ fill.style.animationDuration = record.duration + "ms";
317
+ fill.style.animationPlayState = "running";
318
+ bar.appendChild(fill);
319
+ return bar;
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
+ }
346
+ function buildClose(record) {
347
+ if (record.opts.closable === false) return null;
348
+ const btn = document.createElement("button");
349
+ btn.type = "button";
350
+ btn.className = "notification-close";
351
+ btn.setAttribute("aria-label", "\u5173\u95ED");
352
+ const svg = sanitizeSvgIcon(CLOSE_SVG);
353
+ if (svg) btn.appendChild(svg);
354
+ btn.addEventListener("click", (ev) => {
355
+ ev.stopPropagation();
356
+ dismissRecord(record.id);
357
+ });
358
+ return btn;
359
+ }
360
+ function renderNotification(record, container, getHandle) {
361
+ const el = document.createElement("div");
362
+ let cls = record.kind === "info" ? "notification" : `notification notification--${record.kind}`;
363
+ if (record.opts.priority === "high") cls += " is-priority";
364
+ if (record.opts.unread) cls += " is-unread";
365
+ if (record.opts.progress !== void 0 && record.opts.progress >= 0) cls += " is-progress";
366
+ if (record.opts.avatar) cls += " has-avatar";
367
+ if (record.opts.onClick) {
368
+ cls += " is-clickable";
369
+ el.setAttribute("role", "button");
370
+ el.tabIndex = 0;
371
+ } else {
372
+ el.setAttribute("role", "alert");
373
+ }
374
+ el.className = cls;
375
+ el.id = record.id;
376
+ const icon = buildIcon(record);
377
+ el.appendChild(icon);
378
+ const content = document.createElement("div");
379
+ content.className = "notification-content";
380
+ content.appendChild(buildTitle(record.title));
381
+ if (record.opts.description) content.appendChild(buildDescription(record.opts.description));
382
+ const actions = buildActions(record, getHandle);
383
+ if (actions) content.appendChild(actions);
384
+ const link = buildLink(record);
385
+ if (link) content.appendChild(link);
386
+ const progress = buildProgressBar(record);
387
+ if (progress) content.appendChild(progress);
388
+ el.appendChild(content);
389
+ const close = buildClose(record);
390
+ if (close) el.appendChild(close);
391
+ const countdown = buildCountdownBar(record);
392
+ if (countdown) el.appendChild(countdown);
393
+ if (record.opts.onClick) {
394
+ const onClick = record.opts.onClick;
395
+ const handleClick = () => onClick(getHandle());
396
+ el.addEventListener("click", handleClick);
397
+ el.addEventListener("keydown", (ev) => {
398
+ if (ev.key === "Enter" || ev.key === " ") {
399
+ ev.preventDefault();
400
+ handleClick();
401
+ }
402
+ });
403
+ }
404
+ record.el = el;
405
+ return el;
406
+ }
407
+ function pushToContainer(container, el, priority) {
408
+ if (priority === "high") {
409
+ const firstNormal = Array.from(container.children).find(
410
+ (c) => c instanceof HTMLElement && !c.classList.contains("is-priority")
411
+ );
412
+ if (firstNormal) container.insertBefore(el, firstNormal);
413
+ else container.appendChild(el);
414
+ } else {
415
+ container.appendChild(el);
416
+ }
417
+ }
418
+ function removeOne(el, onClose) {
419
+ if (el.classList.contains("is-leaving")) return;
420
+ el.classList.add("is-leaving");
421
+ let done = false;
422
+ const finish = () => {
423
+ if (done) return;
424
+ done = true;
425
+ el.remove();
426
+ onClose?.();
427
+ };
428
+ el.addEventListener("transitionend", finish, { once: true });
429
+ window.setTimeout(finish, 360);
430
+ }
431
+ function trimStack(container) {
432
+ for (; ; ) {
433
+ const items = Array.from(container.querySelectorAll(":scope > .notification"));
434
+ if (items.length <= cfg.maxStack) return;
435
+ const target = items.find((it) => !it.classList.contains("is-priority")) ?? items[0];
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
+ }
444
+ removeOne(target);
445
+ if (target.id) store.delete(target.id);
446
+ return;
447
+ }
448
+ }
449
+ function attachTimer(record, handle) {
450
+ const el = record.el;
451
+ if (!el) return;
452
+ const start = () => {
453
+ if (record.timer) window.clearTimeout(record.timer);
454
+ record.timer = 0;
455
+ if (record.remaining <= 0) return;
456
+ record.startedAt = Date.now();
457
+ record.timer = window.setTimeout(() => dismissRecord(record.id), record.remaining);
458
+ record.paused = false;
459
+ };
460
+ const pause = () => {
461
+ if (record.paused) return;
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));
466
+ record.paused = true;
467
+ el.classList.add("is-paused");
468
+ const fill = el.querySelector(".notification-countdown-fill");
469
+ if (fill) fill.style.animationPlayState = "paused";
470
+ };
471
+ const resume = () => {
472
+ if (!record.paused) return;
473
+ record.paused = false;
474
+ el.classList.remove("is-paused");
475
+ const fill = el.querySelector(".notification-countdown-fill");
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");
486
+ start();
487
+ };
488
+ const pauseOnHover = record.opts.pauseOnHover ?? record.cfgRef?.pauseOnHover ?? cfg.pauseOnHover;
489
+ if (pauseOnHover && !record.hoverBound) {
490
+ record.hoverBound = true;
491
+ el.addEventListener("mouseenter", () => handle.pause());
492
+ el.addEventListener("mouseleave", () => handle.resume());
493
+ }
494
+ handle.pause = pause;
495
+ handle.resume = resume;
496
+ if (record.duration > 0) {
497
+ record.remaining = record.duration;
498
+ start();
499
+ }
500
+ }
501
+ function makeHandle(record) {
502
+ const handle = {
503
+ id: record.id,
504
+ el: record.el ?? document.createElement("div"),
505
+ dismiss: () => dismissRecord(record.id),
506
+ update: (patch) => updateRecord(record.id, patch),
507
+ setProgress: (value, label) => updateRecord(record.id, {
508
+ progress: Math.min(1, Math.max(0, value)),
509
+ progressLabel: label
510
+ }),
511
+ markRead: () => updateRecord(record.id, { unread: false }),
512
+ pause: () => {
513
+ },
514
+ resume: () => {
515
+ }
516
+ };
517
+ return handle;
518
+ }
519
+ function showRecord(title, kind, opts) {
520
+ if (!isBrowser()) return null;
521
+ const container = ensureContainer();
522
+ const id = opts.id ?? genId();
523
+ if (store.has(id)) dismissRecord(id);
524
+ const duration = opts.duration ?? 0;
525
+ const record = {
526
+ id,
527
+ title,
528
+ kind,
529
+ opts: { unread: opts.unread ?? false, ...opts, id },
530
+ el: null,
531
+ createdAt: Date.now(),
532
+ duration,
533
+ remaining: duration,
534
+ paused: false,
535
+ timer: 0,
536
+ startedAt: 0
537
+ };
538
+ const handle = makeHandle(record);
539
+ const el = renderNotification(record, container, () => handle);
540
+ record.el = el;
541
+ handle.el = el;
542
+ pushToContainer(container, el, record.opts.priority ?? "normal");
543
+ store.set(id, record);
544
+ attachTimer(record, handle);
545
+ trimStack(container);
546
+ persistSave();
547
+ playSound(record.cfgRef?.sound ?? cfg.sound);
548
+ opts.onShow?.(handle);
549
+ return handle;
550
+ }
551
+ function dismissRecord(id) {
552
+ const record = store.get(id);
553
+ if (!record) {
554
+ const el = document.getElementById(id);
555
+ if (el && el.classList.contains("notification")) removeOne(el);
556
+ return;
557
+ }
558
+ if (record.timer) window.clearTimeout(record.timer);
559
+ const handle = makeHandle(record);
560
+ const onClose = record.opts.onClose;
561
+ store.delete(id);
562
+ record.ownerStore?.delete(id);
563
+ if (record.el) removeOne(record.el, () => onClose?.(handle));
564
+ else onClose?.(handle);
565
+ persistSave();
566
+ }
567
+ function updateRecord(id, patch) {
568
+ const record = store.get(id);
569
+ if (!record) return;
570
+ if (patch.title !== void 0) record.title = patch.title;
571
+ if (patch.description !== void 0) record.opts.description = patch.description;
572
+ if (patch.duration !== void 0) {
573
+ record.duration = patch.duration;
574
+ record.remaining = patch.duration;
575
+ }
576
+ if (patch.unread !== void 0) record.opts.unread = patch.unread;
577
+ if (patch.progress !== void 0) record.opts.progress = patch.progress;
578
+ if (patch.progressLabel !== void 0) record.opts.progressLabel = patch.progressLabel;
579
+ if (patch.actions !== void 0) record.opts.actions = patch.actions;
580
+ if (patch.tag !== void 0) record.opts.tag = patch.tag;
581
+ if (patch.priority !== void 0) record.opts.priority = patch.priority;
582
+ if (patch.icon !== void 0) record.opts.icon = patch.icon;
583
+ if (patch.avatar !== void 0) record.opts.avatar = patch.avatar;
584
+ if (patch.link !== void 0) record.opts.link = patch.link;
585
+ if (patch.linkLabel !== void 0) record.opts.linkLabel = patch.linkLabel;
586
+ const el = record.el;
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
+ }
593
+ if (patch.title !== void 0) {
594
+ const t = el.querySelector(".notification-title");
595
+ if (t) t.textContent = patch.title;
596
+ }
597
+ if (patch.description !== void 0) {
598
+ let m = el.querySelector(".notification-message");
599
+ if (patch.description) {
600
+ if (!m) {
601
+ m = document.createElement("p");
602
+ m.className = "notification-message";
603
+ m.style.whiteSpace = "pre-wrap";
604
+ const content = el.querySelector(".notification-content");
605
+ const titleEl = content?.querySelector(".notification-title");
606
+ if (titleEl && titleEl.nextSibling) content?.insertBefore(m, titleEl.nextSibling);
607
+ else content?.appendChild(m);
608
+ }
609
+ m.textContent = patch.description;
610
+ } else if (m) {
611
+ m.remove();
612
+ }
613
+ }
614
+ if (patch.unread !== void 0) {
615
+ el.classList.toggle("is-unread", patch.unread);
616
+ }
617
+ if (patch.priority !== void 0) {
618
+ el.classList.toggle("is-priority", patch.priority === "high");
619
+ }
620
+ if (patch.progress !== void 0 || patch.progressLabel !== void 0) {
621
+ el.classList.add("is-progress");
622
+ let progWrap = el.querySelector(".notification-progress");
623
+ const pct = Math.min(100, Math.max(0, (record.opts.progress ?? 0) * 100));
624
+ if (!progWrap) {
625
+ progWrap = document.createElement("div");
626
+ progWrap.className = "notification-progress";
627
+ const track = document.createElement("div");
628
+ track.className = "notification-progress-track";
629
+ const fill2 = document.createElement("div");
630
+ fill2.className = "notification-progress-bar";
631
+ track.appendChild(fill2);
632
+ progWrap.appendChild(track);
633
+ el.querySelector(".notification-content")?.appendChild(progWrap);
634
+ }
635
+ const fill = progWrap.querySelector(".notification-progress-bar");
636
+ if (fill) fill.style.width = pct + "%";
637
+ let lbl = progWrap.querySelector(".notification-progress-label");
638
+ const labelText = record.opts.progressLabel ?? Math.round(pct) + "%";
639
+ if (!lbl) {
640
+ lbl = document.createElement("span");
641
+ lbl.className = "notification-progress-label";
642
+ progWrap.appendChild(lbl);
643
+ }
644
+ lbl.textContent = labelText;
645
+ }
646
+ persistSave();
647
+ }
648
+ function confirmDialog(title, opts = {}) {
649
+ return new Promise((resolve) => {
650
+ if (!isBrowser()) {
651
+ resolve(false);
652
+ return;
653
+ }
654
+ let resolved = false;
655
+ const settle = (val) => {
656
+ if (resolved) return;
657
+ resolved = true;
658
+ resolve(val);
659
+ };
660
+ const confirmLabel = opts.confirmLabel ?? "\u786E\u8BA4";
661
+ const cancelLabel = opts.cancelLabel ?? "\u53D6\u6D88";
662
+ const actions = [
663
+ {
664
+ label: cancelLabel,
665
+ type: "ghost",
666
+ onClick: () => settle(false)
667
+ },
668
+ {
669
+ label: confirmLabel,
670
+ type: "primary",
671
+ onClick: () => settle(true)
672
+ }
673
+ ];
674
+ const handle = showRecord(title, "info", {
675
+ ...opts,
676
+ actions,
677
+ closable: false,
678
+ onClose: () => settle(false)
679
+ });
680
+ if (!handle) settle(false);
681
+ });
682
+ }
683
+ function createNotificationCenter(parent, opts = {}) {
684
+ if (!isBrowser()) {
685
+ return {
686
+ push: () => null,
687
+ dismiss() {
688
+ },
689
+ clear() {
690
+ },
691
+ markAllRead() {
692
+ },
693
+ destroy() {
694
+ },
695
+ get: () => null,
696
+ getAll: () => [],
697
+ config() {
698
+ },
699
+ el: {}
700
+ };
701
+ }
702
+ const mount = parent ?? document.body;
703
+ const local = {
704
+ position: opts.position ?? "top-right",
705
+ maxStack: opts.maxStack ?? 5,
706
+ pauseOnHover: opts.pauseOnHover ?? true,
707
+ persist: opts.persist ?? false,
708
+ sound: opts.sound ?? false
709
+ };
710
+ const container = document.createElement("div");
711
+ container.className = "notification-container";
712
+ container.setAttribute("role", "region");
713
+ container.setAttribute("aria-label", "\u901A\u77E5");
714
+ container.dataset.position = local.position;
715
+ mount.appendChild(container);
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
+ }
727
+ function push(input) {
728
+ if (destroyed) return null;
729
+ const kind = input.kind ?? "info";
730
+ const opts2 = input.options ?? {};
731
+ const id = opts2.id ?? genId();
732
+ const duration = opts2.duration ?? 0;
733
+ const record = {
734
+ id,
735
+ title: input.title,
736
+ kind,
737
+ opts: { unread: opts2.unread ?? false, ...opts2, id },
738
+ el: null,
739
+ createdAt: Date.now(),
740
+ duration,
741
+ remaining: duration,
742
+ paused: false,
743
+ timer: 0,
744
+ startedAt: 0,
745
+ cfgRef: local,
746
+ ownerStore: localStore
747
+ };
748
+ const handle = makeHandle(record);
749
+ const el = renderNotification(record, container, () => handle);
750
+ record.el = el;
751
+ handle.el = el;
752
+ pushToContainer(container, el, record.opts.priority ?? "normal");
753
+ localStore.set(id, record);
754
+ store.set(id, record);
755
+ attachTimer(record, handle);
756
+ for (; ; ) {
757
+ const items = Array.from(container.querySelectorAll(":scope > .notification"));
758
+ if (items.length <= local.maxStack) break;
759
+ const target = items.find((it) => !it.classList.contains("is-priority")) ?? items[0];
760
+ if (!target) break;
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
+ }
772
+ removeOne(target);
773
+ if (tid) {
774
+ localStore.delete(tid);
775
+ store.delete(tid);
776
+ }
777
+ break;
778
+ }
779
+ persistSave();
780
+ playSound(local.sound);
781
+ opts2.onShow?.(handle);
782
+ return handle;
783
+ }
784
+ function dismiss(id) {
785
+ if (id) {
786
+ const rec = localStore.get(id);
787
+ if (!rec) return;
788
+ if (rec.timer) window.clearTimeout(rec.timer);
789
+ const handle = makeHandle(rec);
790
+ localStore.delete(id);
791
+ store.delete(id);
792
+ if (rec.el) removeOne(rec.el, () => rec.opts.onClose?.(handle));
793
+ persistSave();
794
+ return;
795
+ }
796
+ Array.from(localStore.keys()).forEach((k) => dismiss(k));
797
+ }
798
+ function clear() {
799
+ dismiss();
800
+ }
801
+ function markAllRead() {
802
+ localStore.forEach((rec) => {
803
+ rec.opts.unread = false;
804
+ rec.el?.classList.remove("is-unread");
805
+ });
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
+ }
817
+ return {
818
+ push,
819
+ dismiss,
820
+ clear,
821
+ markAllRead,
822
+ destroy,
823
+ get: (id) => localStore.get(id) ?? null,
824
+ getAll: () => Array.from(localStore.values()),
825
+ config: (next) => {
826
+ if (next.position) {
827
+ local.position = next.position;
828
+ container.dataset.position = next.position;
829
+ }
830
+ if (typeof next.maxStack === "number" && next.maxStack > 0) local.maxStack = next.maxStack;
831
+ if (typeof next.pauseOnHover === "boolean") local.pauseOnHover = next.pauseOnHover;
832
+ if (next.persist !== void 0) {
833
+ local.persist = next.persist;
834
+ const k = persistKeyFor(next.persist);
835
+ if (k) knownPersistKeys.add(k);
836
+ }
837
+ if (next.sound !== void 0) local.sound = next.sound;
838
+ },
839
+ el: container
840
+ };
841
+ }
842
+ var notify = Object.assign(
843
+ (title, opts) => showRecord(title, "info", opts ?? {}),
844
+ {
845
+ success: (title, opts) => showRecord(title, "success", opts ?? {}),
846
+ warn: (title, opts) => showRecord(title, "warning", opts ?? {}),
847
+ error: (title, opts) => showRecord(title, "error", opts ?? {}),
848
+ info: (title, opts) => showRecord(title, "info", opts ?? {}),
849
+ progress: (title, opts) => showRecord(title, "info", { progress: opts?.progress ?? 0, ...opts }),
850
+ confirm: (title, opts) => confirmDialog(title, opts),
851
+ dismiss: (id) => {
852
+ if (!isBrowser()) return;
853
+ if (id) {
854
+ dismissRecord(id);
855
+ return;
856
+ }
857
+ Array.from(store.keys()).forEach((k) => dismissRecord(k));
858
+ },
859
+ clear: () => {
860
+ if (!isBrowser()) return;
861
+ Array.from(store.keys()).forEach((k) => dismissRecord(k));
862
+ },
863
+ markRead: (id) => {
864
+ if (!isBrowser()) return;
865
+ if (id) {
866
+ updateRecord(id, { unread: false });
867
+ return;
868
+ }
869
+ store.forEach((_, k) => updateRecord(k, { unread: false }));
870
+ },
871
+ update: (id, patch) => updateRecord(id, patch),
872
+ get: (id) => store.get(id) ?? null,
873
+ getAll: () => Array.from(store.values()),
874
+ config: (opts) => {
875
+ if (opts.position) {
876
+ cfg.position = opts.position;
877
+ const c = document.body.querySelector(".notification-container[data-notif-default]");
878
+ if (c) c.dataset.position = opts.position;
879
+ }
880
+ if (typeof opts.maxStack === "number" && opts.maxStack > 0) cfg.maxStack = opts.maxStack;
881
+ if (typeof opts.pauseOnHover === "boolean") cfg.pauseOnHover = opts.pauseOnHover;
882
+ if (opts.persist !== void 0) {
883
+ cfg.persist = opts.persist;
884
+ const k = persistKeyFor(opts.persist);
885
+ if (k) knownPersistKeys.add(k);
886
+ }
887
+ if (opts.sound !== void 0) cfg.sound = opts.sound;
888
+ }
889
+ }
890
+ );
891
+ function initNotification() {
892
+ if (!isBrowser()) return;
893
+ if (globalInit.done) return;
894
+ globalInit.done = true;
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);
901
+ }
902
+ }
903
+ }
904
+ export {
905
+ createNotificationCenter,
906
+ initNotification,
907
+ notify
908
+ };