@icen.ai/ui 0.8.0 → 0.9.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 +63 -14
- package/README.md +17 -9
- package/dist/base.css +4 -3
- package/dist/behaviors/accordion.d.mts +5 -2
- package/dist/behaviors/accordion.mjs +36 -11
- package/dist/behaviors/ai-branch.d.mts +19 -0
- package/dist/behaviors/ai-branch.mjs +217 -0
- package/dist/behaviors/ai-chat.d.mts +11 -7
- package/dist/behaviors/ai-chat.mjs +134 -51
- package/dist/behaviors/ai-composer.d.mts +28 -3
- package/dist/behaviors/ai-composer.mjs +399 -229
- package/dist/behaviors/ai-core.d.mts +9 -4
- package/dist/behaviors/ai-core.mjs +21 -8
- package/dist/behaviors/ai-diff.d.mts +2 -2
- package/dist/behaviors/ai-diff.mjs +172 -41
- package/dist/behaviors/ai-feedback.d.mts +26 -0
- package/dist/behaviors/ai-feedback.mjs +303 -0
- package/dist/behaviors/ai-panel.d.mts +26 -9
- package/dist/behaviors/ai-panel.mjs +211 -110
- package/dist/behaviors/ai-provider.mjs +54 -48
- package/dist/behaviors/ai-threads.d.mts +38 -0
- package/dist/behaviors/ai-threads.mjs +319 -0
- package/dist/behaviors/ai-tool.d.mts +12 -4
- package/dist/behaviors/ai-tool.mjs +316 -263
- package/dist/behaviors/ai-tools.d.mts +9 -6
- package/dist/behaviors/ai-tools.mjs +264 -155
- package/dist/behaviors/back-top.mjs +16 -1
- package/dist/behaviors/carousel.d.mts +5 -2
- package/dist/behaviors/carousel.mjs +53 -26
- package/dist/behaviors/charts.d.mts +170 -22
- package/dist/behaviors/charts.mjs +1016 -150
- package/dist/behaviors/command-palette.d.mts +40 -6
- package/dist/behaviors/command-palette.mjs +143 -15
- package/dist/behaviors/context-menu.d.mts +21 -4
- package/dist/behaviors/context-menu.mjs +122 -38
- package/dist/behaviors/controls.d.mts +59 -0
- package/dist/behaviors/controls.mjs +601 -0
- package/dist/behaviors/copy.d.mts +5 -2
- package/dist/behaviors/copy.mjs +37 -12
- package/dist/behaviors/datatable.d.mts +12 -0
- package/dist/behaviors/datatable.mjs +226 -43
- package/dist/behaviors/date-picker.d.mts +2 -2
- package/dist/behaviors/date-picker.mjs +171 -32
- package/dist/behaviors/dropdown.d.mts +6 -2
- package/dist/behaviors/dropdown.mjs +164 -25
- package/dist/behaviors/events.d.mts +523 -0
- package/dist/behaviors/events.mjs +208 -0
- package/dist/behaviors/input.mjs +25 -12
- package/dist/behaviors/kb-access.d.mts +34 -0
- package/dist/behaviors/kb-access.mjs +569 -0
- package/dist/behaviors/kb-acl.d.mts +50 -0
- package/dist/behaviors/kb-acl.mjs +405 -0
- package/dist/behaviors/kb-answer.d.mts +38 -0
- package/dist/behaviors/kb-answer.mjs +2055 -0
- package/dist/behaviors/kb-audit.d.mts +23 -0
- package/dist/behaviors/kb-audit.mjs +375 -0
- package/dist/behaviors/kb-canvas.d.mts +44 -0
- package/dist/behaviors/kb-canvas.mjs +589 -0
- package/dist/behaviors/kb-chain.d.mts +15 -0
- package/dist/behaviors/kb-chain.mjs +270 -0
- package/dist/behaviors/kb-checkpoint.d.mts +16 -0
- package/dist/behaviors/kb-checkpoint.mjs +342 -0
- package/dist/behaviors/kb-chunks.d.mts +31 -0
- package/dist/behaviors/kb-chunks.mjs +475 -0
- package/dist/behaviors/kb-citation.d.mts +38 -0
- package/dist/behaviors/kb-citation.mjs +677 -0
- package/dist/behaviors/kb-clarify.d.mts +20 -0
- package/dist/behaviors/kb-clarify.mjs +248 -0
- package/dist/behaviors/kb-conflict.d.mts +22 -0
- package/dist/behaviors/kb-conflict.mjs +284 -0
- package/dist/behaviors/kb-connector.d.mts +17 -0
- package/dist/behaviors/kb-connector.mjs +375 -0
- package/dist/behaviors/kb-core.d.mts +529 -0
- package/dist/behaviors/kb-core.mjs +890 -0
- package/dist/behaviors/kb-eval.d.mts +22 -0
- package/dist/behaviors/kb-eval.mjs +408 -0
- package/dist/behaviors/kb-explain.d.mts +11 -0
- package/dist/behaviors/kb-explain.mjs +243 -0
- package/dist/behaviors/kb-filter.d.mts +31 -0
- package/dist/behaviors/kb-filter.mjs +587 -0
- package/dist/behaviors/kb-gap.d.mts +27 -0
- package/dist/behaviors/kb-gap.mjs +273 -0
- package/dist/behaviors/kb-hittest.d.mts +34 -0
- package/dist/behaviors/kb-hittest.mjs +409 -0
- package/dist/behaviors/kb-metadata.d.mts +29 -0
- package/dist/behaviors/kb-metadata.mjs +326 -0
- package/dist/behaviors/kb-passage.d.mts +41 -0
- package/dist/behaviors/kb-passage.mjs +529 -0
- package/dist/behaviors/kb-pipeline.d.mts +18 -0
- package/dist/behaviors/kb-pipeline.mjs +381 -0
- package/dist/behaviors/kb-qa.d.mts +26 -0
- package/dist/behaviors/kb-qa.mjs +332 -0
- package/dist/behaviors/kb-rerank.d.mts +20 -0
- package/dist/behaviors/kb-rerank.mjs +315 -0
- package/dist/behaviors/kb-retrieval.d.mts +49 -0
- package/dist/behaviors/kb-retrieval.mjs +746 -0
- package/dist/behaviors/kb-review.d.mts +44 -0
- package/dist/behaviors/kb-review.mjs +451 -0
- package/dist/behaviors/kb-sandbox.d.mts +33 -0
- package/dist/behaviors/kb-sandbox.mjs +325 -0
- package/dist/behaviors/kb-segment.d.mts +32 -0
- package/dist/behaviors/kb-segment.mjs +499 -0
- package/dist/behaviors/kb-sources.d.mts +27 -0
- package/dist/behaviors/kb-sources.mjs +373 -0
- package/dist/behaviors/kb-sql.d.mts +43 -0
- package/dist/behaviors/kb-sql.mjs +948 -0
- package/dist/behaviors/kb-trace.d.mts +16 -0
- package/dist/behaviors/kb-trace.mjs +414 -0
- package/dist/behaviors/kb-visibility.d.mts +44 -0
- package/dist/behaviors/kb-visibility.mjs +403 -0
- package/dist/behaviors/kb-who-can.d.mts +32 -0
- package/dist/behaviors/kb-who-can.mjs +379 -0
- package/dist/behaviors/modal.d.mts +24 -5
- package/dist/behaviors/modal.mjs +76 -10
- package/dist/behaviors/nav.d.mts +1 -1
- package/dist/behaviors/nav.mjs +33 -7
- package/dist/behaviors/notification.d.mts +20 -4
- package/dist/behaviors/notification.mjs +53 -73
- package/dist/behaviors/popover.d.mts +39 -3
- package/dist/behaviors/popover.mjs +90 -4
- package/dist/behaviors/select.d.mts +4 -2
- package/dist/behaviors/select.mjs +161 -24
- package/dist/behaviors/sidebar.d.mts +5 -2
- package/dist/behaviors/sidebar.mjs +43 -13
- package/dist/behaviors/slider.d.mts +5 -2
- package/dist/behaviors/slider.mjs +54 -43
- package/dist/behaviors/split-pane.d.mts +6 -2
- package/dist/behaviors/split-pane.mjs +62 -21
- package/dist/behaviors/tabs.d.mts +3 -2
- package/dist/behaviors/tabs.mjs +65 -14
- package/dist/behaviors/tag-input.d.mts +5 -2
- package/dist/behaviors/tag-input.mjs +61 -21
- package/dist/behaviors/theme.d.mts +13 -2
- package/dist/behaviors/theme.mjs +48 -4
- package/dist/behaviors/toast.d.mts +30 -8
- package/dist/behaviors/toast.mjs +74 -7
- package/dist/behaviors/tree.d.mts +5 -2
- package/dist/behaviors/tree.mjs +38 -15
- package/dist/behaviors/upload.mjs +21 -23
- package/dist/cli.mjs +1 -1
- package/dist/components/ai-branch.mjs +3 -0
- package/dist/components/ai-chat.css +295 -0
- package/dist/components/ai-feedback.mjs +3 -0
- package/dist/components/ai-panel.css +218 -1
- package/dist/components/ai-threads.mjs +3 -0
- package/dist/components/ai-tool-call.mjs +1 -1
- package/dist/components/badge.css +1 -1
- package/dist/components/breadcrumb.css +0 -32
- package/dist/components/chart-calendar.mjs +1 -2
- package/dist/components/chart-graph.css +57 -0
- package/dist/components/chart-graph.mjs +4 -0
- package/dist/components/chart-map.mjs +3 -0
- package/dist/components/chart-pie.css +0 -6
- package/dist/components/chart-scatter.mjs +1 -1
- package/dist/components/charts.css +33 -1
- package/dist/components/charts.mjs +1 -0
- package/dist/components/content.css +3 -16
- package/dist/components/datatable.css +1 -1
- package/dist/components/date-picker.css +1 -1
- package/dist/components/kb-access.mjs +4 -0
- package/dist/components/kb-acl.mjs +4 -0
- package/dist/components/kb-agent.css +481 -0
- package/dist/components/kb-answer.mjs +4 -0
- package/dist/components/kb-audit.mjs +4 -0
- package/dist/components/kb-canvas.mjs +4 -0
- package/dist/components/kb-chain.mjs +4 -0
- package/dist/components/kb-checkpoint.mjs +4 -0
- package/dist/components/kb-chunks.mjs +4 -0
- package/dist/components/kb-citation.mjs +4 -0
- package/dist/components/kb-clarify.mjs +4 -0
- package/dist/components/kb-conflict.mjs +4 -0
- package/dist/components/kb-connector.mjs +4 -0
- package/dist/components/kb-data.css +649 -0
- package/dist/components/kb-eval.mjs +4 -0
- package/dist/components/kb-explain.mjs +4 -0
- package/dist/components/kb-filter.mjs +4 -0
- package/dist/components/kb-gap.mjs +4 -0
- package/dist/components/kb-ground.css +566 -0
- package/dist/components/kb-hittest.mjs +4 -0
- package/dist/components/kb-ingest.css +898 -0
- package/dist/components/kb-metadata.mjs +4 -0
- package/dist/components/kb-ops.css +548 -0
- package/dist/components/kb-passage.mjs +4 -0
- package/dist/components/kb-perm.css +444 -0
- package/dist/components/kb-pipeline.mjs +4 -0
- package/dist/components/kb-qa.mjs +4 -0
- package/dist/components/kb-rerank.mjs +4 -0
- package/dist/components/kb-retrieval.mjs +4 -0
- package/dist/components/kb-review.mjs +4 -0
- package/dist/components/kb-sandbox.mjs +4 -0
- package/dist/components/kb-search.css +940 -0
- package/dist/components/kb-segment.mjs +4 -0
- package/dist/components/kb-sources.mjs +4 -0
- package/dist/components/kb-sql.mjs +4 -0
- package/dist/components/kb-trace.mjs +4 -0
- package/dist/components/kb-visibility.mjs +4 -0
- package/dist/components/kb-who-can.mjs +4 -0
- package/dist/components/kb.css +252 -0
- package/dist/components/kb.mjs +9 -0
- package/dist/components/notification.css +2 -3
- package/dist/components/pagination.css +3 -1
- package/dist/components/pagination.mjs +1 -0
- package/dist/components/rating.mjs +1 -0
- package/dist/components/segmented.css +9 -1
- package/dist/components/segmented.mjs +1 -1
- package/dist/components/select.css +67 -4
- package/dist/components/steps.css +13 -3
- package/dist/components/steps.mjs +1 -0
- package/dist/components/switch.css +3 -1
- package/dist/components/switch.mjs +1 -0
- package/dist/components/table.mjs +1 -0
- package/dist/components/tabs.css +23 -0
- package/dist/components/tag-input.css +1 -6
- package/dist/components/upload.css +0 -15
- package/dist/index.d.mts +48 -11
- package/dist/index.mjs +13205 -3615
- package/dist/registry.json +366 -15
- package/dist/retro-effects.css +3 -16
- package/dist/tokens/retro-effects.css +3 -16
- package/dist/tokens/style-profiles.css +3 -2
- package/dist/tokens/typography.css +2 -0
- package/dist/tokens.css +5 -2
- package/dist/ui.css +5713 -267
- package/docs/research/2026-10-03-ai-kb-components.md +392 -0
- package/docs/research/2026-10-03-kb-permissions.md +992 -0
- package/docs/spec/ai-native.md +7 -4
- package/docs/spec/kb-family.md +443 -0
- package/package.json +2 -2
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
// src/behaviors/ai-core.ts
|
|
2
|
+
var svg = (body) => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${body}</svg>`;
|
|
3
|
+
var ICON_TERMINAL = svg('<path d="m4 17 6-6-6-6"/><path d="M12 19h8"/>');
|
|
4
|
+
var ICON_FILE = svg(
|
|
5
|
+
'<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/>'
|
|
6
|
+
);
|
|
7
|
+
var ICON_FILE_PEN = svg(
|
|
8
|
+
'<path d="M12.5 22H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8l5 5v3.5"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M19 6.5 16.5 4"/><path d="M21.378 16.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"/>'
|
|
9
|
+
);
|
|
10
|
+
var ICON_FILE_PLUS = svg(
|
|
11
|
+
'<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M9 15h6"/><path d="M12 12v6"/>'
|
|
12
|
+
);
|
|
13
|
+
var ICON_TRASH = svg(
|
|
14
|
+
'<path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M10 11v6"/><path d="M14 11v6"/>'
|
|
15
|
+
);
|
|
16
|
+
var ICON_SEARCH = svg('<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>');
|
|
17
|
+
var ICON_FOLDER_SEARCH = svg(
|
|
18
|
+
'<path d="M11 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v4"/><circle cx="17" cy="17" r="3"/><path d="m21 21-1.9-1.9"/>'
|
|
19
|
+
);
|
|
20
|
+
var ICON_SCAN_SEARCH = svg(
|
|
21
|
+
'<path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><circle cx="12" cy="12" r="3"/><path d="m16 16-1.9-1.9"/>'
|
|
22
|
+
);
|
|
23
|
+
var ICON_GLOBE = svg(
|
|
24
|
+
'<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>'
|
|
25
|
+
);
|
|
26
|
+
var ICON_DOWNLOAD = svg(
|
|
27
|
+
'<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/><path d="M12 15V3"/>'
|
|
28
|
+
);
|
|
29
|
+
var ICON_BLOCKS = svg(
|
|
30
|
+
'<rect width="7" height="7" x="4" y="4" rx="1"/><rect width="7" height="7" x="13" y="4" rx="1"/><rect width="7" height="7" x="4" y="13" rx="1"/><rect width="7" height="7" x="13" y="13" rx="1"/>'
|
|
31
|
+
);
|
|
32
|
+
var ICON_ZAP = svg(
|
|
33
|
+
'<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/>'
|
|
34
|
+
);
|
|
35
|
+
var ICON_LIST = svg(
|
|
36
|
+
'<path d="M3 12h.01"/><path d="M3 18h.01"/><path d="M3 6h.01"/><path d="M8 12h13"/><path d="M8 18h13"/><path d="M8 6h13"/>'
|
|
37
|
+
);
|
|
38
|
+
var ICON_LIST_CHECKS = svg(
|
|
39
|
+
'<path d="m3 17 2 2 4-4"/><path d="m3 7 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/>'
|
|
40
|
+
);
|
|
41
|
+
var ICON_BOT = svg(
|
|
42
|
+
'<path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/>'
|
|
43
|
+
);
|
|
44
|
+
var ICON_CHART = svg(
|
|
45
|
+
'<path d="M3 3v18h18"/><path d="M7 15l4-6 3 4 5-8"/>'
|
|
46
|
+
);
|
|
47
|
+
var ICON_FILE_TEXT = svg(
|
|
48
|
+
'<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/>'
|
|
49
|
+
);
|
|
50
|
+
function isObj(v) {
|
|
51
|
+
return typeof v === "object" && v !== null;
|
|
52
|
+
}
|
|
53
|
+
function pickStr(input, keys) {
|
|
54
|
+
if (!isObj(input)) return void 0;
|
|
55
|
+
for (const k of keys) {
|
|
56
|
+
const v = input[k];
|
|
57
|
+
if (typeof v === "string" && v) return v;
|
|
58
|
+
}
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
function pickNum(src, keys) {
|
|
62
|
+
if (!isObj(src)) return void 0;
|
|
63
|
+
for (const k of keys) {
|
|
64
|
+
const v = src[k];
|
|
65
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
66
|
+
}
|
|
67
|
+
return void 0;
|
|
68
|
+
}
|
|
69
|
+
function firstLine(s, max = 80) {
|
|
70
|
+
const line = s.split("\n", 1)[0]?.trim() ?? "";
|
|
71
|
+
return line.length > max ? `${line.slice(0, max)}\u2026` : line;
|
|
72
|
+
}
|
|
73
|
+
function filePathOf(input) {
|
|
74
|
+
return pickStr(input, ["file_path", "path", "file", "filename"]);
|
|
75
|
+
}
|
|
76
|
+
var summarizePath = (input) => filePathOf(input);
|
|
77
|
+
function summarizeRead(input) {
|
|
78
|
+
const p = filePathOf(input);
|
|
79
|
+
if (!p) return void 0;
|
|
80
|
+
const offset = pickNum(input, ["offset", "start_line"]);
|
|
81
|
+
if (offset == null) return p;
|
|
82
|
+
const limit = pickNum(input, ["limit", "end_line"]);
|
|
83
|
+
return limit != null ? `${p}:${offset}-${offset + limit}` : `${p}:${offset}`;
|
|
84
|
+
}
|
|
85
|
+
function summarizeShell(input) {
|
|
86
|
+
const cmd = typeof input === "string" ? input : pickStr(input, ["command", "cmd", "script"]);
|
|
87
|
+
return cmd ? firstLine(cmd) : void 0;
|
|
88
|
+
}
|
|
89
|
+
function summarizeGrep(input) {
|
|
90
|
+
const pattern = pickStr(input, ["pattern", "regex", "query"]);
|
|
91
|
+
const p = filePathOf(input);
|
|
92
|
+
if (pattern) return p ? `${firstLine(pattern, 40)} \xB7 ${p}` : firstLine(pattern);
|
|
93
|
+
return p;
|
|
94
|
+
}
|
|
95
|
+
var sum = (fn) => {
|
|
96
|
+
return (input) => fn(input) ?? "";
|
|
97
|
+
};
|
|
98
|
+
var BUILTIN_KINDS = {
|
|
99
|
+
shell: { label: "\u547D\u4EE4", icon: ICON_TERMINAL, tint: "accent", summarize: sum(summarizeShell) },
|
|
100
|
+
read: { label: "\u8BFB\u53D6", icon: ICON_FILE, tint: "info", summarize: sum(summarizeRead) },
|
|
101
|
+
edit: { label: "\u7F16\u8F91", icon: ICON_FILE_PEN, tint: "warning", summarize: sum(summarizePath) },
|
|
102
|
+
write: { label: "\u5199\u5165", icon: ICON_FILE_PLUS, tint: "warning", summarize: sum(summarizePath) },
|
|
103
|
+
rm: { label: "\u5220\u9664", icon: ICON_TRASH, tint: "error", summarize: sum(summarizePath) },
|
|
104
|
+
grep: { label: "\u641C\u7D22", icon: ICON_SEARCH, tint: "info", summarize: sum(summarizeGrep) },
|
|
105
|
+
glob: { label: "\u5339\u914D", icon: ICON_FOLDER_SEARCH, tint: "info", summarize: sum((i) => pickStr(i, ["pattern", "glob", "path"])) },
|
|
106
|
+
browser: { label: "\u6D4F\u89C8", icon: ICON_GLOBE, tint: "accent", summarize: sum((i) => pickStr(i, ["url", "uri", "target"])) },
|
|
107
|
+
search: { label: "\u68C0\u7D22", icon: ICON_SCAN_SEARCH, tint: "accent", summarize: sum((i) => pickStr(i, ["query", "q", "pattern"])) },
|
|
108
|
+
fetch: { label: "\u8BF7\u6C42", icon: ICON_DOWNLOAD, tint: "info", summarize: sum((i) => pickStr(i, ["url", "uri", "endpoint"])) },
|
|
109
|
+
mcp: { label: "MCP", icon: ICON_BLOCKS, tint: "muted", summarize: sum((i) => pickStr(i, ["tool", "method", "name"])) },
|
|
110
|
+
skill: { label: "\u6280\u80FD", icon: ICON_ZAP, tint: "accent", summarize: sum((i) => pickStr(i, ["skill", "name", "command"])) },
|
|
111
|
+
todo: {
|
|
112
|
+
label: "\u5F85\u529E",
|
|
113
|
+
icon: ICON_LIST,
|
|
114
|
+
tint: "info",
|
|
115
|
+
summarize: sum((input) => {
|
|
116
|
+
const items = input && typeof input === "object" && Array.isArray(input.items) ? input.items : Array.isArray(input) ? input : [];
|
|
117
|
+
const done = items.filter((it) => it?.status === "done").length;
|
|
118
|
+
return items.length > 0 ? `${done}/${items.length} \u5B8C\u6210` : "";
|
|
119
|
+
})
|
|
120
|
+
},
|
|
121
|
+
plan: { label: "\u8BA1\u5212", icon: ICON_LIST_CHECKS, tint: "info" },
|
|
122
|
+
subagent: { label: "\u5B50\u4EE3\u7406", icon: ICON_BOT, tint: "accent", summarize: sum((i) => pickStr(i, ["task", "description", "prompt", "message", "subagent_type"])) },
|
|
123
|
+
note: { label: "\u7B14\u8BB0", icon: ICON_FILE_TEXT, tint: "muted" },
|
|
124
|
+
chart: { label: "\u56FE\u8868", icon: ICON_CHART, tint: "info", summarize: sum((i) => pickStr(i, ["title", "name"])) }
|
|
125
|
+
};
|
|
126
|
+
var kindRegistry = new Map(Object.entries(BUILTIN_KINDS));
|
|
127
|
+
function h(tag, className, text) {
|
|
128
|
+
const node = document.createElement(tag);
|
|
129
|
+
if (className) node.className = className;
|
|
130
|
+
if (text != null) node.textContent = text;
|
|
131
|
+
return node;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/behaviors/kb-core.ts
|
|
135
|
+
var ic = (label, d) => ({ label, icon: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">${d}</svg>` });
|
|
136
|
+
var BUILTIN_SOURCE_TYPES = {
|
|
137
|
+
doc: ic("\u6587\u6863", '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/>'),
|
|
138
|
+
web: ic("\u7F51\u9875", '<circle cx="12" cy="12" r="10"/><path d="M2 12h20"/><path d="M12 2a15 15 0 0 1 0 20 15 15 0 0 1 0-20z"/>'),
|
|
139
|
+
wiki: ic("\u77E5\u8BC6\u5E93", '<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/>'),
|
|
140
|
+
sheet: ic("\u8868\u683C", '<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M3 15h18M9 3v18M15 3v18"/>'),
|
|
141
|
+
db: ic("\u6570\u636E\u5E93", '<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 1.7 4 3 9 3s9-1.3 9-3V5"/><path d="M3 12c0 1.7 4 3 9 3s9-1.3 9-3"/>'),
|
|
142
|
+
ticket: ic("\u5DE5\u5355", '<path d="M3 9V7a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v2a2 2 0 0 0 0 6v2a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-2a2 2 0 0 0 0-6z"/><path d="M13 5v14"/>'),
|
|
143
|
+
code: ic("\u4EE3\u7801", '<path d="m16 18 6-6-6-6"/><path d="m8 6-6 6 6 6"/>'),
|
|
144
|
+
api: ic("\u63A5\u53E3", '<path d="M4 7a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v3a2 2 0 0 0 0 4v3a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-3a2 2 0 0 0 0-4z"/>'),
|
|
145
|
+
image: ic("\u56FE\u7247", '<rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-4.6-4.6a2 2 0 0 0-2.8 0L3 21"/>'),
|
|
146
|
+
mail: ic("\u90AE\u4EF6", '<rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-10 7L2 7"/>')
|
|
147
|
+
};
|
|
148
|
+
var sourceTypeRegistry = new Map(Object.entries(BUILTIN_SOURCE_TYPES));
|
|
149
|
+
var KB_ROLES = ["viewer", "commenter", "editor", "owner"];
|
|
150
|
+
function kbRoleLabel(r) {
|
|
151
|
+
return r === "viewer" ? "\u53EF\u67E5\u770B" : r === "commenter" ? "\u53EF\u8BC4\u8BBA" : r === "editor" ? "\u53EF\u7F16\u8F91" : r === "owner" ? "\u8D1F\u8D23\u4EBA" : "\u65E0\u6743\u9650";
|
|
152
|
+
}
|
|
153
|
+
function roleAtLeast(role, floor) {
|
|
154
|
+
return KB_ROLES.indexOf(role) >= KB_ROLES.indexOf(floor);
|
|
155
|
+
}
|
|
156
|
+
function normalizeAclEntry(raw) {
|
|
157
|
+
const r = raw;
|
|
158
|
+
const s = r.subject ?? {};
|
|
159
|
+
const kind = s.kind === "user" || s.kind === "group" || s.kind === "org" || s.kind === "anyone" || s.kind === "link" ? s.kind : "user";
|
|
160
|
+
return {
|
|
161
|
+
subject: { kind, id: String(s.id ?? ""), label: String(s.label ?? "\u672A\u547D\u540D\u4E3B\u4F53"), note: typeof s.note === "string" ? s.note : void 0 },
|
|
162
|
+
role: KB_ROLES.includes(r.role) ? r.role : "viewer",
|
|
163
|
+
inheritedFrom: typeof r.inheritedFrom === "string" && r.inheritedFrom ? r.inheritedFrom : void 0,
|
|
164
|
+
expiresAt: typeof r.expiresAt === "string" && r.expiresAt ? r.expiresAt : void 0,
|
|
165
|
+
deny: r.deny === true,
|
|
166
|
+
system: r.system === true
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function normalizeIdentity(raw) {
|
|
170
|
+
const r = raw;
|
|
171
|
+
return {
|
|
172
|
+
user: String(r.user ?? ""),
|
|
173
|
+
groups: Array.isArray(r.groups) ? r.groups.map(String) : [],
|
|
174
|
+
label: typeof r.label === "string" ? r.label : void 0,
|
|
175
|
+
note: typeof r.note === "string" ? r.note : void 0
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function visibilityForRole(role, policy) {
|
|
179
|
+
if (role) return "full";
|
|
180
|
+
if (policy?.summaryAllowed === true) return "summary";
|
|
181
|
+
return policy?.discoverable === false ? "hidden" : "metadata";
|
|
182
|
+
}
|
|
183
|
+
function subjectMatches(subject, identity) {
|
|
184
|
+
if (subject.kind === "user") return subject.id === identity.user || subject.label === identity.user;
|
|
185
|
+
if (subject.kind === "group") return identity.groups.includes(subject.id) || identity.groups.includes(subject.label);
|
|
186
|
+
if (subject.kind === "org" || subject.kind === "anyone") return true;
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
function evaluateAcl(entries, identity, policy) {
|
|
190
|
+
const list = (Array.isArray(entries) ? entries : []).map((e) => normalizeAclEntry(e));
|
|
191
|
+
const id = normalizeIdentity(identity);
|
|
192
|
+
const matched = list.filter((e) => subjectMatches(e.subject, id));
|
|
193
|
+
const chain = matched.map((entry) => ({
|
|
194
|
+
entry,
|
|
195
|
+
via: entry.subject.kind === "link" ? "link" : entry.inheritedFrom ? "inherit" : "direct"
|
|
196
|
+
}));
|
|
197
|
+
const deny = matched.find((e) => e.deny);
|
|
198
|
+
if (deny) {
|
|
199
|
+
return { role: null, visibility: visibilityForRole(null, { ...policy, summaryAllowed: false }), chain, deniedBy: deny.subject };
|
|
200
|
+
}
|
|
201
|
+
const grants = matched.filter((e) => !e.deny);
|
|
202
|
+
const role = grants.reduce((acc, e) => acc && roleAtLeast(acc, e.role) ? acc : e.role, null);
|
|
203
|
+
const futureExpiry = grants.map((e) => Date.parse(e.expiresAt ?? "")).filter((t) => Number.isFinite(t) && t > Date.now()).sort((a, b) => a - b)[0];
|
|
204
|
+
return {
|
|
205
|
+
role,
|
|
206
|
+
visibility: visibilityForRole(role, policy),
|
|
207
|
+
chain,
|
|
208
|
+
expiresAt: Number.isFinite(futureExpiry) ? new Date(futureExpiry).toISOString() : void 0
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function relativeTime(input, now = /* @__PURE__ */ new Date()) {
|
|
212
|
+
const t = input instanceof Date ? input.getTime() : typeof input === "number" ? input : Date.parse(input);
|
|
213
|
+
if (!Number.isFinite(t)) return typeof input === "string" ? input : "\u2014";
|
|
214
|
+
const diff = now.getTime() - t;
|
|
215
|
+
const MIN = 6e4, HOUR = 36e5, DAY = 864e5;
|
|
216
|
+
if (diff < MIN) return "\u521A\u521A";
|
|
217
|
+
if (diff < HOUR) return `${Math.floor(diff / MIN)} \u5206\u949F\u524D`;
|
|
218
|
+
if (diff < DAY) return `${Math.floor(diff / HOUR)} \u5C0F\u65F6\u524D`;
|
|
219
|
+
if (diff < 30 * DAY) return `${Math.floor(diff / DAY)} \u5929\u524D`;
|
|
220
|
+
const d = new Date(t);
|
|
221
|
+
const ymd = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
222
|
+
return ymd;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/behaviors/events.ts
|
|
226
|
+
function emitIcen(source, type, detail) {
|
|
227
|
+
return source.dispatchEvent(
|
|
228
|
+
new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/behaviors/kb-who-can.ts
|
|
233
|
+
var VIA_LABEL = {
|
|
234
|
+
direct: "\u76F4\u63A5",
|
|
235
|
+
inherit: "\u7EE7\u627F",
|
|
236
|
+
link: "\u94FE\u63A5"
|
|
237
|
+
};
|
|
238
|
+
function untilLabel(input, now) {
|
|
239
|
+
const t = Date.parse(input);
|
|
240
|
+
if (!Number.isFinite(t)) return relativeTime(input, now);
|
|
241
|
+
const diff = t - now.getTime();
|
|
242
|
+
if (diff <= 0) return relativeTime(t, now);
|
|
243
|
+
const days = Math.floor(diff / 864e5);
|
|
244
|
+
if (days >= 1) return `${days} \u5929\u540E`;
|
|
245
|
+
const hours = Math.floor(diff / 36e5);
|
|
246
|
+
if (hours >= 1) return `${hours} \u5C0F\u65F6\u540E`;
|
|
247
|
+
return "\u5373\u5C06\u5230\u671F";
|
|
248
|
+
}
|
|
249
|
+
function renderKbWhoCan(el, opts) {
|
|
250
|
+
if (typeof document === "undefined" || !el) {
|
|
251
|
+
const stub = {
|
|
252
|
+
setIdentity: () => {
|
|
253
|
+
},
|
|
254
|
+
getResult: () => null,
|
|
255
|
+
destroy: () => {
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
return stub;
|
|
259
|
+
}
|
|
260
|
+
const marked = el;
|
|
261
|
+
marked.__icenKbWhoCan?.destroy();
|
|
262
|
+
const identities = (Array.isArray(opts.identities) ? opts.identities : []).map(
|
|
263
|
+
(i) => normalizeIdentity(i)
|
|
264
|
+
);
|
|
265
|
+
const entries = (Array.isArray(opts.entries) ? opts.entries : []).map(
|
|
266
|
+
(e) => normalizeAclEntry(e)
|
|
267
|
+
);
|
|
268
|
+
const evaluate = opts.evaluate ?? ((identity, ents) => evaluateAcl(ents, identity));
|
|
269
|
+
const ctrl = new AbortController();
|
|
270
|
+
const listen = (type, fn) => {
|
|
271
|
+
el.addEventListener(type, fn, { signal: ctrl.signal });
|
|
272
|
+
};
|
|
273
|
+
let destroyed = false;
|
|
274
|
+
let decision = null;
|
|
275
|
+
el.textContent = "";
|
|
276
|
+
el.classList.add("kb-whocan");
|
|
277
|
+
const select = h("select", "kb-whocan-user");
|
|
278
|
+
select.setAttribute("aria-label", "\u68C0\u67E5\u8EAB\u4EFD");
|
|
279
|
+
for (const identity of identities) {
|
|
280
|
+
const option = h("option", void 0, identity.label ?? identity.user);
|
|
281
|
+
option.value = identity.user;
|
|
282
|
+
select.appendChild(option);
|
|
283
|
+
}
|
|
284
|
+
const checkBtn = h("button", "kb-whocan-check", "\u68C0\u67E5");
|
|
285
|
+
checkBtn.type = "button";
|
|
286
|
+
checkBtn.dataset.whocanCheck = "";
|
|
287
|
+
checkBtn.setAttribute("aria-label", "\u91CD\u65B0\u68C0\u67E5\u5F53\u524D\u8EAB\u4EFD\u7684\u6709\u6548\u6743\u9650");
|
|
288
|
+
const bar = h("div", "kb-whocan-bar");
|
|
289
|
+
bar.append(select, checkBtn);
|
|
290
|
+
const result = h("div", "kb-whocan-result");
|
|
291
|
+
el.append(bar, result);
|
|
292
|
+
function renderResult(d) {
|
|
293
|
+
result.textContent = "";
|
|
294
|
+
result.classList.remove("is-allowed", "is-denied");
|
|
295
|
+
const allowed = d.role != null;
|
|
296
|
+
result.classList.add(allowed ? "is-allowed" : "is-denied");
|
|
297
|
+
result.appendChild(h("div", "kb-whocan-verdict", kbRoleLabel(d.role)));
|
|
298
|
+
if (d.deniedBy) {
|
|
299
|
+
result.appendChild(h("div", "kb-whocan-deniedby", `\u88AB\u7B56\u7565\u62D2\u7EDD\uFF1A${d.deniedBy.label}`));
|
|
300
|
+
}
|
|
301
|
+
const chain = h("ul", "kb-whocan-chain");
|
|
302
|
+
if (!d.chain.length) {
|
|
303
|
+
chain.appendChild(h("li", "kb-whocan-none kb-meta", "\u672A\u547D\u4E2D\u4EFB\u4F55\u6388\u6743\u2014\u2014\u9ED8\u8BA4\u62D2\u7EDD"));
|
|
304
|
+
} else {
|
|
305
|
+
for (const link of d.chain) {
|
|
306
|
+
const li = h("li", "kb-whocan-reason");
|
|
307
|
+
li.classList.add(link.entry.deny ? "is-deny" : `is-${link.via}`);
|
|
308
|
+
li.appendChild(h("span", "kb-badge", link.entry.deny ? "\u62D2\u7EDD" : VIA_LABEL[link.via]));
|
|
309
|
+
li.appendChild(h("span", "kb-whocan-source", link.entry.subject.label));
|
|
310
|
+
li.appendChild(h("span", "kb-whocan-role kb-chip", kbRoleLabel(link.entry.role)));
|
|
311
|
+
if (link.entry.subject.note) li.appendChild(h("span", "kb-meta", link.entry.subject.note));
|
|
312
|
+
chain.appendChild(li);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
result.appendChild(chain);
|
|
316
|
+
result.appendChild(
|
|
317
|
+
h(
|
|
318
|
+
"div",
|
|
319
|
+
"kb-whocan-rule",
|
|
320
|
+
`\u53E0\u52A0\u89C4\u5219\uFF1A\u591A\u6765\u6E90\u5E76\u96C6 \u2192 ${kbRoleLabel(d.role)}\uFF1B${d.deniedBy ? "\u88AB Deny \u4E00\u7968\u5426\u51B3" : "\u65E0 Deny \u751F\u6548"}`
|
|
321
|
+
)
|
|
322
|
+
);
|
|
323
|
+
if (d.expiresAt) {
|
|
324
|
+
result.appendChild(h("div", "kb-whocan-expires kb-meta", `\u6700\u8FD1\u5230\u671F ${untilLabel(d.expiresAt, /* @__PURE__ */ new Date())}`));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
function run() {
|
|
328
|
+
if (destroyed) return;
|
|
329
|
+
const identity = identities.find((i) => i.user === select.value);
|
|
330
|
+
if (!identity) {
|
|
331
|
+
decision = null;
|
|
332
|
+
result.textContent = "";
|
|
333
|
+
result.classList.remove("is-allowed", "is-denied");
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
decision = evaluate(identity, entries);
|
|
337
|
+
renderResult(decision);
|
|
338
|
+
emitIcen(el, "icen:kb-whocan-check", { identity, decision });
|
|
339
|
+
opts.onCheck?.(identity, decision);
|
|
340
|
+
}
|
|
341
|
+
listen("change", (ev) => {
|
|
342
|
+
if (ev.target === select) run();
|
|
343
|
+
});
|
|
344
|
+
listen("click", (ev) => {
|
|
345
|
+
const t = ev.target;
|
|
346
|
+
if (t instanceof Element && t.closest("[data-whocan-check]")) run();
|
|
347
|
+
});
|
|
348
|
+
if (!identities.length) {
|
|
349
|
+
select.disabled = true;
|
|
350
|
+
checkBtn.disabled = true;
|
|
351
|
+
result.appendChild(h("div", "kb-empty", "\u672A\u63D0\u4F9B\u53EF\u68C0\u67E5\u7684\u8EAB\u4EFD"));
|
|
352
|
+
} else {
|
|
353
|
+
run();
|
|
354
|
+
}
|
|
355
|
+
const handle = {
|
|
356
|
+
setIdentity(id) {
|
|
357
|
+
if (destroyed) return;
|
|
358
|
+
if (!identities.some((i) => i.user === id)) return;
|
|
359
|
+
select.value = id;
|
|
360
|
+
run();
|
|
361
|
+
},
|
|
362
|
+
getResult() {
|
|
363
|
+
return decision;
|
|
364
|
+
},
|
|
365
|
+
destroy() {
|
|
366
|
+
if (destroyed) return;
|
|
367
|
+
destroyed = true;
|
|
368
|
+
ctrl.abort();
|
|
369
|
+
el.classList.remove("kb-whocan");
|
|
370
|
+
el.textContent = "";
|
|
371
|
+
delete el.__icenKbWhoCan;
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
marked.__icenKbWhoCan = handle;
|
|
375
|
+
return handle;
|
|
376
|
+
}
|
|
377
|
+
export {
|
|
378
|
+
renderKbWhoCan
|
|
379
|
+
};
|
|
@@ -1,11 +1,30 @@
|
|
|
1
|
-
/**
|
|
2
|
-
declare
|
|
3
|
-
/**
|
|
1
|
+
/** 焦点陷阱的可聚焦元素选择器(modal / command-palette 焦点陷阱全库共用)。 */
|
|
2
|
+
declare const FOCUSABLE = "button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"]), [contenteditable]:not([contenteditable=\"false\"]), audio, video, details>summary";
|
|
3
|
+
/** openModal 可选项。 */
|
|
4
|
+
interface ModalOpenOptions {
|
|
5
|
+
/**
|
|
6
|
+
* 初始焦点:缺省 = 聚焦面板内第一个可聚焦元素(无则面板自身);
|
|
7
|
+
* 传 HTMLElement = 聚焦该元素;'none' = 不动焦点(调用方自管,焦点陷阱仍生效)。
|
|
8
|
+
*/
|
|
9
|
+
initialFocus?: HTMLElement | 'none';
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* 打开指定 id 的 modal;已有其他 modal 打开时先关闭它。
|
|
13
|
+
* 打开成功后从 document 派发 icen:modal-open { id }(广播)。
|
|
14
|
+
*/
|
|
15
|
+
declare function openModal(id: string, opts?: ModalOpenOptions): void;
|
|
16
|
+
/**
|
|
17
|
+
* 关闭指定 id 的 modal(backdrop 已被外部移除时也照常复位状态/监听/滚动锁)。
|
|
18
|
+
* 关闭已打开的 modal 后从 document 派发 icen:modal-close { id }(广播);
|
|
19
|
+
* 关闭未被接管打开的 modal(幂等空操作路径)不派发。
|
|
20
|
+
*/
|
|
4
21
|
declare function closeModal(id: string): void;
|
|
5
22
|
/**
|
|
6
23
|
* 绑定触发器/关闭钮/backdrop 点击(document 级事件委托,重复调用安全)。
|
|
7
24
|
* root 用于扫描 [data-modal-open] 补 aria-haspopup=dialog。
|
|
25
|
+
* 返回销毁函数:摘除 document 委托并复位幂等标记(销毁后可重新 init);
|
|
26
|
+
* 销毁时若有 modal 开着会顺带关闭(复位滚动锁/监听/焦点)。SSR 下返回 no-op。
|
|
8
27
|
*/
|
|
9
|
-
declare function initModal(root?: ParentNode): void;
|
|
28
|
+
declare function initModal(root?: ParentNode): () => void;
|
|
10
29
|
|
|
11
|
-
export { closeModal, initModal, openModal };
|
|
30
|
+
export { FOCUSABLE, type ModalOpenOptions, closeModal, initModal, openModal };
|
package/dist/behaviors/modal.mjs
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
|
+
// src/behaviors/popover.ts
|
|
2
|
+
var PANEL_SIZING_ATTRS = {
|
|
3
|
+
width: "data-panel-width",
|
|
4
|
+
minWidth: "data-panel-min",
|
|
5
|
+
maxWidth: "data-panel-max",
|
|
6
|
+
minHeight: "data-panel-min-height",
|
|
7
|
+
maxHeight: "data-panel-max-height"
|
|
8
|
+
};
|
|
9
|
+
function readPanelSizing(el) {
|
|
10
|
+
const out = {};
|
|
11
|
+
if (!el) return out;
|
|
12
|
+
for (const key of Object.keys(PANEL_SIZING_ATTRS)) {
|
|
13
|
+
const raw = el.getAttribute(PANEL_SIZING_ATTRS[key]);
|
|
14
|
+
if (raw == null) continue;
|
|
15
|
+
const n = Number(raw);
|
|
16
|
+
if (Number.isFinite(n) && n > 0) out[key] = n;
|
|
17
|
+
}
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
function applyPanelSizing(panel, sizing) {
|
|
21
|
+
if (sizing == null) {
|
|
22
|
+
panel.style.width = "";
|
|
23
|
+
panel.style.minWidth = "";
|
|
24
|
+
panel.style.maxWidth = "";
|
|
25
|
+
panel.style.minHeight = "";
|
|
26
|
+
panel.style.maxHeight = "";
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const vw = typeof document === "undefined" ? Number.POSITIVE_INFINITY : document.documentElement.clientWidth;
|
|
30
|
+
const fitW = (n) => Math.min(n, Math.max(80, vw - 24));
|
|
31
|
+
if (sizing.width != null) {
|
|
32
|
+
const w = fitW(sizing.width);
|
|
33
|
+
panel.style.width = `${w}px`;
|
|
34
|
+
panel.style.maxWidth = `${w}px`;
|
|
35
|
+
}
|
|
36
|
+
if (sizing.minWidth != null) panel.style.minWidth = `${fitW(sizing.minWidth)}px`;
|
|
37
|
+
if (sizing.maxWidth != null && sizing.width == null) panel.style.maxWidth = `${fitW(sizing.maxWidth)}px`;
|
|
38
|
+
if (sizing.minHeight != null) panel.style.minHeight = `${sizing.minHeight}px`;
|
|
39
|
+
if (sizing.maxHeight != null) panel.style.maxHeight = `${sizing.maxHeight}px`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/behaviors/events.ts
|
|
43
|
+
function emitIcen(source, type, detail) {
|
|
44
|
+
return source.dispatchEvent(
|
|
45
|
+
new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
1
49
|
// src/behaviors/modal.ts
|
|
2
|
-
var FOCUSABLE = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
50
|
+
var FOCUSABLE = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]), [contenteditable]:not([contenteditable="false"]), audio, video, details>summary';
|
|
3
51
|
var openId = null;
|
|
4
52
|
var restoreFocusTo = null;
|
|
5
53
|
var prevBodyOverflow = "";
|
|
@@ -11,7 +59,7 @@ function findBackdrop(id) {
|
|
|
11
59
|
function portal(backdrop) {
|
|
12
60
|
if (backdrop.parentElement !== document.body) document.body.appendChild(backdrop);
|
|
13
61
|
}
|
|
14
|
-
function openModal(id) {
|
|
62
|
+
function openModal(id, opts) {
|
|
15
63
|
if (typeof document === "undefined") return;
|
|
16
64
|
if (openId === id) return;
|
|
17
65
|
if (openId) closeModal(openId);
|
|
@@ -22,6 +70,7 @@ function openModal(id) {
|
|
|
22
70
|
}
|
|
23
71
|
const panel = backdrop.querySelector(".modal");
|
|
24
72
|
if (!panel) return;
|
|
73
|
+
applyPanelSizing(panel, readPanelSizing(backdrop));
|
|
25
74
|
portal(backdrop);
|
|
26
75
|
restoreFocusTo = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
27
76
|
openId = id;
|
|
@@ -34,14 +83,14 @@ function openModal(id) {
|
|
|
34
83
|
if (!panel.hasAttribute("aria-labelledby")) {
|
|
35
84
|
const title = panel.querySelector(".modal-title");
|
|
36
85
|
if (title) {
|
|
37
|
-
if (!title.id) title.id = `modal-title-${
|
|
86
|
+
if (!title.id) title.id = `modal-title-${id.replace(/[^\w-]/g, "-")}`;
|
|
38
87
|
panel.setAttribute("aria-labelledby", title.id);
|
|
39
88
|
}
|
|
40
89
|
}
|
|
41
90
|
if (!panel.hasAttribute("aria-describedby")) {
|
|
42
91
|
const desc = panel.querySelector(".modal-description");
|
|
43
92
|
if (desc) {
|
|
44
|
-
if (!desc.id) desc.id = `modal-desc-${
|
|
93
|
+
if (!desc.id) desc.id = `modal-desc-${id.replace(/[^\w-]/g, "-")}`;
|
|
45
94
|
panel.setAttribute("aria-describedby", desc.id);
|
|
46
95
|
}
|
|
47
96
|
}
|
|
@@ -76,8 +125,14 @@ function openModal(id) {
|
|
|
76
125
|
}
|
|
77
126
|
};
|
|
78
127
|
window.addEventListener("keydown", keyHandler);
|
|
79
|
-
|
|
80
|
-
(
|
|
128
|
+
if (opts?.initialFocus === "none") {
|
|
129
|
+
} else if (opts?.initialFocus instanceof HTMLElement) {
|
|
130
|
+
opts.initialFocus.focus();
|
|
131
|
+
} else {
|
|
132
|
+
const focusable = panel.querySelector(FOCUSABLE);
|
|
133
|
+
(focusable ?? panel).focus();
|
|
134
|
+
}
|
|
135
|
+
emitIcen(document, "icen:modal-open", { id });
|
|
81
136
|
}
|
|
82
137
|
function closeModal(id) {
|
|
83
138
|
if (typeof document === "undefined") return;
|
|
@@ -92,14 +147,17 @@ function closeModal(id) {
|
|
|
92
147
|
document.body.style.overflow = prevBodyOverflow;
|
|
93
148
|
if (restoreFocusTo && restoreFocusTo.isConnected) restoreFocusTo.focus();
|
|
94
149
|
restoreFocusTo = null;
|
|
150
|
+
emitIcen(document, "icen:modal-close", { id });
|
|
95
151
|
}
|
|
96
152
|
function initModal(root) {
|
|
97
|
-
if (typeof document === "undefined") return
|
|
153
|
+
if (typeof document === "undefined") return () => {
|
|
154
|
+
};
|
|
98
155
|
const scope = root ?? document;
|
|
99
156
|
scope.querySelectorAll("[data-modal-open]").forEach((el) => el.setAttribute("aria-haspopup", "dialog"));
|
|
100
|
-
if (initialized) return
|
|
157
|
+
if (initialized) return () => {
|
|
158
|
+
};
|
|
101
159
|
initialized = true;
|
|
102
|
-
|
|
160
|
+
const onClick = (e) => {
|
|
103
161
|
const target = e.target instanceof Element ? e.target : null;
|
|
104
162
|
if (!target) return;
|
|
105
163
|
const opener = target.closest("[data-modal-open]");
|
|
@@ -117,9 +175,17 @@ function initModal(root) {
|
|
|
117
175
|
if (target instanceof HTMLElement && target.classList.contains("modal-backdrop") && target.dataset.modal && target.dataset.closeOnOverlay !== "false") {
|
|
118
176
|
closeModal(target.dataset.modal);
|
|
119
177
|
}
|
|
120
|
-
}
|
|
178
|
+
};
|
|
179
|
+
document.addEventListener("click", onClick);
|
|
180
|
+
const destroy = () => {
|
|
181
|
+
document.removeEventListener("click", onClick);
|
|
182
|
+
if (openId) closeModal(openId);
|
|
183
|
+
initialized = false;
|
|
184
|
+
};
|
|
185
|
+
return destroy;
|
|
121
186
|
}
|
|
122
187
|
export {
|
|
188
|
+
FOCUSABLE,
|
|
123
189
|
closeModal,
|
|
124
190
|
initModal,
|
|
125
191
|
openModal
|
package/dist/behaviors/nav.d.mts
CHANGED
package/dist/behaviors/nav.mjs
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
|
+
// src/behaviors/events.ts
|
|
2
|
+
function emitIcen(source, type, detail) {
|
|
3
|
+
return source.dispatchEvent(
|
|
4
|
+
new CustomEvent(type, { detail, bubbles: true, composed: true, cancelable: true })
|
|
5
|
+
);
|
|
6
|
+
}
|
|
7
|
+
|
|
1
8
|
// src/behaviors/nav.ts
|
|
2
9
|
function prefersReducedMotion() {
|
|
3
10
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
4
11
|
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
5
12
|
}
|
|
13
|
+
var NAV_ITEM_SELECTOR = ".nav-btn, .nav-link";
|
|
14
|
+
function isNavItemDisabled(item) {
|
|
15
|
+
return item.classList.contains("is-disabled") || item.getAttribute("aria-disabled") === "true" || item instanceof HTMLButtonElement && item.disabled;
|
|
16
|
+
}
|
|
6
17
|
function setup(nav) {
|
|
7
18
|
const el = nav;
|
|
8
19
|
if (el.__icenNavInit) return void 0;
|
|
@@ -36,17 +47,32 @@ function setup(nav) {
|
|
|
36
47
|
ticking = true;
|
|
37
48
|
window.requestAnimationFrame(apply);
|
|
38
49
|
};
|
|
39
|
-
const onResize = () => {
|
|
40
|
-
if (ticking) return;
|
|
41
|
-
ticking = true;
|
|
42
|
-
window.requestAnimationFrame(apply);
|
|
43
|
-
};
|
|
44
50
|
window.addEventListener("scroll", onScroll, { passive: true });
|
|
45
|
-
window.addEventListener("resize",
|
|
51
|
+
window.addEventListener("resize", onScroll, { passive: true });
|
|
46
52
|
apply();
|
|
53
|
+
const onSelect = (ev) => {
|
|
54
|
+
const target = ev.target;
|
|
55
|
+
if (!(target instanceof Element)) return;
|
|
56
|
+
const item = target.closest(NAV_ITEM_SELECTOR);
|
|
57
|
+
if (!item || item.closest(".nav") !== nav) return;
|
|
58
|
+
if (isNavItemDisabled(item)) return;
|
|
59
|
+
const items = Array.from(nav.querySelectorAll(NAV_ITEM_SELECTOR));
|
|
60
|
+
const index = items.indexOf(item);
|
|
61
|
+
if (index === -1) return;
|
|
62
|
+
items.forEach((it) => {
|
|
63
|
+
if (it === item) return;
|
|
64
|
+
it.classList.remove("is-active");
|
|
65
|
+
if (it.getAttribute("aria-current") === "page") it.removeAttribute("aria-current");
|
|
66
|
+
});
|
|
67
|
+
item.classList.add("is-active");
|
|
68
|
+
item.setAttribute("aria-current", "page");
|
|
69
|
+
emitIcen(item, "icen:nav-select", { item, index });
|
|
70
|
+
};
|
|
71
|
+
nav.addEventListener("click", onSelect);
|
|
47
72
|
return () => {
|
|
48
73
|
window.removeEventListener("scroll", onScroll);
|
|
49
|
-
window.removeEventListener("resize",
|
|
74
|
+
window.removeEventListener("resize", onScroll);
|
|
75
|
+
nav.removeEventListener("click", onSelect);
|
|
50
76
|
el.__icenNavInit = false;
|
|
51
77
|
};
|
|
52
78
|
}
|