@icen.ai/ui 0.5.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 (138) 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 +49 -32
  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 +24 -12
  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 +104 -36
  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 +29 -22
  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 +243 -89
  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 +131 -26
  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 +13 -13
  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 +63 -26
  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/badge.css +4 -8
  86. package/dist/components/breadcrumb.css +1 -5
  87. package/dist/components/btn.css +4 -7
  88. package/dist/components/card.css +3 -6
  89. package/dist/components/chart-bar.css +10 -0
  90. package/dist/components/chart-calendar.mjs +5 -0
  91. package/dist/components/chart-gauge.css +1 -1
  92. package/dist/components/chart-heatmap.css +40 -0
  93. package/dist/components/chart-scatter.css +24 -0
  94. package/dist/components/chart-scatter.mjs +4 -0
  95. package/dist/components/charts.css +86 -1
  96. package/dist/components/charts.mjs +1 -0
  97. package/dist/components/command-palette.css +2 -2
  98. package/dist/components/content.css +8 -9
  99. package/dist/components/copy.css +8 -8
  100. package/dist/components/datatable.css +4 -8
  101. package/dist/components/date-picker.css +3 -7
  102. package/dist/components/empty.css +0 -4
  103. package/dist/components/feedback.css +10 -17
  104. package/dist/components/form.css +13 -14
  105. package/dist/components/input.css +2 -2
  106. package/dist/components/layout.css +36 -36
  107. package/dist/components/media.css +12 -8
  108. package/dist/components/menu.css +2 -2
  109. package/dist/components/modal.css +1 -1
  110. package/dist/components/nav.css +11 -6
  111. package/dist/components/notification.css +28 -28
  112. package/dist/components/pagination.css +1 -1
  113. package/dist/components/panel.css +16 -16
  114. package/dist/components/pill.css +15 -15
  115. package/dist/components/segmented.css +5 -5
  116. package/dist/components/segmented.mjs +1 -0
  117. package/dist/components/select.css +12 -20
  118. package/dist/components/sidebar.css +1 -1
  119. package/dist/components/split-pane.css +0 -1
  120. package/dist/components/stat.css +14 -18
  121. package/dist/components/steps.css +2 -2
  122. package/dist/components/switch.css +8 -12
  123. package/dist/components/table.css +46 -47
  124. package/dist/components/tabs.css +35 -46
  125. package/dist/components/tag-input.css +4 -4
  126. package/dist/components/toast.css +16 -13
  127. package/dist/components/tooltip.css +2 -2
  128. package/dist/index.d.mts +37 -0
  129. package/dist/index.mjs +7890 -1199
  130. package/dist/registry.json +113 -4
  131. package/dist/retro-effects.css +21 -17
  132. package/dist/tokens/colors.css +4 -1
  133. package/dist/tokens/retro-effects.css +21 -17
  134. package/dist/tokens/style-profiles.css +21 -0
  135. package/dist/tokens.css +25 -1
  136. package/dist/ui.css +3168 -433
  137. package/docs/spec/ai-native.md +659 -0
  138. 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 };
@@ -13,7 +13,9 @@ function findBackdrop(id) {
13
13
  }
14
14
  function getVisibleItems(panel) {
15
15
  return Array.from(
16
- panel.querySelectorAll(".command-palette-item:not([hidden]):not([disabled])")
16
+ panel.querySelectorAll(
17
+ '.command-palette-item:not([hidden]):not([disabled]):not([aria-disabled="true"])'
18
+ )
17
19
  ).filter((el) => el.offsetParent !== null || el.getClientRects().length > 0);
18
20
  }
