@icen.ai/ui 0.4.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +256 -0
- package/README.md +47 -13
- package/dist/base.css +31 -4
- package/dist/behaviors/accordion.d.mts +4 -0
- package/dist/behaviors/accordion.mjs +4 -3
- package/dist/behaviors/ai-chat.d.mts +57 -0
- package/dist/behaviors/ai-chat.mjs +557 -0
- package/dist/behaviors/ai-composer.d.mts +112 -0
- package/dist/behaviors/ai-composer.mjs +2032 -0
- package/dist/behaviors/ai-core.d.mts +151 -0
- package/dist/behaviors/ai-core.mjs +491 -0
- package/dist/behaviors/ai-diff.d.mts +53 -0
- package/dist/behaviors/ai-diff.mjs +282 -0
- package/dist/behaviors/ai-panel.d.mts +98 -0
- package/dist/behaviors/ai-panel.mjs +1012 -0
- package/dist/behaviors/ai-provider.d.mts +198 -0
- package/dist/behaviors/ai-provider.mjs +1055 -0
- package/dist/behaviors/ai-tool.d.mts +18 -0
- package/dist/behaviors/ai-tool.mjs +2125 -0
- package/dist/behaviors/ai-tools.d.mts +81 -0
- package/dist/behaviors/ai-tools.mjs +1658 -0
- package/dist/behaviors/back-top.d.mts +20 -0
- package/dist/behaviors/back-top.mjs +101 -0
- package/dist/behaviors/carousel.d.mts +4 -0
- package/dist/behaviors/carousel.mjs +34 -16
- package/dist/behaviors/charts.d.mts +250 -0
- package/dist/behaviors/charts.mjs +1301 -448
- package/dist/behaviors/command-palette.d.mts +8 -0
- package/dist/behaviors/command-palette.mjs +189 -0
- package/dist/behaviors/context-menu.d.mts +36 -0
- package/dist/behaviors/context-menu.mjs +6 -2
- package/dist/behaviors/copy.d.mts +4 -0
- package/dist/behaviors/copy.mjs +7 -6
- package/dist/behaviors/datatable.d.mts +78 -0
- package/dist/behaviors/datatable.mjs +54 -11
- package/dist/behaviors/date-picker.d.mts +4 -0
- package/dist/behaviors/date-picker.mjs +296 -0
- package/dist/behaviors/dropdown.d.mts +4 -0
- package/dist/behaviors/dropdown.mjs +7 -3
- package/dist/behaviors/input.d.mts +4 -0
- package/dist/behaviors/input.mjs +184 -8
- package/dist/behaviors/modal.d.mts +11 -0
- package/dist/behaviors/modal.mjs +11 -6
- package/dist/behaviors/nav.d.mts +7 -0
- package/dist/behaviors/nav.mjs +25 -9
- package/dist/behaviors/notification.d.mts +172 -0
- package/dist/behaviors/notification.mjs +908 -0
- package/dist/behaviors/popover.d.mts +46 -0
- package/dist/behaviors/popover.mjs +2 -1
- package/dist/behaviors/select.d.mts +4 -0
- package/dist/behaviors/select.mjs +268 -34
- package/dist/behaviors/sidebar.d.mts +4 -0
- package/dist/behaviors/sidebar.mjs +4 -3
- package/dist/behaviors/slider.d.mts +4 -0
- package/dist/behaviors/slider.mjs +4 -3
- package/dist/behaviors/split-pane.d.mts +4 -0
- package/dist/behaviors/split-pane.mjs +102 -0
- package/dist/behaviors/tabs.d.mts +8 -0
- package/dist/behaviors/tabs.mjs +44 -19
- package/dist/behaviors/tag-input.d.mts +4 -0
- package/dist/behaviors/tag-input.mjs +12 -3
- package/dist/behaviors/theme.d.mts +25 -0
- package/dist/behaviors/theme.mjs +3 -1
- package/dist/behaviors/toast.d.mts +26 -0
- package/dist/behaviors/toast.mjs +24 -18
- package/dist/behaviors/tree.d.mts +4 -0
- package/dist/behaviors/tree.mjs +4 -3
- package/dist/behaviors/upload.d.mts +4 -0
- package/dist/behaviors/upload.mjs +205 -17
- package/dist/components/ai-chat.css +1037 -0
- package/dist/components/ai-chat.mjs +3 -0
- package/dist/components/ai-composer.mjs +3 -0
- package/dist/components/ai-context.mjs +2 -0
- package/dist/components/ai-diff.css +311 -0
- package/dist/components/ai-diff.mjs +3 -0
- package/dist/components/ai-files.mjs +3 -0
- package/dist/components/ai-message.mjs +3 -0
- package/dist/components/ai-panel.css +682 -0
- package/dist/components/ai-reasoning.mjs +3 -0
- package/dist/components/ai-subagent.mjs +2 -0
- package/dist/components/ai-todo.mjs +2 -0
- package/dist/components/ai-tool-call.mjs +2 -0
- package/dist/components/ai-tool.css +532 -0
- package/dist/components/ai-usage.mjs +3 -0
- package/dist/components/back-top.css +88 -0
- package/dist/components/back-top.mjs +3 -0
- package/dist/components/badge.css +127 -0
- package/dist/components/badge.mjs +2 -0
- package/dist/components/breadcrumb.css +53 -0
- package/dist/components/btn.css +4 -7
- package/dist/components/card.css +66 -1
- package/dist/components/chart-bar.css +10 -0
- package/dist/components/chart-calendar.mjs +5 -0
- package/dist/components/chart-gauge.css +1 -1
- package/dist/components/chart-heatmap.css +40 -0
- package/dist/components/chart-scatter.css +24 -0
- package/dist/components/chart-scatter.mjs +4 -0
- package/dist/components/charts.css +86 -1
- package/dist/components/charts.mjs +1 -0
- package/dist/components/command-palette.css +246 -0
- package/dist/components/command-palette.mjs +3 -0
- package/dist/components/content.css +221 -8
- package/dist/components/copy.css +93 -0
- package/dist/components/copy.mjs +3 -0
- package/dist/components/datatable.css +4 -8
- package/dist/components/date-picker.css +240 -0
- package/dist/components/date-picker.mjs +3 -0
- package/dist/components/empty.css +69 -3
- package/dist/components/feedback.css +241 -10
- package/dist/components/form.css +129 -11
- package/dist/components/form.mjs +2 -0
- package/dist/components/input.css +25 -2
- package/dist/components/layout.css +36 -36
- package/dist/components/media.css +153 -6
- package/dist/components/menu.css +2 -2
- package/dist/components/modal.css +1 -1
- package/dist/components/nav.css +106 -2
- package/dist/components/notification.css +387 -0
- package/dist/components/notification.mjs +3 -0
- package/dist/components/pagination.css +1 -1
- package/dist/components/panel.css +104 -9
- package/dist/components/pill.css +82 -12
- package/dist/components/scroll-area.css +112 -0
- package/dist/components/scroll-area.mjs +2 -0
- package/dist/components/segmented.css +80 -5
- package/dist/components/segmented.mjs +1 -0
- package/dist/components/select.css +104 -17
- package/dist/components/sidebar.css +119 -1
- package/dist/components/slider.css +113 -0
- package/dist/components/split-pane.css +111 -0
- package/dist/components/split-pane.mjs +3 -0
- package/dist/components/stat.css +131 -12
- package/dist/components/steps.css +98 -1
- package/dist/components/switch.css +145 -7
- package/dist/components/table.css +111 -29
- package/dist/components/tabs.css +132 -22
- package/dist/components/tag-input.css +4 -4
- package/dist/components/tag.css +164 -0
- package/dist/components/tag.mjs +2 -0
- package/dist/components/toast.css +122 -5
- package/dist/components/toolbar.css +101 -0
- package/dist/components/toolbar.mjs +2 -0
- package/dist/components/tooltip.css +2 -2
- package/dist/components/upload.css +61 -4
- package/dist/index.d.mts +37 -0
- package/dist/index.mjs +9409 -929
- package/dist/registry.json +176 -4
- package/dist/retro-effects.css +21 -17
- package/dist/tokens/colors.css +4 -1
- package/dist/tokens/retro-effects.css +21 -17
- package/dist/tokens/style-profiles.css +21 -0
- package/dist/tokens.css +25 -1
- package/dist/ui.css +9616 -2839
- package/docs/spec/ai-native.md +659 -0
- package/package.json +15 -5
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
interface PopoverRect {
|
|
2
|
+
left: number;
|
|
3
|
+
top: number;
|
|
4
|
+
right: number;
|
|
5
|
+
bottom: number;
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
}
|
|
9
|
+
interface PopoverViewport {
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
}
|
|
13
|
+
type PopoverSide = 'top' | 'bottom';
|
|
14
|
+
type PopoverAlign = 'start' | 'center' | 'end' | 'stretch';
|
|
15
|
+
interface PopoverLayoutOptions {
|
|
16
|
+
side?: PopoverSide;
|
|
17
|
+
align?: PopoverAlign;
|
|
18
|
+
offset?: number;
|
|
19
|
+
margin?: number;
|
|
20
|
+
minWidth?: number;
|
|
21
|
+
maxWidth?: number;
|
|
22
|
+
minHeight?: number;
|
|
23
|
+
maxHeight?: number;
|
|
24
|
+
/** 内容预期高度:避免用被裁剪的实测高度参与选侧 */
|
|
25
|
+
contentHeightHint?: number;
|
|
26
|
+
}
|
|
27
|
+
interface PopoverLayout {
|
|
28
|
+
side: PopoverSide;
|
|
29
|
+
left: number;
|
|
30
|
+
top: number;
|
|
31
|
+
width: number;
|
|
32
|
+
maxHeight: number;
|
|
33
|
+
}
|
|
34
|
+
interface OpenPopoverOptions extends PopoverLayoutOptions {
|
|
35
|
+
anchor: Element | PopoverRect;
|
|
36
|
+
/** 关闭(含外点/Esc 触发的关闭)后的回调 */
|
|
37
|
+
onClose?: () => void;
|
|
38
|
+
}
|
|
39
|
+
/** 视口智能布局:返回 {side, left, top, width, maxHeight}。 */
|
|
40
|
+
declare function computePopoverLayout(anchor: PopoverRect, viewport: PopoverViewport, opts?: PopoverLayoutOptions): PopoverLayout;
|
|
41
|
+
/** 打开浮层:panel 移入 body 并按 anchor 布局;重复调用同一 panel 为先关后开。 */
|
|
42
|
+
declare function openPopover(panel: HTMLElement, options: OpenPopoverOptions): void;
|
|
43
|
+
/** 关闭浮层:移除监听并把 panel 还原到打开前的位置与 hidden 状态。 */
|
|
44
|
+
declare function closePopover(panel: HTMLElement): void;
|
|
45
|
+
|
|
46
|
+
export { type OpenPopoverOptions, type PopoverAlign, type PopoverLayout, type PopoverLayoutOptions, type PopoverRect, type PopoverSide, type PopoverViewport, closePopover, computePopoverLayout, openPopover };
|
|
@@ -139,7 +139,8 @@ function closePopover(panel) {
|
|
|
139
139
|
if (!state) return;
|
|
140
140
|
openPanels.delete(panel);
|
|
141
141
|
panel.style.visibility = state.prevVisibility;
|
|
142
|
-
panel.hidden
|
|
142
|
+
if (typeof state.wasHidden === "string") panel.setAttribute("hidden", state.wasHidden);
|
|
143
|
+
else panel.hidden = state.wasHidden;
|
|
143
144
|
panel.style.left = "";
|
|
144
145
|
panel.style.top = "";
|
|
145
146
|
panel.style.width = "";
|
|
@@ -1,4 +1,101 @@
|
|
|
1
|
+
// src/behaviors/popover.ts
|
|
2
|
+
var DEFAULT_MIN_WIDTH = 220;
|
|
3
|
+
var DEFAULT_MAX_WIDTH = 420;
|
|
4
|
+
var DEFAULT_MIN_HEIGHT = 120;
|
|
5
|
+
var DEFAULT_MAX_HEIGHT = 420;
|
|
6
|
+
function clamp(value, min, max) {
|
|
7
|
+
if (max < min) return min;
|
|
8
|
+
return Math.min(max, Math.max(min, value));
|
|
9
|
+
}
|
|
10
|
+
function chooseSide(preferredSide, availableTop, availableBottom, desiredHeight) {
|
|
11
|
+
const preferredSpace = preferredSide === "top" ? availableTop : availableBottom;
|
|
12
|
+
const oppositeSpace = preferredSide === "top" ? availableBottom : availableTop;
|
|
13
|
+
if (preferredSpace >= desiredHeight) return preferredSide;
|
|
14
|
+
if (oppositeSpace >= desiredHeight) return preferredSide === "top" ? "bottom" : "top";
|
|
15
|
+
if (preferredSpace >= oppositeSpace) return preferredSide;
|
|
16
|
+
return preferredSide === "top" ? "bottom" : "top";
|
|
17
|
+
}
|
|
18
|
+
function horizontalAnchor(anchor, width, align) {
|
|
19
|
+
if (align === "center") return anchor.left + anchor.width / 2 - width / 2;
|
|
20
|
+
if (align === "end") return anchor.right - width;
|
|
21
|
+
return anchor.left;
|
|
22
|
+
}
|
|
23
|
+
function computePopoverLayout(anchor, viewport, opts = {}) {
|
|
24
|
+
const margin = opts.margin ?? 12;
|
|
25
|
+
const offset = opts.offset ?? 8;
|
|
26
|
+
const availableWidth = Math.max(80, viewport.width - margin * 2);
|
|
27
|
+
const requestedMinWidth = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
|
|
28
|
+
const requestedMaxWidth = Math.max(requestedMinWidth, opts.maxWidth ?? DEFAULT_MAX_WIDTH);
|
|
29
|
+
const maxWidth = Math.min(requestedMaxWidth, availableWidth);
|
|
30
|
+
const minWidth = Math.min(requestedMinWidth, maxWidth);
|
|
31
|
+
const preferredSide = opts.side ?? "top";
|
|
32
|
+
const align = opts.align ?? "start";
|
|
33
|
+
const availableTop = Math.max(0, anchor.top - margin - offset);
|
|
34
|
+
const availableBottom = Math.max(0, viewport.height - anchor.bottom - margin - offset);
|
|
35
|
+
const availableHeight = Math.max(80, viewport.height - margin * 2);
|
|
36
|
+
const minHeight = Math.min(Math.max(80, opts.minHeight ?? DEFAULT_MIN_HEIGHT), availableHeight);
|
|
37
|
+
const requestedMaxHeight = Math.max(minHeight, opts.maxHeight ?? DEFAULT_MAX_HEIGHT);
|
|
38
|
+
const fallbackHeight = Math.min(260, requestedMaxHeight);
|
|
39
|
+
const contentHeight = Math.max(opts.contentHeightHint ?? 0, fallbackHeight);
|
|
40
|
+
const desiredHeight = clamp(contentHeight, minHeight, Math.min(requestedMaxHeight, availableHeight));
|
|
41
|
+
const side = chooseSide(preferredSide, availableTop, availableBottom, desiredHeight);
|
|
42
|
+
const sideSpace = side === "top" ? availableTop : availableBottom;
|
|
43
|
+
const maxHeight = Math.min(
|
|
44
|
+
requestedMaxHeight,
|
|
45
|
+
Math.max(80, sideSpace || Math.max(availableTop, availableBottom)),
|
|
46
|
+
availableHeight
|
|
47
|
+
);
|
|
48
|
+
const positionedHeight = clamp(desiredHeight, 40, maxHeight);
|
|
49
|
+
const width = align === "stretch" ? clamp(anchor.width, minWidth, maxWidth) : maxWidth;
|
|
50
|
+
const left = clamp(horizontalAnchor(anchor, width, align), margin, viewport.width - margin - width);
|
|
51
|
+
const desiredTop = side === "top" ? anchor.top - offset - positionedHeight : anchor.bottom + offset;
|
|
52
|
+
const top = clamp(desiredTop, margin, viewport.height - margin - positionedHeight);
|
|
53
|
+
return { side, left, top, width, maxHeight };
|
|
54
|
+
}
|
|
55
|
+
|
|
1
56
|
// src/behaviors/select.ts
|
|
57
|
+
var openSelect = null;
|
|
58
|
+
var globalBound = false;
|
|
59
|
+
var PANEL_OFFSET = 4;
|
|
60
|
+
var PANEL_MAX_HEIGHT = 240;
|
|
61
|
+
function onGlobalPointerDown(ev) {
|
|
62
|
+
const cur = openSelect;
|
|
63
|
+
if (!cur) return;
|
|
64
|
+
const t = ev.target;
|
|
65
|
+
if (!(t instanceof Node)) return;
|
|
66
|
+
if (cur.container.contains(t) || cur.panel.contains(t)) return;
|
|
67
|
+
cur.close();
|
|
68
|
+
}
|
|
69
|
+
function onGlobalKeyDown(ev) {
|
|
70
|
+
if (ev.key !== "Escape") return;
|
|
71
|
+
const cur = openSelect;
|
|
72
|
+
if (!cur) return;
|
|
73
|
+
cur.close();
|
|
74
|
+
cur.trigger.focus();
|
|
75
|
+
}
|
|
76
|
+
function onWindowChange() {
|
|
77
|
+
const cur = openSelect;
|
|
78
|
+
if (!cur) return;
|
|
79
|
+
if (!cur.trigger.isConnected) {
|
|
80
|
+
cur.close();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
cur.reposition();
|
|
84
|
+
}
|
|
85
|
+
function bindGlobalListeners() {
|
|
86
|
+
if (globalBound) return;
|
|
87
|
+
globalBound = true;
|
|
88
|
+
document.addEventListener("pointerdown", onGlobalPointerDown);
|
|
89
|
+
document.addEventListener("keydown", onGlobalKeyDown);
|
|
90
|
+
}
|
|
91
|
+
function bindOpenListeners() {
|
|
92
|
+
window.addEventListener("scroll", onWindowChange, true);
|
|
93
|
+
window.addEventListener("resize", onWindowChange);
|
|
94
|
+
}
|
|
95
|
+
function unbindOpenListeners() {
|
|
96
|
+
window.removeEventListener("scroll", onWindowChange, true);
|
|
97
|
+
window.removeEventListener("resize", onWindowChange);
|
|
98
|
+
}
|
|
2
99
|
function setup(container) {
|
|
3
100
|
const el = container;
|
|
4
101
|
if (el.__icenSelectInit) return;
|
|
@@ -10,21 +107,39 @@ function setup(container) {
|
|
|
10
107
|
if (!triggerEl || !panelEl) return;
|
|
11
108
|
const trigger = triggerEl;
|
|
12
109
|
const panel = panelEl;
|
|
13
|
-
const
|
|
14
|
-
const
|
|
110
|
+
const isMultiple = container.hasAttribute("data-select-multiple");
|
|
111
|
+
const isSearchable = container.hasAttribute("data-select-search");
|
|
112
|
+
const maxSelect = Number(container.getAttribute("data-select-max")) || 0;
|
|
113
|
+
const placeholder = container.getAttribute("data-select-placeholder") ?? (valueEl?.classList.contains("is-empty") ? valueEl?.textContent ?? "" : "");
|
|
114
|
+
if (isMultiple) panel.setAttribute("data-select-multiple", "");
|
|
115
|
+
let searchInput = null;
|
|
15
116
|
let focusIdx = -1;
|
|
117
|
+
if (isSearchable && !panel.querySelector(".select-search-input")) {
|
|
118
|
+
const searchWrap = document.createElement("div");
|
|
119
|
+
searchWrap.className = "select-search";
|
|
120
|
+
const input = document.createElement("input");
|
|
121
|
+
input.className = "select-search-input";
|
|
122
|
+
input.type = "search";
|
|
123
|
+
input.placeholder = "\u641C\u7D22\u2026";
|
|
124
|
+
searchWrap.appendChild(input);
|
|
125
|
+
panel.insertBefore(searchWrap, panel.firstChild);
|
|
126
|
+
}
|
|
127
|
+
searchInput = panel.querySelector(".select-search-input");
|
|
128
|
+
const allOptions = () => Array.from(panel.querySelectorAll(".select-option"));
|
|
129
|
+
const visibleOptions = () => allOptions().filter((o) => !o.hidden);
|
|
130
|
+
const isOpen = () => !panel.hidden;
|
|
16
131
|
function setFocus(idx) {
|
|
17
|
-
const opts =
|
|
132
|
+
const opts = visibleOptions();
|
|
18
133
|
focusIdx = Math.max(-1, Math.min(idx, opts.length - 1));
|
|
19
134
|
opts.forEach((o, i) => o.classList.toggle("is-focused", i === focusIdx));
|
|
20
135
|
const cur = focusIdx >= 0 ? opts[focusIdx] : void 0;
|
|
21
136
|
cur?.scrollIntoView({ block: "nearest" });
|
|
22
137
|
}
|
|
23
138
|
function firstEnabled() {
|
|
24
|
-
return
|
|
139
|
+
return visibleOptions().findIndex((o) => !o.classList.contains("is-disabled"));
|
|
25
140
|
}
|
|
26
141
|
function moveFocus(delta) {
|
|
27
|
-
const opts =
|
|
142
|
+
const opts = visibleOptions();
|
|
28
143
|
if (opts.length === 0) return;
|
|
29
144
|
let n = focusIdx;
|
|
30
145
|
for (let guard = 0; guard < opts.length; guard++) {
|
|
@@ -34,13 +149,83 @@ function setup(container) {
|
|
|
34
149
|
}
|
|
35
150
|
setFocus(n);
|
|
36
151
|
}
|
|
152
|
+
let emptyEl = panel.querySelector(".select-empty");
|
|
153
|
+
if (!emptyEl) {
|
|
154
|
+
emptyEl = document.createElement("div");
|
|
155
|
+
emptyEl.className = "select-empty";
|
|
156
|
+
emptyEl.textContent = "\u65E0\u5339\u914D\u9879";
|
|
157
|
+
emptyEl.hidden = true;
|
|
158
|
+
panel.appendChild(emptyEl);
|
|
159
|
+
}
|
|
160
|
+
function updateEmptyState() {
|
|
161
|
+
const hasVisible = visibleOptions().length > 0;
|
|
162
|
+
if (emptyEl) emptyEl.hidden = hasVisible;
|
|
163
|
+
}
|
|
164
|
+
function filterOptions(query) {
|
|
165
|
+
const q = query.trim().toLowerCase();
|
|
166
|
+
const groups = Array.from(panel.querySelectorAll(".select-group"));
|
|
167
|
+
for (const opt of allOptions()) {
|
|
168
|
+
const text = (opt.textContent ?? "").toLowerCase();
|
|
169
|
+
const kw = (opt.getAttribute("data-keywords") ?? "").toLowerCase();
|
|
170
|
+
opt.hidden = q !== "" && !text.includes(q) && !kw.includes(q);
|
|
171
|
+
}
|
|
172
|
+
for (const g of groups) {
|
|
173
|
+
const hasVisible = Array.from(g.querySelectorAll(".select-option")).some((o) => !o.hidden);
|
|
174
|
+
g.hidden = !hasVisible;
|
|
175
|
+
}
|
|
176
|
+
updateEmptyState();
|
|
177
|
+
setFocus(firstEnabled());
|
|
178
|
+
}
|
|
179
|
+
if (searchInput) {
|
|
180
|
+
searchInput.addEventListener("input", () => filterOptions(searchInput.value));
|
|
181
|
+
searchInput.addEventListener("keydown", (ev) => {
|
|
182
|
+
if (ev.key === "Escape") {
|
|
183
|
+
ev.stopPropagation();
|
|
184
|
+
searchInput.value = "";
|
|
185
|
+
filterOptions("");
|
|
186
|
+
searchInput.focus();
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
function positionPanel() {
|
|
191
|
+
const rect = trigger.getBoundingClientRect();
|
|
192
|
+
const layout = computePopoverLayout(
|
|
193
|
+
rect,
|
|
194
|
+
{ width: window.innerWidth, height: window.innerHeight },
|
|
195
|
+
{
|
|
196
|
+
side: "bottom",
|
|
197
|
+
align: "stretch",
|
|
198
|
+
offset: PANEL_OFFSET,
|
|
199
|
+
minWidth: rect.width,
|
|
200
|
+
maxWidth: rect.width,
|
|
201
|
+
maxHeight: PANEL_MAX_HEIGHT
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
panel.style.left = `${layout.left + window.scrollX}px`;
|
|
205
|
+
panel.style.top = `${layout.top + window.scrollY}px`;
|
|
206
|
+
panel.style.minWidth = `${layout.width}px`;
|
|
207
|
+
panel.style.width = `${layout.width}px`;
|
|
208
|
+
panel.style.maxHeight = `${layout.maxHeight}px`;
|
|
209
|
+
panel.dataset.side = layout.side;
|
|
210
|
+
}
|
|
37
211
|
function openPanel() {
|
|
38
212
|
if (isOpen()) return;
|
|
213
|
+
openSelect?.close();
|
|
214
|
+
if (panel.parentElement !== document.body) document.body.appendChild(panel);
|
|
39
215
|
panel.hidden = false;
|
|
40
216
|
container.classList.add("is-open");
|
|
41
217
|
trigger.setAttribute("aria-expanded", "true");
|
|
42
|
-
|
|
43
|
-
|
|
218
|
+
positionPanel();
|
|
219
|
+
openSelect = { container, trigger, panel, close: closePanel, reposition: positionPanel };
|
|
220
|
+
bindOpenListeners();
|
|
221
|
+
if (searchInput) {
|
|
222
|
+
searchInput.value = "";
|
|
223
|
+
filterOptions("");
|
|
224
|
+
setTimeout(() => searchInput?.focus(), 0);
|
|
225
|
+
} else {
|
|
226
|
+
const sel = allOptions().findIndex((o) => o.classList.contains("is-selected"));
|
|
227
|
+
setFocus(sel >= 0 ? sel : firstEnabled());
|
|
228
|
+
}
|
|
44
229
|
}
|
|
45
230
|
function closePanel() {
|
|
46
231
|
if (!isOpen()) return;
|
|
@@ -48,22 +233,77 @@ function setup(container) {
|
|
|
48
233
|
container.classList.remove("is-open");
|
|
49
234
|
trigger.setAttribute("aria-expanded", "false");
|
|
50
235
|
setFocus(-1);
|
|
236
|
+
if (openSelect?.panel === panel) openSelect = null;
|
|
237
|
+
unbindOpenListeners();
|
|
51
238
|
}
|
|
52
|
-
function
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
239
|
+
function getSelectedValues() {
|
|
240
|
+
return allOptions().filter((o) => o.classList.contains("is-selected")).map((o) => o.getAttribute("data-value") ?? "");
|
|
241
|
+
}
|
|
242
|
+
function updateMultiTrigger() {
|
|
243
|
+
if (!valueEl) return;
|
|
244
|
+
const selected = allOptions().filter((o) => o.classList.contains("is-selected"));
|
|
245
|
+
if (selected.length === 0) {
|
|
246
|
+
valueEl.textContent = placeholder;
|
|
247
|
+
valueEl.classList.add("is-empty");
|
|
248
|
+
container.removeAttribute("data-value");
|
|
249
|
+
} else {
|
|
57
250
|
valueEl.classList.remove("is-empty");
|
|
251
|
+
const maxLabel = Number(container.getAttribute("data-select-max-label")) || 2;
|
|
252
|
+
if (selected.length <= maxLabel) {
|
|
253
|
+
valueEl.textContent = selected.map((o) => o.textContent ?? "").join("\u3001");
|
|
254
|
+
} else {
|
|
255
|
+
valueEl.textContent = `\u5DF2\u9009 ${selected.length} \u9879`;
|
|
256
|
+
}
|
|
58
257
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
258
|
+
if (maxSelect > 0) {
|
|
259
|
+
const atMax = selected.length >= maxSelect;
|
|
260
|
+
allOptions().forEach((o) => {
|
|
261
|
+
if (!o.classList.contains("is-selected")) {
|
|
262
|
+
o.classList.toggle("is-disabled", atMax);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
64
265
|
}
|
|
65
|
-
|
|
66
|
-
|
|
266
|
+
}
|
|
267
|
+
function syncHiddenInput() {
|
|
268
|
+
if (!hiddenInput) return;
|
|
269
|
+
const values = getSelectedValues();
|
|
270
|
+
hiddenInput.value = isMultiple ? values.join(",") : values[0] ?? "";
|
|
271
|
+
hiddenInput.dispatchEvent(new Event("change", { bubbles: true }));
|
|
272
|
+
}
|
|
273
|
+
function choose(opt) {
|
|
274
|
+
if (opt.classList.contains("is-disabled")) return;
|
|
275
|
+
if (isMultiple) {
|
|
276
|
+
opt.classList.toggle("is-selected");
|
|
277
|
+
container.setAttribute(
|
|
278
|
+
"data-value",
|
|
279
|
+
getSelectedValues().join(",")
|
|
280
|
+
);
|
|
281
|
+
updateMultiTrigger();
|
|
282
|
+
syncHiddenInput();
|
|
283
|
+
if (searchInput) searchInput.focus();
|
|
284
|
+
else opt.focus();
|
|
285
|
+
} else {
|
|
286
|
+
allOptions().forEach((o) => o.classList.toggle("is-selected", o === opt));
|
|
287
|
+
if (valueEl) {
|
|
288
|
+
valueEl.textContent = opt.textContent ?? "";
|
|
289
|
+
valueEl.classList.remove("is-empty");
|
|
290
|
+
}
|
|
291
|
+
const v = opt.getAttribute("data-value") ?? "";
|
|
292
|
+
container.setAttribute("data-value", v);
|
|
293
|
+
syncHiddenInput();
|
|
294
|
+
closePanel();
|
|
295
|
+
trigger.focus();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const clearBtn = container.querySelector(".select-clear");
|
|
299
|
+
if (clearBtn && isMultiple) {
|
|
300
|
+
clearBtn.addEventListener("click", (ev) => {
|
|
301
|
+
ev.stopPropagation();
|
|
302
|
+
allOptions().forEach((o) => o.classList.remove("is-selected"));
|
|
303
|
+
updateMultiTrigger();
|
|
304
|
+
syncHiddenInput();
|
|
305
|
+
});
|
|
306
|
+
clearBtn.addEventListener("pointerdown", (ev) => ev.stopPropagation());
|
|
67
307
|
}
|
|
68
308
|
trigger.addEventListener("click", () => isOpen() ? closePanel() : openPanel());
|
|
69
309
|
trigger.addEventListener("keydown", (ev) => {
|
|
@@ -86,7 +326,7 @@ function setup(container) {
|
|
|
86
326
|
case "Enter":
|
|
87
327
|
case " ": {
|
|
88
328
|
ev.preventDefault();
|
|
89
|
-
const opt =
|
|
329
|
+
const opt = visibleOptions()[focusIdx];
|
|
90
330
|
if (opt) choose(opt);
|
|
91
331
|
break;
|
|
92
332
|
}
|
|
@@ -106,25 +346,19 @@ function setup(container) {
|
|
|
106
346
|
const t = ev.target;
|
|
107
347
|
if (!(t instanceof Element)) return;
|
|
108
348
|
const opt = t.closest(".select-option");
|
|
109
|
-
if (!opt) return;
|
|
110
|
-
const idx =
|
|
349
|
+
if (!opt || opt.hidden) return;
|
|
350
|
+
const idx = visibleOptions().indexOf(opt);
|
|
111
351
|
if (idx >= 0 && !opt.classList.contains("is-disabled")) setFocus(idx);
|
|
112
352
|
});
|
|
113
|
-
|
|
114
|
-
if (isOpen() && ev.target instanceof Node && !container.contains(ev.target)) closePanel();
|
|
115
|
-
});
|
|
116
|
-
document.addEventListener("keydown", (ev) => {
|
|
117
|
-
if (ev.key === "Escape" && isOpen()) {
|
|
118
|
-
closePanel();
|
|
119
|
-
trigger.focus();
|
|
120
|
-
}
|
|
121
|
-
});
|
|
353
|
+
if (isMultiple) updateMultiTrigger();
|
|
122
354
|
}
|
|
123
|
-
function initSelect(root
|
|
355
|
+
function initSelect(root) {
|
|
124
356
|
if (typeof document === "undefined") return;
|
|
357
|
+
const scope = root ?? document;
|
|
358
|
+
bindGlobalListeners();
|
|
125
359
|
const containers = [];
|
|
126
|
-
if (
|
|
127
|
-
containers.push(...Array.from(
|
|
360
|
+
if (scope instanceof Element && scope.matches("[data-select]")) containers.push(scope);
|
|
361
|
+
containers.push(...Array.from(scope.querySelectorAll("[data-select]")));
|
|
128
362
|
for (const c of containers) setup(c);
|
|
129
363
|
}
|
|
130
364
|
export {
|
|
@@ -27,11 +27,12 @@ function setup(sidebar) {
|
|
|
27
27
|
}));
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
|
-
function initSidebar(root
|
|
30
|
+
function initSidebar(root) {
|
|
31
31
|
if (typeof document === "undefined") return;
|
|
32
|
+
const scope = root ?? document;
|
|
32
33
|
const sidebars = [];
|
|
33
|
-
if (
|
|
34
|
-
sidebars.push(...Array.from(
|
|
34
|
+
if (scope instanceof Element && scope.matches(".sidebar")) sidebars.push(scope);
|
|
35
|
+
sidebars.push(...Array.from(scope.querySelectorAll(".sidebar")));
|
|
35
36
|
for (const s of sidebars) setup(s);
|
|
36
37
|
}
|
|
37
38
|
export {
|
|
@@ -92,11 +92,12 @@ function setup(slider) {
|
|
|
92
92
|
if (slider.classList.contains("slider--dual")) setupDual(slider, natives);
|
|
93
93
|
else setupSingle(slider, natives[0]);
|
|
94
94
|
}
|
|
95
|
-
function initSlider(root
|
|
95
|
+
function initSlider(root) {
|
|
96
96
|
if (typeof document === "undefined") return;
|
|
97
|
+
const scope = root ?? document;
|
|
97
98
|
const containers = [];
|
|
98
|
-
if (
|
|
99
|
-
containers.push(...Array.from(
|
|
99
|
+
if (scope instanceof Element && scope.matches(".slider")) containers.push(scope);
|
|
100
|
+
containers.push(...Array.from(scope.querySelectorAll(".slider")));
|
|
100
101
|
for (const c of containers) setup(c);
|
|
101
102
|
}
|
|
102
103
|
export {
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// src/behaviors/split-pane.ts
|
|
2
|
+
function numOr(value, fallback) {
|
|
3
|
+
if (value === void 0 || value === "") return fallback;
|
|
4
|
+
const n = Number(value);
|
|
5
|
+
return Number.isFinite(n) ? n : fallback;
|
|
6
|
+
}
|
|
7
|
+
function pct(value, min, max) {
|
|
8
|
+
return Math.min(max, Math.max(min, value));
|
|
9
|
+
}
|
|
10
|
+
function setup(container) {
|
|
11
|
+
const el = container;
|
|
12
|
+
if (el.__icenSplitPaneInit) return;
|
|
13
|
+
el.__icenSplitPaneInit = true;
|
|
14
|
+
const dividerQuery = el.querySelector(".split-pane-divider");
|
|
15
|
+
if (!dividerQuery) return;
|
|
16
|
+
const divider = dividerQuery;
|
|
17
|
+
const min = numOr(el.dataset.splitMin, 10);
|
|
18
|
+
const max = numOr(el.dataset.splitMax, 90);
|
|
19
|
+
const step = numOr(el.dataset.splitStep, 2);
|
|
20
|
+
const vertical = el.classList.contains("split-pane--vertical");
|
|
21
|
+
const orientation = vertical ? "vertical" : "horizontal";
|
|
22
|
+
divider.setAttribute("role", "separator");
|
|
23
|
+
divider.setAttribute("aria-orientation", orientation === "vertical" ? "horizontal" : "vertical");
|
|
24
|
+
divider.setAttribute("aria-valuemin", String(min));
|
|
25
|
+
divider.setAttribute("aria-valuemax", String(max));
|
|
26
|
+
divider.tabIndex = 0;
|
|
27
|
+
function readCurrent() {
|
|
28
|
+
const v = parseFloat(getComputedStyle(el).getPropertyValue("--split"));
|
|
29
|
+
return Number.isFinite(v) ? v : 50;
|
|
30
|
+
}
|
|
31
|
+
function write(p) {
|
|
32
|
+
const v = pct(p, min, max);
|
|
33
|
+
el.style.setProperty("--split", v + "%");
|
|
34
|
+
divider.setAttribute("aria-valuenow", String(Math.round(v)));
|
|
35
|
+
}
|
|
36
|
+
if (!el.style.getPropertyValue("--split")) write(50);
|
|
37
|
+
divider.setAttribute("aria-valuenow", String(Math.round(readCurrent())));
|
|
38
|
+
let dragging = false;
|
|
39
|
+
divider.addEventListener("pointerdown", (ev) => {
|
|
40
|
+
dragging = true;
|
|
41
|
+
divider.classList.add("is-dragging");
|
|
42
|
+
divider.setPointerCapture(ev.pointerId);
|
|
43
|
+
el.style.userSelect = "none";
|
|
44
|
+
});
|
|
45
|
+
const onUp = (ev) => {
|
|
46
|
+
if (!dragging) return;
|
|
47
|
+
dragging = false;
|
|
48
|
+
divider.classList.remove("is-dragging");
|
|
49
|
+
try {
|
|
50
|
+
divider.releasePointerCapture(ev.pointerId);
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
el.style.userSelect = "";
|
|
54
|
+
};
|
|
55
|
+
divider.addEventListener("pointerup", onUp);
|
|
56
|
+
divider.addEventListener("pointercancel", () => {
|
|
57
|
+
dragging = false;
|
|
58
|
+
divider.classList.remove("is-dragging");
|
|
59
|
+
el.style.userSelect = "";
|
|
60
|
+
});
|
|
61
|
+
divider.addEventListener("pointermove", (ev) => {
|
|
62
|
+
if (!dragging) return;
|
|
63
|
+
const rect = el.getBoundingClientRect();
|
|
64
|
+
if (vertical) {
|
|
65
|
+
if (rect.height <= 0) return;
|
|
66
|
+
const p = (ev.clientY - rect.top) / rect.height * 100;
|
|
67
|
+
write(p);
|
|
68
|
+
} else {
|
|
69
|
+
if (rect.width <= 0) return;
|
|
70
|
+
const p = (ev.clientX - rect.left) / rect.width * 100;
|
|
71
|
+
write(p);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
divider.addEventListener("dblclick", () => write(50));
|
|
75
|
+
divider.addEventListener("keydown", (ev) => {
|
|
76
|
+
const current = readCurrent();
|
|
77
|
+
let next = null;
|
|
78
|
+
if (vertical) {
|
|
79
|
+
if (ev.key === "ArrowUp") next = current - step;
|
|
80
|
+
else if (ev.key === "ArrowDown") next = current + step;
|
|
81
|
+
} else {
|
|
82
|
+
if (ev.key === "ArrowLeft") next = current - step;
|
|
83
|
+
else if (ev.key === "ArrowRight") next = current + step;
|
|
84
|
+
}
|
|
85
|
+
if (ev.key === "Home") next = min;
|
|
86
|
+
else if (ev.key === "End") next = max;
|
|
87
|
+
if (next === null) return;
|
|
88
|
+
ev.preventDefault();
|
|
89
|
+
write(next);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function initSplitPane(root) {
|
|
93
|
+
if (typeof document === "undefined") return;
|
|
94
|
+
const scope = root ?? document;
|
|
95
|
+
const panes = [];
|
|
96
|
+
if (scope instanceof Element && scope.matches(".split-pane[data-split-pane]")) panes.push(scope);
|
|
97
|
+
panes.push(...Array.from(scope.querySelectorAll(".split-pane[data-split-pane]")));
|
|
98
|
+
for (const p of panes) setup(p);
|
|
99
|
+
}
|
|
100
|
+
export {
|
|
101
|
+
initSplitPane
|
|
102
|
+
};
|