@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,10 +1,151 @@
|
|
|
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 applyPanelSizing(panel2, sizing) {
|
|
30
|
+
if (sizing == null) {
|
|
31
|
+
panel2.style.width = "";
|
|
32
|
+
panel2.style.minWidth = "";
|
|
33
|
+
panel2.style.maxWidth = "";
|
|
34
|
+
panel2.style.minHeight = "";
|
|
35
|
+
panel2.style.maxHeight = "";
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const vw = typeof document === "undefined" ? Number.POSITIVE_INFINITY : document.documentElement.clientWidth;
|
|
39
|
+
const fitW = (n) => Math.min(n, Math.max(80, vw - 24));
|
|
40
|
+
if (sizing.width != null) {
|
|
41
|
+
const w = fitW(sizing.width);
|
|
42
|
+
panel2.style.width = `${w}px`;
|
|
43
|
+
panel2.style.maxWidth = `${w}px`;
|
|
44
|
+
}
|
|
45
|
+
if (sizing.minWidth != null) panel2.style.minWidth = `${fitW(sizing.minWidth)}px`;
|
|
46
|
+
if (sizing.maxWidth != null && sizing.width == null) panel2.style.maxWidth = `${fitW(sizing.maxWidth)}px`;
|
|
47
|
+
if (sizing.minHeight != null) panel2.style.minHeight = `${sizing.minHeight}px`;
|
|
48
|
+
if (sizing.maxHeight != null) panel2.style.maxHeight = `${sizing.maxHeight}px`;
|
|
49
|
+
}
|
|
50
|
+
var DEFAULT_MIN_WIDTH = 220;
|
|
51
|
+
var DEFAULT_MAX_WIDTH = 420;
|
|
52
|
+
var DEFAULT_MIN_HEIGHT = 120;
|
|
53
|
+
var DEFAULT_MAX_HEIGHT = 420;
|
|
54
|
+
function clamp(value, min, max) {
|
|
55
|
+
if (max < min) return min;
|
|
56
|
+
return Math.min(max, Math.max(min, value));
|
|
57
|
+
}
|
|
58
|
+
function chooseSide(preferredSide, availableTop, availableBottom, desiredHeight) {
|
|
59
|
+
const preferredSpace = preferredSide === "top" ? availableTop : availableBottom;
|
|
60
|
+
const oppositeSpace = preferredSide === "top" ? availableBottom : availableTop;
|
|
61
|
+
if (preferredSpace >= desiredHeight) return preferredSide;
|
|
62
|
+
if (oppositeSpace >= desiredHeight) return preferredSide === "top" ? "bottom" : "top";
|
|
63
|
+
if (preferredSpace >= oppositeSpace) return preferredSide;
|
|
64
|
+
return preferredSide === "top" ? "bottom" : "top";
|
|
65
|
+
}
|
|
66
|
+
function horizontalAnchor(anchor, width, align) {
|
|
67
|
+
if (align === "center") return anchor.left + anchor.width / 2 - width / 2;
|
|
68
|
+
if (align === "end") return anchor.right - width;
|
|
69
|
+
return anchor.left;
|
|
70
|
+
}
|
|
71
|
+
function computePopoverLayout(anchor, viewport, opts = {}) {
|
|
72
|
+
const margin = opts.margin ?? 12;
|
|
73
|
+
const offset = opts.offset ?? 8;
|
|
74
|
+
const preferredSide = opts.side ?? "top";
|
|
75
|
+
const align = opts.align ?? "start";
|
|
76
|
+
if (preferredSide === "left" || preferredSide === "right") {
|
|
77
|
+
const availableLeft = Math.max(0, anchor.left - margin - offset);
|
|
78
|
+
const availableRight = Math.max(0, viewport.width - anchor.right - margin - offset);
|
|
79
|
+
const side2 = (preferredSide === "left" ? availableLeft : availableRight) >= DEFAULT_MIN_WIDTH ? preferredSide : preferredSide === "left" ? "right" : "left";
|
|
80
|
+
const minWidth2 = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
|
|
81
|
+
const maxWidth2 = Math.min(
|
|
82
|
+
Math.max(opts.maxWidth ?? DEFAULT_MAX_WIDTH, minWidth2),
|
|
83
|
+
side2 === "left" ? availableLeft : availableRight
|
|
84
|
+
);
|
|
85
|
+
const width2 = minWidth2;
|
|
86
|
+
const left2 = clamp(
|
|
87
|
+
side2 === "left" ? anchor.left - offset - width2 : anchor.right + offset,
|
|
88
|
+
margin,
|
|
89
|
+
viewport.width - margin - width2
|
|
90
|
+
);
|
|
91
|
+
const availableHeight2 = Math.max(80, viewport.height - margin * 2);
|
|
92
|
+
const maxHeight2 = Math.min(opts.maxHeight ?? DEFAULT_MAX_HEIGHT, availableHeight2);
|
|
93
|
+
const desired = clamp(opts.contentHeightHint ?? Math.min(anchor.height, maxHeight2), 40, maxHeight2);
|
|
94
|
+
const anchorTop = align === "center" ? anchor.top + anchor.height / 2 - desired / 2 : align === "end" ? anchor.bottom - desired : anchor.top;
|
|
95
|
+
const top2 = clamp(anchorTop, margin, viewport.height - margin - desired);
|
|
96
|
+
return { side: side2, left: left2, top: top2, width: width2, maxHeight: maxHeight2 };
|
|
97
|
+
}
|
|
98
|
+
const availableWidth = Math.max(80, viewport.width - margin * 2);
|
|
99
|
+
const requestedMinWidth = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
|
|
100
|
+
const requestedMaxWidth = Math.max(requestedMinWidth, opts.maxWidth ?? DEFAULT_MAX_WIDTH);
|
|
101
|
+
const maxWidth = Math.min(requestedMaxWidth, availableWidth);
|
|
102
|
+
const minWidth = Math.min(requestedMinWidth, maxWidth);
|
|
103
|
+
const availableTop = Math.max(0, anchor.top - margin - offset);
|
|
104
|
+
const availableBottom = Math.max(0, viewport.height - anchor.bottom - margin - offset);
|
|
105
|
+
const availableHeight = Math.max(80, viewport.height - margin * 2);
|
|
106
|
+
const minHeight = Math.min(Math.max(80, opts.minHeight ?? DEFAULT_MIN_HEIGHT), availableHeight);
|
|
107
|
+
const requestedMaxHeight = Math.max(minHeight, opts.maxHeight ?? DEFAULT_MAX_HEIGHT);
|
|
108
|
+
const fallbackHeight = Math.min(260, requestedMaxHeight);
|
|
109
|
+
const contentHeight = Math.max(opts.contentHeightHint ?? 0, fallbackHeight);
|
|
110
|
+
const desiredHeight = clamp(contentHeight, minHeight, Math.min(requestedMaxHeight, availableHeight));
|
|
111
|
+
const side = chooseSide(preferredSide, availableTop, availableBottom, desiredHeight);
|
|
112
|
+
const sideSpace = side === "top" ? availableTop : availableBottom;
|
|
113
|
+
const maxHeight = Math.min(
|
|
114
|
+
requestedMaxHeight,
|
|
115
|
+
Math.max(80, sideSpace || Math.max(availableTop, availableBottom)),
|
|
116
|
+
availableHeight
|
|
117
|
+
);
|
|
118
|
+
const positionedHeight = clamp(desiredHeight, 40, maxHeight);
|
|
119
|
+
const width = align === "stretch" ? clamp(anchor.width, minWidth, maxWidth) : maxWidth;
|
|
120
|
+
const left = clamp(horizontalAnchor(anchor, width, align), margin, viewport.width - margin - width);
|
|
121
|
+
const desiredTop = side === "top" ? anchor.top - offset - positionedHeight : anchor.bottom + offset;
|
|
122
|
+
const top = clamp(desiredTop, margin, viewport.height - margin - positionedHeight);
|
|
123
|
+
return { side, left, top, width, maxHeight };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/behaviors/events.ts
|
|
127
|
+
function emitIcen(source, type, detail) {
|
|
128
|
+
return source.dispatchEvent(
|
|
129
|
+
new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
1
133
|
// src/behaviors/context-menu.ts
|
|
2
134
|
var FLIP_MARGIN = 10;
|
|
3
135
|
var CLAMP_MARGIN = 8;
|
|
136
|
+
function emitMenuSelect(source, item) {
|
|
137
|
+
emitIcen(item, "icen:menu-select", {
|
|
138
|
+
source,
|
|
139
|
+
item,
|
|
140
|
+
value: item.dataset.value ?? "",
|
|
141
|
+
/* 优先专用 label 子元素(避开快捷键/图标文本混入),无则退整项文本 */
|
|
142
|
+
label: (item.querySelector(".menu-item-label, .command-item-label")?.textContent ?? item.textContent ?? "").trim().slice(0, 80)
|
|
143
|
+
});
|
|
144
|
+
}
|
|
4
145
|
var panel = null;
|
|
5
146
|
var focusableItems = [];
|
|
6
147
|
var focusedIndex = -1;
|
|
7
|
-
function buildItem(item) {
|
|
148
|
+
function buildItem(item, menuPanel) {
|
|
8
149
|
if ("type" in item && item.type === "separator") {
|
|
9
150
|
const sep = document.createElement("div");
|
|
10
151
|
sep.className = "menu-separator";
|
|
@@ -54,19 +195,21 @@ function buildItem(item) {
|
|
|
54
195
|
btn.appendChild(shortcut);
|
|
55
196
|
}
|
|
56
197
|
btn.addEventListener("click", () => {
|
|
198
|
+
emitMenuSelect(menuPanel, btn);
|
|
57
199
|
entry.onClick?.();
|
|
58
|
-
|
|
200
|
+
closeContextMenu();
|
|
59
201
|
});
|
|
60
202
|
return btn;
|
|
61
203
|
}
|
|
62
|
-
function openContextMenu(x, y, items) {
|
|
204
|
+
function openContextMenu(x, y, items, opts) {
|
|
63
205
|
if (typeof document === "undefined") return;
|
|
64
|
-
|
|
206
|
+
closeContextMenu();
|
|
65
207
|
panel = document.createElement("div");
|
|
66
208
|
panel.className = "menu menu--context";
|
|
67
209
|
panel.setAttribute("role", "menu");
|
|
68
210
|
panel.style.visibility = "hidden";
|
|
69
|
-
|
|
211
|
+
applyPanelSizing(panel, opts?.sizing ?? {});
|
|
212
|
+
for (const item of items) panel.appendChild(buildItem(item, panel));
|
|
70
213
|
document.body.appendChild(panel);
|
|
71
214
|
const rect = panel.getBoundingClientRect();
|
|
72
215
|
const vw = window.innerWidth;
|
|
@@ -77,6 +220,7 @@ function openContextMenu(x, y, items) {
|
|
|
77
220
|
if (y + rect.height > vh - FLIP_MARGIN) ny = y - rect.height;
|
|
78
221
|
if (nx < CLAMP_MARGIN) nx = CLAMP_MARGIN;
|
|
79
222
|
if (ny < CLAMP_MARGIN) ny = CLAMP_MARGIN;
|
|
223
|
+
panel.dataset.side = ny < y ? "top" : "bottom";
|
|
80
224
|
panel.style.left = `${nx}px`;
|
|
81
225
|
panel.style.top = `${ny}px`;
|
|
82
226
|
panel.style.visibility = "";
|
|
@@ -87,18 +231,18 @@ function openContextMenu(x, y, items) {
|
|
|
87
231
|
setFocused(0);
|
|
88
232
|
});
|
|
89
233
|
document.addEventListener("pointerdown", onOutsidePointerDown, true);
|
|
90
|
-
document.addEventListener("scroll",
|
|
234
|
+
document.addEventListener("scroll", onScrollSignal, true);
|
|
91
235
|
window.addEventListener("resize", onCloseSignal);
|
|
92
236
|
window.addEventListener("keydown", onKeyDown);
|
|
93
237
|
}
|
|
94
|
-
function
|
|
238
|
+
function closeContextMenu() {
|
|
95
239
|
if (!panel) return;
|
|
96
240
|
panel.remove();
|
|
97
241
|
panel = null;
|
|
98
242
|
focusableItems = [];
|
|
99
243
|
focusedIndex = -1;
|
|
100
244
|
document.removeEventListener("pointerdown", onOutsidePointerDown, true);
|
|
101
|
-
document.removeEventListener("scroll",
|
|
245
|
+
document.removeEventListener("scroll", onScrollSignal, true);
|
|
102
246
|
window.removeEventListener("resize", onCloseSignal);
|
|
103
247
|
window.removeEventListener("keydown", onKeyDown);
|
|
104
248
|
}
|
|
@@ -110,16 +254,20 @@ function setFocused(next) {
|
|
|
110
254
|
function onOutsidePointerDown(e) {
|
|
111
255
|
if (!panel) return;
|
|
112
256
|
const target = e.target;
|
|
113
|
-
if (!(target instanceof Node) || !panel.contains(target))
|
|
257
|
+
if (!(target instanceof Node) || !panel.contains(target)) closeContextMenu();
|
|
258
|
+
}
|
|
259
|
+
function onScrollSignal(e) {
|
|
260
|
+
if (panel && e.target instanceof Node && panel.contains(e.target)) return;
|
|
261
|
+
closeContextMenu();
|
|
114
262
|
}
|
|
115
263
|
function onCloseSignal() {
|
|
116
|
-
|
|
264
|
+
closeContextMenu();
|
|
117
265
|
}
|
|
118
266
|
function onKeyDown(e) {
|
|
119
267
|
if (!panel) return;
|
|
120
268
|
if (e.key === "Escape") {
|
|
121
269
|
e.preventDefault();
|
|
122
|
-
|
|
270
|
+
closeContextMenu();
|
|
123
271
|
return;
|
|
124
272
|
}
|
|
125
273
|
if (focusableItems.length === 0) return;
|
|
@@ -175,7 +323,9 @@ function noopHandle() {
|
|
|
175
323
|
getData: () => [],
|
|
176
324
|
exportCSV: noop,
|
|
177
325
|
showColumn: noop,
|
|
178
|
-
hideColumn: noop
|
|
326
|
+
hideColumn: noop,
|
|
327
|
+
setPage: noop,
|
|
328
|
+
setPageSize: noop
|
|
179
329
|
};
|
|
180
330
|
}
|
|
181
331
|
function createTable(el, opts) {
|
|
@@ -223,7 +373,7 @@ function createTable(el, opts) {
|
|
|
223
373
|
if (search) {
|
|
224
374
|
const q = search.toLowerCase();
|
|
225
375
|
rows = rows.filter(
|
|
226
|
-
(row) => cols().some((c) => String(row[c.key] ?? "").toLowerCase().includes(q))
|
|
376
|
+
(row) => opts.searchFn ? opts.searchFn(search, row) : cols().some((c) => String(row[c.key] ?? "").toLowerCase().includes(q))
|
|
227
377
|
);
|
|
228
378
|
}
|
|
229
379
|
if (filters.size > 0) {
|
|
@@ -364,6 +514,20 @@ function createTable(el, opts) {
|
|
|
364
514
|
return input;
|
|
365
515
|
}
|
|
366
516
|
let openPanel = null;
|
|
517
|
+
function positionDropdownPanel(panel2, btn) {
|
|
518
|
+
panel2.hidden = false;
|
|
519
|
+
const sizing = resolvePanelSizing(el);
|
|
520
|
+
applyPanelSizing(panel2, sizing);
|
|
521
|
+
const rect = btn.getBoundingClientRect();
|
|
522
|
+
const pr = panel2.getBoundingClientRect();
|
|
523
|
+
const layout = computePopoverLayout(
|
|
524
|
+
{ left: rect.left, top: rect.top, right: rect.right, bottom: rect.bottom, width: rect.width, height: rect.height },
|
|
525
|
+
{ width: window.innerWidth, height: window.innerHeight },
|
|
526
|
+
{ side: "bottom", align: "end", offset: 4, margin: 8, minWidth: Math.max(pr.width, 180), maxWidth: Math.max(pr.width, 180), contentHeightHint: pr.height, maxHeight: sizing.maxHeight }
|
|
527
|
+
);
|
|
528
|
+
panel2.style.left = `${layout.left}px`;
|
|
529
|
+
panel2.style.top = `${layout.top}px`;
|
|
530
|
+
}
|
|
367
531
|
function onPanelScroll(ev) {
|
|
368
532
|
if (openPanel && ev.target instanceof Node && openPanel.el.contains(ev.target)) return;
|
|
369
533
|
closeFilterPanel();
|
|
@@ -381,15 +545,7 @@ function createTable(el, opts) {
|
|
|
381
545
|
document.body.appendChild(panel2);
|
|
382
546
|
openPanel = { key: col.key, el: panel2 };
|
|
383
547
|
btn.classList.add("is-open");
|
|
384
|
-
|
|
385
|
-
const pr = panel2.getBoundingClientRect();
|
|
386
|
-
let x = rect.right - pr.width;
|
|
387
|
-
let y = rect.bottom + 4;
|
|
388
|
-
if (x < 8) x = 8;
|
|
389
|
-
if (y + pr.height > window.innerHeight - 8) y = rect.top - pr.height - 4;
|
|
390
|
-
panel2.style.left = `${x}px`;
|
|
391
|
-
panel2.style.top = `${y}px`;
|
|
392
|
-
panel2.hidden = false;
|
|
548
|
+
positionDropdownPanel(panel2, btn);
|
|
393
549
|
window.addEventListener("scroll", onPanelScroll, true);
|
|
394
550
|
window.addEventListener("resize", onPanelScroll);
|
|
395
551
|
}
|
|
@@ -410,7 +566,7 @@ function createTable(el, opts) {
|
|
|
410
566
|
else allKeys.forEach((k) => selected.delete(k));
|
|
411
567
|
emitSelection();
|
|
412
568
|
renderBody();
|
|
413
|
-
|
|
569
|
+
syncHeadState();
|
|
414
570
|
});
|
|
415
571
|
cell.appendChild(box);
|
|
416
572
|
headRow.appendChild(cell);
|
|
@@ -447,6 +603,7 @@ function createTable(el, opts) {
|
|
|
447
603
|
} else if (sortDir === "asc") sortDir = "desc";
|
|
448
604
|
else sortKey = null;
|
|
449
605
|
invalidate();
|
|
606
|
+
if (sortKey) emitIcen(root, "icen:table-sort-change", { key: sortKey, dir: sortDir });
|
|
450
607
|
opts.onSortChange?.(sortKey, sortDir);
|
|
451
608
|
renderAll();
|
|
452
609
|
};
|
|
@@ -466,6 +623,7 @@ function createTable(el, opts) {
|
|
|
466
623
|
const btn = document.createElement("button");
|
|
467
624
|
btn.type = "button";
|
|
468
625
|
btn.className = "dt-filter-btn";
|
|
626
|
+
btn.dataset.colKey = col.key;
|
|
469
627
|
if (hasFilter) btn.classList.add("is-active");
|
|
470
628
|
btn.setAttribute("aria-label", `\u7B5B\u9009\u300C${col.title}\u300D`);
|
|
471
629
|
btn.appendChild(svgIcon(ICON_FUNNEL, 12));
|
|
@@ -491,6 +649,33 @@ function createTable(el, opts) {
|
|
|
491
649
|
headRow.appendChild(cell);
|
|
492
650
|
}
|
|
493
651
|
}
|
|
652
|
+
function syncHeadState() {
|
|
653
|
+
const headBox = headRow.querySelector(".dt-cell--check .dt-check");
|
|
654
|
+
if (headBox) {
|
|
655
|
+
const allKeys = pipeline().map((r) => keyOfRow(r));
|
|
656
|
+
const selCount = allKeys.filter((k) => selected.has(k)).length;
|
|
657
|
+
headBox.checked = selCount > 0 && selCount === allKeys.length;
|
|
658
|
+
headBox.indeterminate = selCount > 0 && selCount < allKeys.length;
|
|
659
|
+
}
|
|
660
|
+
headRow.querySelectorAll(".dt-filter-btn").forEach((b) => {
|
|
661
|
+
const key = b.dataset.colKey;
|
|
662
|
+
if (key) b.classList.toggle("is-active", filters.has(key));
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
function renderHeadKeepPanel() {
|
|
666
|
+
const keep = openPanel;
|
|
667
|
+
renderHead();
|
|
668
|
+
if (!keep) return;
|
|
669
|
+
openPanel = keep;
|
|
670
|
+
document.body.appendChild(keep.el);
|
|
671
|
+
window.addEventListener("scroll", onPanelScroll, true);
|
|
672
|
+
window.addEventListener("resize", onPanelScroll);
|
|
673
|
+
if (keep.key !== "__cols") {
|
|
674
|
+
headRow.querySelectorAll(".dt-filter-btn").forEach((b) => {
|
|
675
|
+
if (b.dataset.colKey === keep.key) b.classList.add("is-open");
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
}
|
|
494
679
|
function attachResize(handle, col) {
|
|
495
680
|
handle.addEventListener("pointerdown", (ev) => {
|
|
496
681
|
ev.preventDefault();
|
|
@@ -508,12 +693,17 @@ function createTable(el, opts) {
|
|
|
508
693
|
});
|
|
509
694
|
};
|
|
510
695
|
const onUp = (e) => {
|
|
511
|
-
|
|
696
|
+
try {
|
|
697
|
+
handle.releasePointerCapture(e.pointerId);
|
|
698
|
+
} catch {
|
|
699
|
+
}
|
|
512
700
|
handle.removeEventListener("pointermove", onMove);
|
|
513
701
|
handle.removeEventListener("pointerup", onUp);
|
|
702
|
+
handle.removeEventListener("pointercancel", onUp);
|
|
514
703
|
};
|
|
515
704
|
handle.addEventListener("pointermove", onMove);
|
|
516
705
|
handle.addEventListener("pointerup", onUp);
|
|
706
|
+
handle.addEventListener("pointercancel", onUp);
|
|
517
707
|
});
|
|
518
708
|
}
|
|
519
709
|
function openColumnPanel(btn) {
|
|
@@ -532,8 +722,10 @@ function createTable(el, opts) {
|
|
|
532
722
|
box.addEventListener("change", () => {
|
|
533
723
|
if (box.checked) colHidden.delete(col.key);
|
|
534
724
|
else colHidden.set(col.key, true);
|
|
725
|
+
emitIcen(root, "icen:table-columns-change", { hidden: Array.from(colHidden.keys()) });
|
|
535
726
|
invalidate();
|
|
536
|
-
|
|
727
|
+
renderBody();
|
|
728
|
+
renderHeadKeepPanel();
|
|
537
729
|
});
|
|
538
730
|
const text = document.createElement("span");
|
|
539
731
|
text.textContent = col.title;
|
|
@@ -543,20 +735,13 @@ function createTable(el, opts) {
|
|
|
543
735
|
panel2.appendChild(list);
|
|
544
736
|
document.body.appendChild(panel2);
|
|
545
737
|
openPanel = { key: "__cols", el: panel2 };
|
|
546
|
-
|
|
547
|
-
const pr = panel2.getBoundingClientRect();
|
|
548
|
-
let x = rect.right - pr.width;
|
|
549
|
-
let y = rect.bottom + 4;
|
|
550
|
-
if (x < 8) x = 8;
|
|
551
|
-
if (y + pr.height > window.innerHeight - 8) y = rect.top - pr.height - 4;
|
|
552
|
-
panel2.style.left = `${x}px`;
|
|
553
|
-
panel2.style.top = `${y}px`;
|
|
554
|
-
panel2.hidden = false;
|
|
738
|
+
positionDropdownPanel(panel2, btn);
|
|
555
739
|
window.addEventListener("scroll", onPanelScroll, true);
|
|
556
740
|
window.addEventListener("resize", onPanelScroll);
|
|
557
741
|
}
|
|
558
742
|
function exportCSV(filename) {
|
|
559
743
|
const rows = pipeline();
|
|
744
|
+
emitIcen(root, "icen:table-export", { count: rows.length });
|
|
560
745
|
const visible = cols();
|
|
561
746
|
const header = visible.map((c) => csvEscape(c.title)).join(",");
|
|
562
747
|
const lines = rows.map(
|
|
@@ -569,7 +754,7 @@ function createTable(el, opts) {
|
|
|
569
754
|
a.href = url;
|
|
570
755
|
a.download = filename ?? "export.csv";
|
|
571
756
|
a.click();
|
|
572
|
-
URL.revokeObjectURL(url);
|
|
757
|
+
window.setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
573
758
|
}
|
|
574
759
|
function csvEscape(s) {
|
|
575
760
|
if (/[",\r\n]/.test(s)) return `"${s.replace(/"/g, '""')}"`;
|
|
@@ -593,9 +778,11 @@ function createTable(el, opts) {
|
|
|
593
778
|
else cur.delete(v);
|
|
594
779
|
if (cur.size === values.length) filters.delete(col.key);
|
|
595
780
|
else filters.set(col.key, cur);
|
|
781
|
+
emitIcen(root, "icen:table-filter-change", { key: col.key, values: Array.from(filters.get(col.key) ?? []) });
|
|
596
782
|
invalidate();
|
|
597
783
|
page = 1;
|
|
598
|
-
|
|
784
|
+
renderBody();
|
|
785
|
+
syncHeadState();
|
|
599
786
|
});
|
|
600
787
|
const text = document.createElement("span");
|
|
601
788
|
text.textContent = v === "" ? "\uFF08\u7A7A\uFF09" : v;
|
|
@@ -612,14 +799,17 @@ function createTable(el, opts) {
|
|
|
612
799
|
filters.delete(col.key);
|
|
613
800
|
invalidate();
|
|
614
801
|
page = 1;
|
|
615
|
-
|
|
616
|
-
|
|
802
|
+
list.querySelectorAll(".dt-check").forEach((b) => {
|
|
803
|
+
b.checked = true;
|
|
804
|
+
});
|
|
805
|
+
renderBody();
|
|
806
|
+
syncHeadState();
|
|
617
807
|
});
|
|
618
808
|
footRow.appendChild(reset);
|
|
619
809
|
panel2.append(list, footRow);
|
|
620
810
|
return panel2;
|
|
621
811
|
}
|
|
622
|
-
function makeRow(row, rowIndex, key) {
|
|
812
|
+
function makeRow(row, rowIndex, key, offsets) {
|
|
623
813
|
const tr = document.createElement("div");
|
|
624
814
|
tr.className = "dt-row";
|
|
625
815
|
tr.setAttribute("role", "row");
|
|
@@ -633,17 +823,19 @@ function createTable(el, opts) {
|
|
|
633
823
|
if (selectable) {
|
|
634
824
|
const cell = document.createElement("div");
|
|
635
825
|
cell.className = "dt-cell dt-cell--check";
|
|
636
|
-
if (
|
|
826
|
+
if (offsets.size > 0) {
|
|
637
827
|
cell.classList.add("dt-cell--frozen");
|
|
638
828
|
cell.style.left = "0px";
|
|
639
829
|
}
|
|
640
830
|
const box = makeCheck(selected.has(key), "\u9009\u62E9\u8BE5\u884C");
|
|
831
|
+
let shiftClicked = false;
|
|
641
832
|
box.addEventListener("click", (ev) => {
|
|
642
|
-
|
|
833
|
+
shiftClicked = ev.shiftKey;
|
|
643
834
|
ev.stopPropagation();
|
|
644
835
|
});
|
|
645
836
|
box.addEventListener("change", () => {
|
|
646
|
-
const shift =
|
|
837
|
+
const shift = shiftClicked;
|
|
838
|
+
shiftClicked = false;
|
|
647
839
|
if (shift && lastClickedIndex >= 0 && lastClickedIndex !== rowIndex) {
|
|
648
840
|
const rows = pipeline();
|
|
649
841
|
const [lo, hi] = lastClickedIndex < rowIndex ? [lastClickedIndex, rowIndex] : [rowIndex, lastClickedIndex];
|
|
@@ -661,8 +853,8 @@ function createTable(el, opts) {
|
|
|
661
853
|
}
|
|
662
854
|
lastClickedIndex = rowIndex;
|
|
663
855
|
emitSelection();
|
|
664
|
-
|
|
665
|
-
renderFoot(pipeline().length);
|
|
856
|
+
syncHeadState();
|
|
857
|
+
renderFoot(opts.remote ? opts.totalCount ?? data.length : pipeline().length);
|
|
666
858
|
});
|
|
667
859
|
cell.appendChild(box);
|
|
668
860
|
tr.appendChild(cell);
|
|
@@ -680,12 +872,12 @@ function createTable(el, opts) {
|
|
|
680
872
|
ev.stopPropagation();
|
|
681
873
|
if (expanded.has(key)) expanded.delete(key);
|
|
682
874
|
else expanded.add(key);
|
|
875
|
+
emitIcen(root, "icen:table-row-toggle", { row, index: rowIndex, expanded: expanded.has(key) });
|
|
683
876
|
renderBody();
|
|
684
877
|
});
|
|
685
878
|
cell.appendChild(btn);
|
|
686
879
|
tr.appendChild(cell);
|
|
687
880
|
}
|
|
688
|
-
const offsets = frozenOffsets();
|
|
689
881
|
for (const col of cols()) {
|
|
690
882
|
const cell = document.createElement("div");
|
|
691
883
|
cell.className = "dt-cell";
|
|
@@ -702,11 +894,16 @@ function createTable(el, opts) {
|
|
|
702
894
|
tr.classList.add("is-clickable");
|
|
703
895
|
tr.addEventListener("click", () => opts.onRowClick(row));
|
|
704
896
|
}
|
|
897
|
+
tr.addEventListener("click", () => {
|
|
898
|
+
emitIcen(root, "icen:table-row-click", { row, index: rowIndex });
|
|
899
|
+
});
|
|
705
900
|
if (opts.contextMenu) {
|
|
706
901
|
tr.addEventListener("contextmenu", (ev) => {
|
|
707
902
|
ev.preventDefault();
|
|
708
903
|
ev.stopPropagation();
|
|
709
|
-
openContextMenu(ev.clientX, ev.clientY, opts.contextMenu(row)
|
|
904
|
+
openContextMenu(ev.clientX, ev.clientY, opts.contextMenu(row), {
|
|
905
|
+
sizing: readPanelSizing(el)
|
|
906
|
+
});
|
|
710
907
|
});
|
|
711
908
|
}
|
|
712
909
|
return tr;
|
|
@@ -721,7 +918,7 @@ function createTable(el, opts) {
|
|
|
721
918
|
}
|
|
722
919
|
function visibleRange(total) {
|
|
723
920
|
if (!virtual) {
|
|
724
|
-
if (opts.pagination === false) return [0, total];
|
|
921
|
+
if (opts.remote || opts.pagination === false) return [0, total];
|
|
725
922
|
const start2 = (page - 1) * pageSize;
|
|
726
923
|
return [start2, Math.min(total, start2 + pageSize)];
|
|
727
924
|
}
|
|
@@ -733,7 +930,7 @@ function createTable(el, opts) {
|
|
|
733
930
|
}
|
|
734
931
|
function renderBody() {
|
|
735
932
|
const rows = pipeline();
|
|
736
|
-
const total = rows.length;
|
|
933
|
+
const total = opts.remote ? opts.totalCount ?? data.length : rows.length;
|
|
737
934
|
const keepTop = virtual ? scroll.scrollTop : 0;
|
|
738
935
|
body.textContent = "";
|
|
739
936
|
if (opts.loading) {
|
|
@@ -760,6 +957,7 @@ function createTable(el, opts) {
|
|
|
760
957
|
}
|
|
761
958
|
const [start, end] = visibleRange(total);
|
|
762
959
|
const frag = document.createDocumentFragment();
|
|
960
|
+
const offsets = frozenOffsets();
|
|
763
961
|
if (virtual && start > 0) {
|
|
764
962
|
const top = document.createElement("div");
|
|
765
963
|
top.style.height = `${start * rowH}px`;
|
|
@@ -768,7 +966,7 @@ function createTable(el, opts) {
|
|
|
768
966
|
for (let i = start; i < end; i++) {
|
|
769
967
|
const row = rows[i];
|
|
770
968
|
const key = keyOfRow(row);
|
|
771
|
-
frag.appendChild(makeRow(row, i, key));
|
|
969
|
+
frag.appendChild(makeRow(row, i, key, offsets));
|
|
772
970
|
if (expandable && expanded.has(key)) frag.appendChild(makeExpandRow(row));
|
|
773
971
|
}
|
|
774
972
|
if (virtual && end < total) {
|
|
@@ -797,7 +995,8 @@ function createTable(el, opts) {
|
|
|
797
995
|
clear.addEventListener("click", () => {
|
|
798
996
|
selected.clear();
|
|
799
997
|
emitSelection();
|
|
800
|
-
|
|
998
|
+
renderBody();
|
|
999
|
+
syncHeadState();
|
|
801
1000
|
});
|
|
802
1001
|
sel.appendChild(clear);
|
|
803
1002
|
foot.appendChild(sel);
|
|
@@ -820,6 +1019,10 @@ function createTable(el, opts) {
|
|
|
820
1019
|
sizeSel.addEventListener("change", () => {
|
|
821
1020
|
pageSize = Number(sizeSel.value);
|
|
822
1021
|
page = 1;
|
|
1022
|
+
emitIcen(root, "icen:table-page-size-change", { pageSize });
|
|
1023
|
+
emitIcen(root, "icen:table-page-change", { page, pageSize });
|
|
1024
|
+
opts.onPageSizeChange?.(pageSize);
|
|
1025
|
+
opts.onPageChange?.(page, pageSize);
|
|
823
1026
|
renderAll();
|
|
824
1027
|
});
|
|
825
1028
|
pager.appendChild(sizeSel);
|
|
@@ -833,6 +1036,8 @@ function createTable(el, opts) {
|
|
|
833
1036
|
if (opts2.aria) b.setAttribute("aria-label", opts2.aria);
|
|
834
1037
|
b.addEventListener("click", () => {
|
|
835
1038
|
page = target;
|
|
1039
|
+
emitIcen(root, "icen:table-page-change", { page, pageSize });
|
|
1040
|
+
opts.onPageChange?.(page, pageSize);
|
|
836
1041
|
renderAll();
|
|
837
1042
|
});
|
|
838
1043
|
return b;
|
|
@@ -861,6 +1066,7 @@ function createTable(el, opts) {
|
|
|
861
1066
|
window.clearTimeout(searchTimer);
|
|
862
1067
|
searchTimer = window.setTimeout(() => {
|
|
863
1068
|
search = searchInput?.value.trim() ?? "";
|
|
1069
|
+
emitIcen(root, "icen:table-search-change", { query: search });
|
|
864
1070
|
invalidate();
|
|
865
1071
|
page = 1;
|
|
866
1072
|
renderAll();
|
|
@@ -884,9 +1090,9 @@ function createTable(el, opts) {
|
|
|
884
1090
|
}
|
|
885
1091
|
document.addEventListener("keydown", onDocKeydown);
|
|
886
1092
|
function emitSelection() {
|
|
887
|
-
if (!opts.onSelectionChange) return;
|
|
888
1093
|
const rows = data.filter((r) => selected.has(keyOfRow(r)));
|
|
889
|
-
|
|
1094
|
+
emitIcen(root, "icen:table-selection-change", { selected: rows });
|
|
1095
|
+
opts.onSelectionChange?.(rows);
|
|
890
1096
|
}
|
|
891
1097
|
function renderAll() {
|
|
892
1098
|
if (destroyed) return;
|
|
@@ -895,7 +1101,26 @@ function createTable(el, opts) {
|
|
|
895
1101
|
}
|
|
896
1102
|
rekey();
|
|
897
1103
|
renderAll();
|
|
1104
|
+
function setPage(n) {
|
|
1105
|
+
if (!Number.isFinite(n) || n < 1) return;
|
|
1106
|
+
page = Math.round(n);
|
|
1107
|
+
emitIcen(root, "icen:table-page-change", { page, pageSize });
|
|
1108
|
+
opts.onPageChange?.(page, pageSize);
|
|
1109
|
+
renderAll();
|
|
1110
|
+
}
|
|
1111
|
+
function setPageSize(n) {
|
|
1112
|
+
if (!Number.isFinite(n) || n <= 0) return;
|
|
1113
|
+
pageSize = Math.round(n);
|
|
1114
|
+
page = 1;
|
|
1115
|
+
emitIcen(root, "icen:table-page-size-change", { pageSize });
|
|
1116
|
+
emitIcen(root, "icen:table-page-change", { page, pageSize });
|
|
1117
|
+
opts.onPageSizeChange?.(pageSize);
|
|
1118
|
+
opts.onPageChange?.(page, pageSize);
|
|
1119
|
+
renderAll();
|
|
1120
|
+
}
|
|
898
1121
|
return {
|
|
1122
|
+
setPage,
|
|
1123
|
+
setPageSize,
|
|
899
1124
|
setData(next) {
|
|
900
1125
|
data = next;
|
|
901
1126
|
rekey();
|
|
@@ -938,7 +1163,8 @@ function createTable(el, opts) {
|
|
|
938
1163
|
exportCSV,
|
|
939
1164
|
showColumn(key) {
|
|
940
1165
|
colHidden.delete(key);
|
|
941
|
-
opts.columns.find((c) => c.key === key)
|
|
1166
|
+
const col = opts.columns.find((c) => c.key === key);
|
|
1167
|
+
if (col) col.hidden = false;
|
|
942
1168
|
invalidate();
|
|
943
1169
|
renderAll();
|
|
944
1170
|
},
|