@icen.ai/ui 0.4.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/AGENTS.md +256 -0
  2. package/README.md +47 -13
  3. package/dist/base.css +31 -4
  4. package/dist/behaviors/accordion.d.mts +4 -0
  5. package/dist/behaviors/accordion.mjs +4 -3
  6. package/dist/behaviors/ai-chat.d.mts +57 -0
  7. package/dist/behaviors/ai-chat.mjs +557 -0
  8. package/dist/behaviors/ai-composer.d.mts +112 -0
  9. package/dist/behaviors/ai-composer.mjs +2032 -0
  10. package/dist/behaviors/ai-core.d.mts +151 -0
  11. package/dist/behaviors/ai-core.mjs +491 -0
  12. package/dist/behaviors/ai-diff.d.mts +53 -0
  13. package/dist/behaviors/ai-diff.mjs +282 -0
  14. package/dist/behaviors/ai-panel.d.mts +98 -0
  15. package/dist/behaviors/ai-panel.mjs +1012 -0
  16. package/dist/behaviors/ai-provider.d.mts +198 -0
  17. package/dist/behaviors/ai-provider.mjs +1055 -0
  18. package/dist/behaviors/ai-tool.d.mts +18 -0
  19. package/dist/behaviors/ai-tool.mjs +2125 -0
  20. package/dist/behaviors/ai-tools.d.mts +81 -0
  21. package/dist/behaviors/ai-tools.mjs +1658 -0
  22. package/dist/behaviors/back-top.d.mts +20 -0
  23. package/dist/behaviors/back-top.mjs +101 -0
  24. package/dist/behaviors/carousel.d.mts +4 -0
  25. package/dist/behaviors/carousel.mjs +34 -16
  26. package/dist/behaviors/charts.d.mts +250 -0
  27. package/dist/behaviors/charts.mjs +1301 -448
  28. package/dist/behaviors/command-palette.d.mts +8 -0
  29. package/dist/behaviors/command-palette.mjs +189 -0
  30. package/dist/behaviors/context-menu.d.mts +36 -0
  31. package/dist/behaviors/context-menu.mjs +6 -2
  32. package/dist/behaviors/copy.d.mts +4 -0
  33. package/dist/behaviors/copy.mjs +7 -6
  34. package/dist/behaviors/datatable.d.mts +78 -0
  35. package/dist/behaviors/datatable.mjs +54 -11
  36. package/dist/behaviors/date-picker.d.mts +4 -0
  37. package/dist/behaviors/date-picker.mjs +296 -0
  38. package/dist/behaviors/dropdown.d.mts +4 -0
  39. package/dist/behaviors/dropdown.mjs +7 -3
  40. package/dist/behaviors/input.d.mts +4 -0
  41. package/dist/behaviors/input.mjs +184 -8
  42. package/dist/behaviors/modal.d.mts +11 -0
  43. package/dist/behaviors/modal.mjs +11 -6
  44. package/dist/behaviors/nav.d.mts +7 -0
  45. package/dist/behaviors/nav.mjs +25 -9
  46. package/dist/behaviors/notification.d.mts +172 -0
  47. package/dist/behaviors/notification.mjs +908 -0
  48. package/dist/behaviors/popover.d.mts +46 -0
  49. package/dist/behaviors/popover.mjs +2 -1
  50. package/dist/behaviors/select.d.mts +4 -0
  51. package/dist/behaviors/select.mjs +268 -34
  52. package/dist/behaviors/sidebar.d.mts +4 -0
  53. package/dist/behaviors/sidebar.mjs +4 -3
  54. package/dist/behaviors/slider.d.mts +4 -0
  55. package/dist/behaviors/slider.mjs +4 -3
  56. package/dist/behaviors/split-pane.d.mts +4 -0
  57. package/dist/behaviors/split-pane.mjs +102 -0
  58. package/dist/behaviors/tabs.d.mts +8 -0
  59. package/dist/behaviors/tabs.mjs +44 -19
  60. package/dist/behaviors/tag-input.d.mts +4 -0
  61. package/dist/behaviors/tag-input.mjs +12 -3
  62. package/dist/behaviors/theme.d.mts +25 -0
  63. package/dist/behaviors/theme.mjs +3 -1
  64. package/dist/behaviors/toast.d.mts +26 -0
  65. package/dist/behaviors/toast.mjs +24 -18
  66. package/dist/behaviors/tree.d.mts +4 -0
  67. package/dist/behaviors/tree.mjs +4 -3
  68. package/dist/behaviors/upload.d.mts +4 -0
  69. package/dist/behaviors/upload.mjs +205 -17
  70. package/dist/components/ai-chat.css +1037 -0
  71. package/dist/components/ai-chat.mjs +3 -0
  72. package/dist/components/ai-composer.mjs +3 -0
  73. package/dist/components/ai-context.mjs +2 -0
  74. package/dist/components/ai-diff.css +311 -0
  75. package/dist/components/ai-diff.mjs +3 -0
  76. package/dist/components/ai-files.mjs +3 -0
  77. package/dist/components/ai-message.mjs +3 -0
  78. package/dist/components/ai-panel.css +682 -0
  79. package/dist/components/ai-reasoning.mjs +3 -0
  80. package/dist/components/ai-subagent.mjs +2 -0
  81. package/dist/components/ai-todo.mjs +2 -0
  82. package/dist/components/ai-tool-call.mjs +2 -0
  83. package/dist/components/ai-tool.css +532 -0
  84. package/dist/components/ai-usage.mjs +3 -0
  85. package/dist/components/back-top.css +88 -0
  86. package/dist/components/back-top.mjs +3 -0
  87. package/dist/components/badge.css +127 -0
  88. package/dist/components/badge.mjs +2 -0
  89. package/dist/components/breadcrumb.css +53 -0
  90. package/dist/components/btn.css +4 -7
  91. package/dist/components/card.css +66 -1
  92. package/dist/components/chart-bar.css +10 -0
  93. package/dist/components/chart-calendar.mjs +5 -0
  94. package/dist/components/chart-gauge.css +1 -1
  95. package/dist/components/chart-heatmap.css +40 -0
  96. package/dist/components/chart-scatter.css +24 -0
  97. package/dist/components/chart-scatter.mjs +4 -0
  98. package/dist/components/charts.css +86 -1
  99. package/dist/components/charts.mjs +1 -0
  100. package/dist/components/command-palette.css +246 -0
  101. package/dist/components/command-palette.mjs +3 -0
  102. package/dist/components/content.css +221 -8
  103. package/dist/components/copy.css +93 -0
  104. package/dist/components/copy.mjs +3 -0
  105. package/dist/components/datatable.css +4 -8
  106. package/dist/components/date-picker.css +240 -0
  107. package/dist/components/date-picker.mjs +3 -0
  108. package/dist/components/empty.css +69 -3
  109. package/dist/components/feedback.css +241 -10
  110. package/dist/components/form.css +129 -11
  111. package/dist/components/form.mjs +2 -0
  112. package/dist/components/input.css +25 -2
  113. package/dist/components/layout.css +36 -36
  114. package/dist/components/media.css +153 -6
  115. package/dist/components/menu.css +2 -2
  116. package/dist/components/modal.css +1 -1
  117. package/dist/components/nav.css +106 -2
  118. package/dist/components/notification.css +387 -0
  119. package/dist/components/notification.mjs +3 -0
  120. package/dist/components/pagination.css +1 -1
  121. package/dist/components/panel.css +104 -9
  122. package/dist/components/pill.css +82 -12
  123. package/dist/components/scroll-area.css +112 -0
  124. package/dist/components/scroll-area.mjs +2 -0
  125. package/dist/components/segmented.css +80 -5
  126. package/dist/components/segmented.mjs +1 -0
  127. package/dist/components/select.css +104 -17
  128. package/dist/components/sidebar.css +119 -1
  129. package/dist/components/slider.css +113 -0
  130. package/dist/components/split-pane.css +111 -0
  131. package/dist/components/split-pane.mjs +3 -0
  132. package/dist/components/stat.css +131 -12
  133. package/dist/components/steps.css +98 -1
  134. package/dist/components/switch.css +145 -7
  135. package/dist/components/table.css +111 -29
  136. package/dist/components/tabs.css +132 -22
  137. package/dist/components/tag-input.css +4 -4
  138. package/dist/components/tag.css +164 -0
  139. package/dist/components/tag.mjs +2 -0
  140. package/dist/components/toast.css +122 -5
  141. package/dist/components/toolbar.css +101 -0
  142. package/dist/components/toolbar.mjs +2 -0
  143. package/dist/components/tooltip.css +2 -2
  144. package/dist/components/upload.css +61 -4
  145. package/dist/index.d.mts +37 -0
  146. package/dist/index.mjs +9409 -929
  147. package/dist/registry.json +176 -4
  148. package/dist/retro-effects.css +21 -17
  149. package/dist/tokens/colors.css +4 -1
  150. package/dist/tokens/retro-effects.css +21 -17
  151. package/dist/tokens/style-profiles.css +21 -0
  152. package/dist/tokens.css +25 -1
  153. package/dist/ui.css +9616 -2839
  154. package/docs/spec/ai-native.md +659 -0
  155. package/package.json +15 -5
