@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.
Files changed (151) hide show
  1. package/AGENTS.md +262 -0
  2. package/README.md +52 -13
  3. package/dist/base.css +33 -5
  4. package/dist/behaviors/accordion.d.mts +7 -0
  5. package/dist/behaviors/accordion.mjs +37 -11
  6. package/dist/behaviors/ai-chat.d.mts +59 -0
  7. package/dist/behaviors/ai-chat.mjs +580 -0
  8. package/dist/behaviors/ai-composer.d.mts +137 -0
  9. package/dist/behaviors/ai-composer.mjs +2114 -0
  10. package/dist/behaviors/ai-core.d.mts +156 -0
  11. package/dist/behaviors/ai-core.mjs +504 -0
  12. package/dist/behaviors/ai-diff.d.mts +53 -0
  13. package/dist/behaviors/ai-diff.mjs +413 -0
  14. package/dist/behaviors/ai-panel.d.mts +101 -0
  15. package/dist/behaviors/ai-panel.mjs +1087 -0
  16. package/dist/behaviors/ai-provider.d.mts +198 -0
  17. package/dist/behaviors/ai-provider.mjs +1061 -0
  18. package/dist/behaviors/ai-tool.d.mts +26 -0
  19. package/dist/behaviors/ai-tool.mjs +2173 -0
  20. package/dist/behaviors/ai-tools.d.mts +84 -0
  21. package/dist/behaviors/ai-tools.mjs +1762 -0
  22. package/dist/behaviors/back-top.d.mts +20 -0
  23. package/dist/behaviors/back-top.mjs +64 -32
  24. package/dist/behaviors/carousel.d.mts +7 -0
  25. package/dist/behaviors/carousel.mjs +72 -27
  26. package/dist/behaviors/charts.d.mts +296 -0
  27. package/dist/behaviors/charts.mjs +1364 -440
  28. package/dist/behaviors/command-palette.d.mts +42 -0
  29. package/dist/behaviors/command-palette.mjs +167 -27
  30. package/dist/behaviors/context-menu.d.mts +53 -0
  31. package/dist/behaviors/context-menu.mjs +127 -39
  32. package/dist/behaviors/controls.d.mts +59 -0
  33. package/dist/behaviors/controls.mjs +601 -0
  34. package/dist/behaviors/copy.d.mts +7 -0
  35. package/dist/behaviors/copy.mjs +41 -15
  36. package/dist/behaviors/datatable.d.mts +90 -0
  37. package/dist/behaviors/datatable.mjs +279 -53
  38. package/dist/behaviors/date-picker.d.mts +4 -0
  39. package/dist/behaviors/date-picker.mjs +276 -69
  40. package/dist/behaviors/dropdown.d.mts +8 -0
  41. package/dist/behaviors/dropdown.mjs +171 -28
  42. package/dist/behaviors/events.d.mts +330 -0
  43. package/dist/behaviors/events.mjs +208 -0
  44. package/dist/behaviors/input.d.mts +4 -0
  45. package/dist/behaviors/input.mjs +52 -32
  46. package/dist/behaviors/modal.d.mts +30 -0
  47. package/dist/behaviors/modal.mjs +87 -16
  48. package/dist/behaviors/nav.d.mts +7 -0
  49. package/dist/behaviors/nav.mjs +57 -15
  50. package/dist/behaviors/notification.d.mts +188 -0
  51. package/dist/behaviors/notification.mjs +274 -140
  52. package/dist/behaviors/popover.d.mts +82 -0
  53. package/dist/behaviors/popover.mjs +92 -5
  54. package/dist/behaviors/select.d.mts +6 -0
  55. package/dist/behaviors/select.mjs +287 -45
  56. package/dist/behaviors/sidebar.d.mts +7 -0
  57. package/dist/behaviors/sidebar.mjs +44 -13
  58. package/dist/behaviors/slider.d.mts +7 -0
  59. package/dist/behaviors/slider.mjs +55 -43
  60. package/dist/behaviors/split-pane.d.mts +8 -0
  61. package/dist/behaviors/split-pane.mjs +70 -29
  62. package/dist/behaviors/tabs.d.mts +9 -0
  63. package/dist/behaviors/tabs.mjs +102 -26
  64. package/dist/behaviors/tag-input.d.mts +7 -0
  65. package/dist/behaviors/tag-input.mjs +66 -17
  66. package/dist/behaviors/theme.d.mts +36 -0
  67. package/dist/behaviors/theme.mjs +51 -5
  68. package/dist/behaviors/toast.d.mts +48 -0
  69. package/dist/behaviors/toast.mjs +98 -25
  70. package/dist/behaviors/tree.d.mts +7 -0
  71. package/dist/behaviors/tree.mjs +39 -15
  72. package/dist/behaviors/upload.d.mts +4 -0
  73. package/dist/behaviors/upload.mjs +79 -44
  74. package/dist/cli.mjs +1 -1
  75. package/dist/components/ai-chat.css +1037 -0
  76. package/dist/components/ai-chat.mjs +3 -0
  77. package/dist/components/ai-composer.mjs +3 -0
  78. package/dist/components/ai-context.mjs +2 -0
  79. package/dist/components/ai-diff.css +311 -0
  80. package/dist/components/ai-diff.mjs +3 -0
  81. package/dist/components/ai-files.mjs +3 -0
  82. package/dist/components/ai-message.mjs +3 -0
  83. package/dist/components/ai-panel.css +682 -0
  84. package/dist/components/ai-reasoning.mjs +3 -0
  85. package/dist/components/ai-subagent.mjs +2 -0
  86. package/dist/components/ai-todo.mjs +2 -0
  87. package/dist/components/ai-tool-call.mjs +2 -0
  88. package/dist/components/ai-tool.css +532 -0
  89. package/dist/components/ai-usage.mjs +3 -0
  90. package/dist/components/badge.css +5 -9
  91. package/dist/components/breadcrumb.css +0 -36
  92. package/dist/components/btn.css +4 -7
  93. package/dist/components/card.css +3 -6
  94. package/dist/components/chart-bar.css +10 -0
  95. package/dist/components/chart-calendar.mjs +4 -0
  96. package/dist/components/chart-gauge.css +1 -1
  97. package/dist/components/chart-heatmap.css +40 -0
  98. package/dist/components/chart-pie.css +0 -6
  99. package/dist/components/chart-scatter.css +24 -0
  100. package/dist/components/chart-scatter.mjs +4 -0
  101. package/dist/components/charts.css +87 -2
  102. package/dist/components/charts.mjs +1 -0
  103. package/dist/components/command-palette.css +2 -2
  104. package/dist/components/content.css +11 -25
  105. package/dist/components/copy.css +8 -8
  106. package/dist/components/datatable.css +5 -9
  107. package/dist/components/date-picker.css +4 -8
  108. package/dist/components/empty.css +0 -4
  109. package/dist/components/feedback.css +10 -17
  110. package/dist/components/form.css +13 -14
  111. package/dist/components/input.css +2 -2
  112. package/dist/components/layout.css +36 -36
  113. package/dist/components/media.css +12 -8
  114. package/dist/components/menu.css +2 -2
  115. package/dist/components/modal.css +1 -1
  116. package/dist/components/nav.css +11 -6
  117. package/dist/components/notification.css +27 -28
  118. package/dist/components/pagination.css +4 -2
  119. package/dist/components/pagination.mjs +1 -0
  120. package/dist/components/panel.css +16 -16
  121. package/dist/components/pill.css +15 -15
  122. package/dist/components/rating.mjs +1 -0
  123. package/dist/components/segmented.css +14 -6
  124. package/dist/components/segmented.mjs +1 -0
  125. package/dist/components/select.css +79 -24
  126. package/dist/components/sidebar.css +1 -1
  127. package/dist/components/split-pane.css +0 -1
  128. package/dist/components/stat.css +14 -18
  129. package/dist/components/steps.css +15 -5
  130. package/dist/components/steps.mjs +1 -0
  131. package/dist/components/switch.css +11 -13
  132. package/dist/components/switch.mjs +1 -0
  133. package/dist/components/table.css +46 -47
  134. package/dist/components/table.mjs +1 -0
  135. package/dist/components/tabs.css +57 -45
  136. package/dist/components/tag-input.css +5 -10
  137. package/dist/components/toast.css +16 -13
  138. package/dist/components/tooltip.css +2 -2
  139. package/dist/components/upload.css +0 -15
  140. package/dist/index.d.mts +39 -0
  141. package/dist/index.mjs +11243 -3312
  142. package/dist/registry.json +147 -9
  143. package/dist/retro-effects.css +23 -32
  144. package/dist/tokens/colors.css +4 -1
  145. package/dist/tokens/retro-effects.css +23 -32
  146. package/dist/tokens/style-profiles.css +24 -2
  147. package/dist/tokens/typography.css +2 -0
  148. package/dist/tokens.css +30 -3
  149. package/dist/ui.css +3420 -645
  150. package/docs/spec/ai-native.md +662 -0
  151. 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
