@icen.ai/ui 0.2.1 → 0.3.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 +168 -0
- package/dist/behaviors/copy.mjs +4 -3
- package/dist/behaviors/datatable.mjs +748 -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/charts.css +225 -33
- package/dist/components/charts.mjs +3 -0
- package/dist/components/content.css +256 -74
- package/dist/components/context-menu.mjs +3 -0
- package/dist/components/datatable.css +343 -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 +623 -42
- package/dist/registry.json +197 -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 +4861 -1019
- 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) {
|