@@ -0,0 +1,8 @@
1
+ /** 打开指定 id 的命令面板。 */
2
+ declare function openCommandPalette(id: string): void;
3
+ /** 关闭指定 id 的命令面板(backdrop 已被外部移除时也照常复位状态/监听/滚动锁)。 */
4
+ declare function closeCommandPalette(id: string): void;
5
+ /** 初始化:委托监听触发器 / 搜索 / 外点 / 全局快捷键。 */
6
+ declare function initCommandPalette(root?: ParentNode): void;
7
+
8
+ export { closeCommandPalette, initCommandPalette, openCommandPalette };
@@ -0,0 +1,189 @@
1
+ // src/behaviors/command-palette.ts
2
+ var FOCUSABLE = 'button:not([disabled]), [href], input:not([disabled]), [tabindex]:not([tabindex="-1"])';
3
+ var openId = null;
4
+ var restoreFocusTo = null;
5
+ var prevBodyOverflow = "";
6
+ var keyHandler = null;
7
+ var globalKeyHandler = null;
8
+ var initialized = false;
9
+ function findBackdrop(id) {
10
+ return document.querySelector(
11
+ `.command-palette-backdrop[data-command-palette="${CSS.escape(id)}"]`
12
+ );
13
+ }
14
+ function getVisibleItems(panel) {
15
+ return Array.from(
16
+ panel.querySelectorAll(
17
+ '.command-palette-item:not([hidden]):not([disabled]):not([aria-disabled="true"])'
18
+ )
19
+ ).filter((el) => el.offsetParent !== null || el.getClientRects().length > 0);
20
+ }
21
+ function isDisabledItem(el) {
22
+ return el.hasAttribute("disabled") || el.getAttribute("aria-disabled") === "true";
23
+ }
24
+ function setActive(panel, idx) {
25
+ const items = getVisibleItems(panel);
26
+ items.forEach((el, i) => el.classList.toggle("is-active", i === idx));
27
+ const active = items[idx];
28
+ if (active && typeof active.scrollIntoView === "function") {
29
+ active.scrollIntoView({ block: "nearest" });
30
+ }
31
+ }
32
+ function filterPanel(panel, query) {
33
+ const q = query.trim().toLowerCase();
34
+ let visibleCount = 0;
35
+ panel.querySelectorAll(".command-palette-item").forEach((item) => {
36
+ if (isDisabledItem(item)) return;
37
+ const label = (item.querySelector(".command-palette-item-label")?.textContent ?? "").toLowerCase();
38
+ const hint = (item.querySelector(".command-palette-item-hint")?.textContent ?? "").toLowerCase();
39
+ const keyword = (item.dataset.commandPaletteKeyword ?? "").toLowerCase();
40
+ const match = !q || label.includes(q) || hint.includes(q) || keyword.includes(q);
41
+ item.hidden = !match;
42
+ item.classList.remove("is-active");
43
+ if (match) visibleCount++;
44
+ });
45
+ panel.querySelectorAll(".command-palette-group").forEach((group) => {
46
+ const hasVisible = Array.from(group.querySelectorAll(".command-palette-item")).some(
47
+ (it) => !it.hidden
48
+ );
49
+ group.hidden = !hasVisible;
50
+ });
51
+ const empty = panel.querySelector(".command-palette-empty");
52
+ if (empty) empty.hidden = visibleCount !== 0;
53
+ if (visibleCount > 0) setActive(panel, 0);
54
+ }
55
+ function openCommandPalette(id) {
56
+ if (typeof document === "undefined") return;
57
+ if (openId === id) return;
58
+ if (openId) closeCommandPalette(openId);
59
+ const backdrop = findBackdrop(id);
60
+ if (!backdrop) {
61
+ console.warn(`[@icen.ai/ui] command-palette: \u672A\u627E\u5230 data-command-palette="${id}" \u7684 .command-palette-backdrop\uFF0C\u5DF2\u8DF3\u8FC7\u6253\u5F00`);
62
+ return;
63
+ }
64
+ const panel = backdrop.querySelector(".command-palette");
65
+ if (!panel) return;
66
+ if (backdrop.parentElement !== document.body) document.body.appendChild(backdrop);
67
+ restoreFocusTo = document.activeElement instanceof HTMLElement ? document.activeElement : null;
68
+ openId = id;
69
+ backdrop.hidden = false;
70
+ prevBodyOverflow = document.body.style.overflow;
71
+ document.body.style.overflow = "hidden";
72
+ const input = panel.querySelector(".command-palette-input");
73
+ if (input) {
74
+ input.value = "";
75
+ input.focus();
76
+ } else {
77
+ panel.focus();
78
+ }
79
+ filterPanel(panel, "");
80
+ keyHandler = (e) => {
81
+ if (e.key === "Escape") {
82
+ e.preventDefault();
83
+ closeCommandPalette(id);
84
+ return;
85
+ }
86
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
87
+ e.preventDefault();
88
+ const items = getVisibleItems(panel);
89
+ if (items.length === 0) return;
90
+ const cur = items.findIndex((el) => el.classList.contains("is-active"));
91
+ const next = e.key === "ArrowDown" ? (cur + 1) % items.length : (cur - 1 + items.length) % items.length;
92
+ setActive(panel, next);
93
+ return;
94
+ }
95
+ if (e.key === "Enter") {
96
+ const active = panel.querySelector(".command-palette-item.is-active");
97
+ if (active && !isDisabledItem(active)) {
98
+ e.preventDefault();
99
+ active.click();
100
+ }
101
+ return;
102
+ }
103
+ if (e.key === "Tab") {
104
+ e.preventDefault();
105
+ const els = Array.from(panel.querySelectorAll(FOCUSABLE)).filter(
106
+ (el) => !el.hidden && el.offsetParent !== null
107
+ );
108
+ if (els.length === 0) return;
109
+ const cur = els.findIndex((el) => el === document.activeElement);
110
+ const next = e.shiftKey ? (cur - 1 + els.length) % els.length : (cur + 1) % els.length;
111
+ els[next]?.focus();
112
+ }
113
+ };
114
+ window.addEventListener("keydown", keyHandler);
115
+ }
116
+ function closeCommandPalette(id) {
117
+ if (typeof document === "undefined") return;
118
+ const backdrop = findBackdrop(id);
119
+ if (backdrop) backdrop.hidden = true;
120
+ if (openId !== id) return;
121
+ openId = null;
122
+ if (keyHandler) {
123
+ window.removeEventListener("keydown", keyHandler);
124
+ keyHandler = null;
125
+ }
126
+ document.body.style.overflow = prevBodyOverflow;
127
+ if (restoreFocusTo && restoreFocusTo.isConnected) restoreFocusTo.focus();
128
+ restoreFocusTo = null;
129
+ }
130
+ function initCommandPalette(root) {
131
+ if (typeof document === "undefined") return;
132
+ if (typeof window === "undefined") return;
133
+ const scope = root ?? document;
134
+ scope.querySelectorAll("[data-command-palette-open]").forEach((el) => el.setAttribute("aria-haspopup", "dialog"));
135
+ if (initialized) return;
136
+ initialized = true;
137
+ document.addEventListener("click", (e) => {
138
+ const target = e.target instanceof Element ? e.target : null;
139
+ if (!target) return;
140
+ const opener = target.closest("[data-command-palette-open]");
141
+ if (opener?.dataset.commandPaletteOpen) {
142
+ openCommandPalette(opener.dataset.commandPaletteOpen);
143
+ return;
144
+ }
145
+ const item = target.closest(".command-palette-item");
146
+ if (item) {
147
+ const backdrop = item.closest("[data-command-palette]");
148
+ if (backdrop?.dataset.commandPalette) {
149
+ if (!item.hasAttribute("data-command-palette-keep-open")) {
150
+ closeCommandPalette(backdrop.dataset.commandPalette);
151
+ }
152
+ }
153
+ return;
154
+ }
155
+ if (target instanceof HTMLElement && target.classList.contains("command-palette-backdrop") && target.dataset.commandPalette) {
156
+ closeCommandPalette(target.dataset.commandPalette);
157
+ }
158
+ });
159
+ document.addEventListener("input", (e) => {
160
+ if (!(e.target instanceof HTMLElement)) return;
161
+ if (!e.target.matches(".command-palette-input")) return;
162
+ const backdrop = e.target.closest("[data-command-palette]");
163
+ const panel = backdrop?.querySelector(".command-palette");
164
+ if (panel) filterPanel(panel, e.target.value);
165
+ });
166
+ globalKeyHandler = (e) => {
167
+ if (!(e.metaKey || e.ctrlKey)) return;
168
+ const key = e.key.toLowerCase();
169
+ const palettes = document.querySelectorAll(
170
+ ".command-palette-backdrop[data-command-palette]"
171
+ );
172
+ for (const backdrop of Array.from(palettes)) {
173
+ const paletteKey = (backdrop.dataset.commandPaletteKey ?? "k").toLowerCase();
174
+ if (paletteKey !== key) continue;
175
+ const id = backdrop.dataset.commandPalette;
176
+ if (!id) continue;
177
+ e.preventDefault();
178
+ if (openId === id) closeCommandPalette(id);
179
+ else openCommandPalette(id);
180
+ return;
181
+ }
182
+ };
183
+ window.addEventListener("keydown", globalKeyHandler);
184
+ }
185
+ export {
186
+ closeCommandPalette,
187
+ initCommandPalette,
188
+ openCommandPalette
189
+ };
@@ -0,0 +1,36 @@
1
+ interface ContextMenuEntry {
2
+ label: string;
3
+ subLabel?: string;
4
+ /** 可信 svg 字符串(唯一走 innerHTML 的字段) */
5
+ icon?: string;
6
+ shortcut?: string;
7
+ danger?: boolean;
8
+ disabled?: boolean;
9
+ onClick?: () => void;
10
+ }
11
+ interface ContextMenuSeparator {
12
+ type: 'separator';
13
+ }
14
+ interface ContextMenuHeader {
15
+ type: 'header';
16
+ label: string;
17
+ description?: string;
18
+ }
19
+ type ContextMenuItem = ContextMenuEntry | ContextMenuSeparator | ContextMenuHeader;
20
+ interface ContextMenuOptions {
21
+ /** 替换内置默认菜单(未命中任何注册 id 时使用) */
22
+ items?: ContextMenuItem[];
23
+ }
24
+ /** 注册指定 id 的右键菜单项(对应 DOM 中的 [data-context-menu="id"])。 */
25
+ declare function registerContextMenu(id: string, items: ContextMenuItem[]): void;
26
+ /** 在 (x, y) 打开右键菜单(视口边缘自动翻转并 clamp)。 */
27
+ declare function openContextMenu(x: number, y: number, items: ContextMenuItem[]): void;
28
+ /** 关闭当前右键菜单(未打开时为 no-op)。 */
29
+ declare function closeMenu(): void;
30
+ /**
31
+ * 启动全局右键菜单(重复调用安全)。
32
+ * opts.items 提供时替换内置默认菜单。
33
+ */
34
+ declare function initContextMenu(opts?: ContextMenuOptions): void;
35
+
36
+ export { type ContextMenuEntry, type ContextMenuHeader, type ContextMenuItem, type ContextMenuOptions, type ContextMenuSeparator, closeMenu, initContextMenu, openContextMenu, registerContextMenu };
@@ -127,7 +127,7 @@ function openContextMenu(x, y, items) {
127
127
  setFocused(0);
128
128
  });
