@icen.ai/ui 0.2.1 → 0.4.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/README.md +29 -16
- package/dist/base.css +389 -48
- package/dist/behaviors/accordion.mjs +20 -0
- package/dist/behaviors/carousel.mjs +30 -1
- package/dist/behaviors/charts.mjs +356 -0
- package/dist/behaviors/copy.mjs +4 -3
- package/dist/behaviors/datatable.mjs +968 -0
- package/dist/behaviors/input.mjs +20 -1
- package/dist/behaviors/modal.mjs +14 -0
- package/dist/behaviors/nav.mjs +31 -4
- package/dist/behaviors/sidebar.mjs +12 -0
- package/dist/behaviors/slider.mjs +35 -3
- package/dist/behaviors/tabs.mjs +100 -9
- package/dist/behaviors/tag-input.mjs +11 -0
- package/dist/behaviors/theme.mjs +26 -0
- package/dist/behaviors/toast.mjs +83 -15
- package/dist/behaviors/tree.mjs +57 -1
- package/dist/behaviors/upload.mjs +2 -3
- package/dist/cli.mjs +74 -0
- package/dist/components/accordion.mjs +3 -0
- package/dist/components/alert.mjs +2 -0
- package/dist/components/avatar.mjs +2 -0
- package/dist/components/breadcrumb.css +91 -18
- package/dist/components/breadcrumb.mjs +2 -0
- package/dist/components/btn.css +174 -14
- package/dist/components/btn.mjs +2 -0
- package/dist/components/card.css +118 -18
- package/dist/components/card.mjs +2 -0
- package/dist/components/carousel.mjs +3 -0
- package/dist/components/chart-area.css +15 -0
- package/dist/components/chart-area.mjs +4 -0
- package/dist/components/chart-bar.css +79 -0
- package/dist/components/chart-bar.mjs +4 -0
- package/dist/components/chart-gauge.css +38 -0
- package/dist/components/chart-gauge.mjs +4 -0
- package/dist/components/chart-heatmap.css +52 -0
- package/dist/components/chart-heatmap.mjs +4 -0
- package/dist/components/chart-line.css +15 -0
- package/dist/components/chart-line.mjs +4 -0
- package/dist/components/chart-pie.css +26 -0
- package/dist/components/chart-pie.mjs +4 -0
- package/dist/components/chart-radar.css +19 -0
- package/dist/components/chart-radar.mjs +4 -0
- package/dist/components/chart-sparkline.css +14 -0
- package/dist/components/chart-sparkline.mjs +4 -0
- package/dist/components/chart-stack.css +30 -0
- package/dist/components/chart-stack.mjs +4 -0
- package/dist/components/charts.css +66 -74
- package/dist/components/charts.mjs +12 -0
- package/dist/components/content.css +256 -74
- package/dist/components/context-menu.mjs +3 -0
- package/dist/components/datatable.css +429 -0
- package/dist/components/datatable.mjs +4 -0
- package/dist/components/desc.mjs +2 -0
- package/dist/components/dropdown.mjs +3 -0
- package/dist/components/empty.css +50 -10
- package/dist/components/empty.mjs +2 -0
- package/dist/components/feedback.css +158 -47
- package/dist/components/form.css +139 -22
- package/dist/components/input.css +165 -45
- package/dist/components/input.mjs +3 -0
- package/dist/components/kbd.mjs +2 -0
- package/dist/components/layout.mjs +2 -0
- package/dist/components/list.mjs +2 -0
- package/dist/components/media-card.mjs +2 -0
- package/dist/components/media.css +187 -40
- package/dist/components/menu.css +159 -32
- package/dist/components/modal.css +140 -34
- package/dist/components/modal.mjs +3 -0
- package/dist/components/nav.css +134 -32
- package/dist/components/nav.mjs +3 -0
- package/dist/components/pagination.css +105 -19
- package/dist/components/pagination.mjs +2 -0
- package/dist/components/panel.css +61 -6
- package/dist/components/panel.mjs +2 -0
- package/dist/components/pill.css +74 -14
- package/dist/components/pill.mjs +2 -0
- package/dist/components/popover.css +109 -7
- package/dist/components/popover.mjs +3 -0
- package/dist/components/progress.mjs +2 -0
- package/dist/components/rating.mjs +2 -0
- package/dist/components/result.mjs +2 -0
- package/dist/components/segmented.css +199 -50
- package/dist/components/segmented.mjs +2 -0
- package/dist/components/select.css +140 -39
- package/dist/components/select.mjs +3 -0
- package/dist/components/sidebar.css +162 -34
- package/dist/components/sidebar.mjs +3 -0
- package/dist/components/skeleton.mjs +2 -0
- package/dist/components/slider.css +169 -31
- package/dist/components/slider.mjs +3 -0
- package/dist/components/spinner.mjs +2 -0
- package/dist/components/stat.css +103 -8
- package/dist/components/stat.mjs +2 -0
- package/dist/components/steps.css +105 -31
- package/dist/components/steps.mjs +2 -0
- package/dist/components/switch.css +171 -54
- package/dist/components/switch.mjs +2 -0
- package/dist/components/table.css +114 -12
- package/dist/components/table.mjs +2 -0
- package/dist/components/tabs.css +104 -17
- package/dist/components/tabs.mjs +3 -0
- package/dist/components/tag-input.css +119 -23
- package/dist/components/tag-input.mjs +3 -0
- package/dist/components/timeline.mjs +2 -0
- package/dist/components/toast.css +85 -12
- package/dist/components/toast.mjs +3 -0
- package/dist/components/tooltip.css +86 -26
- package/dist/components/tooltip.mjs +2 -0
- package/dist/components/tree.mjs +3 -0
- package/dist/components/upload.css +144 -17
- package/dist/components/upload.mjs +3 -0
- package/dist/index.mjs +811 -42
- package/dist/registry.json +310 -2
- package/dist/retro-effects.css +264 -0
- package/dist/tokens/colors.css +18 -90
- package/dist/tokens/retro-effects.css +264 -0
- package/dist/tokens/style-profiles.css +47 -47
- package/dist/tokens.css +65 -137
- package/dist/ui.css +5069 -1026
- package/package.json +9 -1
package/dist/behaviors/input.mjs
CHANGED
|
@@ -48,14 +48,25 @@ function setupOtp(group) {
|
|
|
48
48
|
if (el.__icenInputInit) return;
|
|
49
49
|
el.__icenInputInit = true;
|
|
50
50
|
const cells = Array.from(group.querySelectorAll(".otp-cell"));
|
|
51
|
+
const syncFilled = (cell) => {
|
|
52
|
+
cell.classList.toggle("is-filled", cell.value.length > 0);
|
|
53
|
+
};
|
|
51
54
|
cells.forEach((cell, i) => {
|
|
55
|
+
if (!cell.hasAttribute("aria-label")) {
|
|
56
|
+
cell.setAttribute("aria-label", `\u7B2C ${i + 1} \u4F4D\uFF0C\u5171 ${cells.length} \u4F4D`);
|
|
57
|
+
}
|
|
58
|
+
syncFilled(cell);
|
|
52
59
|
cell.addEventListener("input", () => {
|
|
53
60
|
const ch = cell.value.slice(-1);
|
|
54
61
|
cell.value = ch;
|
|
62
|
+
syncFilled(cell);
|
|
55
63
|
if (ch && i < cells.length - 1) cells[i + 1].focus();
|
|
56
64
|
});
|
|
57
65
|
cell.addEventListener("keydown", (ev) => {
|
|
58
|
-
if (ev.key === "Backspace" && !cell.value && i > 0)
|
|
66
|
+
if (ev.key === "Backspace" && !cell.value && i > 0) {
|
|
67
|
+
ev.preventDefault();
|
|
68
|
+
cells[i - 1].focus();
|
|
69
|
+
}
|
|
59
70
|
if (ev.key === "ArrowLeft" && i > 0) {
|
|
60
71
|
ev.preventDefault();
|
|
61
72
|
cells[i - 1].focus();
|
|
@@ -71,10 +82,18 @@ function setupOtp(group) {
|
|
|
71
82
|
for (let k = 0; k < text.length; k++) {
|
|
72
83
|
const target = cells[i + k];
|
|
73
84
|
target.value = text[k] ?? "";
|
|
85
|
+
syncFilled(target);
|
|
74
86
|
}
|
|
75
87
|
cells[Math.min(i + text.length, cells.length - 1)].focus();
|
|
76
88
|
});
|
|
77
89
|
});
|
|
90
|
+
const fireComplete = () => {
|
|
91
|
+
const code = cells.map((c) => c.value).join("");
|
|
92
|
+
if (code.length === cells.length && cells.every((c) => c.value.length > 0)) {
|
|
93
|
+
group.dispatchEvent(new CustomEvent("otp:complete", { detail: { code }, bubbles: true }));
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
cells.forEach((cell) => cell.addEventListener("input", fireComplete));
|
|
78
97
|
}
|
|
79
98
|
function initInput(root = document) {
|
|
80
99
|
if (typeof document === "undefined") return;
|
package/dist/behaviors/modal.mjs
CHANGED
|
@@ -28,6 +28,20 @@ function openModal(id) {
|
|
|
28
28
|
panel.setAttribute("role", "dialog");
|
|
29
29
|
panel.setAttribute("aria-modal", "true");
|
|
30
30
|
if (!panel.hasAttribute("tabindex")) panel.tabIndex = -1;
|
|
31
|
+
if (!panel.hasAttribute("aria-labelledby")) {
|
|
32
|
+
const title = panel.querySelector(".modal-title");
|
|
33
|
+
if (title) {
|
|
34
|
+
if (!title.id) title.id = `modal-title-${CSS.escape(id)}`;
|
|
35
|
+
panel.setAttribute("aria-labelledby", title.id);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (!panel.hasAttribute("aria-describedby")) {
|
|
39
|
+
const desc = panel.querySelector(".modal-description");
|
|
40
|
+
if (desc) {
|
|
41
|
+
if (!desc.id) desc.id = `modal-desc-${CSS.escape(id)}`;
|
|
42
|
+
panel.setAttribute("aria-describedby", desc.id);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
31
45
|
const closeOnEscape = backdrop.dataset.closeOnEscape !== "false";
|
|
32
46
|
keyHandler = (e) => {
|
|
33
47
|
if (e.key === "Escape") {
|
package/dist/behaviors/nav.mjs
CHANGED
|
@@ -1,20 +1,47 @@
|
|
|
1
1
|
// src/behaviors/nav.ts
|
|
2
|
+
function prefersReducedMotion() {
|
|
3
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
4
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
5
|
+
}
|
|
2
6
|
function setup(nav) {
|
|
3
7
|
const el = nav;
|
|
4
8
|
if (el.__icenNavInit) return;
|
|
5
9
|
el.__icenNavInit = true;
|
|
10
|
+
const threshold = Number(nav.dataset.navThreshold ?? "12") || 12;
|
|
11
|
+
const hideOnScroll = nav.hasAttribute("data-nav-hide-on-scroll");
|
|
6
12
|
let ticking = false;
|
|
7
|
-
|
|
13
|
+
let lastY = window.scrollY;
|
|
14
|
+
let hidden = false;
|
|
15
|
+
const apply = () => {
|
|
8
16
|
ticking = false;
|
|
9
|
-
|
|
17
|
+
const y = window.scrollY;
|
|
18
|
+
nav.classList.toggle("is-scrolled", y > threshold);
|
|
19
|
+
if (hideOnScroll && !prefersReducedMotion()) {
|
|
20
|
+
const dy = y - lastY;
|
|
21
|
+
if (y < 8) {
|
|
22
|
+
hidden = false;
|
|
23
|
+
} else if (dy > 6 && !hidden) {
|
|
24
|
+
hidden = true;
|
|
25
|
+
} else if (dy < -6 && hidden) {
|
|
26
|
+
hidden = false;
|
|
27
|
+
}
|
|
28
|
+
nav.classList.toggle("is-nav-hidden", hidden);
|
|
29
|
+
}
|
|
30
|
+
lastY = y;
|
|
10
31
|
};
|
|
11
32
|
const onScroll = () => {
|
|
12
33
|
if (ticking) return;
|
|
13
34
|
ticking = true;
|
|
14
|
-
window.requestAnimationFrame(
|
|
35
|
+
window.requestAnimationFrame(apply);
|
|
36
|
+
};
|
|
37
|
+
const onResize = () => {
|
|
38
|
+
if (ticking) return;
|
|
39
|
+
ticking = true;
|
|
40
|
+
window.requestAnimationFrame(apply);
|
|
15
41
|
};
|
|
16
42
|
window.addEventListener("scroll", onScroll, { passive: true });
|
|
17
|
-
|
|
43
|
+
window.addEventListener("resize", onResize, { passive: true });
|
|
44
|
+
apply();
|
|
18
45
|
}
|
|
19
46
|
function initNav(root = document) {
|
|
20
47
|
if (typeof document === "undefined") return;
|
|
@@ -3,6 +3,14 @@ function setup(sidebar) {
|
|
|
3
3
|
const el = sidebar;
|
|
4
4
|
if (el.__icenSidebarInit) return;
|
|
5
5
|
el.__icenSidebarInit = true;
|
|
6
|
+
sidebar.querySelectorAll(".sidebar-group-title").forEach((title) => {
|
|
7
|
+
const group = title.closest(".sidebar-group");
|
|
8
|
+
const panel = group?.querySelector(".sidebar-group-panel");
|
|
9
|
+
if (panel && !title.hasAttribute("aria-controls")) {
|
|
10
|
+
if (!panel.id) panel.id = `sb-panel-${Math.random().toString(36).slice(2, 9)}`;
|
|
11
|
+
title.setAttribute("aria-controls", panel.id);
|
|
12
|
+
}
|
|
13
|
+
});
|
|
6
14
|
el.addEventListener("click", (ev) => {
|
|
7
15
|
const target = ev.target;
|
|
8
16
|
if (!(target instanceof Element)) return;
|
|
@@ -13,6 +21,10 @@ function setup(sidebar) {
|
|
|
13
21
|
if (!group) return;
|
|
14
22
|
const open = group.classList.toggle("is-open");
|
|
15
23
|
title.setAttribute("aria-expanded", String(open));
|
|
24
|
+
sidebar.dispatchEvent(new CustomEvent("icen:sidebar-toggle", {
|
|
25
|
+
bubbles: true,
|
|
26
|
+
detail: { group, open }
|
|
27
|
+
}));
|
|
16
28
|
});
|
|
17
29
|
}
|
|
18
30
|
function initSidebar(root = document) {
|
|
@@ -14,7 +14,19 @@ function setupSingle(slider, native) {
|
|
|
14
14
|
if (fill) fill.style.width = `${p}%`;
|
|
15
15
|
if (thumb) thumb.style.left = `calc(${p}% - 8px)`;
|
|
16
16
|
};
|
|
17
|
-
native.addEventListener("input",
|
|
17
|
+
native.addEventListener("input", () => {
|
|
18
|
+
sync();
|
|
19
|
+
slider.dispatchEvent(new CustomEvent("icen:slider-input", {
|
|
20
|
+
bubbles: true,
|
|
21
|
+
detail: { value: Number(native.value) }
|
|
22
|
+
}));
|
|
23
|
+
});
|
|
24
|
+
native.addEventListener("change", () => {
|
|
25
|
+
slider.dispatchEvent(new CustomEvent("icen:slider-change", {
|
|
26
|
+
bubbles: true,
|
|
27
|
+
detail: { value: Number(native.value) }
|
|
28
|
+
}));
|
|
29
|
+
});
|
|
18
30
|
sync();
|
|
19
31
|
}
|
|
20
32
|
function setupDual(slider, natives) {
|
|
@@ -47,8 +59,28 @@ function setupDual(slider, natives) {
|
|
|
47
59
|
lo.style.clipPath = `inset(0 ${100 - pLo}% 0 0)`;
|
|
48
60
|
hi.style.clipPath = `inset(0 0 0 ${pHi}%)`;
|
|
49
61
|
};
|
|
50
|
-
lo.addEventListener("input", () =>
|
|
51
|
-
|
|
62
|
+
lo.addEventListener("input", () => {
|
|
63
|
+
sync(lo);
|
|
64
|
+
slider.dispatchEvent(new CustomEvent("icen:slider-input", {
|
|
65
|
+
bubbles: true,
|
|
66
|
+
detail: { lo: Number(lo.value), hi: Number(hi.value) }
|
|
67
|
+
}));
|
|
68
|
+
});
|
|
69
|
+
hi.addEventListener("input", () => {
|
|
70
|
+
sync(hi);
|
|
71
|
+
slider.dispatchEvent(new CustomEvent("icen:slider-input", {
|
|
72
|
+
bubbles: true,
|
|
73
|
+
detail: { lo: Number(lo.value), hi: Number(hi.value) }
|
|
74
|
+
}));
|
|
75
|
+
});
|
|
76
|
+
const onChange = () => {
|
|
77
|
+
slider.dispatchEvent(new CustomEvent("icen:slider-change", {
|
|
78
|
+
bubbles: true,
|
|
79
|
+
detail: { lo: Number(lo.value), hi: Number(hi.value) }
|
|
80
|
+
}));
|
|
81
|
+
};
|
|
82
|
+
lo.addEventListener("change", onChange);
|
|
83
|
+
hi.addEventListener("change", onChange);
|
|
52
84
|
sync(null);
|
|
53
85
|
}
|
|
54
86
|
function setup(slider) {
|
package/dist/behaviors/tabs.mjs
CHANGED
|
@@ -1,25 +1,55 @@
|
|
|
1
1
|
// src/behaviors/tabs.ts
|
|
2
|
+
function tabButtons(container) {
|
|
3
|
+
return Array.from(container.querySelectorAll("[data-tab]"));
|
|
4
|
+
}
|
|
2
5
|
function tabNames(container) {
|
|
3
|
-
return
|
|
6
|
+
return tabButtons(container).map((t) => t.getAttribute("data-tab")).filter((n) => n !== null);
|
|
4
7
|
}
|
|
5
8
|
function currentHash() {
|
|
6
9
|
return window.location.hash.replace(/^#/, "");
|
|
7
10
|
}
|
|
8
11
|
function activate(container, name, writeHash) {
|
|
12
|
+
const buttons = tabButtons(container);
|
|
9
13
|
let matched = false;
|
|
10
|
-
|
|
14
|
+
buttons.forEach((t) => {
|
|
11
15
|
const on = t.getAttribute("data-tab") === name;
|
|
12
16
|
if (on) matched = true;
|
|
13
17
|
t.classList.toggle("active", on);
|
|
18
|
+
t.setAttribute("aria-selected", String(on));
|
|
19
|
+
t.tabIndex = on ? 0 : -1;
|
|
14
20
|
});
|
|
15
21
|
if (!matched) return;
|
|
16
22
|
container.querySelectorAll("[data-tab-panel]").forEach((p) => {
|
|
17
|
-
|
|
23
|
+
const isOn = p.getAttribute("data-tab-panel") === name;
|
|
24
|
+
p.hidden = !isOn;
|
|
25
|
+
if (isOn && !p.getAttribute("aria-labelledby")) {
|
|
26
|
+
const btn = buttons.find((b) => b.getAttribute("data-tab") === name);
|
|
27
|
+
if (btn?.id) p.setAttribute("aria-labelledby", btn.id);
|
|
28
|
+
}
|
|
18
29
|
});
|
|
19
30
|
if (writeHash && typeof window !== "undefined") {
|
|
20
31
|
window.history.replaceState(null, "", `#${name}`);
|
|
21
32
|
}
|
|
22
33
|
}
|
|
34
|
+
function isDisabled(btn) {
|
|
35
|
+
return btn.hasAttribute("disabled") || btn.getAttribute("aria-disabled") === "true" || btn.classList.contains("is-disabled");
|
|
36
|
+
}
|
|
37
|
+
function focusTab(container, idx) {
|
|
38
|
+
const buttons = tabButtons(container);
|
|
39
|
+
const target = buttons[idx];
|
|
40
|
+
if (!target || isDisabled(target)) return;
|
|
41
|
+
target.focus();
|
|
42
|
+
}
|
|
43
|
+
function nextEnabled(container, from, dir) {
|
|
44
|
+
const buttons = tabButtons(container);
|
|
45
|
+
if (buttons.length === 0) return -1;
|
|
46
|
+
let idx = from;
|
|
47
|
+
for (let i = 0; i < buttons.length; i++) {
|
|
48
|
+
idx = (idx + dir + buttons.length) % buttons.length;
|
|
49
|
+
if (!isDisabled(buttons[idx])) return idx;
|
|
50
|
+
}
|
|
51
|
+
return -1;
|
|
52
|
+
}
|
|
23
53
|
function setup(container) {
|
|
24
54
|
const el = container;
|
|
25
55
|
if (el.__icenTabsInit) return;
|
|
@@ -27,14 +57,69 @@ function setup(container) {
|
|
|
27
57
|
const useHash = container.hasAttribute("data-tabs-hash");
|
|
28
58
|
const names = tabNames(container);
|
|
29
59
|
if (names.length === 0) return;
|
|
30
|
-
|
|
60
|
+
const buttons = tabButtons(container);
|
|
61
|
+
buttons.forEach((b) => {
|
|
62
|
+
if (!b.getAttribute("role")) b.setAttribute("role", "tab");
|
|
63
|
+
if (!b.hasAttribute("aria-selected")) b.setAttribute("aria-selected", "false");
|
|
64
|
+
});
|
|
65
|
+
const tablist = buttons[0]?.parentElement;
|
|
66
|
+
if (tablist && !tablist.getAttribute("role")) tablist.setAttribute("role", "tablist");
|
|
67
|
+
const onClick = (ev) => {
|
|
31
68
|
const target = ev.target;
|
|
32
69
|
if (!(target instanceof Element)) return;
|
|
33
70
|
const btn = target.closest("[data-tab]");
|
|
34
71
|
if (!btn || btn.closest("[data-tabs]") !== container) return;
|
|
72
|
+
if (btn instanceof HTMLElement && isDisabled(btn)) return;
|
|
35
73
|
const name = btn.getAttribute("data-tab");
|
|
36
74
|
if (name) activate(container, name, useHash);
|
|
37
|
-
}
|
|
75
|
+
};
|
|
76
|
+
const onKeyDown = (ev) => {
|
|
77
|
+
const kev = ev;
|
|
78
|
+
const target = kev.target;
|
|
79
|
+
if (!(target instanceof Element)) return;
|
|
80
|
+
const btn = target.closest("[data-tab]");
|
|
81
|
+
if (!btn || btn.closest("[data-tabs]") !== container) return;
|
|
82
|
+
const buttonsList = tabButtons(container);
|
|
83
|
+
const currentIdx = buttonsList.indexOf(btn);
|
|
84
|
+
if (currentIdx === -1) return;
|
|
85
|
+
switch (kev.key) {
|
|
86
|
+
case "ArrowRight":
|
|
87
|
+
case "ArrowDown": {
|
|
88
|
+
kev.preventDefault();
|
|
89
|
+
const next = nextEnabled(container, currentIdx, 1);
|
|
90
|
+
if (next >= 0) focusTab(container, next);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
case "ArrowLeft":
|
|
94
|
+
case "ArrowUp": {
|
|
95
|
+
kev.preventDefault();
|
|
96
|
+
const prev = nextEnabled(container, currentIdx, -1);
|
|
97
|
+
if (prev >= 0) focusTab(container, prev);
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
case "Home": {
|
|
101
|
+
kev.preventDefault();
|
|
102
|
+
focusTab(container, 0);
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
case "End": {
|
|
106
|
+
kev.preventDefault();
|
|
107
|
+
focusTab(container, buttonsList.length - 1);
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case "Enter":
|
|
111
|
+
case " ": {
|
|
112
|
+
if (btn instanceof HTMLElement && !isDisabled(btn)) {
|
|
113
|
+
kev.preventDefault();
|
|
114
|
+
const name = btn.getAttribute("data-tab");
|
|
115
|
+
if (name) activate(container, name, useHash);
|
|
116
|
+
}
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
container.addEventListener("click", onClick);
|
|
122
|
+
container.addEventListener("keydown", onKeyDown);
|
|
38
123
|
let initial;
|
|
39
124
|
if (useHash) {
|
|
40
125
|
const h = currentHash();
|
|
@@ -44,12 +129,18 @@ function setup(container) {
|
|
|
44
129
|
if (n && names.includes(n)) initial = n;
|
|
45
130
|
}
|
|
46
131
|
activate(container, initial ?? names[0], false);
|
|
132
|
+
const onHash = () => {
|
|
133
|
+
const h = currentHash();
|
|
134
|
+
if (names.includes(h)) activate(container, h, false);
|
|
135
|
+
};
|
|
47
136
|
if (useHash && typeof window !== "undefined") {
|
|
48
|
-
window.addEventListener("hashchange",
|
|
49
|
-
const h = currentHash();
|
|
50
|
-
if (names.includes(h)) activate(container, h, false);
|
|
51
|
-
});
|
|
137
|
+
window.addEventListener("hashchange", onHash);
|
|
52
138
|
}
|
|
139
|
+
el.__icenTabsCleanup = () => {
|
|
140
|
+
container.removeEventListener("click", onClick);
|
|
141
|
+
container.removeEventListener("keydown", onKeyDown);
|
|
142
|
+
window.removeEventListener("hashchange", onHash);
|
|
143
|
+
};
|
|
53
144
|
}
|
|
54
145
|
function initTabs(root = document) {
|
|
55
146
|
if (typeof document === "undefined") return;
|
|
@@ -14,10 +14,20 @@ function setup(wrap) {
|
|
|
14
14
|
wrap.setAttribute("data-tags", tags.join(","));
|
|
15
15
|
wrap.classList.toggle("is-max", tags.length >= max);
|
|
16
16
|
field.placeholder = tags.length === 0 ? placeholder : "";
|
|
17
|
+
if (!wrap.hasAttribute("aria-label")) {
|
|
18
|
+
wrap.setAttribute("aria-label", `\u6807\u7B7E\u8F93\u5165\uFF0C\u5171 ${tags.length} \u4E2A`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function emit() {
|
|
22
|
+
wrap.dispatchEvent(new CustomEvent("icen:tags-change", {
|
|
23
|
+
bubbles: true,
|
|
24
|
+
detail: { tags: [...tags] }
|
|
25
|
+
}));
|
|
17
26
|
}
|
|
18
27
|
function remove(index) {
|
|
19
28
|
tags.splice(index, 1);
|
|
20
29
|
render();
|
|
30
|
+
emit();
|
|
21
31
|
}
|
|
22
32
|
function chip(tag, index) {
|
|
23
33
|
const el2 = document.createElement("span");
|
|
@@ -50,6 +60,7 @@ function setup(wrap) {
|
|
|
50
60
|
tags.push(v);
|
|
51
61
|
field.value = "";
|
|
52
62
|
render();
|
|
63
|
+
emit();
|
|
53
64
|
}
|
|
54
65
|
field.addEventListener("keydown", (ev) => {
|
|
55
66
|
if (ev.key === "Enter" || ev.key === ",") {
|
package/dist/behaviors/theme.mjs
CHANGED
|
@@ -65,8 +65,34 @@ function toggleDark() {
|
|
|
65
65
|
function initTheme() {
|
|
66
66
|
const t = getTheme();
|
|
67
67
|
applyTheme(t);
|
|
68
|
+
if (isBrowser() && typeof window.matchMedia === "function" && !hasPersistedPreference()) {
|
|
69
|
+
let lastDark = t.dark;
|
|
70
|
+
try {
|
|
71
|
+
const mql = window.matchMedia("(prefers-color-scheme: dark)");
|
|
72
|
+
const onChange = (ev) => {
|
|
73
|
+
if (hasPersistedPreference()) return;
|
|
74
|
+
if (ev.matches === lastDark) return;
|
|
75
|
+
lastDark = ev.matches;
|
|
76
|
+
applyTheme({ ...getTheme(), dark: ev.matches });
|
|
77
|
+
};
|
|
78
|
+
if (typeof mql.addEventListener === "function") {
|
|
79
|
+
mql.addEventListener("change", onChange);
|
|
80
|
+
} else {
|
|
81
|
+
mql.addListener(onChange);
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
}
|
|
85
|
+
}
|
|
68
86
|
return t;
|
|
69
87
|
}
|
|
88
|
+
function hasPersistedPreference() {
|
|
89
|
+
if (!isBrowser()) return false;
|
|
90
|
+
try {
|
|
91
|
+
return window.localStorage.getItem(STORAGE_KEY) !== null;
|
|
92
|
+
} catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
70
96
|
export {
|
|
71
97
|
PRESETS,
|
|
72
98
|
STORAGE_KEY,
|
package/dist/behaviors/toast.mjs
CHANGED
|
@@ -1,35 +1,103 @@
|
|
|
1
1
|
// src/behaviors/toast.ts
|
|
2
|
+
var DEFAULT_MS = 2600;
|
|
3
|
+
var ACTION_MS = 6e3;
|
|
4
|
+
var maxStack = 5;
|
|
5
|
+
function isBrowser() {
|
|
6
|
+
return typeof document !== "undefined";
|
|
7
|
+
}
|
|
2
8
|
function ensureContainer() {
|
|
3
9
|
const existing = document.body.querySelector(".toast-container");
|
|
4
10
|
if (existing) return existing;
|
|
5
11
|
const el = document.createElement("div");
|
|
6
12
|
el.className = "toast-container";
|
|
13
|
+
el.setAttribute("role", "status");
|
|
14
|
+
el.setAttribute("aria-live", "polite");
|
|
15
|
+
el.setAttribute("aria-atomic", "false");
|
|
7
16
|
document.body.appendChild(el);
|
|
8
17
|
return el;
|
|
9
18
|
}
|
|
10
|
-
function
|
|
11
|
-
|
|
19
|
+
function trimStack(container) {
|
|
20
|
+
for (; ; ) {
|
|
21
|
+
const items = Array.from(container.querySelectorAll(":scope > .toast"));
|
|
22
|
+
if (items.length <= maxStack) return;
|
|
23
|
+
const oldest = items[0];
|
|
24
|
+
if (!oldest) return;
|
|
25
|
+
oldest.classList.add("leaving");
|
|
26
|
+
let done = false;
|
|
27
|
+
const finish = () => {
|
|
28
|
+
if (done) return;
|
|
29
|
+
done = true;
|
|
30
|
+
oldest.remove();
|
|
31
|
+
};
|
|
32
|
+
oldest.addEventListener("transitionend", finish, { once: true });
|
|
33
|
+
window.setTimeout(finish, 400);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function show(message, kind = "info", opts = {}) {
|
|
37
|
+
if (!isBrowser()) return;
|
|
38
|
+
const { ms, actionLabel, onAction } = opts;
|
|
39
|
+
const container = ensureContainer();
|
|
12
40
|
const el = document.createElement("div");
|
|
13
41
|
el.className = kind === "info" ? "toast" : `toast ${kind}`;
|
|
14
|
-
el.
|
|
15
|
-
|
|
42
|
+
el.setAttribute("role", kind === "err" ? "alert" : "status");
|
|
43
|
+
const msgNode = document.createElement("span");
|
|
44
|
+
msgNode.className = "toast-msg";
|
|
45
|
+
msgNode.textContent = message;
|
|
46
|
+
el.appendChild(msgNode);
|
|
47
|
+
let actionBtn = null;
|
|
48
|
+
if (actionLabel) {
|
|
49
|
+
actionBtn = document.createElement("button");
|
|
50
|
+
actionBtn.type = "button";
|
|
51
|
+
actionBtn.className = "toast-action";
|
|
52
|
+
actionBtn.textContent = actionLabel;
|
|
53
|
+
el.appendChild(actionBtn);
|
|
54
|
+
}
|
|
55
|
+
container.appendChild(el);
|
|
56
|
+
trimStack(container);
|
|
16
57
|
let removed = false;
|
|
17
58
|
const remove = () => {
|
|
18
59
|
if (removed) return;
|
|
19
60
|
removed = true;
|
|
20
|
-
el.remove();
|
|
21
|
-
};
|
|
22
|
-
window.setTimeout(() => {
|
|
23
61
|
el.classList.add("leaving");
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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);
|
|
70
|
+
};
|
|
71
|
+
const duration = ms ?? (actionLabel ? ACTION_MS : DEFAULT_MS);
|
|
72
|
+
const timer = window.setTimeout(remove, duration);
|
|
73
|
+
if (actionBtn && onAction) {
|
|
74
|
+
actionBtn.addEventListener("click", () => {
|
|
75
|
+
window.clearTimeout(timer);
|
|
76
|
+
const ret = onAction();
|
|
77
|
+
if (ret === false) return;
|
|
78
|
+
remove();
|
|
79
|
+
});
|
|
80
|
+
}
|
|
27
81
|
}
|
|
28
|
-
var toast = Object.assign(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
})
|
|
82
|
+
var toast = Object.assign(
|
|
83
|
+
(message, kind = "info", ms) => show(message, kind, { ms }),
|
|
84
|
+
{
|
|
85
|
+
ok: (message, ms) => show(message, "ok", { ms }),
|
|
86
|
+
err: (message, ms) => show(message, "err", { ms }),
|
|
87
|
+
warn: (message, ms) => show(message, "warn", { ms }),
|
|
88
|
+
info: (message, ms) => show(message, "info", { ms }),
|
|
89
|
+
action: (message, actionLabel, onAction, ms) => show(message, "info", { actionLabel, onAction, ms }),
|
|
90
|
+
dismiss: () => {
|
|
91
|
+
if (!isBrowser()) return;
|
|
92
|
+
const c = document.body.querySelector(".toast-container");
|
|
93
|
+
if (!c) return;
|
|
94
|
+
c.querySelectorAll(".toast").forEach((t) => t.remove());
|
|
95
|
+
},
|
|
96
|
+
config: (opts) => {
|
|
97
|
+
if (typeof opts.maxStack === "number" && opts.maxStack > 0) maxStack = opts.maxStack;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
);
|
|
33
101
|
export {
|
|
34
102
|
toast
|
|
35
103
|
};
|
package/dist/behaviors/tree.mjs
CHANGED
|
@@ -3,6 +3,9 @@ function setup(tree) {
|
|
|
3
3
|
const el = tree;
|
|
4
4
|
if (el.__icenTreeInit) return;
|
|
5
5
|
el.__icenTreeInit = true;
|
|
6
|
+
tree.querySelectorAll(".tree-node").forEach((node) => {
|
|
7
|
+
if (!node.hasAttribute("tabindex")) node.tabIndex = 0;
|
|
8
|
+
});
|
|
6
9
|
tree.addEventListener("click", (ev) => {
|
|
7
10
|
const target = ev.target;
|
|
8
11
|
if (!(target instanceof Element)) return;
|
|
@@ -28,8 +31,61 @@ function setup(tree) {
|
|
|
28
31
|
return;
|
|
29
32
|
}
|
|
30
33
|
if (childUl) return;
|
|
31
|
-
tree.querySelectorAll(
|
|
34
|
+
tree.querySelectorAll('.tree-node.is-selected, .tree-node[aria-selected="true"]').forEach((n) => {
|
|
35
|
+
n.classList.remove("is-selected");
|
|
36
|
+
n.removeAttribute("aria-selected");
|
|
37
|
+
});
|
|
32
38
|
node.classList.add("is-selected");
|
|
39
|
+
node.setAttribute("aria-selected", "true");
|
|
40
|
+
tree.dispatchEvent(new CustomEvent("icen:tree-select", {
|
|
41
|
+
bubbles: true,
|
|
42
|
+
detail: { node }
|
|
43
|
+
}));
|
|
44
|
+
});
|
|
45
|
+
tree.addEventListener("keydown", (ev) => {
|
|
46
|
+
const kev = ev;
|
|
47
|
+
const target = kev.target;
|
|
48
|
+
if (!(target instanceof Element)) return;
|
|
49
|
+
const node = target.closest(".tree-node");
|
|
50
|
+
if (!node || node.closest(".tree") !== tree) return;
|
|
51
|
+
if (node.classList.contains("is-disabled")) return;
|
|
52
|
+
const allNodes = Array.from(tree.querySelectorAll(".tree-node:not(.is-disabled)"));
|
|
53
|
+
const idx = allNodes.indexOf(node);
|
|
54
|
+
if (idx === -1) return;
|
|
55
|
+
switch (kev.key) {
|
|
56
|
+
case "ArrowDown": {
|
|
57
|
+
kev.preventDefault();
|
|
58
|
+
allNodes[(idx + 1) % allNodes.length]?.focus();
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
case "ArrowUp": {
|
|
62
|
+
kev.preventDefault();
|
|
63
|
+
allNodes[(idx - 1 + allNodes.length) % allNodes.length]?.focus();
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case "ArrowRight": {
|
|
67
|
+
const li = node.closest("li");
|
|
68
|
+
const childUl = li?.querySelector(":scope > ul");
|
|
69
|
+
if (childUl?.hasAttribute("hidden")) {
|
|
70
|
+
kev.preventDefault();
|
|
71
|
+
childUl.removeAttribute("hidden");
|
|
72
|
+
node.querySelector(".tree-toggle")?.setAttribute("aria-expanded", "true");
|
|
73
|
+
node.querySelector(".tree-toggle")?.classList.remove("is-collapsed");
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case "ArrowLeft": {
|
|
78
|
+
const li = node.closest("li");
|
|
79
|
+
const childUl = li?.querySelector(":scope > ul");
|
|
80
|
+
if (childUl && !childUl.hasAttribute("hidden")) {
|
|
81
|
+
kev.preventDefault();
|
|
82
|
+
childUl.setAttribute("hidden", "");
|
|
83
|
+
node.querySelector(".tree-toggle")?.setAttribute("aria-expanded", "false");
|
|
84
|
+
node.querySelector(".tree-toggle")?.classList.add("is-collapsed");
|
|
85
|
+
}
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
33
89
|
});
|
|
34
90
|
}
|
|
35
91
|
function initTree(root = document) {
|
|
@@ -37,10 +37,9 @@ function setup(zone) {
|
|
|
37
37
|
}
|
|
38
38
|
if (assigned) {
|
|
39
39
|
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
40
|
-
} else {
|
|
41
|
-
const list = Array.from(files);
|
|
42
|
-
zone.dispatchEvent(new CustomEvent("icn:upload", { bubbles: true, detail: { files: list } }));
|
|
43
40
|
}
|
|
41
|
+
const list = Array.from(files);
|
|
42
|
+
zone.dispatchEvent(new CustomEvent("icen:upload", { bubbles: true, detail: { files: list } }));
|
|
44
43
|
});
|
|
45
44
|
}
|
|
46
45
|
function initUpload(root = document) {
|