@icen.ai/ui 0.8.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/AGENTS.md +15 -9
  2. package/README.md +8 -3
  3. package/dist/base.css +4 -3
  4. package/dist/behaviors/accordion.d.mts +5 -2
  5. package/dist/behaviors/accordion.mjs +36 -11
  6. package/dist/behaviors/ai-chat.d.mts +3 -1
  7. package/dist/behaviors/ai-chat.mjs +66 -43
  8. package/dist/behaviors/ai-composer.d.mts +28 -3
  9. package/dist/behaviors/ai-composer.mjs +291 -209
  10. package/dist/behaviors/ai-core.d.mts +9 -4
  11. package/dist/behaviors/ai-core.mjs +21 -8
  12. package/dist/behaviors/ai-diff.d.mts +2 -2
  13. package/dist/behaviors/ai-diff.mjs +172 -41
  14. package/dist/behaviors/ai-panel.d.mts +11 -8
  15. package/dist/behaviors/ai-panel.mjs +185 -110
  16. package/dist/behaviors/ai-provider.mjs +54 -48
  17. package/dist/behaviors/ai-tool.d.mts +12 -4
  18. package/dist/behaviors/ai-tool.mjs +311 -263
  19. package/dist/behaviors/ai-tools.d.mts +9 -6
  20. package/dist/behaviors/ai-tools.mjs +259 -155
  21. package/dist/behaviors/back-top.mjs +16 -1
  22. package/dist/behaviors/carousel.d.mts +5 -2
  23. package/dist/behaviors/carousel.mjs +53 -26
  24. package/dist/behaviors/charts.d.mts +68 -22
  25. package/dist/behaviors/charts.mjs +221 -150
  26. package/dist/behaviors/command-palette.d.mts +40 -6
  27. package/dist/behaviors/command-palette.mjs +143 -15
  28. package/dist/behaviors/context-menu.d.mts +21 -4
  29. package/dist/behaviors/context-menu.mjs +122 -38
  30. package/dist/behaviors/controls.d.mts +59 -0
  31. package/dist/behaviors/controls.mjs +601 -0
  32. package/dist/behaviors/copy.d.mts +5 -2
  33. package/dist/behaviors/copy.mjs +37 -12
  34. package/dist/behaviors/datatable.d.mts +12 -0
  35. package/dist/behaviors/datatable.mjs +226 -43
  36. package/dist/behaviors/date-picker.d.mts +2 -2
  37. package/dist/behaviors/date-picker.mjs +171 -32
  38. package/dist/behaviors/dropdown.d.mts +6 -2
  39. package/dist/behaviors/dropdown.mjs +164 -25
  40. package/dist/behaviors/events.d.mts +330 -0
  41. package/dist/behaviors/events.mjs +208 -0
  42. package/dist/behaviors/input.mjs +25 -12
  43. package/dist/behaviors/modal.d.mts +24 -5
  44. package/dist/behaviors/modal.mjs +76 -10
  45. package/dist/behaviors/nav.d.mts +1 -1
  46. package/dist/behaviors/nav.mjs +33 -7
  47. package/dist/behaviors/notification.d.mts +20 -4
  48. package/dist/behaviors/notification.mjs +53 -73
  49. package/dist/behaviors/popover.d.mts +39 -3
  50. package/dist/behaviors/popover.mjs +90 -4
  51. package/dist/behaviors/select.d.mts +4 -2
  52. package/dist/behaviors/select.mjs +161 -24
  53. package/dist/behaviors/sidebar.d.mts +5 -2
  54. package/dist/behaviors/sidebar.mjs +43 -13
  55. package/dist/behaviors/slider.d.mts +5 -2
  56. package/dist/behaviors/slider.mjs +54 -43
  57. package/dist/behaviors/split-pane.d.mts +6 -2
  58. package/dist/behaviors/split-pane.mjs +62 -21
  59. package/dist/behaviors/tabs.d.mts +3 -2
  60. package/dist/behaviors/tabs.mjs +65 -14
  61. package/dist/behaviors/tag-input.d.mts +5 -2
  62. package/dist/behaviors/tag-input.mjs +61 -21
  63. package/dist/behaviors/theme.d.mts +13 -2
  64. package/dist/behaviors/theme.mjs +48 -4
  65. package/dist/behaviors/toast.d.mts +30 -8
  66. package/dist/behaviors/toast.mjs +74 -7
  67. package/dist/behaviors/tree.d.mts +5 -2
  68. package/dist/behaviors/tree.mjs +38 -15
  69. package/dist/behaviors/upload.mjs +21 -23
  70. package/dist/cli.mjs +1 -1
  71. package/dist/components/ai-panel.css +1 -1
  72. package/dist/components/ai-tool-call.mjs +1 -1
  73. package/dist/components/badge.css +1 -1
  74. package/dist/components/breadcrumb.css +0 -32
  75. package/dist/components/chart-calendar.mjs +1 -2
  76. package/dist/components/chart-pie.css +0 -6
  77. package/dist/components/charts.css +1 -1
  78. package/dist/components/content.css +3 -16
  79. package/dist/components/datatable.css +1 -1
  80. package/dist/components/date-picker.css +1 -1
  81. package/dist/components/notification.css +2 -3
  82. package/dist/components/pagination.css +3 -1
  83. package/dist/components/pagination.mjs +1 -0
  84. package/dist/components/rating.mjs +1 -0
  85. package/dist/components/segmented.css +9 -1
  86. package/dist/components/segmented.mjs +1 -1
  87. package/dist/components/select.css +67 -4
  88. package/dist/components/steps.css +13 -3
  89. package/dist/components/steps.mjs +1 -0
  90. package/dist/components/switch.css +3 -1
  91. package/dist/components/switch.mjs +1 -0
  92. package/dist/components/table.mjs +1 -0
  93. package/dist/components/tabs.css +23 -0
  94. package/dist/components/tag-input.css +1 -6
  95. package/dist/components/upload.css +0 -15
  96. package/dist/index.d.mts +12 -10
  97. package/dist/index.mjs +2700 -1460
  98. package/dist/registry.json +42 -13
  99. package/dist/retro-effects.css +3 -16
  100. package/dist/tokens/retro-effects.css +3 -16
  101. package/dist/tokens/style-profiles.css +3 -2
  102. package/dist/tokens/typography.css +2 -0
  103. package/dist/tokens.css +5 -2
  104. package/dist/ui.css +138 -98
  105. package/docs/spec/ai-native.md +7 -4
  106. package/package.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import { ContextMenuItem } from './context-menu.js';