129
129
  document.addEventListener("pointerdown", onOutsidePointerDown, true);
130
- document.addEventListener("scroll", onCloseSignal, true);
130
+ document.addEventListener("scroll", onScrollSignal, true);
131
131
  window.addEventListener("resize", onCloseSignal);
132
132
  window.addEventListener("keydown", onKeyDown);
133
133
  }
@@ -138,7 +138,7 @@ function closeMenu() {
138
138
  focusableItems = [];
139
139
  focusedIndex = -1;
140
140
  document.removeEventListener("pointerdown", onOutsidePointerDown, true);
141
- document.removeEventListener("scroll", onCloseSignal, true);
141
+ document.removeEventListener("scroll", onScrollSignal, true);
142
142
  window.removeEventListener("resize", onCloseSignal);
143
143
  window.removeEventListener("keydown", onKeyDown);
144
144
  }
@@ -152,6 +152,10 @@ function onOutsidePointerDown(e) {
152
152
  const target = e.target;
153
153
  if (!(target instanceof Node) || !panel.contains(target)) closeMenu();
154
154
  }
155
+ function onScrollSignal(e) {
156
+ if (panel && e.target instanceof Node && panel.contains(e.target)) return;
157
+ closeMenu();
158
+ }
155
159
  function onCloseSignal() {
156
160
  closeMenu();
157
161
  }
