@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
|
@@ -75,7 +75,10 @@ function onOutsidePointerDown(e) {
|
|
|
75
75
|
if (active.panel.contains(target) || active.trigger.contains(target)) return;
|
|
76
76
|
closeDropdown();
|
|
77
77
|
}
|
|
78
|
-
function onScroll() {
|
|
78
|
+
function onScroll(e) {
|
|
79
|
+
if (!active) return;
|
|
80
|
+
const target = e.target;
|
|
81
|
+
if (target instanceof Node && active.panel.contains(target)) return;
|
|
79
82
|
closeDropdown();
|
|
80
83
|
}
|
|
81
84
|
function onKeyDown(e) {
|
|
@@ -101,9 +104,10 @@ function onKeyDown(e) {
|
|
|
101
104
|
item.click();
|
|
102
105
|
}
|
|
103
106
|
}
|
|
104
|
-
function initDropdown(root
|
|
107
|
+
function initDropdown(root) {
|
|
105
108
|
if (typeof document === "undefined") return;
|
|
106
|
-
root
|
|
109
|
+
const scope = root ?? document;
|
|
110
|
+
scope.querySelectorAll("[data-dropdown-trigger]").forEach((el) => el.setAttribute("aria-expanded", "false"));
|
|
107
111
|
if (initialized) return;
|
|
108
112
|
initialized = true;
|
|
109
113
|
document.addEventListener("click", (e) => {
|
package/dist/behaviors/input.mjs
CHANGED
|
@@ -53,9 +53,12 @@ function setupAutosize(ta) {
|
|
|
53
53
|
resizeTextarea(ta);
|
|
54
54
|
}
|
|
55
55
|
function setupCounter(field) {
|
|
56
|
+
const el = field;
|
|
57
|
+
if (el.__icenCounterInit) return;
|
|
56
58
|
const maxAttr = field.getAttribute("maxlength");
|
|
57
59
|
const dataCount = field.getAttribute("data-count");
|
|
58
60
|
if (!maxAttr && dataCount === null) return;
|
|
61
|
+
el.__icenCounterInit = true;
|
|
59
62
|
const max = maxAttr ? Number(maxAttr) : 0;
|
|
60
63
|
let counter = null;
|
|
61
64
|
const wrap = field.closest(".input-wrap, .textarea-wrap");
|
|
@@ -88,8 +91,11 @@ function setupCounter(field) {
|
|
|
88
91
|
update();
|
|
89
92
|
}
|
|
90
93
|
function setupMask(field) {
|
|
94
|
+
const el = field;
|
|
95
|
+
if (el.__icenMaskInit) return;
|
|
91
96
|
const mask = field.getAttribute("data-mask");
|
|
92
97
|
if (!mask) return;
|
|
98
|
+
el.__icenMaskInit = true;
|
|
93
99
|
const maskChars = [];
|
|
94
100
|
for (const ch of mask) maskChars.push(ch);
|
|
95
101
|
function isInputSlot(ch) {
|
|
@@ -163,10 +169,6 @@ function setupMask(field) {
|
|
|
163
169
|
if (ev.ctrlKey || ev.metaKey || ev.altKey) return;
|
|
164
170
|
if (["Backspace", "Delete", "Tab", "Escape", "ArrowLeft", "ArrowRight", "Home", "End", "Enter"].includes(ev.key)) return;
|
|
165
171
|
const currentRaw = extractRaw(field.value);
|
|
166
|
-
const nextSlotIdx = maskChars.findIndex((ch, i) => {
|
|
167
|
-
if (!isInputSlot(ch)) return false;
|
|
168
|
-
return i >= field.value.length || !isInputSlot(maskChars[Math.min(field.value.length, maskChars.length - 1)]);
|
|
169
|
-
});
|
|
170
172
|
const nextSlot = maskChars.find((ch, i) => {
|
|
171
173
|
const filledSlots = maskChars.slice(0, i).filter(isInputSlot).length;
|
|
172
174
|
return isInputSlot(ch) && filledSlots >= currentRaw.length;
|
|
@@ -178,6 +180,9 @@ function setupMask(field) {
|
|
|
178
180
|
}
|
|
179
181
|
function setupTrim(field) {
|
|
180
182
|
if (!field.hasAttribute("data-trim")) return;
|
|
183
|
+
const el = field;
|
|
184
|
+
if (el.__icenTrimInit) return;
|
|
185
|
+
el.__icenTrimInit = true;
|
|
181
186
|
field.addEventListener("blur", () => {
|
|
182
187
|
const trimmed = field.value.trim();
|
|
183
188
|
if (trimmed !== field.value) {
|
|
@@ -234,16 +239,18 @@ function setupOtp(group) {
|
|
|
234
239
|
const fireComplete = () => {
|
|
235
240
|
const code = cells.map((c) => c.value).join("");
|
|
236
241
|
if (code.length === cells.length && cells.every((c) => c.value.length > 0)) {
|
|
242
|
+
group.dispatchEvent(new CustomEvent("icen:otp-complete", { detail: { code }, bubbles: true }));
|
|
237
243
|
group.dispatchEvent(new CustomEvent("otp:complete", { detail: { code }, bubbles: true }));
|
|
238
244
|
}
|
|
239
245
|
};
|
|
240
246
|
cells.forEach((cell) => cell.addEventListener("input", fireComplete));
|
|
241
247
|
}
|
|
242
|
-
function initInput(root
|
|
248
|
+
function initInput(root) {
|
|
243
249
|
if (typeof document === "undefined") return;
|
|
250
|
+
const scope = root ?? document;
|
|
244
251
|
const wraps = [];
|
|
245
|
-
if (
|
|
246
|
-
wraps.push(...Array.from(
|
|
252
|
+
if (scope instanceof Element && scope.matches(".input-wrap")) wraps.push(scope);
|
|
253
|
+
wraps.push(...Array.from(scope.querySelectorAll(".input-wrap")));
|
|
247
254
|
for (const w of wraps) {
|
|
248
255
|
const el = w;
|
|
249
256
|
if (el.__icenInputInit) continue;
|
|
@@ -252,37 +259,37 @@ function initInput(root = document) {
|
|
|
252
259
|
setupPasswordToggle(w);
|
|
253
260
|
}
|
|
254
261
|
const textareas = [];
|
|
255
|
-
if (
|
|
256
|
-
textareas.push(...Array.from(
|
|
262
|
+
if (scope instanceof HTMLTextAreaElement && scope.matches("[data-autosize]")) textareas.push(scope);
|
|
263
|
+
textareas.push(...Array.from(scope.querySelectorAll("textarea[data-autosize]")));
|
|
257
264
|
for (const ta of textareas) setupAutosize(ta);
|
|
258
265
|
const countedFields = [];
|
|
259
|
-
if (
|
|
260
|
-
const fields = Array.from(
|
|
266
|
+
if (scope instanceof Element) {
|
|
267
|
+
const fields = Array.from(scope.querySelectorAll(
|
|
261
268
|
"input[maxlength], input[data-count], textarea[maxlength], textarea[data-count]"
|
|
262
269
|
));
|
|
263
270
|
countedFields.push(...fields);
|
|
264
|
-
if (
|
|
265
|
-
if (
|
|
271
|
+
if (scope instanceof HTMLInputElement || scope instanceof HTMLTextAreaElement) {
|
|
272
|
+
if (scope.hasAttribute("maxlength") || scope.hasAttribute("data-count")) countedFields.push(scope);
|
|
266
273
|
}
|
|
267
274
|
}
|
|
268
275
|
for (const f of countedFields) setupCounter(f);
|
|
269
276
|
const maskedInputs = [];
|
|
270
|
-
if (
|
|
271
|
-
maskedInputs.push(...Array.from(
|
|
272
|
-
if (
|
|
277
|
+
if (scope instanceof Element) {
|
|
278
|
+
maskedInputs.push(...Array.from(scope.querySelectorAll("input[data-mask]")));
|
|
279
|
+
if (scope instanceof HTMLInputElement && scope.hasAttribute("data-mask")) maskedInputs.push(scope);
|
|
273
280
|
}
|
|
274
281
|
for (const inp of maskedInputs) setupMask(inp);
|
|
275
282
|
const trimFields = [];
|
|
276
|
-
if (
|
|
277
|
-
trimFields.push(...Array.from(
|
|
278
|
-
if ((
|
|
279
|
-
trimFields.push(
|
|
283
|
+
if (scope instanceof Element) {
|
|
284
|
+
trimFields.push(...Array.from(scope.querySelectorAll("[data-trim]")));
|
|
285
|
+
if ((scope instanceof HTMLInputElement || scope instanceof HTMLTextAreaElement) && scope.hasAttribute("data-trim")) {
|
|
286
|
+
trimFields.push(scope);
|
|
280
287
|
}
|
|
281
288
|
}
|
|
282
289
|
for (const f of trimFields) setupTrim(f);
|
|
283
290
|
const otps = [];
|
|
284
|
-
if (
|
|
285
|
-
otps.push(...Array.from(
|
|
291
|
+
if (scope instanceof Element && scope.matches(".otp")) otps.push(scope);
|
|
292
|
+
otps.push(...Array.from(scope.querySelectorAll(".otp")));
|
|
286
293
|
for (const g of otps) setupOtp(g);
|
|
287
294
|
}
|
|
288
295
|
export {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** 打开指定 id 的 modal;已有其他 modal 打开时先关闭它。 */
|
|
2
|
+
declare function openModal(id: string): void;
|
|
3
|
+
/** 关闭指定 id 的 modal(backdrop 已被外部移除时也照常复位状态/监听/滚动锁)。 */
|
|
4
|
+
declare function closeModal(id: string): void;
|
|
5
|
+
/**
|
|
6
|
+
* 绑定触发器/关闭钮/backdrop 点击(document 级事件委托,重复调用安全)。
|
|
7
|
+
* root 用于扫描 [data-modal-open] 补 aria-haspopup=dialog。
|
|
8
|
+
*/
|
|
9
|
+
declare function initModal(root?: ParentNode): void;
|
|
10
|
+
|
|
11
|
+
export { closeModal, initModal, openModal };
|
package/dist/behaviors/modal.mjs
CHANGED
|
@@ -16,7 +16,10 @@ function openModal(id) {
|
|
|
16
16
|
if (openId === id) return;
|
|
17
17
|
if (openId) closeModal(openId);
|
|
18
18
|
const backdrop = findBackdrop(id);
|
|
19
|
-
if (!backdrop)
|
|
19
|
+
if (!backdrop) {
|
|
20
|
+
console.warn(`[@icen.ai/ui] modal: \u672A\u627E\u5230 data-modal="${id}" \u7684 .modal-backdrop\uFF0C\u5DF2\u8DF3\u8FC7\u6253\u5F00`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
20
23
|
const panel = backdrop.querySelector(".modal");
|
|
21
24
|
if (!panel) return;
|
|
22
25
|
portal(backdrop);
|
|
@@ -52,7 +55,9 @@ function openModal(id) {
|
|
|
52
55
|
return;
|
|
53
56
|
}
|
|
54
57
|
if (e.key !== "Tab") return;
|
|
55
|
-
const els = panel.querySelectorAll(FOCUSABLE)
|
|
58
|
+
const els = Array.from(panel.querySelectorAll(FOCUSABLE)).filter(
|
|
59
|
+
(el) => !el.hidden && el.offsetParent !== null
|
|
60
|
+
);
|
|
56
61
|
if (els.length === 0) {
|
|
57
62
|
e.preventDefault();
|
|
58
63
|
panel.focus();
|
|
@@ -77,8 +82,7 @@ function openModal(id) {
|
|
|
77
82
|
function closeModal(id) {
|
|
78
83
|
if (typeof document === "undefined") return;
|
|
79
84
|
const backdrop = findBackdrop(id);
|
|
80
|
-
if (
|
|
81
|
-
backdrop.hidden = true;
|
|
85
|
+
if (backdrop) backdrop.hidden = true;
|
|
82
86
|
if (openId !== id) return;
|
|
83
87
|
openId = null;
|
|
84
88
|
if (keyHandler) {
|
|
@@ -89,9 +93,10 @@ function closeModal(id) {
|
|
|
89
93
|
if (restoreFocusTo && restoreFocusTo.isConnected) restoreFocusTo.focus();
|
|
90
94
|
restoreFocusTo = null;
|
|
91
95
|
}
|
|
92
|
-
function initModal(root
|
|
96
|
+
function initModal(root) {
|
|
93
97
|
if (typeof document === "undefined") return;
|
|
94
|
-
root
|
|
98
|
+
const scope = root ?? document;
|
|
99
|
+
scope.querySelectorAll("[data-modal-open]").forEach((el) => el.setAttribute("aria-haspopup", "dialog"));
|
|
95
100
|
if (initialized) return;
|
|
96
101
|
initialized = true;
|
|
97
102
|
document.addEventListener("click", (e) => {
|
package/dist/behaviors/nav.mjs
CHANGED
|
@@ -5,9 +5,11 @@ function prefersReducedMotion() {
|
|
|
5
5
|
}
|
|
6
6
|
function setup(nav) {
|
|
7
7
|
const el = nav;
|
|
8
|
-
if (el.__icenNavInit) return;
|
|
8
|
+
if (el.__icenNavInit) return void 0;
|
|
9
9
|
el.__icenNavInit = true;
|
|
10
|
-
const
|
|
10
|
+
const rawThreshold = nav.dataset.navThreshold;
|
|
11
|
+
const parsedThreshold = rawThreshold === void 0 || rawThreshold === "" ? NaN : Number(rawThreshold);
|
|
12
|
+
const threshold = Number.isFinite(parsedThreshold) ? parsedThreshold : 12;
|
|
11
13
|
const hideOnScroll = nav.hasAttribute("data-nav-hide-on-scroll");
|
|
12
14
|
let ticking = false;
|
|
13
15
|
let lastY = window.scrollY;
|
|
@@ -42,14 +44,28 @@ function setup(nav) {
|
|
|
42
44
|
window.addEventListener("scroll", onScroll, { passive: true });
|
|
43
45
|
window.addEventListener("resize", onResize, { passive: true });
|
|
44
46
|
apply();
|
|
47
|
+
return () => {
|
|
48
|
+
window.removeEventListener("scroll", onScroll);
|
|
49
|
+
window.removeEventListener("resize", onResize);
|
|
50
|
+
el.__icenNavInit = false;
|
|
51
|
+
};
|
|
45
52
|
}
|
|
46
|
-
function initNav(root
|
|
47
|
-
|
|
48
|
-
if (typeof window
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
function initNav(root) {
|
|
54
|
+
const cleanups = [];
|
|
55
|
+
if (typeof document !== "undefined" && typeof window !== "undefined") {
|
|
56
|
+
const scope = root ?? document;
|
|
57
|
+
const navs = [];
|
|
58
|
+
if (scope instanceof Element && scope.matches(".nav[data-scroll-reactive]")) navs.push(scope);
|
|
59
|
+
navs.push(...Array.from(scope.querySelectorAll(".nav[data-scroll-reactive]")));
|
|
60
|
+
for (const n of navs) {
|
|
61
|
+
const cleanup = setup(n);
|
|
62
|
+
if (cleanup) cleanups.push(cleanup);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return () => {
|
|
66
|
+
for (const fn of cleanups) fn();
|
|
67
|
+
cleanups.length = 0;
|
|
68
|
+
};
|
|
53
69
|
}
|
|
54
70
|
export {
|
|
55
71
|
initNav
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
type NotificationKind = 'success' | 'warning' | 'error' | 'info';
|
|
2
|
+
interface NotificationAction {
|
|
3
|
+
/** 按钮文字 */
|
|
4
|
+
label: string;
|
|
5
|
+
/** 点击回调;返回 false 阻止通知关闭 */
|
|
6
|
+
onClick?: () => void | Promise<void> | boolean;
|
|
7
|
+
/** 视觉变体:默认 ghost / primary 实底 / danger 危险色 */
|
|
8
|
+
type?: 'primary' | 'ghost' | 'danger';
|
|
9
|
+
/** 点击后是否保持通知打开(默认 false) */
|
|
10
|
+
keepOpen?: boolean;
|
|
11
|
+
}
|
|
12
|
+
interface NotificationOptions {
|
|
13
|
+
/** 显式 id(不传自动生成;用于后续 update/dismiss/get) */
|
|
14
|
+
id?: string;
|
|
15
|
+
/** 副标题(正文) */
|
|
16
|
+
description?: string;
|
|
17
|
+
/** 自动关闭延时(毫秒),默认 0 = 不自动关闭 */
|
|
18
|
+
duration?: number;
|
|
19
|
+
/** 是否显示关闭按钮,默认 true */
|
|
20
|
+
closable?: boolean;
|
|
21
|
+
/** hover 时暂停自动关闭倒计时,默认跟随全局 config.pauseOnHover */
|
|
22
|
+
pauseOnHover?: boolean;
|
|
23
|
+
/** duration > 0 时是否显示底部倒计时进度条,默认 true */
|
|
24
|
+
showCountdown?: boolean;
|
|
25
|
+
/** 自定义图标:SVG 字符串 / 图片 URL / false(关闭) */
|
|
26
|
+
icon?: string | false;
|
|
27
|
+
/** 头像 URL(会覆盖 kind 图标) */
|
|
28
|
+
avatar?: string;
|
|
29
|
+
/** 优先级:normal(默认)/ high(置顶) */
|
|
30
|
+
priority?: 'normal' | 'high';
|
|
31
|
+
/** 未读标记,默认 false(push 后可 markRead) */
|
|
32
|
+
unread?: boolean;
|
|
33
|
+
/** 多动作按钮(actions 优先于 actionLabel) */
|
|
34
|
+
actions?: NotificationAction[];
|
|
35
|
+
/** 兼容旧版单按钮 */
|
|
36
|
+
actionLabel?: string;
|
|
37
|
+
onAction?: () => void | Promise<void> | boolean;
|
|
38
|
+
/** 进度(0–1);progress > 0 时显示进度条。推荐用 notify.progress() 返回的 handle.setProgress */
|
|
39
|
+
progress?: number;
|
|
40
|
+
/** 进度条下方文字(如「正在导出 64%」) */
|
|
41
|
+
progressLabel?: string;
|
|
42
|
+
/** 详情链接(绝对 / 相对路径) */
|
|
43
|
+
link?: string;
|
|
44
|
+
linkLabel?: string;
|
|
45
|
+
/** 分组键:同 tag 的多条会聚合显示计数 */
|
|
46
|
+
tag?: string;
|
|
47
|
+
/** 自定义数据(持久化时保留) */
|
|
48
|
+
data?: unknown;
|
|
49
|
+
/** 点击通知体的回调(不含按钮与关闭) */
|
|
50
|
+
onClick?: (handle: NotificationHandle) => void;
|
|
51
|
+
/** 显示后回调 */
|
|
52
|
+
onShow?: (handle: NotificationHandle) => void;
|
|
53
|
+
/** 关闭后回调 */
|
|
54
|
+
onClose?: (handle: NotificationHandle) => void;
|
|
55
|
+
}
|
|
56
|
+
interface NotificationConfig {
|
|
57
|
+
/** 位置:top-right(默认)/ top-left / bottom-left / bottom-right */
|
|
58
|
+
position?: 'top-right' | 'top-left' | 'bottom-left' | 'bottom-right';
|
|
59
|
+
/** 同屏最大堆叠,默认 5;超出从最早的开始折叠/移除 */
|
|
60
|
+
maxStack?: number;
|
|
61
|
+
/** hover 暂停倒计时,默认 true */
|
|
62
|
+
pauseOnHover?: boolean;
|
|
63
|
+
/** 持久化:true 用默认 key / 字符串=自定义 key / 不传=不持久化 */
|
|
64
|
+
persist?: boolean | string;
|
|
65
|
+
/** 声音提示:true 用默认蜂鸣 / 字符串=data URL / 不传=无声 */
|
|
66
|
+
sound?: boolean | string;
|
|
67
|
+
}
|
|
68
|
+
interface NotificationHandle {
|
|
69
|
+
/** 通知 id */
|
|
70
|
+
id: string;
|
|
71
|
+
/** 当前 DOM 元素(可能已脱离 DOM) */
|
|
72
|
+
el: HTMLElement;
|
|
73
|
+
/** 关闭本条 */
|
|
74
|
+
dismiss(): void;
|
|
75
|
+
/** 部分更新(title / description / progress / unread / actions / tag 等) */
|
|
76
|
+
update(patch: NotificationUpdatePatch): void;
|
|
77
|
+
/** 设置进度(0–1)与可选标签 */
|
|
78
|
+
setProgress(value: number, label?: string): void;
|
|
79
|
+
/** 标记已读 */
|
|
80
|
+
markRead(): void;
|
|
81
|
+
/** 暂停自动关闭倒计时 */
|
|
82
|
+
pause(): void;
|
|
83
|
+
/** 恢复自动关闭倒计时(继续剩余时间) */
|
|
84
|
+
resume(): void;
|
|
85
|
+
}
|
|
86
|
+
/** update 接受的局部字段(title 在 push 时传,update 时也接受) */
|
|
87
|
+
interface NotificationUpdatePatch {
|
|
88
|
+
title?: string;
|
|
89
|
+
description?: string;
|
|
90
|
+
duration?: number;
|
|
91
|
+
unread?: boolean;
|
|
92
|
+
progress?: number;
|
|
93
|
+
progressLabel?: string;
|
|
94
|
+
actions?: NotificationAction[];
|
|
95
|
+
tag?: string;
|
|
96
|
+
priority?: 'normal' | 'high';
|
|
97
|
+
icon?: string | false;
|
|
98
|
+
avatar?: string;
|
|
99
|
+
link?: string;
|
|
100
|
+
linkLabel?: string;
|
|
101
|
+
}
|
|
102
|
+
interface NotificationRecord {
|
|
103
|
+
id: string;
|
|
104
|
+
title: string;
|
|
105
|
+
kind: NotificationKind;
|
|
106
|
+
opts: NotificationOptions;
|
|
107
|
+
el: HTMLElement | null;
|
|
108
|
+
createdAt: number;
|
|
109
|
+
duration: number;
|
|
110
|
+
remaining: number;
|
|
111
|
+
paused: boolean;
|
|
112
|
+
timer: number;
|
|
113
|
+
/** 本次计时的起始时间戳(pause 时据此扣减 remaining) */
|
|
114
|
+
startedAt: number;
|
|
115
|
+
/** 所属 NotificationCenter 的局部配置(全局单例通知为 undefined) */
|
|
116
|
+
cfgRef?: Required<NotificationConfig>;
|
|
117
|
+
/** 所属 center 的 localStore(全局通知为 undefined);dismiss 时同步清理,避免残留僵尸记录 */
|
|
118
|
+
ownerStore?: Map<string, NotificationRecord>;
|
|
119
|
+
/** attachTimer 注入的重启入口(update duration 时按新时长重启计时) */
|
|
120
|
+
restartTimer?: () => void;
|
|
121
|
+
/** hover 暂停监听是否已绑定(attachTimer 幂等保护) */
|
|
122
|
+
hoverBound?: boolean;
|
|
123
|
+
}
|
|
124
|
+
interface NotifyFn {
|
|
125
|
+
(title: string, opts?: NotificationOptions): NotificationHandle | null;
|
|
126
|
+
success(title: string, opts?: NotificationOptions): NotificationHandle | null;
|
|
127
|
+
warn(title: string, opts?: NotificationOptions): NotificationHandle | null;
|
|
128
|
+
error(title: string, opts?: NotificationOptions): NotificationHandle | null;
|
|
129
|
+
info(title: string, opts?: NotificationOptions): NotificationHandle | null;
|
|
130
|
+
progress(title: string, opts?: NotificationOptions): NotificationHandle | null;
|
|
131
|
+
confirm(title: string, opts?: NotificationConfirmOptions): Promise<boolean>;
|
|
132
|
+
dismiss(id?: string): void;
|
|
133
|
+
clear(): void;
|
|
134
|
+
markRead(id?: string): void;
|
|
135
|
+
update(id: string, patch: NotificationUpdatePatch): void;
|
|
136
|
+
get(id: string): NotificationRecord | null;
|
|
137
|
+
getAll(): NotificationRecord[];
|
|
138
|
+
config(opts: NotificationConfig): void;
|
|
139
|
+
}
|
|
140
|
+
interface NotificationConfirmOptions extends Omit<NotificationOptions, 'actions' | 'duration' | 'closable'> {
|
|
141
|
+
confirmLabel?: string;
|
|
142
|
+
cancelLabel?: string;
|
|
143
|
+
/** 自动关闭毫秒(默认 0 = 不自动关闭) */
|
|
144
|
+
duration?: number;
|
|
145
|
+
}
|
|
146
|
+
interface NotificationCenterHandle {
|
|
147
|
+
push(notif: NotificationPushInput): NotificationHandle | null;
|
|
148
|
+
dismiss(id?: string): void;
|
|
149
|
+
clear(): void;
|
|
150
|
+
markAllRead(): void;
|
|
151
|
+
/** 销毁容器:移除 DOM、清掉其所有 timer 与监听器;之后 push 返回 null */
|
|
152
|
+
destroy(): void;
|
|
153
|
+
get(id: string): NotificationRecord | null;
|
|
154
|
+
getAll(): NotificationRecord[];
|
|
155
|
+
config(opts: NotificationConfig): void;
|
|
156
|
+
el: HTMLElement;
|
|
157
|
+
}
|
|
158
|
+
interface NotificationPushInput {
|
|
159
|
+
title: string;
|
|
160
|
+
kind?: NotificationKind;
|
|
161
|
+
options?: NotificationOptions;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 创建一个独立的通知中心容器(可同时存在多个,比如顶部=警告、右下=消息)。
|
|
165
|
+
* 注意:默认全局 notify.* 走单例容器,与本函数创建的额外容器并存。
|
|
166
|
+
*/
|
|
167
|
+
declare function createNotificationCenter(parent?: ParentNode, opts?: NotificationConfig): NotificationCenterHandle;
|
|
168
|
+
declare const notify: NotifyFn;
|
|
169
|
+
/** 委托初始化:建立默认容器 + 恢复持久化通知。幂等。 */
|
|
170
|
+
declare function initNotification(): void;
|
|
171
|
+
|
|
172
|
+
export { type NotificationAction, type NotificationCenterHandle, type NotificationConfig, type NotificationConfirmOptions, type NotificationHandle, type NotificationKind, type NotificationOptions, type NotificationPushInput, type NotificationUpdatePatch, createNotificationCenter, initNotification, notify };
|