- closeMenu();
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
- closeMenu();
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
- for (const item of items) panel.appendChild(buildItem(item));
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", onCloseSignal, true);
234
+ document.addEventListener("scroll", onScrollSignal, true);
91
235
  window.addEventListener("resize", onCloseSignal);
92
236
  window.addEventListener("keydown", onKeyDown);
93
237
  }
94
- function closeMenu() {
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", onCloseSignal, true);
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)) closeMenu();
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
- closeMenu();
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
- closeMenu();
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
- const rect = btn.getBoundingClientRect();
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
- renderHead();
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
- handle.releasePointerCapture(e.pointerId);
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
- renderAll();
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
- const rect = btn.getBoundingClientRect();
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
- renderAll();
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
- closeFilterPanel();
616
- renderAll();
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 (frozenOffsets().size > 0 && selectable) {
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
- box._shift = ev.shiftKey;
833
+ shiftClicked = ev.shiftKey;
643
834
  ev.stopPropagation();
644
835
  });
645
836
  box.addEventListener("change", () => {
646
- const shift = box._shift === true;
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
- renderHead();
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
- renderAll();
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
- opts.onSelectionChange(rows);
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).hidden = false;
1166
+ const col = opts.columns.find((c) => c.key === key);
1167
+ if (col) col.hidden = false;
942
1168
  invalidate();
943
1169
  renderAll();
944
1170
  },
@@ -0,0 +1,4 @@
1
+ /** 返回销毁函数:摘除本次初始化容器的全部监听并复位幂等标记(可重新 init)。 */
2
+ declare function initDatePicker(root?: ParentNode): () => void;
3
+
4
+ export { initDatePicker };