@@ -0,0 +1,4 @@
1
+ /** 委托监听 click;同一 root(含默认的 document)重复调用幂等。 */
2
+ declare function initCopy(root?: ParentNode): void;
3
+
4
+ export { initCopy };
@@ -34,10 +34,10 @@ function flashDone(btn) {
34
34
  const iconOnly = btn.hasAttribute("data-copy-icon");
35
35
  const original = prev ? prev.original : iconOnly ? null : btn.textContent;
36
36
  if (!iconOnly) btn.textContent = "\u5DF2\u590D\u5236";
37
- btn.classList.add("done");
37
+ btn.classList.add("is-done");
38
38
  const timer = window.setTimeout(() => {
39
39
  if (!iconOnly) btn.textContent = original;
40
- btn.classList.remove("done");
40
+ btn.classList.remove("is-done");
41
41
  flashing.delete(btn);
42
42
  }, 1400);
43
43
  flashing.set(btn, { original, timer });
@@ -52,11 +52,12 @@ function onClick(root, ev) {
52
52
  if (ok) flashDone(btn);
53
53
  });
54
54
  }
55
- function initCopy(root = document) {
55
+ function initCopy(root) {
56
56
  if (typeof document === "undefined") return;
57
- if (initialized.has(root)) return;
58
- initialized.add(root);
59
- root.addEventListener("click", (ev) => onClick(root, ev));
57
+ const scope = root ?? document;
58
+ if (initialized.has(scope)) return;
59
+ initialized.add(scope);
60
+ scope.addEventListener("click", (ev) => onClick(scope, ev));
60
61
  }
61
62
  export {
62
63
  initCopy
@@ -0,0 +1,78 @@
1
+ import { ContextMenuItem } from './context-menu.js';
2
+
3
+ interface TableColumn<Row = Record<string, unknown>> {
4
+ key: string;
5
+ title: string;
6
+ /** CSS 宽度:'72px' / '120px' / '2fr';缺省 minmax(0,1fr) */
7
+ width?: string;
8
+ align?: 'left' | 'center' | 'right';
9
+ sortable?: boolean;
10
+ /** 自定义比较(默认:数字感知的 localeCompare) */
11
+ sortFn?: (a: Row, b: Row) => number;
12
+ filterable?: boolean;
13
+ /** 返回 Node 可嵌套任意组件;返回 string/number 走 textContent(安全) */
14
+ render?: (value: unknown, row: Row, rowIndex: number) => Node | string | number | null;
15
+ hidden?: boolean;
16
+ /** 冻结列(左固定,横向滚动时不跟随) */
17
+ frozen?: boolean;
18
+ }
19
+ interface TableOptions<Row = Record<string, unknown>> {
20
+ columns: TableColumn<Row>[];
21
+ data: Row[];
22
+ /** 行唯一键:字段名或函数(多选/展开跨页保持的依据),缺省用行索引 */
23
+ rowKey?: keyof Row | ((row: Row) => string);
24
+ searchable?: boolean;
25
+ searchPlaceholder?: string;
26
+ selectable?: boolean;
27
+ /** 分页(默认开启,pageSize 默认 10 条/页;传 false 关闭;virtual 时忽略) */
28
+ pagination?: boolean;
29
+ pageSize?: number;
30
+ pageSizes?: number[];
31
+ /** 虚拟滚动(大数据量;与 pagination 互斥,virtual 优先) */
32
+ virtual?: boolean;
33
+ /** 虚拟行高 px(默认 44)与窗口高度(默认 '420px') */
34
+ rowHeight?: number;
35
+ height?: string;
36
+ /** 行展开内容(虚拟模式下忽略) */
37
+ expandable?: (row: Row) => Node | string;
38
+ /** 行右键菜单(复用 context-menu;需 menu.css,kit 入口已带) */
39
+ contextMenu?: (row: Row) => ContextMenuItem[];
40
+ striped?: boolean;
41
+ emptyLabel?: string;
42
+ loading?: boolean;
43
+ /** 密度:normal 默认 44px 行高 / compact 紧凑 32px */
44
+ density?: 'normal' | 'compact';
45
+ /** 列宽可拖拽调整(默认 true) */
46
+ resizable?: boolean;
47
+ /** 工具栏列显隐开关(默认 false) */
48
+ columnVisibility?: boolean;
49
+ /** 导出 CSV 按钮显示在工具栏(默认 false) */
50
+ exportable?: boolean;
51
+ /** 行自定义 class(条件着色 / 状态行) */
52
+ rowClassName?: (row: Row, index: number) => string;
53
+ onSelectionChange?: (rows: Row[]) => void;
54
+ onRowClick?: (row: Row) => void;
55
+ onSortChange?: (key: string | null, dir: 'asc' | 'desc') => void;
56
+ }
57
+ interface TableHandle<Row = Record<string, unknown>> {
58
+ setData(data: Row[]): void;
59
+ getSelected(): Row[];
60
+ clearSelection(): void;
61
+ setSearch(query: string): void;
62
+ /** values=null 清除该列筛选 */
63
+ setFilter(key: string, values: string[] | null): void;
64
+ setSort(key: string | null, dir?: 'asc' | 'desc'): void;
65
+ /** 获取当前管线(搜索→筛选→排序)后的数据 */
66
+ getData(): Row[];
67
+ /** 导出当前管线数据为 CSV 并触发下载 */
68
+ exportCSV(filename?: string): void;
69
+ /** 显示/隐藏列 */
70
+ showColumn(key: string): void;
71
+ hideColumn(key: string): void;
72
+ /** 数据被外部原地修改后重渲 */
73
+ refresh(): void;
74
+ destroy(): void;
75
+ }
76
+ declare function createTable<Row = Record<string, unknown>>(el: HTMLElement, opts: TableOptions<Row>): TableHandle<Row>;
77
+
78
+ export { type TableColumn, type TableHandle, type TableOptions, createTable };
@@ -87,7 +87,7 @@ function openContextMenu(x, y, items) {
87
87
  setFocused(0);
88
88
  });
89
89
  document.addEventListener("pointerdown", onOutsidePointerDown, true);
90
- document.addEventListener("scroll", onCloseSignal, true);
90
+ document.addEventListener("scroll", onScrollSignal, true);
91
91
  window.addEventListener("resize", onCloseSignal);
92
92
  window.addEventListener("keydown", onKeyDown);
93
93
  }