19
21
  function isDisabledItem(el) {
@@ -55,7 +57,10 @@ function openCommandPalette(id) {
55
57
  if (openId === id) return;
56
58
  if (openId) closeCommandPalette(openId);
57
59
  const backdrop = findBackdrop(id);
58
- if (!backdrop) return;
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
+ }
59
64
  const panel = backdrop.querySelector(".command-palette");
60
65
  if (!panel) return;
61
66
  if (backdrop.parentElement !== document.body) document.body.appendChild(backdrop);
@@ -111,8 +116,7 @@ function openCommandPalette(id) {
111
116
  function closeCommandPalette(id) {
112
117
  if (typeof document === "undefined") return;
113
118
  const backdrop = findBackdrop(id);
114
- if (!backdrop) return;
115
- backdrop.hidden = true;
119
+ if (backdrop) backdrop.hidden = true;
116
120
  if (openId !== id) return;
117
121
  openId = null;
118
122
  if (keyHandler) {
@@ -123,10 +127,11 @@ function closeCommandPalette(id) {
123
127
  if (restoreFocusTo && restoreFocusTo.isConnected) restoreFocusTo.focus();
124
128
  restoreFocusTo = null;
125
129
  }
126
- function initCommandPalette(root = document) {
130
+ function initCommandPalette(root) {
127
131
  if (typeof document === "undefined") return;
128
132
  if (typeof window === "undefined") return;
129
- root.querySelectorAll("[data-command-palette-open]").forEach((el) => el.setAttribute("aria-haspopup", "dialog"));
133
+ const scope = root ?? document;
134
+ scope.querySelectorAll("[data-command-palette-open]").forEach((el) => el.setAttribute("aria-haspopup", "dialog"));
130
135
  if (initialized) return;
131
136
  initialized = true;
132
137
  document.addEventListener("click", (e) => {
@@ -159,13 +164,20 @@ function initCommandPalette(root = document) {
159
164
  if (panel) filterPanel(panel, e.target.value);
160
165
  });
161
166
  globalKeyHandler = (e) => {
162
- if ((e.metaKey || e.ctrlKey) && (e.key === "k" || e.key === "K")) {
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;
163
177
  e.preventDefault();
164
- const first = document.querySelector(".command-palette-backdrop[data-command-palette]");
165
- if (first?.dataset.commandPalette) {
166
- if (openId === first.dataset.commandPalette) closeCommandPalette(openId);
167
- else openCommandPalette(first.dataset.commandPalette);
168
- }
178
+ if (openId === id) closeCommandPalette(id);
179
+ else openCommandPalette(id);
180
+ return;
169
181
  }
170
182
  };
171
183
  window.addEventListener("keydown", globalKeyHandler);
@@ -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 };
@@ -3,6 +3,7 @@ var openPicker = null;
3
3
  var panelEl = null;
4
4
  var outsideHandler = null;
5
5
  var escHandler = null;
6
+ var repositionHandler = null;
6
7
  function pad(n) {
7
8
  return n < 10 ? "0" + n : String(n);
8
9
  }
@@ -43,7 +44,38 @@ function positionPanel(panel, trigger) {
43
44
  panel.style.left = left + "px";
44
45
  panel.style.top = top + "px";
45
46
  }
46
- var CAL_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" x2="16" y1="2" y2="6"/><line x1="8" x2="8" y1="2" y2="6"/><line x1="3" x2="21" y1="10" y2="10"/></svg>';
47
+ var SVG_NS = "http://www.w3.org/2000/svg";
48
+ function buildCalIcon() {
49
+ const svg = document.createElementNS(SVG_NS, "svg");
50
+ svg.setAttribute("viewBox", "0 0 24 24");
51
+ svg.setAttribute("fill", "none");
52
+ svg.setAttribute("stroke", "currentColor");
53
+ svg.setAttribute("stroke-width", "2");
54
+ svg.setAttribute("stroke-linecap", "round");
55
+ svg.setAttribute("stroke-linejoin", "round");
56
+ const rect = document.createElementNS(SVG_NS, "rect");
57
+ rect.setAttribute("x", "3");
58
+ rect.setAttribute("y", "4");
59
+ rect.setAttribute("width", "18");
60
+ rect.setAttribute("height", "18");
61
+ rect.setAttribute("rx", "2");
62
+ rect.setAttribute("ry", "2");
63
+ svg.appendChild(rect);
64
+ const lines = [
65
+ ["16", "16", "2", "6"],
66
+ ["8", "8", "2", "6"],
67
+ ["3", "21", "10", "10"]
68
+ ];
69
+ for (const [x1, x2, y1, y2] of lines) {
70
+ const line = document.createElementNS(SVG_NS, "line");
71
+ line.setAttribute("x1", x1);
72
+ line.setAttribute("x2", x2);
73
+ line.setAttribute("y1", y1);
74
+ line.setAttribute("y2", y2);
75
+ svg.appendChild(line);
76
+ }
77
+ return svg;
78
+ }
47
79
  var WEEK_LABELS_ZH = ["\u4E00", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D", "\u65E5"];
48
80
  var WEEK_LABELS_SUN = ["\u65E5", "\u4E00", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D"];
49
81
  function renderCalendar(panel, ctx) {
@@ -61,49 +93,74 @@ function renderCalendar(panel, ctx) {
61
93
  for (let i = 0; i < 42; i++) {
62
94
  cells.push(new Date(gridStart.getFullYear(), gridStart.getMonth(), gridStart.getDate() + i));
63
95
  }
64
- let html = `<div class="date-picker-head">
65
- <button class="date-picker-nav" data-dp-nav="-1" type="button" aria-label="\u4E0A\u4E00\u6708">\u2039</button>
66
- <span class="date-picker-title">${year} \u5E74 ${month + 1} \u6708</span>
67
- <button class="date-picker-nav" data-dp-nav="1" type="button" aria-label="\u4E0B\u4E00\u6708">\u203A</button>
68
- </div><div class="date-picker-weekdays">${labels.map((l) => `<span>${l}</span>`).join("")}</div><div class="date-picker-grid">`;
96
+ panel.textContent = "";
97
+ const head = document.createElement("div");
98
+ head.className = "date-picker-head";
99
+ const prevBtn = document.createElement("button");
100
+ prevBtn.type = "button";
101
+ prevBtn.className = "date-picker-nav";
102
+ prevBtn.setAttribute("aria-label", "\u4E0A\u4E00\u6708");
103
+ prevBtn.textContent = "\u2039";
104
+ const title = document.createElement("span");
105
+ title.className = "date-picker-title";
106
+ title.textContent = `${year} \u5E74 ${month + 1} \u6708`;
107
+ const nextBtn = document.createElement("button");
108
+ nextBtn.type = "button";
109
+ nextBtn.className = "date-picker-nav";
110
+ nextBtn.setAttribute("aria-label", "\u4E0B\u4E00\u6708");
111
+ nextBtn.textContent = "\u203A";
112
+ head.append(prevBtn, title, nextBtn);
113
+ const weekdays = document.createElement("div");
114
+ weekdays.className = "date-picker-weekdays";
115
+ for (const label of labels) {
116
+ const s = document.createElement("span");
117
+ s.textContent = label;
118
+ weekdays.appendChild(s);
119
+ }
120
+ const grid = document.createElement("div");
121
+ grid.className = "date-picker-grid";
69
122
  for (const d of cells) {
70
123
  const isOutside = d.getMonth() !== month;
71
- const ds = toDateStr(d);
72
124
  const isToday = d.getTime() === today.getTime();
73
- const isSelected = selected && ds === toDateStr(selected);
74
- const disabled = min && d < min || max && d > max;
125
+ const isSelected = selected !== null && toDateStr(d) === toDateStr(selected);
126
+ const disabled = min !== null && d < min || max !== null && d > max;
75
127
  const classes = ["date-picker-cell"];
76
128
  if (isOutside) classes.push("is-outside");
77
129
  if (isToday) classes.push("is-today");
78
130
  if (isSelected) classes.push("is-selected");
79
131
  if (disabled) classes.push("is-disabled");
80
- html += `<button type="button" class="${classes.join(" ")}" data-dp-date="${ds}"${disabled ? " disabled" : ""}>${d.getDate()}</button>`;
132
+ const btn = document.createElement("button");
133
+ btn.type = "button";
134
+ btn.className = classes.join(" ");
135
+ btn.textContent = String(d.getDate());
136
+ if (disabled) btn.disabled = true;
137
+ btn.addEventListener("click", () => onPick(d));
138
+ grid.appendChild(btn);
81
139
  }
82
- html += `</div><div class="date-picker-foot">
83
- <button type="button" class="date-picker-today" data-dp-today>\u4ECA\u5929</button>
84
- <button type="button" class="date-picker-clear" data-dp-clear>\u6E05\u9664</button>
85
- </div>`;
86
- panel.innerHTML = html;
87
- panel.querySelector('[data-dp-nav="-1"]')?.addEventListener("click", () => {
140
+ const foot = document.createElement("div");
141
+ foot.className = "date-picker-foot";
142
+ const todayBtn = document.createElement("button");
143
+ todayBtn.type = "button";
144
+ todayBtn.className = "date-picker-today";
145
+ todayBtn.textContent = "\u4ECA\u5929";
146
+ const clearBtn = document.createElement("button");
147
+ clearBtn.type = "button";
148
+ clearBtn.className = "date-picker-clear";
149
+ clearBtn.textContent = "\u6E05\u9664";
150
+ foot.append(todayBtn, clearBtn);
151
+ panel.append(head, weekdays, grid, foot);
152
+ prevBtn.addEventListener("click", () => {
88
153
  ctx.view = new Date(year, month - 1, 15);
89
154
  renderCalendar(panel, { ...ctx, view: ctx.view });
90
155
  });
91
- panel.querySelector('[data-dp-nav="1"]')?.addEventListener("click", () => {
156
+ nextBtn.addEventListener("click", () => {
92
157
  ctx.view = new Date(year, month + 1, 15);
93
158
  renderCalendar(panel, { ...ctx, view: ctx.view });
94
159
  });
95
- panel.querySelectorAll("[data-dp-date]").forEach((btn) => {
96
- btn.addEventListener("click", () => {
97
- const ds = btn.dataset.dpDate;
98
- if (!ds) return;
99
- const d = parseDateStr(ds);
100
- if (d) onPick(d);
101
- });
102
- });
103
- panel.querySelector("[data-dp-today]")?.addEventListener("click", () => {
160
+ todayBtn.addEventListener("click", () => {
104
161
  onPick(new Date(today.getFullYear(), today.getMonth(), today.getDate()));
105
162
  });
106
- panel.querySelector("[data-dp-clear]")?.addEventListener("click", () => {
163
+ clearBtn.addEventListener("click", () => {
107
164
  onPick(null);
108
165
  });
109
166
  }
@@ -120,6 +177,11 @@ function closePanel() {
120
177
  window.removeEventListener("keydown", escHandler);
121
178
  escHandler = null;
122
179
  }
180
+ if (repositionHandler) {
181
+ window.removeEventListener("scroll", repositionHandler);
182
+ window.removeEventListener("resize", repositionHandler);
183
+ repositionHandler = null;
184
+ }
123
185
  }
124
186
  function setupPicker(container) {
125
187
  const el = container;
@@ -140,7 +202,12 @@ function setupPicker(container) {
140
202
  trigger = document.createElement("button");
141
203
  trigger.type = "button";
142
204
  trigger.className = "date-picker-trigger";
143
- trigger.innerHTML = `<span class="date-picker-value${initialDate ? "" : " is-empty"}"></span><span class="date-picker-icon">${CAL_ICON}</span>`;
205
+ const value = document.createElement("span");
206
+ value.className = initialDate ? "date-picker-value" : "date-picker-value is-empty";
207
+ const icon = document.createElement("span");
208
+ icon.className = "date-picker-icon";
209
+ icon.appendChild(buildCalIcon());
210
+ trigger.append(value, icon);
144
211
  el.prepend(trigger);
145
212
  }
146
213
  trigger.setAttribute("aria-haspopup", "dialog");
@@ -169,7 +236,7 @@ function setupPicker(container) {
169
236
  const panel = ensurePanel();
170
237
  panel.hidden = false;
171
238
  const selected = hidden?.value ? parseDateStr(hidden.value) : null;
172
- const view = selected ?? /* @__PURE__ */ new Date();
239
+ const view = selected ? new Date(selected.getTime()) : /* @__PURE__ */ new Date();
173
240
  view.setDate(15);
174
241
  positionPanel(panel, trigger);
175
242
  renderCalendar(panel, {
@@ -208,19 +275,20 @@ function setupPicker(container) {
208
275
  }
209
276
  };
210
277
  window.addEventListener("keydown", escHandler);
211
- const reposition = () => {
212
- if (openPicker === el && panel && !panel.hidden) positionPanel(panel, trigger);
278
+ repositionHandler = () => {
279
+ if (openPicker === el && !panel.hidden) positionPanel(panel, trigger);
213
280
  };
214
- window.addEventListener("scroll", reposition, { passive: true, once: false });
215
- window.addEventListener("resize", reposition, { passive: true });
281
+ window.addEventListener("scroll", repositionHandler, { passive: true });
282
+ window.addEventListener("resize", repositionHandler, { passive: true });
216
283
  });
217
284
  }
218
- function initDatePicker(root = document) {
285
+ function initDatePicker(root) {
219
286
  if (typeof document === "undefined") return;
220
287
  if (typeof window === "undefined") return;
288
+ const scope = root ?? document;
221
289
  const pickers = [];
222
- if (root instanceof Element && root.matches(".date-picker[data-date-picker]")) pickers.push(root);
223
- pickers.push(...Array.from(root.querySelectorAll(".date-picker[data-date-picker]")));
290
+ if (scope instanceof Element && scope.matches(".date-picker[data-date-picker]")) pickers.push(scope);
291
+ pickers.push(...Array.from(scope.querySelectorAll(".date-picker[data-date-picker]")));
224
292
  for (const p of pickers) setupPicker(p);
225
293
  }
226
294
  export {
@@ -0,0 +1,4 @@
1
+ /** 绑定全部 [data-dropdown-trigger](document 级委托,重复调用安全)。 */
2
+ declare function initDropdown(root?: ParentNode): void;
3
+
4
+ export { initDropdown };