@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
|
@@ -1,4 +1,101 @@
|
|
|
1
|
+
// src/behaviors/popover.ts
|
|
2
|
+
var DEFAULT_MIN_WIDTH = 220;
|
|
3
|
+
var DEFAULT_MAX_WIDTH = 420;
|
|
4
|
+
var DEFAULT_MIN_HEIGHT = 120;
|
|
5
|
+
var DEFAULT_MAX_HEIGHT = 420;
|
|
6
|
+
function clamp(value, min, max) {
|
|
7
|
+
if (max < min) return min;
|
|
8
|
+
return Math.min(max, Math.max(min, value));
|
|
9
|
+
}
|
|
10
|
+
function chooseSide(preferredSide, availableTop, availableBottom, desiredHeight) {
|
|
11
|
+
const preferredSpace = preferredSide === "top" ? availableTop : availableBottom;
|
|
12
|
+
const oppositeSpace = preferredSide === "top" ? availableBottom : availableTop;
|
|
13
|
+
if (preferredSpace >= desiredHeight) return preferredSide;
|
|
14
|
+
if (oppositeSpace >= desiredHeight) return preferredSide === "top" ? "bottom" : "top";
|
|
15
|
+
if (preferredSpace >= oppositeSpace) return preferredSide;
|
|
16
|
+
return preferredSide === "top" ? "bottom" : "top";
|
|
17
|
+
}
|
|
18
|
+
function horizontalAnchor(anchor, width, align) {
|
|
19
|
+
if (align === "center") return anchor.left + anchor.width / 2 - width / 2;
|
|
20
|
+
if (align === "end") return anchor.right - width;
|
|
21
|
+
return anchor.left;
|
|
22
|
+
}
|
|
23
|
+
function computePopoverLayout(anchor, viewport, opts = {}) {
|
|
24
|
+
const margin = opts.margin ?? 12;
|
|
25
|
+
const offset = opts.offset ?? 8;
|
|
26
|
+
const availableWidth = Math.max(80, viewport.width - margin * 2);
|
|
27
|
+
const requestedMinWidth = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
|
|
28
|
+
const requestedMaxWidth = Math.max(requestedMinWidth, opts.maxWidth ?? DEFAULT_MAX_WIDTH);
|
|
29
|
+
const maxWidth = Math.min(requestedMaxWidth, availableWidth);
|
|
30
|
+
const minWidth = Math.min(requestedMinWidth, maxWidth);
|
|
31
|
+
const preferredSide = opts.side ?? "top";
|
|
32
|
+
const align = opts.align ?? "start";
|
|
33
|
+
const availableTop = Math.max(0, anchor.top - margin - offset);
|
|
34
|
+
const availableBottom = Math.max(0, viewport.height - anchor.bottom - margin - offset);
|
|
35
|
+
const availableHeight = Math.max(80, viewport.height - margin * 2);
|
|
36
|
+
const minHeight = Math.min(Math.max(80, opts.minHeight ?? DEFAULT_MIN_HEIGHT), availableHeight);
|
|
37
|
+
const requestedMaxHeight = Math.max(minHeight, opts.maxHeight ?? DEFAULT_MAX_HEIGHT);
|
|
38
|
+
const fallbackHeight = Math.min(260, requestedMaxHeight);
|
|
39
|
+
const contentHeight = Math.max(opts.contentHeightHint ?? 0, fallbackHeight);
|
|
40
|
+
const desiredHeight = clamp(contentHeight, minHeight, Math.min(requestedMaxHeight, availableHeight));
|
|
41
|
+
const side = chooseSide(preferredSide, availableTop, availableBottom, desiredHeight);
|
|
42
|
+
const sideSpace = side === "top" ? availableTop : availableBottom;
|
|
43
|
+
const maxHeight = Math.min(
|
|
44
|
+
requestedMaxHeight,
|
|
45
|
+
Math.max(80, sideSpace || Math.max(availableTop, availableBottom)),
|
|
46
|
+
availableHeight
|
|
47
|
+
);
|
|
48
|
+
const positionedHeight = clamp(desiredHeight, 40, maxHeight);
|
|
49
|
+
const width = align === "stretch" ? clamp(anchor.width, minWidth, maxWidth) : maxWidth;
|
|
50
|
+
const left = clamp(horizontalAnchor(anchor, width, align), margin, viewport.width - margin - width);
|
|
51
|
+
const desiredTop = side === "top" ? anchor.top - offset - positionedHeight : anchor.bottom + offset;
|
|
52
|
+
const top = clamp(desiredTop, margin, viewport.height - margin - positionedHeight);
|
|
53
|
+
return { side, left, top, width, maxHeight };
|
|
54
|
+
}
|
|
55
|
+
|
|
1
56
|
// src/behaviors/select.ts
|
|
57
|
+
var openSelect = null;
|
|
58
|
+
var globalBound = false;
|
|
59
|
+
var PANEL_OFFSET = 4;
|
|
60
|
+
var PANEL_MAX_HEIGHT = 240;
|
|
61
|
+
function onGlobalPointerDown(ev) {
|
|
62
|
+
const cur = openSelect;
|
|
63
|
+
if (!cur) return;
|
|
64
|
+
const t = ev.target;
|
|
65
|
+
if (!(t instanceof Node)) return;
|
|
66
|
+
if (cur.container.contains(t) || cur.panel.contains(t)) return;
|
|
67
|
+
cur.close();
|
|
68
|
+
}
|
|
69
|
+
function onGlobalKeyDown(ev) {
|
|
70
|
+
if (ev.key !== "Escape") return;
|
|
71
|
+
const cur = openSelect;
|
|
72
|
+
if (!cur) return;
|
|
73
|
+
cur.close();
|
|
74
|
+
cur.trigger.focus();
|
|
75
|
+
}
|
|
76
|
+
function onWindowChange() {
|
|
77
|
+
const cur = openSelect;
|
|
78
|
+
if (!cur) return;
|
|
79
|
+
if (!cur.trigger.isConnected) {
|
|
80
|
+
cur.close();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
cur.reposition();
|
|
84
|
+
}
|
|
85
|
+
function bindGlobalListeners() {
|
|
86
|
+
if (globalBound) return;
|
|
87
|
+
globalBound = true;
|
|
88
|
+
document.addEventListener("pointerdown", onGlobalPointerDown);
|
|
89
|
+
document.addEventListener("keydown", onGlobalKeyDown);
|
|
90
|
+
}
|
|
91
|
+
function bindOpenListeners() {
|
|
92
|
+
window.addEventListener("scroll", onWindowChange, true);
|
|
93
|
+
window.addEventListener("resize", onWindowChange);
|
|
94
|
+
}
|
|
95
|
+
function unbindOpenListeners() {
|
|
96
|
+
window.removeEventListener("scroll", onWindowChange, true);
|
|
97
|
+
window.removeEventListener("resize", onWindowChange);
|
|
98
|
+
}
|
|
2
99
|
function setup(container) {
|
|
3
100
|
const el = container;
|
|
4
101
|
if (el.__icenSelectInit) return;
|
|
@@ -14,6 +111,7 @@ function setup(container) {
|
|
|
14
111
|
const isSearchable = container.hasAttribute("data-select-search");
|
|
15
112
|
const maxSelect = Number(container.getAttribute("data-select-max")) || 0;
|
|
16
113
|
const placeholder = container.getAttribute("data-select-placeholder") ?? (valueEl?.classList.contains("is-empty") ? valueEl?.textContent ?? "" : "");
|
|
114
|
+
if (isMultiple) panel.setAttribute("data-select-multiple", "");
|
|
17
115
|
let searchInput = null;
|
|
18
116
|
let focusIdx = -1;
|
|
19
117
|
if (isSearchable && !panel.querySelector(".select-search-input")) {
|
|
@@ -89,11 +187,37 @@ function setup(container) {
|
|
|
89
187
|
}
|
|
90
188
|
});
|
|
91
189
|
}
|
|
190
|
+
function positionPanel() {
|
|
191
|
+
const rect = trigger.getBoundingClientRect();
|
|
192
|
+
const layout = computePopoverLayout(
|
|
193
|
+
rect,
|
|
194
|
+
{ width: window.innerWidth, height: window.innerHeight },
|
|
195
|
+
{
|
|
196
|
+
side: "bottom",
|
|
197
|
+
align: "stretch",
|
|
198
|
+
offset: PANEL_OFFSET,
|
|
199
|
+
minWidth: rect.width,
|
|
200
|
+
maxWidth: rect.width,
|
|
201
|
+
maxHeight: PANEL_MAX_HEIGHT
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
panel.style.left = `${layout.left + window.scrollX}px`;
|
|
205
|
+
panel.style.top = `${layout.top + window.scrollY}px`;
|
|
206
|
+
panel.style.minWidth = `${layout.width}px`;
|
|
207
|
+
panel.style.width = `${layout.width}px`;
|
|
208
|
+
panel.style.maxHeight = `${layout.maxHeight}px`;
|
|
209
|
+
panel.dataset.side = layout.side;
|
|
210
|
+
}
|
|
92
211
|
function openPanel() {
|
|
93
212
|
if (isOpen()) return;
|
|
213
|
+
openSelect?.close();
|
|
214
|
+
if (panel.parentElement !== document.body) document.body.appendChild(panel);
|
|
94
215
|
panel.hidden = false;
|
|
95
216
|
container.classList.add("is-open");
|
|
96
217
|
trigger.setAttribute("aria-expanded", "true");
|
|
218
|
+
positionPanel();
|
|
219
|
+
openSelect = { container, trigger, panel, close: closePanel, reposition: positionPanel };
|
|
220
|
+
bindOpenListeners();
|
|
97
221
|
if (searchInput) {
|
|
98
222
|
searchInput.value = "";
|
|
99
223
|
filterOptions("");
|
|
@@ -109,6 +233,8 @@ function setup(container) {
|
|
|
109
233
|
container.classList.remove("is-open");
|
|
110
234
|
trigger.setAttribute("aria-expanded", "false");
|
|
111
235
|
setFocus(-1);
|
|
236
|
+
if (openSelect?.panel === panel) openSelect = null;
|
|
237
|
+
unbindOpenListeners();
|
|
112
238
|
}
|
|
113
239
|
function getSelectedValues() {
|
|
114
240
|
return allOptions().filter((o) => o.classList.contains("is-selected")).map((o) => o.getAttribute("data-value") ?? "");
|
|
@@ -188,20 +314,6 @@ function setup(container) {
|
|
|
188
314
|
}
|
|
189
315
|
return;
|
|
190
316
|
}
|
|
191
|
-
if (searchInput && document.activeElement === searchInput) {
|
|
192
|
-
if (ev.key === "ArrowDown") {
|
|
193
|
-
ev.preventDefault();
|
|
194
|
-
moveFocus(1);
|
|
195
|
-
} else if (ev.key === "ArrowUp") {
|
|
196
|
-
ev.preventDefault();
|
|
197
|
-
moveFocus(-1);
|
|
198
|
-
} else if (ev.key === "Enter") {
|
|
199
|
-
ev.preventDefault();
|
|
200
|
-
const opt = visibleOptions()[focusIdx];
|
|
201
|
-
if (opt) choose(opt);
|
|
202
|
-
}
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
317
|
switch (ev.key) {
|
|
206
318
|
case "ArrowDown":
|
|
207
319
|
ev.preventDefault();
|
|
@@ -238,22 +350,15 @@ function setup(container) {
|
|
|
238
350
|
const idx = visibleOptions().indexOf(opt);
|
|
239
351
|
if (idx >= 0 && !opt.classList.contains("is-disabled")) setFocus(idx);
|
|
240
352
|
});
|
|
241
|
-
document.addEventListener("pointerdown", (ev) => {
|
|
242
|
-
if (isOpen() && ev.target instanceof Node && !container.contains(ev.target)) closePanel();
|
|
243
|
-
});
|
|
244
|
-
document.addEventListener("keydown", (ev) => {
|
|
245
|
-
if (ev.key === "Escape" && isOpen()) {
|
|
246
|
-
closePanel();
|
|
247
|
-
trigger.focus();
|
|
248
|
-
}
|
|
249
|
-
});
|
|
250
353
|
if (isMultiple) updateMultiTrigger();
|
|
251
354
|
}
|
|
252
|
-
function initSelect(root
|
|
355
|
+
function initSelect(root) {
|
|
253
356
|
if (typeof document === "undefined") return;
|
|
357
|
+
const scope = root ?? document;
|
|
358
|
+
bindGlobalListeners();
|
|
254
359
|
const containers = [];
|
|
255
|
-
if (
|
|
256
|
-
containers.push(...Array.from(
|
|
360
|
+
if (scope instanceof Element && scope.matches("[data-select]")) containers.push(scope);
|
|
361
|
+
containers.push(...Array.from(scope.querySelectorAll("[data-select]")));
|
|
257
362
|
for (const c of containers) setup(c);
|
|
258
363
|
}
|
|
259
364
|
export {
|
|
@@ -27,11 +27,12 @@ function setup(sidebar) {
|
|
|
27
27
|
}));
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
|
-
function initSidebar(root
|
|
30
|
+
function initSidebar(root) {
|
|
31
31
|
if (typeof document === "undefined") return;
|
|
32
|
+
const scope = root ?? document;
|
|
32
33
|
const sidebars = [];
|
|
33
|
-
if (
|
|
34
|
-
sidebars.push(...Array.from(
|
|
34
|
+
if (scope instanceof Element && scope.matches(".sidebar")) sidebars.push(scope);
|
|
35
|
+
sidebars.push(...Array.from(scope.querySelectorAll(".sidebar")));
|
|
35
36
|
for (const s of sidebars) setup(s);
|
|
36
37
|
}
|
|
37
38
|
export {
|
|
@@ -92,11 +92,12 @@ function setup(slider) {
|
|
|
92
92
|
if (slider.classList.contains("slider--dual")) setupDual(slider, natives);
|
|
93
93
|
else setupSingle(slider, natives[0]);
|
|
94
94
|
}
|
|
95
|
-
function initSlider(root
|
|
95
|
+
function initSlider(root) {
|
|
96
96
|
if (typeof document === "undefined") return;
|
|
97
|
+
const scope = root ?? document;
|
|
97
98
|
const containers = [];
|
|
98
|
-
if (
|
|
99
|
-
containers.push(...Array.from(
|
|
99
|
+
if (scope instanceof Element && scope.matches(".slider")) containers.push(scope);
|
|
100
|
+
containers.push(...Array.from(scope.querySelectorAll(".slider")));
|
|
100
101
|
for (const c of containers) setup(c);
|
|
101
102
|
}
|
|
102
103
|
export {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
// src/behaviors/split-pane.ts
|
|
2
|
-
|
|
2
|
+
function numOr(value, fallback) {
|
|
3
|
+
if (value === void 0 || value === "") return fallback;
|
|
4
|
+
const n = Number(value);
|
|
5
|
+
return Number.isFinite(n) ? n : fallback;
|
|
6
|
+
}
|
|
3
7
|
function pct(value, min, max) {
|
|
4
8
|
return Math.min(max, Math.max(min, value));
|
|
5
9
|
}
|
|
@@ -10,9 +14,9 @@ function setup(container) {
|
|
|
10
14
|
const dividerQuery = el.querySelector(".split-pane-divider");
|
|
11
15
|
if (!dividerQuery) return;
|
|
12
16
|
const divider = dividerQuery;
|
|
13
|
-
const min =
|
|
14
|
-
const max =
|
|
15
|
-
const step =
|
|
17
|
+
const min = numOr(el.dataset.splitMin, 10);
|
|
18
|
+
const max = numOr(el.dataset.splitMax, 90);
|
|
19
|
+
const step = numOr(el.dataset.splitStep, 2);
|
|
16
20
|
const vertical = el.classList.contains("split-pane--vertical");
|
|
17
21
|
const orientation = vertical ? "vertical" : "horizontal";
|
|
18
22
|
divider.setAttribute("role", "separator");
|
|
@@ -85,17 +89,13 @@ function setup(container) {
|
|
|
85
89
|
write(next);
|
|
86
90
|
});
|
|
87
91
|
}
|
|
88
|
-
function initSplitPane(root
|
|
92
|
+
function initSplitPane(root) {
|
|
89
93
|
if (typeof document === "undefined") return;
|
|
94
|
+
const scope = root ?? document;
|
|
90
95
|
const panes = [];
|
|
91
|
-
if (
|
|
92
|
-
panes.push(...Array.from(
|
|
93
|
-
for (const p of panes)
|
|
94
|
-
const el = p;
|
|
95
|
-
if (initialized.has(el)) continue;
|
|
96
|
-
initialized.add(el);
|
|
97
|
-
setup(el);
|
|
98
|
-
}
|
|
96
|
+
if (scope instanceof Element && scope.matches(".split-pane[data-split-pane]")) panes.push(scope);
|
|
97
|
+
panes.push(...Array.from(scope.querySelectorAll(".split-pane[data-split-pane]")));
|
|
98
|
+
for (const p of panes) setup(p);
|
|
99
99
|
}
|
|
100
100
|
export {
|
|
101
101
|
initSplitPane
|
package/dist/behaviors/tabs.mjs
CHANGED
|
@@ -8,28 +8,40 @@ function tabNames(container) {
|
|
|
8
8
|
function currentHash() {
|
|
9
9
|
return window.location.hash.replace(/^#/, "");
|
|
10
10
|
}
|
|
11
|
-
function activate(container, name, writeHash) {
|
|
11
|
+
function activate(container, name, writeHash, emit = false) {
|
|
12
12
|
const buttons = tabButtons(container);
|
|
13
13
|
let matched = false;
|
|
14
14
|
buttons.forEach((t) => {
|
|
15
15
|
const on = t.getAttribute("data-tab") === name;
|
|
16
16
|
if (on) matched = true;
|
|
17
|
-
t.classList.toggle("active", on);
|
|
17
|
+
t.classList.toggle("is-active", on);
|
|
18
18
|
t.setAttribute("aria-selected", String(on));
|
|
19
19
|
t.tabIndex = on ? 0 : -1;
|
|
20
20
|
});
|
|
21
21
|
if (!matched) return;
|
|
22
|
+
let panel = null;
|
|
22
23
|
container.querySelectorAll("[data-tab-panel]").forEach((p) => {
|
|
23
24
|
const isOn = p.getAttribute("data-tab-panel") === name;
|
|
24
25
|
p.hidden = !isOn;
|
|
25
|
-
if (isOn
|
|
26
|
-
|
|
27
|
-
if (
|
|
26
|
+
if (isOn) {
|
|
27
|
+
panel = p;
|
|
28
|
+
if (!p.getAttribute("aria-labelledby")) {
|
|
29
|
+
const btn = buttons.find((b) => b.getAttribute("data-tab") === name);
|
|
30
|
+
if (btn?.id) p.setAttribute("aria-labelledby", btn.id);
|
|
31
|
+
}
|
|
28
32
|
}
|
|
29
33
|
});
|
|
30
34
|
if (writeHash && typeof window !== "undefined") {
|
|
31
35
|
window.history.replaceState(null, "", `#${name}`);
|
|
32
36
|
}
|
|
37
|
+
if (emit) {
|
|
38
|
+
const index = buttons.findIndex((b) => b.getAttribute("data-tab") === name);
|
|
39
|
+
const tab = buttons[index] ?? null;
|
|
40
|
+
container.dispatchEvent(new CustomEvent("icen:tab-change", {
|
|
41
|
+
bubbles: true,
|
|
42
|
+
detail: { tab, panel, index }
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
33
45
|
}
|
|
34
46
|
function isDisabled(btn) {
|
|
35
47
|
return btn.hasAttribute("disabled") || btn.getAttribute("aria-disabled") === "true" || btn.classList.contains("is-disabled");
|
|
@@ -52,7 +64,7 @@ function nextEnabled(container, from, dir) {
|
|
|
52
64
|
}
|
|
53
65
|
function setup(container) {
|
|
54
66
|
const el = container;
|
|
55
|
-
if (el.__icenTabsInit) return;
|
|
67
|
+
if (el.__icenTabsInit) return void 0;
|
|
56
68
|
el.__icenTabsInit = true;
|
|
57
69
|
const useHash = container.hasAttribute("data-tabs-hash");
|
|
58
70
|
const names = tabNames(container);
|
|
@@ -71,7 +83,7 @@ function setup(container) {
|
|
|
71
83
|
if (!btn || btn.closest("[data-tabs]") !== container) return;
|
|
72
84
|
if (btn instanceof HTMLElement && isDisabled(btn)) return;
|
|
73
85
|
const name = btn.getAttribute("data-tab");
|
|
74
|
-
if (name) activate(container, name, useHash);
|
|
86
|
+
if (name) activate(container, name, useHash, true);
|
|
75
87
|
};
|
|
76
88
|
const onKeyDown = (ev) => {
|
|
77
89
|
const kev = ev;
|
|
@@ -99,12 +111,12 @@ function setup(container) {
|
|
|
99
111
|
}
|
|
100
112
|
case "Home": {
|
|
101
113
|
kev.preventDefault();
|
|
102
|
-
focusTab(container,
|
|
114
|
+
focusTab(container, nextEnabled(container, -1, 1));
|
|
103
115
|
break;
|
|
104
116
|
}
|
|
105
117
|
case "End": {
|
|
106
118
|
kev.preventDefault();
|
|
107
|
-
focusTab(container, buttonsList.length -
|
|
119
|
+
focusTab(container, nextEnabled(container, buttonsList.length, -1));
|
|
108
120
|
break;
|
|
109
121
|
}
|
|
110
122
|
case "Enter":
|
|
@@ -112,7 +124,7 @@ function setup(container) {
|
|
|
112
124
|
if (btn instanceof HTMLElement && !isDisabled(btn)) {
|
|
113
125
|
kev.preventDefault();
|
|
114
126
|
const name = btn.getAttribute("data-tab");
|
|
115
|
-
if (name) activate(container, name, useHash);
|
|
127
|
+
if (name) activate(container, name, useHash, true);
|
|
116
128
|
}
|
|
117
129
|
break;
|
|
118
130
|
}
|
|
@@ -125,29 +137,42 @@ function setup(container) {
|
|
|
125
137
|
const h = currentHash();
|
|
126
138
|
if (names.includes(h)) initial = h;
|
|
127
139
|
} else {
|
|
128
|
-
const n = container.querySelector("[data-tab].active")?.getAttribute("data-tab");
|
|
140
|
+
const n = container.querySelector("[data-tab].is-active")?.getAttribute("data-tab");
|
|
129
141
|
if (n && names.includes(n)) initial = n;
|
|
130
142
|
}
|
|
131
143
|
activate(container, initial ?? names[0], false);
|
|
132
144
|
const onHash = () => {
|
|
133
145
|
const h = currentHash();
|
|
134
|
-
if (names.includes(h)) activate(container, h, false);
|
|
146
|
+
if (names.includes(h)) activate(container, h, false, true);
|
|
135
147
|
};
|
|
136
148
|
if (useHash && typeof window !== "undefined") {
|
|
137
149
|
window.addEventListener("hashchange", onHash);
|
|
138
150
|
}
|
|
139
|
-
|
|
151
|
+
const cleanup = () => {
|
|
140
152
|
container.removeEventListener("click", onClick);
|
|
141
153
|
container.removeEventListener("keydown", onKeyDown);
|
|
142
154
|
window.removeEventListener("hashchange", onHash);
|
|
155
|
+
el.__icenTabsInit = false;
|
|
143
156
|
};
|
|
157
|
+
el.__icenTabsCleanup = cleanup;
|
|
158
|
+
return cleanup;
|
|
144
159
|
}
|
|
145
|
-
function initTabs(root
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
160
|
+
function initTabs(root) {
|
|
161
|
+
const cleanups = [];
|
|
162
|
+
if (typeof document !== "undefined") {
|
|
163
|
+
const scope = root ?? document;
|
|
164
|
+
const containers = [];
|
|
165
|
+
if (scope instanceof Element && scope.matches("[data-tabs]")) containers.push(scope);
|
|
166
|
+
containers.push(...Array.from(scope.querySelectorAll("[data-tabs]")));
|
|
167
|
+
for (const c of containers) {
|
|
168
|
+
const cleanup = setup(c);
|
|
169
|
+
if (cleanup) cleanups.push(cleanup);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return () => {
|
|
173
|
+
for (const fn of cleanups) fn();
|
|
174
|
+
cleanups.length = 0;
|
|
175
|
+
};
|
|
151
176
|
}
|
|
152
177
|
export {
|
|
153
178
|
initTabs
|
|
@@ -9,6 +9,13 @@ function setup(wrap) {
|
|
|
9
9
|
const maxAttr = wrap.getAttribute("data-max");
|
|
10
10
|
const max = maxAttr ? Number(maxAttr) || Infinity : Infinity;
|
|
11
11
|
const placeholder = field.placeholder;
|
|
12
|
+
let isComposing = false;
|
|
13
|
+
field.addEventListener("compositionstart", () => {
|
|
14
|
+
isComposing = true;
|
|
15
|
+
});
|
|
16
|
+
field.addEventListener("compositionend", () => {
|
|
17
|
+
isComposing = false;
|
|
18
|
+
});
|
|
12
19
|
let tags = (wrap.getAttribute("data-tags") ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
13
20
|
function sync() {
|
|
14
21
|
wrap.setAttribute("data-tags", tags.join(","));
|
|
@@ -63,6 +70,7 @@ function setup(wrap) {
|
|
|
63
70
|
emit();
|
|
64
71
|
}
|
|
65
72
|
field.addEventListener("keydown", (ev) => {
|
|
73
|
+
if (isComposing || ev.isComposing) return;
|
|
66
74
|
if (ev.key === "Enter" || ev.key === ",") {
|
|
67
75
|
ev.preventDefault();
|
|
68
76
|
commit();
|
|
@@ -73,11 +81,12 @@ function setup(wrap) {
|
|
|
73
81
|
field.addEventListener("blur", commit);
|
|
74
82
|
render();
|
|
75
83
|
}
|
|
76
|
-
function initTagInput(root
|
|
84
|
+
function initTagInput(root) {
|
|
77
85
|
if (typeof document === "undefined") return;
|
|
86
|
+
const scope = root ?? document;
|
|
78
87
|
const containers = [];
|
|
79
|
-
if (
|
|
80
|
-
containers.push(...Array.from(
|
|
88
|
+
if (scope instanceof HTMLElement && scope.matches(".tag-input")) containers.push(scope);
|
|
89
|
+
containers.push(...Array.from(scope.querySelectorAll(".tag-input")));
|
|
81
90
|
for (const c of containers) setup(c);
|
|
82
91
|
}
|
|
83
92
|
export {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
interface ThemeState {
|
|
2
|
+
preset: string;
|
|
3
|
+
dark: boolean;
|
|
4
|
+
style: string;
|
|
5
|
+
}
|
|
6
|
+
declare const PRESETS: readonly ["clay", "piano", "art", "vangogh", "ink", "retro"];
|
|
7
|
+
declare const STYLES: readonly ["modern", "retro", "terminal"];
|
|
8
|
+
declare const STORAGE_KEY = "icen.ui.theme";
|
|
9
|
+
/** 默认主题:clay + 跟随系统明暗 + modern。SSR 下返回离线兜底值。 */
|
|
10
|
+
declare function getDefaultTheme(): ThemeState;
|
|
11
|
+
/** 读 localStorage 中的持久化主题;缺失或坏数据回退默认。 */
|
|
12
|
+
declare function getTheme(): ThemeState;
|
|
13
|
+
/** 把主题应用到 <html>:先清掉所有 preset/style/dark 相关类,再按 t 挂回。 */
|
|
14
|
+
declare function applyTheme(t: ThemeState): void;
|
|
15
|
+
/** 合并当前主题与 patch,应用并持久化,返回新态。patch 中的非法枚举值回退默认。 */
|
|
16
|
+
declare function setTheme(patch: Partial<ThemeState>): ThemeState;
|
|
17
|
+
/** 切换明暗,返回新态。 */
|
|
18
|
+
declare function toggleDark(): ThemeState;
|
|
19
|
+
/** 页面加载早期调用:读持久化主题并应用,防闪烁。
|
|
20
|
+
* 无持久化偏好时,自动跟随系统的 prefers-color-scheme 变化(实时);
|
|
21
|
+
* 一旦用户显式 setTheme / toggleDark,跟随停止(持久化优先)。
|
|
22
|
+
* 重复调用幂等(系统监听不会重复挂)。 */
|
|
23
|
+
declare function initTheme(): ThemeState;
|
|
24
|
+
|
|
25
|
+
export { PRESETS, STORAGE_KEY, STYLES, type ThemeState, applyTheme, getDefaultTheme, getTheme, initTheme, setTheme, toggleDark };
|
package/dist/behaviors/theme.mjs
CHANGED
|
@@ -62,10 +62,12 @@ function setTheme(patch) {
|
|
|
62
62
|
function toggleDark() {
|
|
63
63
|
return setTheme({ dark: !getTheme().dark });
|
|
64
64
|
}
|
|
65
|
+
var systemListenerAttached = false;
|
|
65
66
|
function initTheme() {
|
|
66
67
|
const t = getTheme();
|
|
67
68
|
applyTheme(t);
|
|
68
|
-
if (isBrowser() && typeof window.matchMedia === "function" && !hasPersistedPreference()) {
|
|
69
|
+
if (isBrowser() && typeof window.matchMedia === "function" && !systemListenerAttached && !hasPersistedPreference()) {
|
|
70
|
+
systemListenerAttached = true;
|
|
69
71
|
let lastDark = t.dark;
|
|
70
72
|
try {
|
|
71
73
|
const mql = window.matchMedia("(prefers-color-scheme: dark)");
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
type ToastKind = 'ok' | 'err' | 'warn' | 'info';
|
|
2
|
+
interface ToastOptions {
|
|
3
|
+
/** 保留时长(毫秒),默认 2600;带 action 时默认 6000 */
|
|
4
|
+
ms?: number;
|
|
5
|
+
/** 配合 action:可撤销 / 重试 / 查看等 */
|
|
6
|
+
actionLabel?: string;
|
|
7
|
+
/** action 触发回调;返回 false 阻止 toast 关闭 */
|
|
8
|
+
onAction?: () => void | Promise<void> | boolean;
|
|
9
|
+
}
|
|
10
|
+
interface ToastConfig {
|
|
11
|
+
/** 同屏最大堆叠,默认 5;超出从最早的开始移除 */
|
|
12
|
+
maxStack?: number;
|
|
13
|
+
}
|
|
14
|
+
interface ToastFn {
|
|
15
|
+
(message: string, kind?: ToastKind, ms?: number): void;
|
|
16
|
+
ok(message: string, ms?: number): void;
|
|
17
|
+
err(message: string, ms?: number): void;
|
|
18
|
+
warn(message: string, ms?: number): void;
|
|
19
|
+
info(message: string, ms?: number): void;
|
|
20
|
+
action(message: string, actionLabel: string, onAction: () => void, ms?: number): void;
|
|
21
|
+
dismiss(): void;
|
|
22
|
+
config(opts: ToastConfig): void;
|
|
23
|
+
}
|
|
24
|
+
declare const toast: ToastFn;
|
|
25
|
+
|
|
26
|
+
export { type ToastConfig, type ToastKind, type ToastOptions, toast };
|
package/dist/behaviors/toast.mjs
CHANGED
|
@@ -16,21 +16,35 @@ function ensureContainer() {
|
|
|
16
16
|
document.body.appendChild(el);
|
|
17
17
|
return el;
|
|
18
18
|
}
|
|
19
|
+
var KIND_CLASS = {
|
|
20
|
+
ok: "toast--success",
|
|
21
|
+
err: "toast--error",
|
|
22
|
+
warn: "toast--warning",
|
|
23
|
+
info: "toast--info"
|
|
24
|
+
};
|
|
25
|
+
function scheduleRemove(el) {
|
|
26
|
+
el.classList.add("is-leaving");
|
|
27
|
+
let done = false;
|
|
28
|
+
const finish = () => {
|
|
29
|
+
if (done) return;
|
|
30
|
+
done = true;
|
|
31
|
+
el.remove();
|
|
32
|
+
};
|
|
33
|
+
el.addEventListener("transitionend", finish, { once: true });
|
|
34
|
+
window.setTimeout(finish, 400);
|
|
35
|
+
}
|
|
19
36
|
function trimStack(container) {
|
|
20
37
|
for (; ; ) {
|
|
21
38
|
const items = Array.from(container.querySelectorAll(":scope > .toast"));
|
|
22
39
|
if (items.length <= maxStack) return;
|
|
23
40
|
const oldest = items[0];
|
|
24
41
|
if (!oldest) return;
|
|
25
|
-
oldest.classList.
|
|
26
|
-
let done = false;
|
|
27
|
-
const finish = () => {
|
|
28
|
-
if (done) return;
|
|
29
|
-
done = true;
|
|
42
|
+
if (oldest.classList.contains("is-leaving")) {
|
|
30
43
|
oldest.remove();
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
scheduleRemove(oldest);
|
|
47
|
+
return;
|
|
34
48
|
}
|
|
35
49
|
}
|
|
36
50
|
function show(message, kind = "info", opts = {}) {
|
|
@@ -38,7 +52,7 @@ function show(message, kind = "info", opts = {}) {
|
|
|
38
52
|
const { ms, actionLabel, onAction } = opts;
|
|
39
53
|
const container = ensureContainer();
|
|
40
54
|
const el = document.createElement("div");
|
|
41
|
-
el.className =
|
|
55
|
+
el.className = `toast ${KIND_CLASS[kind]}`;
|
|
42
56
|
el.setAttribute("role", kind === "err" ? "alert" : "status");
|
|
43
57
|
const msgNode = document.createElement("span");
|
|
44
58
|
msgNode.className = "toast-msg";
|
|
@@ -58,15 +72,7 @@ function show(message, kind = "info", opts = {}) {
|
|
|
58
72
|
const remove = () => {
|
|
59
73
|
if (removed) return;
|
|
60
74
|
removed = true;
|
|
61
|
-
el
|
|
62
|
-
let done = false;
|
|
63
|
-
const finish = () => {
|
|
64
|
-
if (done) return;
|
|
65
|
-
done = true;
|
|
66
|
-
el.remove();
|
|
67
|
-
};
|
|
68
|
-
el.addEventListener("transitionend", finish, { once: true });
|
|
69
|
-
window.setTimeout(finish, 400);
|
|
75
|
+
scheduleRemove(el);
|
|
70
76
|
};
|
|
71
77
|
const duration = ms ?? (actionLabel ? ACTION_MS : DEFAULT_MS);
|
|
72
78
|
const timer = window.setTimeout(remove, duration);
|
package/dist/behaviors/tree.mjs
CHANGED
|
@@ -88,11 +88,12 @@ function setup(tree) {
|
|
|
88
88
|
}
|
|
89
89
|
});
|
|
90
90
|
}
|
|
91
|
-
function initTree(root
|
|
91
|
+
function initTree(root) {
|
|
92
92
|
if (typeof document === "undefined") return;
|
|
93
|
+
const scope = root ?? document;
|
|
93
94
|
const trees = [];
|
|
94
|
-
if (
|
|
95
|
-
trees.push(...Array.from(
|
|
95
|
+
if (scope instanceof Element && scope.matches(".tree")) trees.push(scope);
|
|
96
|
+
trees.push(...Array.from(scope.querySelectorAll(".tree")));
|
|
96
97
|
for (const t of trees) setup(t);
|
|
97
98
|
}
|
|
98
99
|
export {
|