@@ -98,7 +98,7 @@ function closeMenu() {
98
98
  focusableItems = [];
99
99
  focusedIndex = -1;
100
100
  document.removeEventListener("pointerdown", onOutsidePointerDown, true);
101
- document.removeEventListener("scroll", onCloseSignal, true);
101
+ document.removeEventListener("scroll", onScrollSignal, true);
102
102
  window.removeEventListener("resize", onCloseSignal);
103
103
  window.removeEventListener("keydown", onKeyDown);
104
104
  }
@@ -112,6 +112,10 @@ function onOutsidePointerDown(e) {
112
112
  const target = e.target;
113
113
  if (!(target instanceof Node) || !panel.contains(target)) closeMenu();
114
114
  }
115
+ function onScrollSignal(e) {
116
+ if (panel && e.target instanceof Node && panel.contains(e.target)) return;
117
+ closeMenu();
118
+ }
115
119
  function onCloseSignal() {
116
120
  closeMenu();
117
121
  }
@@ -410,7 +414,7 @@ function createTable(el, opts) {
410
414
  else allKeys.forEach((k) => selected.delete(k));
411
415
  emitSelection();
412
416
  renderBody();
413
- renderHead();
417
+ syncHeadState();
414
418
  });
415
419
  cell.appendChild(box);
