@icen.ai/ui 0.5.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 +262 -0
- package/README.md +52 -13
- package/dist/base.css +33 -5
- package/dist/behaviors/accordion.d.mts +7 -0
- package/dist/behaviors/accordion.mjs +37 -11
- package/dist/behaviors/ai-chat.d.mts +59 -0
- package/dist/behaviors/ai-chat.mjs +580 -0
- package/dist/behaviors/ai-composer.d.mts +137 -0
- package/dist/behaviors/ai-composer.mjs +2114 -0
- package/dist/behaviors/ai-core.d.mts +156 -0
- package/dist/behaviors/ai-core.mjs +504 -0
- package/dist/behaviors/ai-diff.d.mts +53 -0
- package/dist/behaviors/ai-diff.mjs +413 -0
- package/dist/behaviors/ai-panel.d.mts +101 -0
- package/dist/behaviors/ai-panel.mjs +1087 -0
- package/dist/behaviors/ai-provider.d.mts +198 -0
- package/dist/behaviors/ai-provider.mjs +1061 -0
- package/dist/behaviors/ai-tool.d.mts +26 -0
- package/dist/behaviors/ai-tool.mjs +2173 -0
- package/dist/behaviors/ai-tools.d.mts +84 -0
- package/dist/behaviors/ai-tools.mjs +1762 -0
- package/dist/behaviors/back-top.d.mts +20 -0
- package/dist/behaviors/back-top.mjs +64 -32
- package/dist/behaviors/carousel.d.mts +7 -0
- package/dist/behaviors/carousel.mjs +72 -27
- package/dist/behaviors/charts.d.mts +296 -0
- package/dist/behaviors/charts.mjs +1364 -440
- package/dist/behaviors/command-palette.d.mts +42 -0
- package/dist/behaviors/command-palette.mjs +167 -27
- package/dist/behaviors/context-menu.d.mts +53 -0
- package/dist/behaviors/context-menu.mjs +127 -39
- package/dist/behaviors/controls.d.mts +59 -0
- package/dist/behaviors/controls.mjs +601 -0
- package/dist/behaviors/copy.d.mts +7 -0
- package/dist/behaviors/copy.mjs +41 -15
- package/dist/behaviors/datatable.d.mts +90 -0
- package/dist/behaviors/datatable.mjs +279 -53
- package/dist/behaviors/date-picker.d.mts +4 -0
- package/dist/behaviors/date-picker.mjs +276 -69
- package/dist/behaviors/dropdown.d.mts +8 -0
- package/dist/behaviors/dropdown.mjs +171 -28
- package/dist/behaviors/events.d.mts +330 -0
- package/dist/behaviors/events.mjs +208 -0
- package/dist/behaviors/input.d.mts +4 -0
- package/dist/behaviors/input.mjs +52 -32
- package/dist/behaviors/modal.d.mts +30 -0
- package/dist/behaviors/modal.mjs +87 -16
- package/dist/behaviors/nav.d.mts +7 -0
- package/dist/behaviors/nav.mjs +57 -15
- package/dist/behaviors/notification.d.mts +188 -0
- package/dist/behaviors/notification.mjs +274 -140
- package/dist/behaviors/popover.d.mts +82 -0
- package/dist/behaviors/popover.mjs +92 -5
- package/dist/behaviors/select.d.mts +6 -0
- package/dist/behaviors/select.mjs +287 -45
- package/dist/behaviors/sidebar.d.mts +7 -0
- package/dist/behaviors/sidebar.mjs +44 -13
- package/dist/behaviors/slider.d.mts +7 -0
- package/dist/behaviors/slider.mjs +55 -43
- package/dist/behaviors/split-pane.d.mts +8 -0
- package/dist/behaviors/split-pane.mjs +70 -29
- package/dist/behaviors/tabs.d.mts +9 -0
- package/dist/behaviors/tabs.mjs +102 -26
- package/dist/behaviors/tag-input.d.mts +7 -0
- package/dist/behaviors/tag-input.mjs +66 -17
- package/dist/behaviors/theme.d.mts +36 -0
- package/dist/behaviors/theme.mjs +51 -5
- package/dist/behaviors/toast.d.mts +48 -0
- package/dist/behaviors/toast.mjs +98 -25
- package/dist/behaviors/tree.d.mts +7 -0
- package/dist/behaviors/tree.mjs +39 -15
- package/dist/behaviors/upload.d.mts +4 -0
- package/dist/behaviors/upload.mjs +79 -44
- package/dist/cli.mjs +1 -1
- package/dist/components/ai-chat.css +1037 -0
- package/dist/components/ai-chat.mjs +3 -0
- package/dist/components/ai-composer.mjs +3 -0
- package/dist/components/ai-context.mjs +2 -0
- package/dist/components/ai-diff.css +311 -0
- package/dist/components/ai-diff.mjs +3 -0
- package/dist/components/ai-files.mjs +3 -0
- package/dist/components/ai-message.mjs +3 -0
- package/dist/components/ai-panel.css +682 -0
- package/dist/components/ai-reasoning.mjs +3 -0
- package/dist/components/ai-subagent.mjs +2 -0
- package/dist/components/ai-todo.mjs +2 -0
- package/dist/components/ai-tool-call.mjs +2 -0
- package/dist/components/ai-tool.css +532 -0
- package/dist/components/ai-usage.mjs +3 -0
- package/dist/components/badge.css +5 -9
- package/dist/components/breadcrumb.css +0 -36
- package/dist/components/btn.css +4 -7
- package/dist/components/card.css +3 -6
- package/dist/components/chart-bar.css +10 -0
- package/dist/components/chart-calendar.mjs +4 -0
- package/dist/components/chart-gauge.css +1 -1
- package/dist/components/chart-heatmap.css +40 -0
- package/dist/components/chart-pie.css +0 -6
- package/dist/components/chart-scatter.css +24 -0
- package/dist/components/chart-scatter.mjs +4 -0
- package/dist/components/charts.css +87 -2
- package/dist/components/charts.mjs +1 -0
- package/dist/components/command-palette.css +2 -2
- package/dist/components/content.css +11 -25
- package/dist/components/copy.css +8 -8
- package/dist/components/datatable.css +5 -9
- package/dist/components/date-picker.css +4 -8
- package/dist/components/empty.css +0 -4
- package/dist/components/feedback.css +10 -17
- package/dist/components/form.css +13 -14
- package/dist/components/input.css +2 -2
- package/dist/components/layout.css +36 -36
- package/dist/components/media.css +12 -8
- package/dist/components/menu.css +2 -2
- package/dist/components/modal.css +1 -1
- package/dist/components/nav.css +11 -6
- package/dist/components/notification.css +27 -28
- package/dist/components/pagination.css +4 -2
- package/dist/components/pagination.mjs +1 -0
- package/dist/components/panel.css +16 -16
- package/dist/components/pill.css +15 -15
- package/dist/components/rating.mjs +1 -0
- package/dist/components/segmented.css +14 -6
- package/dist/components/segmented.mjs +1 -0
- package/dist/components/select.css +79 -24
- package/dist/components/sidebar.css +1 -1
- package/dist/components/split-pane.css +0 -1
- package/dist/components/stat.css +14 -18
- package/dist/components/steps.css +15 -5
- package/dist/components/steps.mjs +1 -0
- package/dist/components/switch.css +11 -13
- package/dist/components/switch.mjs +1 -0
- package/dist/components/table.css +46 -47
- package/dist/components/table.mjs +1 -0
- package/dist/components/tabs.css +57 -45
- package/dist/components/tag-input.css +5 -10
- package/dist/components/toast.css +16 -13
- package/dist/components/tooltip.css +2 -2
- package/dist/components/upload.css +0 -15
- package/dist/index.d.mts +39 -0
- package/dist/index.mjs +11243 -3312
- package/dist/registry.json +147 -9
- package/dist/retro-effects.css +23 -32
- package/dist/tokens/colors.css +4 -1
- package/dist/tokens/retro-effects.css +23 -32
- package/dist/tokens/style-profiles.css +24 -2
- package/dist/tokens/typography.css +2 -0
- package/dist/tokens.css +30 -3
- package/dist/ui.css +3420 -645
- package/docs/spec/ai-native.md +662 -0
- package/package.json +15 -5
|
@@ -1,12 +1,142 @@
|
|
|
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 applyPanelSizing(panel, sizing) {
|
|
21
|
+
if (sizing == null) {
|
|
22
|
+
panel.style.width = "";
|
|
23
|
+
panel.style.minWidth = "";
|
|
24
|
+
panel.style.maxWidth = "";
|
|
25
|
+
panel.style.minHeight = "";
|
|
26
|
+
panel.style.maxHeight = "";
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const vw = typeof document === "undefined" ? Number.POSITIVE_INFINITY : document.documentElement.clientWidth;
|
|
30
|
+
const fitW = (n) => Math.min(n, Math.max(80, vw - 24));
|
|
31
|
+
if (sizing.width != null) {
|
|
32
|
+
const w = fitW(sizing.width);
|
|
33
|
+
panel.style.width = `${w}px`;
|
|
34
|
+
panel.style.maxWidth = `${w}px`;
|
|
35
|
+
}
|
|
36
|
+
if (sizing.minWidth != null) panel.style.minWidth = `${fitW(sizing.minWidth)}px`;
|
|
37
|
+
if (sizing.maxWidth != null && sizing.width == null) panel.style.maxWidth = `${fitW(sizing.maxWidth)}px`;
|
|
38
|
+
if (sizing.minHeight != null) panel.style.minHeight = `${sizing.minHeight}px`;
|
|
39
|
+
if (sizing.maxHeight != null) panel.style.maxHeight = `${sizing.maxHeight}px`;
|
|
40
|
+
}
|
|
41
|
+
var DEFAULT_MIN_WIDTH = 220;
|
|
42
|
+
var DEFAULT_MAX_WIDTH = 420;
|
|
43
|
+
var DEFAULT_MIN_HEIGHT = 120;
|
|
44
|
+
var DEFAULT_MAX_HEIGHT = 420;
|
|
45
|
+
function clamp(value, min, max) {
|
|
46
|
+
if (max < min) return min;
|
|
47
|
+
return Math.min(max, Math.max(min, value));
|
|
48
|
+
}
|
|
49
|
+
function chooseSide(preferredSide, availableTop, availableBottom, desiredHeight) {
|
|
50
|
+
const preferredSpace = preferredSide === "top" ? availableTop : availableBottom;
|
|
51
|
+
const oppositeSpace = preferredSide === "top" ? availableBottom : availableTop;
|
|
52
|
+
if (preferredSpace >= desiredHeight) return preferredSide;
|
|
53
|
+
if (oppositeSpace >= desiredHeight) return preferredSide === "top" ? "bottom" : "top";
|
|
54
|
+
if (preferredSpace >= oppositeSpace) return preferredSide;
|
|
55
|
+
return preferredSide === "top" ? "bottom" : "top";
|
|
56
|
+
}
|
|
57
|
+
function horizontalAnchor(anchor, width, align) {
|
|
58
|
+
if (align === "center") return anchor.left + anchor.width / 2 - width / 2;
|
|
59
|
+
if (align === "end") return anchor.right - width;
|
|
60
|
+
return anchor.left;
|
|
61
|
+
}
|
|
62
|
+
function computePopoverLayout(anchor, viewport, opts = {}) {
|
|
63
|
+
const margin = opts.margin ?? 12;
|
|
64
|
+
const offset = opts.offset ?? 8;
|
|
65
|
+
const preferredSide = opts.side ?? "top";
|
|
66
|
+
const align = opts.align ?? "start";
|
|
67
|
+
if (preferredSide === "left" || preferredSide === "right") {
|
|
68
|
+
const availableLeft = Math.max(0, anchor.left - margin - offset);
|
|
69
|
+
const availableRight = Math.max(0, viewport.width - anchor.right - margin - offset);
|
|
70
|
+
const side2 = (preferredSide === "left" ? availableLeft : availableRight) >= DEFAULT_MIN_WIDTH ? preferredSide : preferredSide === "left" ? "right" : "left";
|
|
71
|
+
const minWidth2 = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
|
|
72
|
+
const maxWidth2 = Math.min(
|
|
73
|
+
Math.max(opts.maxWidth ?? DEFAULT_MAX_WIDTH, minWidth2),
|
|
74
|
+
side2 === "left" ? availableLeft : availableRight
|
|
75
|
+
);
|
|
76
|
+
const width2 = minWidth2;
|
|
77
|
+
const left2 = clamp(
|
|
78
|
+
side2 === "left" ? anchor.left - offset - width2 : anchor.right + offset,
|
|
79
|
+
margin,
|
|
80
|
+
viewport.width - margin - width2
|
|
81
|
+
);
|
|
82
|
+
const availableHeight2 = Math.max(80, viewport.height - margin * 2);
|
|
83
|
+
const maxHeight2 = Math.min(opts.maxHeight ?? DEFAULT_MAX_HEIGHT, availableHeight2);
|
|
84
|
+
const desired = clamp(opts.contentHeightHint ?? Math.min(anchor.height, maxHeight2), 40, maxHeight2);
|
|
85
|
+
const anchorTop = align === "center" ? anchor.top + anchor.height / 2 - desired / 2 : align === "end" ? anchor.bottom - desired : anchor.top;
|
|
86
|
+
const top2 = clamp(anchorTop, margin, viewport.height - margin - desired);
|
|
87
|
+
return { side: side2, left: left2, top: top2, width: width2, maxHeight: maxHeight2 };
|
|
88
|
+
}
|
|
89
|
+
const availableWidth = Math.max(80, viewport.width - margin * 2);
|
|
90
|
+
const requestedMinWidth = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
|
|
91
|
+
const requestedMaxWidth = Math.max(requestedMinWidth, opts.maxWidth ?? DEFAULT_MAX_WIDTH);
|
|
92
|
+
const maxWidth = Math.min(requestedMaxWidth, availableWidth);
|
|
93
|
+
const minWidth = Math.min(requestedMinWidth, maxWidth);
|
|
94
|
+
const availableTop = Math.max(0, anchor.top - margin - offset);
|
|
95
|
+
const availableBottom = Math.max(0, viewport.height - anchor.bottom - margin - offset);
|
|
96
|
+
const availableHeight = Math.max(80, viewport.height - margin * 2);
|
|
97
|
+
const minHeight = Math.min(Math.max(80, opts.minHeight ?? DEFAULT_MIN_HEIGHT), availableHeight);
|
|
98
|
+
const requestedMaxHeight = Math.max(minHeight, opts.maxHeight ?? DEFAULT_MAX_HEIGHT);
|
|
99
|
+
const fallbackHeight = Math.min(260, requestedMaxHeight);
|
|
100
|
+
const contentHeight = Math.max(opts.contentHeightHint ?? 0, fallbackHeight);
|
|
101
|
+
const desiredHeight = clamp(contentHeight, minHeight, Math.min(requestedMaxHeight, availableHeight));
|
|
102
|
+
const side = chooseSide(preferredSide, availableTop, availableBottom, desiredHeight);
|
|
103
|
+
const sideSpace = side === "top" ? availableTop : availableBottom;
|
|
104
|
+
const maxHeight = Math.min(
|
|
105
|
+
requestedMaxHeight,
|
|
106
|
+
Math.max(80, sideSpace || Math.max(availableTop, availableBottom)),
|
|
107
|
+
availableHeight
|
|
108
|
+
);
|
|
109
|
+
const positionedHeight = clamp(desiredHeight, 40, maxHeight);
|
|
110
|
+
const width = align === "stretch" ? clamp(anchor.width, minWidth, maxWidth) : maxWidth;
|
|
111
|
+
const left = clamp(horizontalAnchor(anchor, width, align), margin, viewport.width - margin - width);
|
|
112
|
+
const desiredTop = side === "top" ? anchor.top - offset - positionedHeight : anchor.bottom + offset;
|
|
113
|
+
const top = clamp(desiredTop, margin, viewport.height - margin - positionedHeight);
|
|
114
|
+
return { side, left, top, width, maxHeight };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/behaviors/events.ts
|
|
118
|
+
function emitIcen(source, type, detail) {
|
|
119
|
+
return source.dispatchEvent(
|
|
120
|
+
new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/behaviors/context-menu.ts
|
|
125
|
+
function emitMenuSelect(source, item) {
|
|
126
|
+
emitIcen(item, "icen:menu-select", {
|
|
127
|
+
source,
|
|
128
|
+
item,
|
|
129
|
+
value: item.dataset.value ?? "",
|
|
130
|
+
/* 优先专用 label 子元素(避开快捷键/图标文本混入),无则退整项文本 */
|
|
131
|
+
label: (item.querySelector(".menu-item-label, .command-item-label")?.textContent ?? item.textContent ?? "").trim().slice(0, 80)
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
1
135
|
// src/behaviors/dropdown.ts
|
|
2
136
|
var VIEWPORT_MARGIN = 12;
|
|
3
137
|
var SIDE_OFFSET = 4;
|
|
4
138
|
var active = null;
|
|
5
139
|
var initialized = false;
|
|
6
|
-
function clamp(value, min, max) {
|
|
7
|
-
if (max < min) return min;
|
|
8
|
-
return Math.min(max, Math.max(min, value));
|
|
9
|
-
}
|
|
10
140
|
function menuItems(panel) {
|
|
11
141
|
return Array.from(panel.querySelectorAll(".menu-item:not(.is-disabled):not(:disabled)"));
|
|
12
142
|
}
|
|
@@ -19,21 +149,14 @@ function place() {
|
|
|
19
149
|
if (!active) return;
|
|
20
150
|
const { trigger, panel } = active;
|
|
21
151
|
const rect = trigger.getBoundingClientRect();
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
top = rect.top - SIDE_OFFSET - ph;
|
|
31
|
-
side = "top";
|
|
32
|
-
}
|
|
33
|
-
top = clamp(top, VIEWPORT_MARGIN, vh - VIEWPORT_MARGIN - ph);
|
|
34
|
-
panel.style.left = `${left}px`;
|
|
35
|
-
panel.style.top = `${top}px`;
|
|
36
|
-
panel.dataset.side = side;
|
|
152
|
+
const layout = computePopoverLayout(
|
|
153
|
+
rect,
|
|
154
|
+
{ width: window.innerWidth, height: window.innerHeight },
|
|
155
|
+
{ side: "bottom", offset: SIDE_OFFSET, margin: VIEWPORT_MARGIN }
|
|
156
|
+
);
|
|
157
|
+
panel.style.left = `${layout.left}px`;
|
|
158
|
+
panel.style.top = `${layout.top}px`;
|
|
159
|
+
panel.dataset.side = layout.side;
|
|
37
160
|
}
|
|
38
161
|
function openDropdown(trigger, highlightFirst = false) {
|
|
39
162
|
closeDropdown();
|
|
@@ -44,6 +167,7 @@ function openDropdown(trigger, highlightFirst = false) {
|
|
|
44
167
|
panel.className = "menu";
|
|
45
168
|
panel.setAttribute("role", "menu");
|
|
46
169
|
panel.style.visibility = "hidden";
|
|
170
|
+
applyPanelSizing(panel, readPanelSizing(root));
|
|
47
171
|
while (template.firstChild) panel.appendChild(template.firstChild);
|
|
48
172
|
panel.querySelectorAll(".menu-item").forEach((el) => el.setAttribute("role", "menuitem"));
|
|
49
173
|
document.body.appendChild(panel);
|
|
@@ -75,7 +199,10 @@ function onOutsidePointerDown(e) {
|
|
|
75
199
|
if (active.panel.contains(target) || active.trigger.contains(target)) return;
|
|
76
200
|
closeDropdown();
|
|
77
201
|
}
|
|
78
|
-
function onScroll() {
|
|
202
|
+
function onScroll(e) {
|
|
203
|
+
if (!active) return;
|
|
204
|
+
const target = e.target;
|
|
205
|
+
if (target instanceof Node && active.panel.contains(target)) return;
|
|
79
206
|
closeDropdown();
|
|
80
207
|
}
|
|
81
208
|
function onKeyDown(e) {
|
|
@@ -97,21 +224,28 @@ function onKeyDown(e) {
|
|
|
97
224
|
if (e.key === "Enter" && active.highlight >= 0) {
|
|
98
225
|
e.preventDefault();
|
|
99
226
|
const item = items[active.highlight];
|
|
227
|
+
emitMenuSelect(active.panel, item);
|
|
100
228
|
closeDropdown(true);
|
|
101
229
|
item.click();
|
|
102
230
|
}
|
|
103
231
|
}
|
|
104
|
-
function initDropdown(root
|
|
105
|
-
if (typeof document === "undefined") return
|
|
106
|
-
|
|
107
|
-
|
|
232
|
+
function initDropdown(root) {
|
|
233
|
+
if (typeof document === "undefined") return () => {
|
|
234
|
+
};
|
|
235
|
+
const scope = root ?? document;
|
|
236
|
+
scope.querySelectorAll("[data-dropdown-trigger]").forEach((el) => el.setAttribute("aria-expanded", "false"));
|
|
237
|
+
if (initialized) return () => {
|
|
238
|
+
};
|
|
108
239
|
initialized = true;
|
|
109
|
-
|
|
240
|
+
const onClick = (e) => {
|
|
110
241
|
const target = e.target instanceof Element ? e.target : null;
|
|
111
242
|
if (!target) return;
|
|
112
243
|
if (active && target.closest(".menu-item") && active.panel.contains(target)) {
|
|
113
244
|
const item = target.closest(".menu-item");
|
|
114
245
|
if (item) {
|
|
246
|
+
if (!item.matches(".is-disabled") && !item.matches(":disabled")) {
|
|
247
|
+
emitMenuSelect(active.panel, item);
|
|
248
|
+
}
|
|
115
249
|
closeDropdown();
|
|
116
250
|
return;
|
|
117
251
|
}
|
|
@@ -120,8 +254,9 @@ function initDropdown(root = document) {
|
|
|
120
254
|
if (!trigger) return;
|
|
121
255
|
if (active && active.trigger === trigger) closeDropdown();
|
|
122
256
|
else openDropdown(trigger);
|
|
123
|
-
}
|
|
124
|
-
document.addEventListener("
|
|
257
|
+
};
|
|
258
|
+
document.addEventListener("click", onClick);
|
|
259
|
+
const onKeyDown2 = (e) => {
|
|
125
260
|
if (e.key !== "ArrowDown") return;
|
|
126
261
|
if (active) return;
|
|
127
262
|
const target = e.target instanceof Element ? e.target : null;
|
|
@@ -129,7 +264,15 @@ function initDropdown(root = document) {
|
|
|
129
264
|
if (!trigger) return;
|
|
130
265
|
e.preventDefault();
|
|
131
266
|
openDropdown(trigger, true);
|
|
132
|
-
}
|
|
267
|
+
};
|
|
268
|
+
document.addEventListener("keydown", onKeyDown2);
|
|
269
|
+
const destroy = () => {
|
|
270
|
+
document.removeEventListener("click", onClick);
|
|
271
|
+
document.removeEventListener("keydown", onKeyDown2);
|
|
272
|
+
closeDropdown();
|
|
273
|
+
initialized = false;
|
|
274
|
+
};
|
|
275
|
+
return destroy;
|
|
133
276
|
}
|
|
134
277
|
export {
|
|
135
278
|
initDropdown
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { ChartEventDetail } from './charts.js';
|
|
2
|
+
import { AiUsage } from './ai-core.js';
|
|
3
|
+
import { AiDoneEventDetail } from './ai-provider.js';
|
|
4
|
+
import { AiComposerRefSource } from './ai-composer.js';
|
|
5
|
+
import './popover.js';
|
|
6
|
+
import './ai-panel.js';
|
|
7
|
+
|
|
8
|
+
interface IcenGestureDetail {
|
|
9
|
+
/** 手势命中源:最近的 [data-gestures] 宿主 */
|
|
10
|
+
source: HTMLElement;
|
|
11
|
+
/** 原始 DOM 事件(click / contextmenu / pointerdown / mouseup…) */
|
|
12
|
+
originalEvent: Event;
|
|
13
|
+
/** 视口坐标(指针手势) */
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
/** 划词文本(仅 icen:text-select) */
|
|
17
|
+
text?: string;
|
|
18
|
+
}
|
|
19
|
+
type IcenPolicy = (e: CustomEvent<IcenGestureDetail>) => void;
|
|
20
|
+
/** 手势层事件名(策略层只认这五种,编译期收死)。 */
|
|
21
|
+
type IcenGestureEvent = 'icen:click' | 'icen:dblclick' | 'icen:contextmenu' | 'icen:longpress' | 'icen:text-select';
|
|
22
|
+
/**
|
|
23
|
+
* 全库事件注册表:事件名 → detail 契约。onIcen 推断、emitIcen 校验,双侧同表。
|
|
24
|
+
* 新增事件必须在此登记(模板签名兜底只留给宿主自定义事件)。
|
|
25
|
+
*/
|
|
26
|
+
interface IcenEventMap {
|
|
27
|
+
'icen:click': IcenGestureDetail;
|
|
28
|
+
'icen:dblclick': IcenGestureDetail;
|
|
29
|
+
'icen:contextmenu': IcenGestureDetail;
|
|
30
|
+
'icen:longpress': IcenGestureDetail;
|
|
31
|
+
'icen:text-select': IcenGestureDetail;
|
|
32
|
+
'icen:chart-hover': ChartEventDetail;
|
|
33
|
+
'icen:chart-click': ChartEventDetail;
|
|
34
|
+
'icen:chart-dblclick': ChartEventDetail;
|
|
35
|
+
'icen:chart-contextmenu': ChartEventDetail;
|
|
36
|
+
'icen:chart-legend-toggle': {
|
|
37
|
+
key: string;
|
|
38
|
+
seriesIndex: number;
|
|
39
|
+
hidden: boolean;
|
|
40
|
+
};
|
|
41
|
+
'icen:chart-legend-visibility': {
|
|
42
|
+
visible: boolean;
|
|
43
|
+
};
|
|
44
|
+
'icen:tab-change': {
|
|
45
|
+
tab: HTMLElement | null;
|
|
46
|
+
panel: HTMLElement | null;
|
|
47
|
+
index: number;
|
|
48
|
+
};
|
|
49
|
+
'icen:tags-change': {
|
|
50
|
+
tags: string[];
|
|
51
|
+
};
|
|
52
|
+
'icen:otp-complete': {
|
|
53
|
+
code: string;
|
|
54
|
+
};
|
|
55
|
+
'icen:slider-input': {
|
|
56
|
+
value?: number;
|
|
57
|
+
lo?: number;
|
|
58
|
+
hi?: number;
|
|
59
|
+
};
|
|
60
|
+
'icen:slider-change': {
|
|
61
|
+
value?: number;
|
|
62
|
+
lo?: number;
|
|
63
|
+
hi?: number;
|
|
64
|
+
};
|
|
65
|
+
'icen:switch-change': {
|
|
66
|
+
el: HTMLElement;
|
|
67
|
+
checked: boolean;
|
|
68
|
+
};
|
|
69
|
+
'icen:check-change': {
|
|
70
|
+
el: HTMLElement;
|
|
71
|
+
checked: boolean | 'mixed';
|
|
72
|
+
};
|
|
73
|
+
'icen:stepper-change': {
|
|
74
|
+
el: HTMLElement;
|
|
75
|
+
value: number;
|
|
76
|
+
delta: number;
|
|
77
|
+
};
|
|
78
|
+
'icen:segmented-change': {
|
|
79
|
+
el: HTMLElement;
|
|
80
|
+
value: string;
|
|
81
|
+
index: number;
|
|
82
|
+
};
|
|
83
|
+
'icen:step-change': {
|
|
84
|
+
step: HTMLElement;
|
|
85
|
+
index: number;
|
|
86
|
+
};
|
|
87
|
+
'icen:rating-change': {
|
|
88
|
+
el: HTMLElement;
|
|
89
|
+
value: number;
|
|
90
|
+
};
|
|
91
|
+
'icen:page-change': {
|
|
92
|
+
page: number;
|
|
93
|
+
};
|
|
94
|
+
'icen:sort-change': {
|
|
95
|
+
th: HTMLElement;
|
|
96
|
+
key: string;
|
|
97
|
+
dir: 'asc' | 'desc' | null;
|
|
98
|
+
};
|
|
99
|
+
'icen:nav-select': {
|
|
100
|
+
item: HTMLElement;
|
|
101
|
+
index: number;
|
|
102
|
+
};
|
|
103
|
+
'icen:sidebar-toggle': {
|
|
104
|
+
group: HTMLElement;
|
|
105
|
+
open: boolean;
|
|
106
|
+
};
|
|
107
|
+
'icen:sidebar-collapse': {
|
|
108
|
+
collapsed: boolean;
|
|
109
|
+
};
|
|
110
|
+
'icen:split-change': {
|
|
111
|
+
value: number;
|
|
112
|
+
};
|
|
113
|
+
'icen:copy-success': {
|
|
114
|
+
text: string;
|
|
115
|
+
};
|
|
116
|
+
'icen:copy-error': {
|
|
117
|
+
text: string;
|
|
118
|
+
reason: string;
|
|
119
|
+
};
|
|
120
|
+
'icen:back-top': Record<string, never>;
|
|
121
|
+
'icen:modal-open': {
|
|
122
|
+
id: string;
|
|
123
|
+
};
|
|
124
|
+
'icen:modal-close': {
|
|
125
|
+
id: string;
|
|
126
|
+
};
|
|
127
|
+
'icen:command-palette-open': Record<string, never>;
|
|
128
|
+
'icen:command-palette-close': Record<string, never>;
|
|
129
|
+
'icen:menu-select': {
|
|
130
|
+
source: HTMLElement;
|
|
131
|
+
item: HTMLElement;
|
|
132
|
+
value: string;
|
|
133
|
+
label: string;
|
|
134
|
+
};
|
|
135
|
+
'icen:select-change': {
|
|
136
|
+
value: string;
|
|
137
|
+
values: string[];
|
|
138
|
+
label: string;
|
|
139
|
+
};
|
|
140
|
+
'icen:date-change': {
|
|
141
|
+
value: string;
|
|
142
|
+
date: Date | null;
|
|
143
|
+
};
|
|
144
|
+
'icen:accordion-toggle': {
|
|
145
|
+
item: HTMLElement;
|
|
146
|
+
index: number;
|
|
147
|
+
open: boolean;
|
|
148
|
+
};
|
|
149
|
+
'icen:tree-select': {
|
|
150
|
+
node: HTMLElement;
|
|
151
|
+
};
|
|
152
|
+
'icen:tree-toggle': {
|
|
153
|
+
node: HTMLElement;
|
|
154
|
+
open: boolean;
|
|
155
|
+
};
|
|
156
|
+
'icen:carousel-change': {
|
|
157
|
+
index: number;
|
|
158
|
+
count: number;
|
|
159
|
+
};
|
|
160
|
+
'icen:theme-change': {
|
|
161
|
+
preset: string;
|
|
162
|
+
dark: boolean;
|
|
163
|
+
style: string;
|
|
164
|
+
};
|
|
165
|
+
'icen:table-sort-change': {
|
|
166
|
+
key: string;
|
|
167
|
+
dir: 'asc' | 'desc';
|
|
168
|
+
};
|
|
169
|
+
'icen:table-page-change': {
|
|
170
|
+
page: number;
|
|
171
|
+
pageSize: number;
|
|
172
|
+
};
|
|
173
|
+
'icen:table-page-size-change': {
|
|
174
|
+
pageSize: number;
|
|
175
|
+
};
|
|
176
|
+
'icen:table-filter-change': {
|
|
177
|
+
key: string;
|
|
178
|
+
values: string[];
|
|
179
|
+
};
|
|
180
|
+
'icen:table-search-change': {
|
|
181
|
+
query: string;
|
|
182
|
+
};
|
|
183
|
+
'icen:table-selection-change': {
|
|
184
|
+
selected: unknown[];
|
|
185
|
+
};
|
|
186
|
+
'icen:table-row-click': {
|
|
187
|
+
row: unknown;
|
|
188
|
+
index: number;
|
|
189
|
+
};
|
|
190
|
+
'icen:table-row-toggle': {
|
|
191
|
+
row: unknown;
|
|
192
|
+
index: number;
|
|
193
|
+
expanded: boolean;
|
|
194
|
+
};
|
|
195
|
+
'icen:table-columns-change': {
|
|
196
|
+
hidden: string[];
|
|
197
|
+
};
|
|
198
|
+
'icen:table-export': {
|
|
199
|
+
count: number;
|
|
200
|
+
};
|
|
201
|
+
'icen:upload': {
|
|
202
|
+
files: File[];
|
|
203
|
+
};
|
|
204
|
+
'icen:upload-error': {
|
|
205
|
+
file: File | null;
|
|
206
|
+
reason: string;
|
|
207
|
+
};
|
|
208
|
+
'icen:upload-remove': {
|
|
209
|
+
file: File;
|
|
210
|
+
};
|
|
211
|
+
'icen:ai-toggle': {
|
|
212
|
+
el: HTMLElement;
|
|
213
|
+
open: boolean;
|
|
214
|
+
};
|
|
215
|
+
'icen:ai-copy': {
|
|
216
|
+
el: HTMLElement;
|
|
217
|
+
};
|
|
218
|
+
'icen:ai-retry': {
|
|
219
|
+
el: HTMLElement;
|
|
220
|
+
};
|
|
221
|
+
'icen:ai-approve': {
|
|
222
|
+
id: string;
|
|
223
|
+
kind: string;
|
|
224
|
+
};
|
|
225
|
+
'icen:ai-reject': {
|
|
226
|
+
id: string;
|
|
227
|
+
kind: string;
|
|
228
|
+
};
|
|
229
|
+
'icen:ai-diff-accept': {
|
|
230
|
+
path: string;
|
|
231
|
+
};
|
|
232
|
+
'icen:ai-diff-reject': {
|
|
233
|
+
path: string;
|
|
234
|
+
};
|
|
235
|
+
'icen:ai-send': {
|
|
236
|
+
text: string;
|
|
237
|
+
};
|
|
238
|
+
'icen:ai-stop': Record<string, never>;
|
|
239
|
+
'icen:ai-queue': {
|
|
240
|
+
text: string;
|
|
241
|
+
};
|
|
242
|
+
'icen:ai-dequeue': {
|
|
243
|
+
index: number;
|
|
244
|
+
};
|
|
245
|
+
'icen:ai-attach': {
|
|
246
|
+
files: File[];
|
|
247
|
+
};
|
|
248
|
+
'icen:ai-model-change': {
|
|
249
|
+
provider: string;
|
|
250
|
+
model: string;
|
|
251
|
+
label: string;
|
|
252
|
+
context?: number;
|
|
253
|
+
};
|
|
254
|
+
'icen:ai-command': {
|
|
255
|
+
name: string;
|
|
256
|
+
args: string;
|
|
257
|
+
};
|
|
258
|
+
'icen:ai-ref': {
|
|
259
|
+
action: 'add' | 'remove';
|
|
260
|
+
ref: AiComposerRefSource;
|
|
261
|
+
};
|
|
262
|
+
'icen:ai-todo-toggle': {
|
|
263
|
+
index: number;
|
|
264
|
+
status: string;
|
|
265
|
+
};
|
|
266
|
+
'icen:ai-audit-clear': Record<string, never>;
|
|
267
|
+
'icen:ai-context-open': Record<string, never>;
|
|
268
|
+
'icen:ai-context-close': Record<string, never>;
|
|
269
|
+
'icen:ai-tool-call': {
|
|
270
|
+
name: string;
|
|
271
|
+
input: unknown;
|
|
272
|
+
el: HTMLElement;
|
|
273
|
+
};
|
|
274
|
+
'icen:ai-tool-result': {
|
|
275
|
+
name: string;
|
|
276
|
+
ok: boolean;
|
|
277
|
+
el: HTMLElement;
|
|
278
|
+
error?: string;
|
|
279
|
+
count: number;
|
|
280
|
+
};
|
|
281
|
+
'icen:ai-tool-evict': {
|
|
282
|
+
name: string;
|
|
283
|
+
el: HTMLElement;
|
|
284
|
+
};
|
|
285
|
+
'icen:ai-usage': AiUsage;
|
|
286
|
+
'icen:ai-done': AiDoneEventDetail;
|
|
287
|
+
[key: `icen:${string}`]: unknown;
|
|
288
|
+
}
|
|
289
|
+
interface IcenListenOptions {
|
|
290
|
+
/** 只监听该子树内冒泡上来的事件(组件实例级绑定)。
|
|
291
|
+
* 广播事件(自 document 派发)不经过元素子树:within 为 Element 时收不到,要收广播请用全局形态 */
|
|
292
|
+
within?: Element | Document;
|
|
293
|
+
/** AbortSignal:abort 即解绑(React useEffect 清理 / 批量卸载场景) */
|
|
294
|
+
signal?: AbortSignal;
|
|
295
|
+
/** 只触发一次(触发后自动解绑) */
|
|
296
|
+
once?: boolean;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* 统一绑定器(全库事件一个入口):
|
|
300
|
+
* onIcen('icen:tab-change', (e) => e.detail.index) 全局
|
|
301
|
+
* onIcen('icen:chart-click', '.chart', (e) => …) 选择器委托
|
|
302
|
+
* onIcen('icen:upload', (e) => …, { within: formEl }) 子树限定
|
|
303
|
+
* onIcen('icen:ai-done', (e) => …, { signal: ctrl.signal }) 广播 + 信号解绑
|
|
304
|
+
* 返回解绑函数 —— React useEffect / Vue onUnmounted 直接 return。
|
|
305
|
+
*/
|
|
306
|
+
declare function onIcen<K extends keyof IcenEventMap>(type: K, handler: (e: CustomEvent<IcenEventMap[K]>) => void, opts?: IcenListenOptions): () => void;
|
|
307
|
+
declare function onIcen<K extends keyof IcenEventMap>(type: K, selector: string, handler: (e: CustomEvent<IcenEventMap[K]>) => void, opts?: IcenListenOptions): () => void;
|
|
308
|
+
/**
|
|
309
|
+
* 全库唯一派生口:bubbles + composed + cancelable。返回 false = 被消费方 preventDefault。
|
|
310
|
+
* 组件作者派发领域事件一律走这里(不要裸写 new CustomEvent);detail 契约见 IcenEventMap。
|
|
311
|
+
* 从 document 派发 = 全局广播(传播只经 [document, window],仅 onIcen 全局形态可收;
|
|
312
|
+
* within 为 Element 收不到,要收广播请用全局形态)。
|
|
313
|
+
*/
|
|
314
|
+
declare function emitIcen<K extends keyof IcenEventMap>(source: Element | Document, type: K, detail: IcenEventMap[K]): boolean;
|
|
315
|
+
/** 注册/覆盖某手势事件的全站默认行为;传 null 移除(= 禁用该默认)。
|
|
316
|
+
* 只接受手势层五种事件——领域事件的默认行为归组件交互契约,不会被静默忽略。 */
|
|
317
|
+
declare function setEventPolicy(type: IcenGestureEvent, policy: IcenPolicy | null): void;
|
|
318
|
+
/** 读取某手势事件当前注册的策略(与 setEventPolicy 对称的调试/测试读取器;未注册返回 undefined)。 */
|
|
319
|
+
declare function getEventPolicy(type: IcenGestureEvent): IcenPolicy | undefined;
|
|
320
|
+
/** 清空全部策略(测试/热重载用)。 */
|
|
321
|
+
declare function resetEventPolicies(): void;
|
|
322
|
+
/**
|
|
323
|
+
* 启动手势层(全局委托一次,幂等;重复调用安全)。
|
|
324
|
+
* 返回销毁函数:摘除全部 document 级委托并复位幂等标记,销毁后可重新 init。
|
|
325
|
+
*/
|
|
326
|
+
declare function initGestures(): () => void;
|
|
327
|
+
/** 摘除手势层委托(等同 initGestures 返回的销毁函数;未启动时为 no-op)。 */
|
|
328
|
+
declare function destroyGestures(): void;
|
|
329
|
+
|
|
330
|
+
export { type IcenEventMap, type IcenGestureDetail, type IcenGestureEvent, type IcenListenOptions, type IcenPolicy, destroyGestures, emitIcen, getEventPolicy, initGestures, onIcen, resetEventPolicies, setEventPolicy };
|