@icen.ai/ui 0.8.0 → 0.8.1
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 +15 -9
- package/README.md +8 -3
- package/dist/base.css +4 -3
- package/dist/behaviors/accordion.d.mts +5 -2
- package/dist/behaviors/accordion.mjs +36 -11
- package/dist/behaviors/ai-chat.d.mts +3 -1
- package/dist/behaviors/ai-chat.mjs +66 -43
- package/dist/behaviors/ai-composer.d.mts +28 -3
- package/dist/behaviors/ai-composer.mjs +291 -209
- package/dist/behaviors/ai-core.d.mts +9 -4
- package/dist/behaviors/ai-core.mjs +21 -8
- package/dist/behaviors/ai-diff.d.mts +2 -2
- package/dist/behaviors/ai-diff.mjs +172 -41
- package/dist/behaviors/ai-panel.d.mts +11 -8
- package/dist/behaviors/ai-panel.mjs +185 -110
- package/dist/behaviors/ai-provider.mjs +54 -48
- package/dist/behaviors/ai-tool.d.mts +12 -4
- package/dist/behaviors/ai-tool.mjs +311 -263
- package/dist/behaviors/ai-tools.d.mts +9 -6
- package/dist/behaviors/ai-tools.mjs +259 -155
- package/dist/behaviors/back-top.mjs +16 -1
- package/dist/behaviors/carousel.d.mts +5 -2
- package/dist/behaviors/carousel.mjs +53 -26
- package/dist/behaviors/charts.d.mts +68 -22
- package/dist/behaviors/charts.mjs +221 -150
- package/dist/behaviors/command-palette.d.mts +40 -6
- package/dist/behaviors/command-palette.mjs +143 -15
- package/dist/behaviors/context-menu.d.mts +21 -4
- package/dist/behaviors/context-menu.mjs +122 -38
- package/dist/behaviors/controls.d.mts +59 -0
- package/dist/behaviors/controls.mjs +601 -0
- package/dist/behaviors/copy.d.mts +5 -2
- package/dist/behaviors/copy.mjs +37 -12
- package/dist/behaviors/datatable.d.mts +12 -0
- package/dist/behaviors/datatable.mjs +226 -43
- package/dist/behaviors/date-picker.d.mts +2 -2
- package/dist/behaviors/date-picker.mjs +171 -32
- package/dist/behaviors/dropdown.d.mts +6 -2
- package/dist/behaviors/dropdown.mjs +164 -25
- package/dist/behaviors/events.d.mts +330 -0
- package/dist/behaviors/events.mjs +208 -0
- package/dist/behaviors/input.mjs +25 -12
- package/dist/behaviors/modal.d.mts +24 -5
- package/dist/behaviors/modal.mjs +76 -10
- package/dist/behaviors/nav.d.mts +1 -1
- package/dist/behaviors/nav.mjs +33 -7
- package/dist/behaviors/notification.d.mts +20 -4
- package/dist/behaviors/notification.mjs +53 -73
- package/dist/behaviors/popover.d.mts +39 -3
- package/dist/behaviors/popover.mjs +90 -4
- package/dist/behaviors/select.d.mts +4 -2
- package/dist/behaviors/select.mjs +161 -24
- package/dist/behaviors/sidebar.d.mts +5 -2
- package/dist/behaviors/sidebar.mjs +43 -13
- package/dist/behaviors/slider.d.mts +5 -2
- package/dist/behaviors/slider.mjs +54 -43
- package/dist/behaviors/split-pane.d.mts +6 -2
- package/dist/behaviors/split-pane.mjs +62 -21
- package/dist/behaviors/tabs.d.mts +3 -2
- package/dist/behaviors/tabs.mjs +65 -14
- package/dist/behaviors/tag-input.d.mts +5 -2
- package/dist/behaviors/tag-input.mjs +61 -21
- package/dist/behaviors/theme.d.mts +13 -2
- package/dist/behaviors/theme.mjs +48 -4
- package/dist/behaviors/toast.d.mts +30 -8
- package/dist/behaviors/toast.mjs +74 -7
- package/dist/behaviors/tree.d.mts +5 -2
- package/dist/behaviors/tree.mjs +38 -15
- package/dist/behaviors/upload.mjs +21 -23
- package/dist/cli.mjs +1 -1
- package/dist/components/ai-panel.css +1 -1
- package/dist/components/ai-tool-call.mjs +1 -1
- package/dist/components/badge.css +1 -1
- package/dist/components/breadcrumb.css +0 -32
- package/dist/components/chart-calendar.mjs +1 -2
- package/dist/components/chart-pie.css +0 -6
- package/dist/components/charts.css +1 -1
- package/dist/components/content.css +3 -16
- package/dist/components/datatable.css +1 -1
- package/dist/components/date-picker.css +1 -1
- package/dist/components/notification.css +2 -3
- package/dist/components/pagination.css +3 -1
- package/dist/components/pagination.mjs +1 -0
- package/dist/components/rating.mjs +1 -0
- package/dist/components/segmented.css +9 -1
- package/dist/components/segmented.mjs +1 -1
- package/dist/components/select.css +67 -4
- package/dist/components/steps.css +13 -3
- package/dist/components/steps.mjs +1 -0
- package/dist/components/switch.css +3 -1
- package/dist/components/switch.mjs +1 -0
- package/dist/components/table.mjs +1 -0
- package/dist/components/tabs.css +23 -0
- package/dist/components/tag-input.css +1 -6
- package/dist/components/upload.css +0 -15
- package/dist/index.d.mts +12 -10
- package/dist/index.mjs +2700 -1460
- package/dist/registry.json +42 -13
- package/dist/retro-effects.css +3 -16
- package/dist/tokens/retro-effects.css +3 -16
- package/dist/tokens/style-profiles.css +3 -2
- package/dist/tokens/typography.css +2 -0
- package/dist/tokens.css +5 -2
- package/dist/ui.css +138 -98
- package/docs/spec/ai-native.md +7 -4
- package/package.json +1 -1
|
@@ -1,4 +1,63 @@
|
|
|
1
1
|
// src/behaviors/popover.ts
|
|
2
|
+
var PANEL_SIZING_ATTRS = {
|
|
3
|
+
width: "data-panel-width",
|
|
4
|
+
minWidth: "data-panel-min",
|
|
5
|
+
maxWidth: "data-panel-max",
|
|
6
|
+
minHeight: "data-panel-min-height",
|
|
7
|
+
maxHeight: "data-panel-max-height"
|
|
8
|
+
};
|
|
9
|
+
function readPanelSizing(el) {
|
|
10
|
+
const out = {};
|
|
11
|
+
if (!el) return out;
|
|
12
|
+
for (const key of Object.keys(PANEL_SIZING_ATTRS)) {
|
|
13
|
+
const raw = el.getAttribute(PANEL_SIZING_ATTRS[key]);
|
|
14
|
+
if (raw == null) continue;
|
|
15
|
+
const n = Number(raw);
|
|
16
|
+
if (Number.isFinite(n) && n > 0) out[key] = n;
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
function resolvePanelSizing(el, overrides) {
|
|
21
|
+
const base = readPanelSizing(el);
|
|
22
|
+
if (!overrides) return base;
|
|
23
|
+
for (const key of Object.keys(overrides)) {
|
|
24
|
+
const v = overrides[key];
|
|
25
|
+
if (typeof v === "number" && Number.isFinite(v) && v > 0) base[key] = v;
|
|
26
|
+
}
|
|
27
|
+
return base;
|
|
28
|
+
}
|
|
29
|
+
function sizingToLayout(sizing, opts) {
|
|
30
|
+
const out = { ...opts };
|
|
31
|
+
if (sizing.minWidth != null) out.minWidth = sizing.minWidth;
|
|
32
|
+
if (sizing.maxWidth != null) out.maxWidth = sizing.maxWidth;
|
|
33
|
+
if (sizing.width != null) {
|
|
34
|
+
out.minWidth = sizing.width;
|
|
35
|
+
out.maxWidth = sizing.width;
|
|
36
|
+
}
|
|
37
|
+
if (sizing.maxHeight != null) out.maxHeight = sizing.maxHeight;
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
function applyPanelSizing(panel, sizing) {
|
|
41
|
+
if (sizing == null) {
|
|
42
|
+
panel.style.width = "";
|
|
43
|
+
panel.style.minWidth = "";
|
|
44
|
+
panel.style.maxWidth = "";
|
|
45
|
+
panel.style.minHeight = "";
|
|
46
|
+
panel.style.maxHeight = "";
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const vw = typeof document === "undefined" ? Number.POSITIVE_INFINITY : document.documentElement.clientWidth;
|
|
50
|
+
const fitW = (n) => Math.min(n, Math.max(80, vw - 24));
|
|
51
|
+
if (sizing.width != null) {
|
|
52
|
+
const w = fitW(sizing.width);
|
|
53
|
+
panel.style.width = `${w}px`;
|
|
54
|
+
panel.style.maxWidth = `${w}px`;
|
|
55
|
+
}
|
|
56
|
+
if (sizing.minWidth != null) panel.style.minWidth = `${fitW(sizing.minWidth)}px`;
|
|
57
|
+
if (sizing.maxWidth != null && sizing.width == null) panel.style.maxWidth = `${fitW(sizing.maxWidth)}px`;
|
|
58
|
+
if (sizing.minHeight != null) panel.style.minHeight = `${sizing.minHeight}px`;
|
|
59
|
+
if (sizing.maxHeight != null) panel.style.maxHeight = `${sizing.maxHeight}px`;
|
|
60
|
+
}
|
|
2
61
|
var DEFAULT_MIN_WIDTH = 220;
|
|
3
62
|
var DEFAULT_MAX_WIDTH = 420;
|
|
4
63
|
var DEFAULT_MIN_HEIGHT = 120;
|
|
@@ -23,13 +82,35 @@ function horizontalAnchor(anchor, width, align) {
|
|
|
23
82
|
function computePopoverLayout(anchor, viewport, opts = {}) {
|
|
24
83
|
const margin = opts.margin ?? 12;
|
|
25
84
|
const offset = opts.offset ?? 8;
|
|
85
|
+
const preferredSide = opts.side ?? "top";
|
|
86
|
+
const align = opts.align ?? "start";
|
|
87
|
+
if (preferredSide === "left" || preferredSide === "right") {
|
|
88
|
+
const availableLeft = Math.max(0, anchor.left - margin - offset);
|
|
89
|
+
const availableRight = Math.max(0, viewport.width - anchor.right - margin - offset);
|
|
90
|
+
const side2 = (preferredSide === "left" ? availableLeft : availableRight) >= DEFAULT_MIN_WIDTH ? preferredSide : preferredSide === "left" ? "right" : "left";
|
|
91
|
+
const minWidth2 = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
|
|
92
|
+
const maxWidth2 = Math.min(
|
|
93
|
+
Math.max(opts.maxWidth ?? DEFAULT_MAX_WIDTH, minWidth2),
|
|
94
|
+
side2 === "left" ? availableLeft : availableRight
|
|
95
|
+
);
|
|
96
|
+
const width2 = minWidth2;
|
|
97
|
+
const left2 = clamp(
|
|
98
|
+
side2 === "left" ? anchor.left - offset - width2 : anchor.right + offset,
|
|
99
|
+
margin,
|
|
100
|
+
viewport.width - margin - width2
|
|
101
|
+
);
|
|
102
|
+
const availableHeight2 = Math.max(80, viewport.height - margin * 2);
|
|
103
|
+
const maxHeight2 = Math.min(opts.maxHeight ?? DEFAULT_MAX_HEIGHT, availableHeight2);
|
|
104
|
+
const desired = clamp(opts.contentHeightHint ?? Math.min(anchor.height, maxHeight2), 40, maxHeight2);
|
|
105
|
+
const anchorTop = align === "center" ? anchor.top + anchor.height / 2 - desired / 2 : align === "end" ? anchor.bottom - desired : anchor.top;
|
|
106
|
+
const top2 = clamp(anchorTop, margin, viewport.height - margin - desired);
|
|
107
|
+
return { side: side2, left: left2, top: top2, width: width2, maxHeight: maxHeight2 };
|
|
108
|
+
}
|
|
26
109
|
const availableWidth = Math.max(80, viewport.width - margin * 2);
|
|
27
110
|
const requestedMinWidth = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
|
|
28
111
|
const requestedMaxWidth = Math.max(requestedMinWidth, opts.maxWidth ?? DEFAULT_MAX_WIDTH);
|
|
29
112
|
const maxWidth = Math.min(requestedMaxWidth, availableWidth);
|
|
30
113
|
const minWidth = Math.min(requestedMinWidth, maxWidth);
|
|
31
|
-
const preferredSide = opts.side ?? "top";
|
|
32
|
-
const align = opts.align ?? "start";
|
|
33
114
|
const availableTop = Math.max(0, anchor.top - margin - offset);
|
|
34
115
|
const availableBottom = Math.max(0, viewport.height - anchor.bottom - margin - offset);
|
|
35
116
|
const availableHeight = Math.max(80, viewport.height - margin * 2);
|
|
@@ -107,7 +188,8 @@ function onKeyDown(e) {
|
|
|
107
188
|
function openPopover(panel, options) {
|
|
108
189
|
if (typeof document === "undefined") return;
|
|
109
190
|
if (openPanels.has(panel)) closePopover(panel);
|
|
110
|
-
const { anchor, onClose, ...
|
|
191
|
+
const { anchor, onClose, sizing, ...rest } = options;
|
|
192
|
+
const layoutOpts = sizingToLayout(resolvePanelSizing(panel, sizing), rest);
|
|
111
193
|
const placeholder = document.createComment("popover-anchor");
|
|
112
194
|
panel.parentNode?.insertBefore(placeholder, panel);
|
|
113
195
|
const wasHidden = panel.hidden;
|
|
@@ -159,7 +241,11 @@ function closePopover(panel) {
|
|
|
159
241
|
state.onClose?.();
|
|
160
242
|
}
|
|
161
243
|
export {
|
|
244
|
+
applyPanelSizing,
|
|
162
245
|
closePopover,
|
|
163
246
|
computePopoverLayout,
|
|
164
|
-
openPopover
|
|
247
|
+
openPopover,
|
|
248
|
+
readPanelSizing,
|
|
249
|
+
resolvePanelSizing,
|
|
250
|
+
sizingToLayout
|
|
165
251
|
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
/** 为 root 下每个 [data-select] 容器初始化(root 自身是 [data-select] 也算)。
|
|
2
|
-
|
|
1
|
+
/** 为 root 下每个 [data-select] 容器初始化(root 自身是 [data-select] 也算)。
|
|
2
|
+
* 返回销毁函数:摘除本次初始化容器的全部监听并复位幂等标记(可重新 init);
|
|
3
|
+
* 最后一个容器销毁时同时摘除 document 级单例委托。 */
|
|
4
|
+
declare function initSelect(root?: ParentNode): () => void;
|
|
3
5
|
|
|
4
6
|
export { initSelect };
|
|
@@ -1,4 +1,31 @@
|
|
|
1
1
|
// src/behaviors/popover.ts
|
|
2
|
+
var PANEL_SIZING_ATTRS = {
|
|
3
|
+
width: "data-panel-width",
|
|
4
|
+
minWidth: "data-panel-min",
|
|
5
|
+
maxWidth: "data-panel-max",
|
|
6
|
+
minHeight: "data-panel-min-height",
|
|
7
|
+
maxHeight: "data-panel-max-height"
|
|
8
|
+
};
|
|
9
|
+
function readPanelSizing(el) {
|
|
10
|
+
const out = {};
|
|
11
|
+
if (!el) return out;
|
|
12
|
+
for (const key of Object.keys(PANEL_SIZING_ATTRS)) {
|
|
13
|
+
const raw = el.getAttribute(PANEL_SIZING_ATTRS[key]);
|
|
14
|
+
if (raw == null) continue;
|
|
15
|
+
const n = Number(raw);
|
|
16
|
+
if (Number.isFinite(n) && n > 0) out[key] = n;
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
function resolvePanelSizing(el, overrides) {
|
|
21
|
+
const base = readPanelSizing(el);
|
|
22
|
+
if (!overrides) return base;
|
|
23
|
+
for (const key of Object.keys(overrides)) {
|
|
24
|
+
const v = overrides[key];
|
|
25
|
+
if (typeof v === "number" && Number.isFinite(v) && v > 0) base[key] = v;
|
|
26
|
+
}
|
|
27
|
+
return base;
|
|
28
|
+
}
|
|
2
29
|
var DEFAULT_MIN_WIDTH = 220;
|
|
3
30
|
var DEFAULT_MAX_WIDTH = 420;
|
|
4
31
|
var DEFAULT_MIN_HEIGHT = 120;
|
|
@@ -23,13 +50,35 @@ function horizontalAnchor(anchor, width, align) {
|
|
|
23
50
|
function computePopoverLayout(anchor, viewport, opts = {}) {
|
|
24
51
|
const margin = opts.margin ?? 12;
|
|
25
52
|
const offset = opts.offset ?? 8;
|
|
53
|
+
const preferredSide = opts.side ?? "top";
|
|
54
|
+
const align = opts.align ?? "start";
|
|
55
|
+
if (preferredSide === "left" || preferredSide === "right") {
|
|
56
|
+
const availableLeft = Math.max(0, anchor.left - margin - offset);
|
|
57
|
+
const availableRight = Math.max(0, viewport.width - anchor.right - margin - offset);
|
|
58
|
+
const side2 = (preferredSide === "left" ? availableLeft : availableRight) >= DEFAULT_MIN_WIDTH ? preferredSide : preferredSide === "left" ? "right" : "left";
|
|
59
|
+
const minWidth2 = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
|
|
60
|
+
const maxWidth2 = Math.min(
|
|
61
|
+
Math.max(opts.maxWidth ?? DEFAULT_MAX_WIDTH, minWidth2),
|
|
62
|
+
side2 === "left" ? availableLeft : availableRight
|
|
63
|
+
);
|
|
64
|
+
const width2 = minWidth2;
|
|
65
|
+
const left2 = clamp(
|
|
66
|
+
side2 === "left" ? anchor.left - offset - width2 : anchor.right + offset,
|
|
67
|
+
margin,
|
|
68
|
+
viewport.width - margin - width2
|
|
69
|
+
);
|
|
70
|
+
const availableHeight2 = Math.max(80, viewport.height - margin * 2);
|
|
71
|
+
const maxHeight2 = Math.min(opts.maxHeight ?? DEFAULT_MAX_HEIGHT, availableHeight2);
|
|
72
|
+
const desired = clamp(opts.contentHeightHint ?? Math.min(anchor.height, maxHeight2), 40, maxHeight2);
|
|
73
|
+
const anchorTop = align === "center" ? anchor.top + anchor.height / 2 - desired / 2 : align === "end" ? anchor.bottom - desired : anchor.top;
|
|
74
|
+
const top2 = clamp(anchorTop, margin, viewport.height - margin - desired);
|
|
75
|
+
return { side: side2, left: left2, top: top2, width: width2, maxHeight: maxHeight2 };
|
|
76
|
+
}
|
|
26
77
|
const availableWidth = Math.max(80, viewport.width - margin * 2);
|
|
27
78
|
const requestedMinWidth = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
|
|
28
79
|
const requestedMaxWidth = Math.max(requestedMinWidth, opts.maxWidth ?? DEFAULT_MAX_WIDTH);
|
|
29
80
|
const maxWidth = Math.min(requestedMaxWidth, availableWidth);
|
|
30
81
|
const minWidth = Math.min(requestedMinWidth, maxWidth);
|
|
31
|
-
const preferredSide = opts.side ?? "top";
|
|
32
|
-
const align = opts.align ?? "start";
|
|
33
82
|
const availableTop = Math.max(0, anchor.top - margin - offset);
|
|
34
83
|
const availableBottom = Math.max(0, viewport.height - anchor.bottom - margin - offset);
|
|
35
84
|
const availableHeight = Math.max(80, viewport.height - margin * 2);
|
|
@@ -53,6 +102,13 @@ function computePopoverLayout(anchor, viewport, opts = {}) {
|
|
|
53
102
|
return { side, left, top, width, maxHeight };
|
|
54
103
|
}
|
|
55
104
|
|
|
105
|
+
// src/behaviors/events.ts
|
|
106
|
+
function emitIcen(source, type, detail) {
|
|
107
|
+
return source.dispatchEvent(
|
|
108
|
+
new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
56
112
|
// src/behaviors/select.ts
|
|
57
113
|
var openSelect = null;
|
|
58
114
|
var globalBound = false;
|
|
@@ -98,18 +154,33 @@ function unbindOpenListeners() {
|
|
|
98
154
|
}
|
|
99
155
|
function setup(container) {
|
|
100
156
|
const el = container;
|
|
101
|
-
if (el.__icenSelectInit) return
|
|
102
|
-
|
|
157
|
+
if (el.__icenSelectInit) return () => {
|
|
158
|
+
};
|
|
159
|
+
const ac = new AbortController();
|
|
160
|
+
const on = (t, ev, fn) => {
|
|
161
|
+
const listener = (evt) => fn(evt);
|
|
162
|
+
t.addEventListener(ev, listener, { signal: ac.signal });
|
|
163
|
+
};
|
|
103
164
|
const triggerEl = container.querySelector(".select-trigger");
|
|
104
165
|
const panelEl = container.querySelector(".select-panel");
|
|
105
166
|
const valueEl = container.querySelector(".select-value");
|
|
106
167
|
const hiddenInput = container.querySelector("input[data-select-value]");
|
|
107
|
-
if (!triggerEl || !panelEl) return
|
|
168
|
+
if (!triggerEl || !panelEl) return () => {
|
|
169
|
+
};
|
|
170
|
+
el.__icenSelectInit = true;
|
|
108
171
|
const trigger = triggerEl;
|
|
109
172
|
const panel = panelEl;
|
|
110
173
|
const isMultiple = container.hasAttribute("data-select-multiple");
|
|
111
174
|
const isSearchable = container.hasAttribute("data-select-search");
|
|
112
175
|
const maxSelect = Number(container.getAttribute("data-select-max")) || 0;
|
|
176
|
+
const panelSizing = resolvePanelSizing(container);
|
|
177
|
+
function resolvedPanelWidth(triggerWidth) {
|
|
178
|
+
let w = triggerWidth;
|
|
179
|
+
if (panelSizing.minWidth != null) w = Math.max(w, panelSizing.minWidth);
|
|
180
|
+
if (panelSizing.maxWidth != null) w = Math.min(w, panelSizing.maxWidth);
|
|
181
|
+
if (panelSizing.width != null) w = panelSizing.width;
|
|
182
|
+
return w;
|
|
183
|
+
}
|
|
113
184
|
const placeholder = container.getAttribute("data-select-placeholder") ?? (valueEl?.classList.contains("is-empty") ? valueEl?.textContent ?? "" : "");
|
|
114
185
|
if (isMultiple) panel.setAttribute("data-select-multiple", "");
|
|
115
186
|
let searchInput = null;
|
|
@@ -177,8 +248,8 @@ function setup(container) {
|
|
|
177
248
|
setFocus(firstEnabled());
|
|
178
249
|
}
|
|
179
250
|
if (searchInput) {
|
|
180
|
-
searchInput
|
|
181
|
-
searchInput
|
|
251
|
+
on(searchInput, "input", () => filterOptions(searchInput.value));
|
|
252
|
+
on(searchInput, "keydown", (ev) => {
|
|
182
253
|
if (ev.key === "Escape") {
|
|
183
254
|
ev.stopPropagation();
|
|
184
255
|
searchInput.value = "";
|
|
@@ -189,6 +260,7 @@ function setup(container) {
|
|
|
189
260
|
}
|
|
190
261
|
function positionPanel() {
|
|
191
262
|
const rect = trigger.getBoundingClientRect();
|
|
263
|
+
const panelWidth = resolvedPanelWidth(rect.width);
|
|
192
264
|
const layout = computePopoverLayout(
|
|
193
265
|
rect,
|
|
194
266
|
{ width: window.innerWidth, height: window.innerHeight },
|
|
@@ -196,9 +268,9 @@ function setup(container) {
|
|
|
196
268
|
side: "bottom",
|
|
197
269
|
align: "stretch",
|
|
198
270
|
offset: PANEL_OFFSET,
|
|
199
|
-
minWidth:
|
|
200
|
-
maxWidth:
|
|
201
|
-
maxHeight: PANEL_MAX_HEIGHT
|
|
271
|
+
minWidth: panelWidth,
|
|
272
|
+
maxWidth: panelWidth,
|
|
273
|
+
maxHeight: panelSizing.maxHeight ?? PANEL_MAX_HEIGHT
|
|
202
274
|
}
|
|
203
275
|
);
|
|
204
276
|
panel.style.left = `${layout.left + window.scrollX}px`;
|
|
@@ -242,19 +314,59 @@ function setup(container) {
|
|
|
242
314
|
function updateMultiTrigger() {
|
|
243
315
|
if (!valueEl) return;
|
|
244
316
|
const selected = allOptions().filter((o) => o.classList.contains("is-selected"));
|
|
317
|
+
let chipsEl = trigger.querySelector(".select-chips");
|
|
245
318
|
if (selected.length === 0) {
|
|
246
319
|
valueEl.textContent = placeholder;
|
|
247
320
|
valueEl.classList.add("is-empty");
|
|
321
|
+
valueEl.hidden = false;
|
|
322
|
+
if (chipsEl) chipsEl.hidden = true;
|
|
248
323
|
container.removeAttribute("data-value");
|
|
249
324
|
} else {
|
|
250
325
|
valueEl.classList.remove("is-empty");
|
|
251
|
-
|
|
252
|
-
if (
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
valueEl.
|
|
326
|
+
valueEl.hidden = true;
|
|
327
|
+
if (!chipsEl) {
|
|
328
|
+
chipsEl = document.createElement("span");
|
|
329
|
+
chipsEl.className = "select-chips";
|
|
330
|
+
valueEl.insertAdjacentElement("afterend", chipsEl);
|
|
331
|
+
}
|
|
332
|
+
chipsEl.hidden = false;
|
|
333
|
+
chipsEl.textContent = "";
|
|
334
|
+
const maxLabel = Number(container.getAttribute("data-select-max-label")) || 3;
|
|
335
|
+
const shown = selected.slice(0, maxLabel);
|
|
336
|
+
const rest = selected.length - shown.length;
|
|
337
|
+
for (const o of shown) {
|
|
338
|
+
const label = o.textContent ?? "";
|
|
339
|
+
const chip = document.createElement("span");
|
|
340
|
+
chip.className = "select-chip";
|
|
341
|
+
const lb = document.createElement("span");
|
|
342
|
+
lb.textContent = label;
|
|
343
|
+
chip.appendChild(lb);
|
|
344
|
+
const x = document.createElement("span");
|
|
345
|
+
x.className = "select-chip-x";
|
|
346
|
+
x.setAttribute("role", "button");
|
|
347
|
+
x.setAttribute("tabindex", "-1");
|
|
348
|
+
x.setAttribute("aria-label", `\u79FB\u9664 ${label}`);
|
|
349
|
+
x.textContent = "\xD7";
|
|
350
|
+
on(x, "pointerdown", (ev) => ev.stopPropagation());
|
|
351
|
+
on(x, "click", (ev) => {
|
|
352
|
+
ev.stopPropagation();
|
|
353
|
+
ev.preventDefault();
|
|
354
|
+
o.classList.remove("is-selected");
|
|
355
|
+
updateMultiTrigger();
|
|
356
|
+
syncHiddenInput();
|
|
357
|
+
});
|
|
358
|
+
chip.appendChild(x);
|
|
359
|
+
chipsEl.appendChild(chip);
|
|
256
360
|
}
|
|
361
|
+
if (rest > 0) {
|
|
362
|
+
const more = document.createElement("span");
|
|
363
|
+
more.className = "select-chip select-chip--more";
|
|
364
|
+
more.textContent = `+${rest}`;
|
|
365
|
+
chipsEl.appendChild(more);
|
|
366
|
+
}
|
|
367
|
+
container.setAttribute("data-value", getSelectedValues().join(","));
|
|
257
368
|
}
|
|
369
|
+
if (clearBtn) clearBtn.hidden = selected.length === 0;
|
|
258
370
|
if (maxSelect > 0) {
|
|
259
371
|
const atMax = selected.length >= maxSelect;
|
|
260
372
|
allOptions().forEach((o) => {
|
|
@@ -265,8 +377,10 @@ function setup(container) {
|
|
|
265
377
|
}
|
|
266
378
|
}
|
|
267
379
|
function syncHiddenInput() {
|
|
268
|
-
if (!hiddenInput) return;
|
|
269
380
|
const values = getSelectedValues();
|
|
381
|
+
const label = allOptions().filter((o) => o.classList.contains("is-selected")).map((o) => (o.textContent ?? "").trim()).join(", ");
|
|
382
|
+
emitIcen(container, "icen:select-change", { value: values[0] ?? "", values, label });
|
|
383
|
+
if (!hiddenInput) return;
|
|
270
384
|
hiddenInput.value = isMultiple ? values.join(",") : values[0] ?? "";
|
|
271
385
|
hiddenInput.dispatchEvent(new Event("change", { bubbles: true }));
|
|
272
386
|
}
|
|
@@ -297,16 +411,16 @@ function setup(container) {
|
|
|
297
411
|
}
|
|
298
412
|
const clearBtn = container.querySelector(".select-clear");
|
|
299
413
|
if (clearBtn && isMultiple) {
|
|
300
|
-
clearBtn
|
|
414
|
+
on(clearBtn, "click", (ev) => {
|
|
301
415
|
ev.stopPropagation();
|
|
302
416
|
allOptions().forEach((o) => o.classList.remove("is-selected"));
|
|
303
417
|
updateMultiTrigger();
|
|
304
418
|
syncHiddenInput();
|
|
305
419
|
});
|
|
306
|
-
clearBtn
|
|
420
|
+
on(clearBtn, "pointerdown", (ev) => ev.stopPropagation());
|
|
307
421
|
}
|
|
308
|
-
trigger
|
|
309
|
-
trigger
|
|
422
|
+
on(trigger, "click", () => isOpen() ? closePanel() : openPanel());
|
|
423
|
+
on(trigger, "keydown", (ev) => {
|
|
310
424
|
if (!isOpen()) {
|
|
311
425
|
if (ev.key === "ArrowDown" || ev.key === "ArrowUp" || ev.key === "Enter" || ev.key === " ") {
|
|
312
426
|
ev.preventDefault();
|
|
@@ -336,13 +450,13 @@ function setup(container) {
|
|
|
336
450
|
break;
|
|
337
451
|
}
|
|
338
452
|
});
|
|
339
|
-
panel
|
|
453
|
+
on(panel, "click", (ev) => {
|
|
340
454
|
const t = ev.target;
|
|
341
455
|
if (!(t instanceof Element)) return;
|
|
342
456
|
const opt = t.closest(".select-option");
|
|
343
457
|
if (opt instanceof HTMLButtonElement && panel.contains(opt)) choose(opt);
|
|
344
458
|
});
|
|
345
|
-
panel
|
|
459
|
+
on(panel, "mouseover", (ev) => {
|
|
346
460
|
const t = ev.target;
|
|
347
461
|
if (!(t instanceof Element)) return;
|
|
348
462
|
const opt = t.closest(".select-option");
|
|
@@ -351,15 +465,38 @@ function setup(container) {
|
|
|
351
465
|
if (idx >= 0 && !opt.classList.contains("is-disabled")) setFocus(idx);
|
|
352
466
|
});
|
|
353
467
|
if (isMultiple) updateMultiTrigger();
|
|
468
|
+
return () => {
|
|
469
|
+
if (openSelect?.panel === panel) closePanel();
|
|
470
|
+
ac.abort();
|
|
471
|
+
el.__icenSelectInit = false;
|
|
472
|
+
};
|
|
354
473
|
}
|
|
474
|
+
var activeSetups = /* @__PURE__ */ new Set();
|
|
355
475
|
function initSelect(root) {
|
|
356
|
-
if (typeof document === "undefined") return
|
|
476
|
+
if (typeof document === "undefined") return () => {
|
|
477
|
+
};
|
|
357
478
|
const scope = root ?? document;
|
|
358
479
|
bindGlobalListeners();
|
|
359
480
|
const containers = [];
|
|
360
481
|
if (scope instanceof Element && scope.matches("[data-select]")) containers.push(scope);
|
|
361
482
|
containers.push(...Array.from(scope.querySelectorAll("[data-select]")));
|
|
362
|
-
|
|
483
|
+
const disposers = [];
|
|
484
|
+
for (const c of containers) {
|
|
485
|
+
const dispose = setup(c);
|
|
486
|
+
activeSetups.add(dispose);
|
|
487
|
+
disposers.push(dispose);
|
|
488
|
+
}
|
|
489
|
+
return () => {
|
|
490
|
+
for (const d of disposers) {
|
|
491
|
+
d();
|
|
492
|
+
activeSetups.delete(d);
|
|
493
|
+
}
|
|
494
|
+
if (activeSetups.size === 0 && globalBound) {
|
|
495
|
+
document.removeEventListener("pointerdown", onGlobalPointerDown);
|
|
496
|
+
document.removeEventListener("keydown", onGlobalKeyDown);
|
|
497
|
+
globalBound = false;
|
|
498
|
+
}
|
|
499
|
+
};
|
|
363
500
|
}
|
|
364
501
|
export {
|
|
365
502
|
initSelect
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 为 root 下每个 .sidebar 初始化(root 自身是 .sidebar 也算)。
|
|
3
|
+
* 返回销毁函数:移除本次挂上的容器监听并复位幂等标记(销毁后可重新 init)。SSR 下返回 no-op。
|
|
4
|
+
*/
|
|
5
|
+
declare function initSidebar(root?: ParentNode): () => void;
|
|
3
6
|
|
|
4
7
|
export { initSidebar };
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
// src/behaviors/events.ts
|
|
2
|
+
function emitIcen(source, type, detail) {
|
|
3
|
+
return source.dispatchEvent(
|
|
4
|
+
new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
|
|
5
|
+
);
|
|
6
|
+
}
|
|
7
|
+
|
|
1
8
|
// src/behaviors/sidebar.ts
|
|
2
9
|
function setup(sidebar) {
|
|
3
10
|
const el = sidebar;
|
|
4
|
-
if (el.__icenSidebarInit) return;
|
|
11
|
+
if (el.__icenSidebarInit) return void 0;
|
|
5
12
|
el.__icenSidebarInit = true;
|
|
6
13
|
sidebar.querySelectorAll(".sidebar-group-title").forEach((title) => {
|
|
7
14
|
const group = title.closest(".sidebar-group");
|
|
@@ -11,9 +18,21 @@ function setup(sidebar) {
|
|
|
11
18
|
title.setAttribute("aria-controls", panel.id);
|
|
12
19
|
}
|
|
13
20
|
});
|
|
14
|
-
|
|
21
|
+
sidebar.querySelectorAll("[data-sidebar-collapse]").forEach((btn) => {
|
|
22
|
+
if (!btn.hasAttribute("aria-expanded")) {
|
|
23
|
+
btn.setAttribute("aria-expanded", String(!sidebar.classList.contains("sidebar--collapsed")));
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
const onClick = (ev) => {
|
|
15
27
|
const target = ev.target;
|
|
16
28
|
if (!(target instanceof Element)) return;
|
|
29
|
+
const collapseBtn = target.closest("[data-sidebar-collapse]");
|
|
30
|
+
if (collapseBtn && collapseBtn.closest(".sidebar") === el) {
|
|
31
|
+
const collapsed = el.classList.toggle("sidebar--collapsed");
|
|
32
|
+
collapseBtn.setAttribute("aria-expanded", String(!collapsed));
|
|
33
|
+
emitIcen(el, "icen:sidebar-collapse", { collapsed });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
17
36
|
const title = target.closest(".sidebar-group-title");
|
|
18
37
|
if (!(title instanceof HTMLElement)) return;
|
|
19
38
|
if (title.closest(".sidebar") !== el) return;
|
|
@@ -21,19 +40,30 @@ function setup(sidebar) {
|
|
|
21
40
|
if (!group) return;
|
|
22
41
|
const open = group.classList.toggle("is-open");
|
|
23
42
|
title.setAttribute("aria-expanded", String(open));
|
|
24
|
-
sidebar
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
43
|
+
emitIcen(sidebar, "icen:sidebar-toggle", { group, open });
|
|
44
|
+
};
|
|
45
|
+
el.addEventListener("click", onClick);
|
|
46
|
+
return () => {
|
|
47
|
+
el.removeEventListener("click", onClick);
|
|
48
|
+
el.__icenSidebarInit = false;
|
|
49
|
+
};
|
|
29
50
|
}
|
|
30
51
|
function initSidebar(root) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
52
|
+
const cleanups = [];
|
|
53
|
+
if (typeof document !== "undefined") {
|
|
54
|
+
const scope = root ?? document;
|
|
55
|
+
const sidebars = [];
|
|
56
|
+
if (scope instanceof Element && scope.matches(".sidebar")) sidebars.push(scope);
|
|
57
|
+
sidebars.push(...Array.from(scope.querySelectorAll(".sidebar")));
|
|
58
|
+
for (const s of sidebars) {
|
|
59
|
+
const cleanup = setup(s);
|
|
60
|
+
if (cleanup) cleanups.push(cleanup);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return () => {
|
|
64
|
+
for (const fn of cleanups) fn();
|
|
65
|
+
cleanups.length = 0;
|
|
66
|
+
};
|
|
37
67
|
}
|
|
38
68
|
export {
|
|
39
69
|
initSidebar
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 为 root 下每个 .slider 容器初始化(root 自身是 .slider 也算)。
|
|
3
|
+
* 返回销毁函数:移除本次挂上的全部监听并复位幂等标记(销毁后可重新 init)。SSR 下返回 no-op。
|
|
4
|
+
*/
|
|
5
|
+
declare function initSlider(root?: ParentNode): () => void;
|
|
3
6
|
|
|
4
7
|
export { initSlider };
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
+
// src/behaviors/events.ts
|
|
2
|
+
function emitIcen(source, type, detail) {
|
|
3
|
+
return source.dispatchEvent(
|
|
4
|
+
new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
|
|
5
|
+
);
|
|
6
|
+
}
|
|
7
|
+
|
|
1
8
|
// src/behaviors/slider.ts
|
|
9
|
+
function bind(target, type, fn, bag) {
|
|
10
|
+
target.addEventListener(type, fn);
|
|
11
|
+
bag.push(() => target.removeEventListener(type, fn));
|
|
12
|
+
}
|
|
2
13
|
function pct(input) {
|
|
3
14
|
const min = Number(input.min || "0");
|
|
4
15
|
const max = Number(input.max || "100");
|
|
@@ -6,7 +17,7 @@ function pct(input) {
|
|
|
6
17
|
if (range <= 0) return 0;
|
|
7
18
|
return (Number(input.value) - min) / range * 100;
|
|
8
19
|
}
|
|
9
|
-
function setupSingle(slider, native) {
|
|
20
|
+
function setupSingle(slider, native, bag) {
|
|
10
21
|
const fill = slider.querySelector(".slider-fill");
|
|
11
22
|
const thumb = slider.querySelector(".slider-thumb");
|
|
12
23
|
const sync = () => {
|
|
@@ -14,22 +25,16 @@ function setupSingle(slider, native) {
|
|
|
14
25
|
if (fill) fill.style.width = `${p}%`;
|
|
15
26
|
if (thumb) thumb.style.left = `calc(${p}% - 8px)`;
|
|
16
27
|
};
|
|
17
|
-
native
|
|
28
|
+
bind(native, "input", () => {
|
|
18
29
|
sync();
|
|
19
|
-
slider
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
});
|
|
30
|
+
emitIcen(slider, "icen:slider-input", { value: Number(native.value) });
|
|
31
|
+
}, bag);
|
|
32
|
+
bind(native, "change", () => {
|
|
33
|
+
emitIcen(slider, "icen:slider-change", { value: Number(native.value) });
|
|
34
|
+
}, bag);
|
|
30
35
|
sync();
|
|
31
36
|
}
|
|
32
|
-
function setupDual(slider, natives) {
|
|
37
|
+
function setupDual(slider, natives, bag) {
|
|
33
38
|
const lo = natives.find((n) => n.getAttribute("data-thumb") === "lo") ?? natives[0];
|
|
34
39
|
const hi = natives.find((n) => n.getAttribute("data-thumb") === "hi") ?? natives[1];
|
|
35
40
|
if (!lo || !hi || lo === hi) return;
|
|
@@ -59,46 +64,52 @@ function setupDual(slider, natives) {
|
|
|
59
64
|
lo.style.clipPath = `inset(0 ${100 - pLo}% 0 0)`;
|
|
60
65
|
hi.style.clipPath = `inset(0 0 0 ${pHi}%)`;
|
|
61
66
|
};
|
|
62
|
-
lo
|
|
67
|
+
bind(lo, "input", () => {
|
|
63
68
|
sync(lo);
|
|
64
|
-
slider
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}));
|
|
68
|
-
});
|
|
69
|
-
hi.addEventListener("input", () => {
|
|
69
|
+
emitIcen(slider, "icen:slider-input", { lo: Number(lo.value), hi: Number(hi.value) });
|
|
70
|
+
}, bag);
|
|
71
|
+
bind(hi, "input", () => {
|
|
70
72
|
sync(hi);
|
|
71
|
-
slider
|
|
72
|
-
|
|
73
|
-
detail: { lo: Number(lo.value), hi: Number(hi.value) }
|
|
74
|
-
}));
|
|
75
|
-
});
|
|
73
|
+
emitIcen(slider, "icen:slider-input", { lo: Number(lo.value), hi: Number(hi.value) });
|
|
74
|
+
}, bag);
|
|
76
75
|
const onChange = () => {
|
|
77
|
-
slider
|
|
78
|
-
bubbles: true,
|
|
79
|
-
detail: { lo: Number(lo.value), hi: Number(hi.value) }
|
|
80
|
-
}));
|
|
76
|
+
emitIcen(slider, "icen:slider-change", { lo: Number(lo.value), hi: Number(hi.value) });
|
|
81
77
|
};
|
|
82
|
-
lo
|
|
83
|
-
hi
|
|
78
|
+
bind(lo, "change", onChange, bag);
|
|
79
|
+
bind(hi, "change", onChange, bag);
|
|
84
80
|
sync(null);
|
|
85
81
|
}
|
|
86
82
|
function setup(slider) {
|
|
87
83
|
const el = slider;
|
|
88
|
-
if (el.__icenSliderInit) return;
|
|
89
|
-
el.__icenSliderInit = true;
|
|
84
|
+
if (el.__icenSliderInit) return void 0;
|
|
90
85
|
const natives = Array.from(slider.querySelectorAll(".slider-native"));
|
|
91
|
-
if (natives.length === 0) return;
|
|
92
|
-
|
|
93
|
-
|
|
86
|
+
if (natives.length === 0) return void 0;
|
|
87
|
+
el.__icenSliderInit = true;
|
|
88
|
+
const bag = [];
|
|
89
|
+
if (slider.classList.contains("slider--dual")) setupDual(slider, natives, bag);
|
|
90
|
+
else setupSingle(slider, natives[0], bag);
|
|
91
|
+
return () => {
|
|
92
|
+
for (const off of bag) off();
|
|
93
|
+
bag.length = 0;
|
|
94
|
+
el.__icenSliderInit = false;
|
|
95
|
+
};
|
|
94
96
|
}
|
|
95
97
|
function initSlider(root) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
const cleanups = [];
|
|
99
|
+
if (typeof document !== "undefined") {
|
|
100
|
+
const scope = root ?? document;
|
|
101
|
+
const containers = [];
|
|
102
|
+
if (scope instanceof Element && scope.matches(".slider")) containers.push(scope);
|
|
103
|
+
containers.push(...Array.from(scope.querySelectorAll(".slider")));
|
|
104
|
+
for (const c of containers) {
|
|
105
|
+
const cleanup = setup(c);
|
|
106
|
+
if (cleanup) cleanups.push(cleanup);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return () => {
|
|
110
|
+
for (const fn of cleanups) fn();
|
|
111
|
+
cleanups.length = 0;
|
|
112
|
+
};
|
|
102
113
|
}
|
|
103
114
|
export {
|
|
104
115
|
initSlider
|