416
420
  headRow.appendChild(cell);
@@ -466,6 +470,7 @@ function createTable(el, opts) {
466
470
  const btn = document.createElement("button");
467
471
  btn.type = "button";
468
472
  btn.className = "dt-filter-btn";
473
+ btn.dataset.colKey = col.key;
469
474
  if (hasFilter) btn.classList.add("is-active");
470
475
  btn.setAttribute("aria-label", `\u7B5B\u9009\u300C${col.title}\u300D`);
471
476
  btn.appendChild(svgIcon(ICON_FUNNEL, 12));
@@ -491,6 +496,33 @@ function createTable(el, opts) {
491
496
  headRow.appendChild(cell);
492
497
  }
493
498
  }
499
+ function syncHeadState() {
500
+ const headBox = headRow.querySelector(".dt-cell--check .dt-check");
501
+ if (headBox) {
502
+ const allKeys = pipeline().map((r) => keyOfRow(r));
503
+ const selCount = allKeys.filter((k) => selected.has(k)).length;
504
+ headBox.checked = selCount > 0 && selCount === allKeys.length;
505
+ headBox.indeterminate = selCount > 0 && selCount < allKeys.length;
506
+ }
507
+ headRow.querySelectorAll(".dt-filter-btn").forEach((b) => {
508
+ const key = b.dataset.colKey;
509
+ if (key) b.classList.toggle("is-active", filters.has(key));
510
+ });
511
+ }
512
+ function renderHeadKeepPanel() {
513
+ const keep = openPanel;
514
+ renderHead();
515
+ if (!keep) return;
516
+ openPanel = keep;
517
+ document.body.appendChild(keep.el);
518
+ window.addEventListener("scroll", onPanelScroll, true);
519
+ window.addEventListener("resize", onPanelScroll);
520
+ if (keep.key !== "__cols") {
521
+ headRow.querySelectorAll(".dt-filter-btn").forEach((b) => {
522
+ if (b.dataset.colKey === keep.key) b.classList.add("is-open");
523
+ });
524
+ }
525
+ }
494
526
  function attachResize(handle, col) {
495
527
  handle.addEventListener("pointerdown", (ev) => {
496
528
  ev.preventDefault();
@@ -508,12 +540,17 @@ function createTable(el, opts) {
508
540
  });
509
541
  };
510
542
  const onUp = (e) => {
511
- handle.releasePointerCapture(e.pointerId);
543
+ try {
544
+ handle.releasePointerCapture(e.pointerId);
545
+ } catch {
546
+ }
512
547
  handle.removeEventListener("pointermove", onMove);
513
548
  handle.removeEventListener("pointerup", onUp);
549
+ handle.removeEventListener("pointercancel", onUp);
514
550
  };
515
551
  handle.addEventListener("pointermove", onMove);
516
552
  handle.addEventListener("pointerup", onUp);
553
+ handle.addEventListener("pointercancel", onUp);
517
554
  });
