@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.
- package/AGENTS.md +256 -0
- package/README.md +47 -13
- package/dist/base.css +31 -4
- package/dist/behaviors/accordion.d.mts +4 -0
- package/dist/behaviors/accordion.mjs +4 -3
- package/dist/behaviors/ai-chat.d.mts +57 -0
- package/dist/behaviors/ai-chat.mjs +557 -0
- package/dist/behaviors/ai-composer.d.mts +112 -0
- package/dist/behaviors/ai-composer.mjs +2032 -0
- package/dist/behaviors/ai-core.d.mts +151 -0
- package/dist/behaviors/ai-core.mjs +491 -0
- package/dist/behaviors/ai-diff.d.mts +53 -0
- package/dist/behaviors/ai-diff.mjs +282 -0
- package/dist/behaviors/ai-panel.d.mts +98 -0
- package/dist/behaviors/ai-panel.mjs +1012 -0
- package/dist/behaviors/ai-provider.d.mts +198 -0
- package/dist/behaviors/ai-provider.mjs +1055 -0
- package/dist/behaviors/ai-tool.d.mts +18 -0
- package/dist/behaviors/ai-tool.mjs +2125 -0
- package/dist/behaviors/ai-tools.d.mts +81 -0
- package/dist/behaviors/ai-tools.mjs +1658 -0
- package/dist/behaviors/back-top.d.mts +20 -0
- package/dist/behaviors/back-top.mjs +49 -32
- package/dist/behaviors/carousel.d.mts +4 -0
- package/dist/behaviors/carousel.mjs +34 -16
- package/dist/behaviors/charts.d.mts +250 -0
- package/dist/behaviors/charts.mjs +1301 -448
- package/dist/behaviors/command-palette.d.mts +8 -0
- package/dist/behaviors/command-palette.mjs +24 -12
- package/dist/behaviors/context-menu.d.mts +36 -0
- package/dist/behaviors/context-menu.mjs +6 -2
- package/dist/behaviors/copy.d.mts +4 -0
- package/dist/behaviors/copy.mjs +7 -6
- package/dist/behaviors/datatable.d.mts +78 -0
- package/dist/behaviors/datatable.mjs +54 -11
- package/dist/behaviors/date-picker.d.mts +4 -0
- package/dist/behaviors/date-picker.mjs +104 -36
- package/dist/behaviors/dropdown.d.mts +4 -0
- package/dist/behaviors/dropdown.mjs +7 -3
- package/dist/behaviors/input.d.mts +4 -0
- package/dist/behaviors/input.mjs +29 -22
- package/dist/behaviors/modal.d.mts +11 -0
- package/dist/behaviors/modal.mjs +11 -6
- package/dist/behaviors/nav.d.mts +7 -0
- package/dist/behaviors/nav.mjs +25 -9
- package/dist/behaviors/notification.d.mts +172 -0
- package/dist/behaviors/notification.mjs +243 -89
- package/dist/behaviors/popover.d.mts +46 -0
- package/dist/behaviors/popover.mjs +2 -1
- package/dist/behaviors/select.d.mts +4 -0
- package/dist/behaviors/select.mjs +131 -26
- package/dist/behaviors/sidebar.d.mts +4 -0
- package/dist/behaviors/sidebar.mjs +4 -3
- package/dist/behaviors/slider.d.mts +4 -0
- package/dist/behaviors/slider.mjs +4 -3
- package/dist/behaviors/split-pane.d.mts +4 -0
- package/dist/behaviors/split-pane.mjs +13 -13
- package/dist/behaviors/tabs.d.mts +8 -0
- package/dist/behaviors/tabs.mjs +44 -19
- package/dist/behaviors/tag-input.d.mts +4 -0
- package/dist/behaviors/tag-input.mjs +12 -3
- package/dist/behaviors/theme.d.mts +25 -0
- package/dist/behaviors/theme.mjs +3 -1
- package/dist/behaviors/toast.d.mts +26 -0
- package/dist/behaviors/toast.mjs +24 -18
- package/dist/behaviors/tree.d.mts +4 -0
- package/dist/behaviors/tree.mjs +4 -3
- package/dist/behaviors/upload.d.mts +4 -0
- package/dist/behaviors/upload.mjs +63 -26
- package/dist/components/ai-chat.css +1037 -0
- package/dist/components/ai-chat.mjs +3 -0
- package/dist/components/ai-composer.mjs +3 -0
- package/dist/components/ai-context.mjs +2 -0
- package/dist/components/ai-diff.css +311 -0
- package/dist/components/ai-diff.mjs +3 -0
- package/dist/components/ai-files.mjs +3 -0
- package/dist/components/ai-message.mjs +3 -0
- package/dist/components/ai-panel.css +682 -0
- package/dist/components/ai-reasoning.mjs +3 -0
- package/dist/components/ai-subagent.mjs +2 -0
- package/dist/components/ai-todo.mjs +2 -0
- package/dist/components/ai-tool-call.mjs +2 -0
- package/dist/components/ai-tool.css +532 -0
- package/dist/components/ai-usage.mjs +3 -0
- package/dist/components/badge.css +4 -8
- package/dist/components/breadcrumb.css +1 -5
- package/dist/components/btn.css +4 -7
- package/dist/components/card.css +3 -6
- package/dist/components/chart-bar.css +10 -0
- package/dist/components/chart-calendar.mjs +5 -0
- package/dist/components/chart-gauge.css +1 -1
- package/dist/components/chart-heatmap.css +40 -0
- package/dist/components/chart-scatter.css +24 -0
- package/dist/components/chart-scatter.mjs +4 -0
- package/dist/components/charts.css +86 -1
- package/dist/components/charts.mjs +1 -0
- package/dist/components/command-palette.css +2 -2
- package/dist/components/content.css +8 -9
- package/dist/components/copy.css +8 -8
- package/dist/components/datatable.css +4 -8
- package/dist/components/date-picker.css +3 -7
- package/dist/components/empty.css +0 -4
- package/dist/components/feedback.css +10 -17
- package/dist/components/form.css +13 -14
- package/dist/components/input.css +2 -2
- package/dist/components/layout.css +36 -36
- package/dist/components/media.css +12 -8
- package/dist/components/menu.css +2 -2
- package/dist/components/modal.css +1 -1
- package/dist/components/nav.css +11 -6
- package/dist/components/notification.css +28 -28
- package/dist/components/pagination.css +1 -1
- package/dist/components/panel.css +16 -16
- package/dist/components/pill.css +15 -15
- package/dist/components/segmented.css +5 -5
- package/dist/components/segmented.mjs +1 -0
- package/dist/components/select.css +12 -20
- package/dist/components/sidebar.css +1 -1
- package/dist/components/split-pane.css +0 -1
- package/dist/components/stat.css +14 -18
- package/dist/components/steps.css +2 -2
- package/dist/components/switch.css +8 -12
- package/dist/components/table.css +46 -47
- package/dist/components/tabs.css +35 -46
- package/dist/components/tag-input.css +4 -4
- package/dist/components/toast.css +16 -13
- package/dist/components/tooltip.css +2 -2
- package/dist/index.d.mts +37 -0
- package/dist/index.mjs +7890 -1199
- package/dist/registry.json +113 -4
- package/dist/retro-effects.css +21 -17
- package/dist/tokens/colors.css +4 -1
- package/dist/tokens/retro-effects.css +21 -17
- package/dist/tokens/style-profiles.css +21 -0
- package/dist/tokens.css +25 -1
- package/dist/ui.css +3168 -433
- package/docs/spec/ai-native.md +659 -0
- 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(
|
|
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)
|
|
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 (
|
|
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
|
|
130
|
+
function initCommandPalette(root) {
|
|
127
131
|
if (typeof document === "undefined") return;
|
|
128
132
|
if (typeof window === "undefined") return;
|
|
129
|
-
root
|
|
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)
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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",
|
|
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",
|
|
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
|
}
|
package/dist/behaviors/copy.mjs
CHANGED
|
@@ -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
|
|
55
|
+
function initCopy(root) {
|
|
56
56
|
if (typeof document === "undefined") return;
|
|
57
|
-
|
|
58
|
-
initialized.
|
|
59
|
-
|
|
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",
|
|
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",
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
616
|
-
|
|
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
|
-
|
|
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
|
-
|
|
842
|
+
renderBody();
|
|
843
|
+
syncHeadState();
|
|
801
844
|
});
|
|
802
845
|
sel.appendChild(clear);
|
|
803
846
|
foot.appendChild(sel);
|
|
@@ -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
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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 &&
|
|
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
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
212
|
-
if (openPicker === el &&
|
|
278
|
+
repositionHandler = () => {
|
|
279
|
+
if (openPicker === el && !panel.hidden) positionPanel(panel, trigger);
|
|
213
280
|
};
|
|
214
|
-
window.addEventListener("scroll",
|
|
215
|
-
window.addEventListener("resize",
|
|
281
|
+
window.addEventListener("scroll", repositionHandler, { passive: true });
|
|
282
|
+
window.addEventListener("resize", repositionHandler, { passive: true });
|
|
216
283
|
});
|
|
217
284
|
}
|
|
218
|
-
function initDatePicker(root
|
|
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 (
|
|
223
|
-
pickers.push(...Array.from(
|
|
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 {
|