2
+ import './popover.js';
2
3
 
3
4
  interface TableColumn<Row = Record<string, unknown>> {
4
5
  key: string;
@@ -53,6 +54,14 @@ interface TableOptions<Row = Record<string, unknown>> {
53
54
  onSelectionChange?: (rows: Row[]) => void;
54
55
  onRowClick?: (row: Row) => void;
55
56
  onSortChange?: (key: string | null, dir: 'asc' | 'desc') => void;
57
+ /** 搜索谓词(默认跨列 String includes;自定义拼音/高亮/索引匹配) */
58
+ searchFn?: (query: string, row: Row) => boolean;
59
+ /** 服务端/受控分页:开启后翻页只派事件与回调,不再本地切片(配 totalCount + setData/setPage 喂数) */
60
+ remote?: boolean;
61
+ /** remote 模式总条数(缺省 data.length) */
62
+ totalCount?: number;
63
+ onPageChange?: (page: number, pageSize: number) => void;
64
+ onPageSizeChange?: (pageSize: number) => void;
56
65
  }
57
66
  interface TableHandle<Row = Record<string, unknown>> {
58
67
  setData(data: Row[]): void;
@@ -68,6 +77,9 @@ interface TableHandle<Row = Record<string, unknown>> {
68
77
  exportCSV(filename?: string): void;
69
78
  /** 显示/隐藏列 */
70
79
  showColumn(key: string): void;
80
+ /** 外部驱动页码(remote 模式与服务端同步) */
81
+ setPage(page: number): void;
82
+ setPageSize(size: number): void;
71
83
  hideColumn(key: string): void;
72
84
  /** 数据被外部原地修改后重渲 */
73
85
  refresh(): void;
@@ -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 = "";
@@ -91,7 +235,7 @@ function openContextMenu(x, y, items) {
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;
@@ -110,20 +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();
114
258
  }
115
259
  function onScrollSignal(e) {
116
260
  if (panel && e.target instanceof Node && panel.contains(e.target)) return;
117
- closeMenu();
261
+ closeContextMenu();
118
262
  }
119
263
  function onCloseSignal() {
120
- closeMenu();
264
+ closeContextMenu();
121
265
  }
122
266
  function onKeyDown(e) {
123
267
  if (!panel) return;
124
268
  if (e.key === "Escape") {
125
269
  e.preventDefault();
126
- closeMenu();
270
+ closeContextMenu();
127
271
  return;
128
272
  }
129
273
  if (focusableItems.length === 0) return;
@@ -179,7 +323,9 @@ function noopHandle() {
179
323
  getData: () => [],
180
324
  exportCSV: noop,
181
325
  showColumn: noop,
182
- hideColumn: noop
326
+ hideColumn: noop,
327
+ setPage: noop,
328
+ setPageSize: noop
183
329
  };
184
330
  }
185
331
  function createTable(el, opts) {
@@ -227,7 +373,7 @@ function createTable(el, opts) {
227
373
  if (search) {
228
374
  const q = search.toLowerCase();
229
375
  rows = rows.filter(
230
- (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))
231
377
  );
232
378
  }
233
379
  if (filters.size > 0) {
@@ -368,6 +514,20 @@ function createTable(el, opts) {
368
514
  return input;
369
515
  }
370
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
+ }
371
531
  function onPanelScroll(ev) {
372
532
  if (openPanel && ev.target instanceof Node && openPanel.el.contains(ev.target)) return;
373
533
  closeFilterPanel();
@@ -385,15 +545,7 @@ function createTable(el, opts) {
385
545
  document.body.appendChild(panel2);
386
546
  openPanel = { key: col.key, el: panel2 };
387
547
  btn.classList.add("is-open");
388
- const rect = btn.getBoundingClientRect();
389
- const pr = panel2.getBoundingClientRect();
390
- let x = rect.right - pr.width;
391
- let y = rect.bottom + 4;
392
- if (x < 8) x = 8;
393
- if (y + pr.height > window.innerHeight - 8) y = rect.top - pr.height - 4;
394
- panel2.style.left = `${x}px`;
395
- panel2.style.top = `${y}px`;
396
- panel2.hidden = false;
548
+ positionDropdownPanel(panel2, btn);
397
549
  window.addEventListener("scroll", onPanelScroll, true);
398
550
  window.addEventListener("resize", onPanelScroll);
399
551
  }
@@ -451,6 +603,7 @@ function createTable(el, opts) {
451
603
  } else if (sortDir === "asc") sortDir = "desc";
452
604
  else sortKey = null;
453
605
  invalidate();
606
+ if (sortKey) emitIcen(root, "icen:table-sort-change", { key: sortKey, dir: sortDir });
454
607
  opts.onSortChange?.(sortKey, sortDir);
455
608
  renderAll();
456
609
  };
@@ -569,6 +722,7 @@ function createTable(el, opts) {
569
722
  box.addEventListener("change", () => {
570
723
  if (box.checked) colHidden.delete(col.key);
571
724
  else colHidden.set(col.key, true);
725
+ emitIcen(root, "icen:table-columns-change", { hidden: Array.from(colHidden.keys()) });
572
726
  invalidate();
573
727
  renderBody();
574
728
  renderHeadKeepPanel();
@@ -581,20 +735,13 @@ function createTable(el, opts) {
581
735
  panel2.appendChild(list);
582
736
  document.body.appendChild(panel2);
583
737
  openPanel = { key: "__cols", el: panel2 };
584
- const rect = btn.getBoundingClientRect();
585
- const pr = panel2.getBoundingClientRect();
586
- let x = rect.right - pr.width;
587
- let y = rect.bottom + 4;
588
- if (x < 8) x = 8;
589
- if (y + pr.height > window.innerHeight - 8) y = rect.top - pr.height - 4;
590
- panel2.style.left = `${x}px`;
591
- panel2.style.top = `${y}px`;
592
- panel2.hidden = false;
738
+ positionDropdownPanel(panel2, btn);
593
739
  window.addEventListener("scroll", onPanelScroll, true);
594
740
  window.addEventListener("resize", onPanelScroll);
595
741
  }
596
742
  function exportCSV(filename) {
597
743
  const rows = pipeline();
744
+ emitIcen(root, "icen:table-export", { count: rows.length });
598
745
  const visible = cols();
599
746
  const header = visible.map((c) => csvEscape(c.title)).join(",");
600
747
  const lines = rows.map(
@@ -631,6 +778,7 @@ function createTable(el, opts) {
631
778
  else cur.delete(v);
632
779
  if (cur.size === values.length) filters.delete(col.key);
633
780
  else filters.set(col.key, cur);
781
+ emitIcen(root, "icen:table-filter-change", { key: col.key, values: Array.from(filters.get(col.key) ?? []) });
634
782
  invalidate();
635
783
  page = 1;
636
784
  renderBody();
@@ -661,7 +809,7 @@ function createTable(el, opts) {
661
809
  panel2.append(list, footRow);
662
810
  return panel2;
663
811
  }
664
- function makeRow(row, rowIndex, key) {
812
+ function makeRow(row, rowIndex, key, offsets) {
665
813
  const tr = document.createElement("div");
666
814
  tr.className = "dt-row";
667
815
  tr.setAttribute("role", "row");
@@ -675,17 +823,19 @@ function createTable(el, opts) {
675
823
  if (selectable) {
676
824
  const cell = document.createElement("div");
677
825
  cell.className = "dt-cell dt-cell--check";
678
- if (frozenOffsets().size > 0 && selectable) {
826
+ if (offsets.size > 0) {
679
827
  cell.classList.add("dt-cell--frozen");
680
828
  cell.style.left = "0px";
681
829
  }
682
830
  const box = makeCheck(selected.has(key), "\u9009\u62E9\u8BE5\u884C");
831
+ let shiftClicked = false;
683
832
  box.addEventListener("click", (ev) => {
684
- box._shift = ev.shiftKey;
833
+ shiftClicked = ev.shiftKey;
685
834
  ev.stopPropagation();
686
835
  });
687
836
  box.addEventListener("change", () => {
688
- const shift = box._shift === true;
837
+ const shift = shiftClicked;
838
+ shiftClicked = false;
689
839
  if (shift && lastClickedIndex >= 0 && lastClickedIndex !== rowIndex) {
690
840
  const rows = pipeline();
691
841
  const [lo, hi] = lastClickedIndex < rowIndex ? [lastClickedIndex, rowIndex] : [rowIndex, lastClickedIndex];
@@ -704,7 +854,7 @@ function createTable(el, opts) {
704
854
  lastClickedIndex = rowIndex;
705
855
  emitSelection();
706
856
  syncHeadState();
707
- renderFoot(pipeline().length);
857
+ renderFoot(opts.remote ? opts.totalCount ?? data.length : pipeline().length);
708
858
  });
709
859
  cell.appendChild(box);
710
860
  tr.appendChild(cell);
@@ -722,12 +872,12 @@ function createTable(el, opts) {
722
872
  ev.stopPropagation();
723
873
  if (expanded.has(key)) expanded.delete(key);
724
874
  else expanded.add(key);
875
+ emitIcen(root, "icen:table-row-toggle", { row, index: rowIndex, expanded: expanded.has(key) });
725
876
  renderBody();
726
877
  });
727
878
  cell.appendChild(btn);
728
879
  tr.appendChild(cell);
729
880
  }
730
- const offsets = frozenOffsets();
731
881
  for (const col of cols()) {
732
882
  const cell = document.createElement("div");
733
883
  cell.className = "dt-cell";
@@ -744,11 +894,16 @@ function createTable(el, opts) {
744
894
  tr.classList.add("is-clickable");
745
895
  tr.addEventListener("click", () => opts.onRowClick(row));
746
896
  }
897
+ tr.addEventListener("click", () => {
898
+ emitIcen(root, "icen:table-row-click", { row, index: rowIndex });
899
+ });
747
900
  if (opts.contextMenu) {
748
901
  tr.addEventListener("contextmenu", (ev) => {
749
902
  ev.preventDefault();
750
903
  ev.stopPropagation();
751
- openContextMenu(ev.clientX, ev.clientY, opts.contextMenu(row));
904
+ openContextMenu(ev.clientX, ev.clientY, opts.contextMenu(row), {
905
+ sizing: readPanelSizing(el)
906
+ });
752
907
  });
753
908
  }
754
909
  return tr;
@@ -763,7 +918,7 @@ function createTable(el, opts) {
763
918
  }
764
919
  function visibleRange(total) {
765
920
  if (!virtual) {
766
- if (opts.pagination === false) return [0, total];
921
+ if (opts.remote || opts.pagination === false) return [0, total];
767
922
  const start2 = (page - 1) * pageSize;
768
923
  return [start2, Math.min(total, start2 + pageSize)];
769
924
  }
@@ -775,7 +930,7 @@ function createTable(el, opts) {
775
930
  }
776
931
  function renderBody() {
777
932
  const rows = pipeline();
778
- const total = rows.length;
933
+ const total = opts.remote ? opts.totalCount ?? data.length : rows.length;
779
934
  const keepTop = virtual ? scroll.scrollTop : 0;
780
935
  body.textContent = "";
781
936
  if (opts.loading) {
@@ -802,6 +957,7 @@ function createTable(el, opts) {
802
957
  }
803
958
  const [start, end] = visibleRange(total);
804
959
  const frag = document.createDocumentFragment();
960
+ const offsets = frozenOffsets();
805
961
  if (virtual && start > 0) {
806
962
  const top = document.createElement("div");
807
963
  top.style.height = `${start * rowH}px`;
@@ -810,7 +966,7 @@ function createTable(el, opts) {
810
966
  for (let i = start; i < end; i++) {
811
967
  const row = rows[i];
812
968
  const key = keyOfRow(row);
813
- frag.appendChild(makeRow(row, i, key));
969
+ frag.appendChild(makeRow(row, i, key, offsets));
814
970
  if (expandable && expanded.has(key)) frag.appendChild(makeExpandRow(row));
815
971
  }
816
972
  if (virtual && end < total) {
@@ -863,6 +1019,10 @@ function createTable(el, opts) {
863
1019
  sizeSel.addEventListener("change", () => {
864
1020
  pageSize = Number(sizeSel.value);
865
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);
866
1026
  renderAll();
867
1027
  });
868
1028
  pager.appendChild(sizeSel);
@@ -876,6 +1036,8 @@ function createTable(el, opts) {
876
1036
  if (opts2.aria) b.setAttribute("aria-label", opts2.aria);
877
1037
  b.addEventListener("click", () => {
878
1038
  page = target;
1039
+ emitIcen(root, "icen:table-page-change", { page, pageSize });
1040
+ opts.onPageChange?.(page, pageSize);
879
1041
  renderAll();
880
1042
  });
881
1043
  return b;
@@ -904,6 +1066,7 @@ function createTable(el, opts) {
904
1066
  window.clearTimeout(searchTimer);
905
1067
  searchTimer = window.setTimeout(() => {
906
1068
  search = searchInput?.value.trim() ?? "";
1069
+ emitIcen(root, "icen:table-search-change", { query: search });
907
1070
  invalidate();
908
1071
  page = 1;
909
1072
  renderAll();
@@ -927,9 +1090,9 @@ function createTable(el, opts) {
927
1090
  }
928
1091
  document.addEventListener("keydown", onDocKeydown);
929
1092
  function emitSelection() {
930
- if (!opts.onSelectionChange) return;
931
1093
  const rows = data.filter((r) => selected.has(keyOfRow(r)));
932
- opts.onSelectionChange(rows);
1094
+ emitIcen(root, "icen:table-selection-change", { selected: rows });
1095
+ opts.onSelectionChange?.(rows);
933
1096
  }
934
1097
  function renderAll() {
935
1098
  if (destroyed) return;
@@ -938,7 +1101,26 @@ function createTable(el, opts) {
938
1101
  }
939
1102
  rekey();
940
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
+ }
941
1121
  return {
1122
+ setPage,
1123
+ setPageSize,
942
1124
  setData(next) {
943
1125
  data = next;
944
1126
  rekey();
@@ -981,7 +1163,8 @@ function createTable(el, opts) {
981
1163
  exportCSV,
982
1164
  showColumn(key) {
983
1165
  colHidden.delete(key);
984
- 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;
985
1168
  invalidate();
986
1169
  renderAll();
987
1170
  },
@@ -1,4 +1,4 @@
1
- /** 初始化:扫描 root 下的 .date-picker[data-date-picker]。 */
2
- declare function initDatePicker(root?: ParentNode): void;
1
+ /** 返回销毁函数:摘除本次初始化容器的全部监听并复位幂等标记(可重新 init)。 */
2
+ declare function initDatePicker(root?: ParentNode): () => void;
3
3
 
4
4
  export { initDatePicker };