518
555
  }
519
556
  function openColumnPanel(btn) {
@@ -533,7 +570,8 @@ function createTable(el, opts) {
533
570
  if (box.checked) colHidden.delete(col.key);
534
571
  else colHidden.set(col.key, true);
535
572
  invalidate();
536
- renderAll();
573
+ renderBody();
574
+ renderHeadKeepPanel();
537
575
  });
538
576
  const text = document.createElement("span");
539
577
  text.textContent = col.title;
@@ -569,7 +607,7 @@ function createTable(el, opts) {
569
607
  a.href = url;
570
608
  a.download = filename ?? "export.csv";
571
609
  a.click();
572
- URL.revokeObjectURL(url);
610
+ window.setTimeout(() => URL.revokeObjectURL(url), 1e3);
573
611
  }
574
612
  function csvEscape(s) {
575
613
  if (/[",\r\n]/.test(s)) return `"${s.replace(/"/g, '""')}"`;
@@ -595,7 +633,8 @@ function createTable(el, opts) {
595
633
  else filters.set(col.key, cur);
596
634
  invalidate();
597
635
  page = 1;
598
- renderAll();
636
+ renderBody();
637
+ syncHeadState();
599
638
  });
600
639
  const text = document.createElement("span");
601
640
  text.textContent = v === "" ? "\uFF08\u7A7A\uFF09" : v;
@@ -612,8 +651,11 @@ function createTable(el, opts) {
612
651
  filters.delete(col.key);
613
652
  invalidate();
614
653
  page = 1;
615
- closeFilterPanel();
616
- renderAll();
654
+ list.querySelectorAll(".dt-check").forEach((b) => {
655
+ b.checked = true;
656
+ });
657
+ renderBody();
658
+ syncHeadState();
617
659
  });
618
660
  footRow.appendChild(reset);
619
661
  panel2.append(list, footRow);
@@ -661,7 +703,7 @@ function createTable(el, opts) {
661
703
  }
662
704
  lastClickedIndex = rowIndex;
663
705
  emitSelection();
664
- renderHead();
706
+ syncHeadState();
665
707
  renderFoot(pipeline().length);
666
708
  });
667
709
  cell.appendChild(box);
@@ -797,7 +839,8 @@ function createTable(el, opts) {
797
839
  clear.addEventListener("click", () => {
798
840
  selected.clear();
799
841
  emitSelection();
800
- renderAll();
842
+ renderBody();
843
+ syncHeadState();
801
844
  });
802
845
  sel.appendChild(clear);
803
846
  foot.appendChild(sel);
@@ -0,0 +1,4 @@
1
+ /** 初始化:扫描 root 下的 .date-picker[data-date-picker]。 */
2
+ declare function initDatePicker(root?: ParentNode): void;
3
+
4
+ export { initDatePicker };