@icen.ai/ui 0.5.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +262 -0
- package/README.md +52 -13
- package/dist/base.css +33 -5
- package/dist/behaviors/accordion.d.mts +7 -0
- package/dist/behaviors/accordion.mjs +37 -11
- package/dist/behaviors/ai-chat.d.mts +59 -0
- package/dist/behaviors/ai-chat.mjs +580 -0
- package/dist/behaviors/ai-composer.d.mts +137 -0
- package/dist/behaviors/ai-composer.mjs +2114 -0
- package/dist/behaviors/ai-core.d.mts +156 -0
- package/dist/behaviors/ai-core.mjs +504 -0
- package/dist/behaviors/ai-diff.d.mts +53 -0
- package/dist/behaviors/ai-diff.mjs +413 -0
- package/dist/behaviors/ai-panel.d.mts +101 -0
- package/dist/behaviors/ai-panel.mjs +1087 -0
- package/dist/behaviors/ai-provider.d.mts +198 -0
- package/dist/behaviors/ai-provider.mjs +1061 -0
- package/dist/behaviors/ai-tool.d.mts +26 -0
- package/dist/behaviors/ai-tool.mjs +2173 -0
- package/dist/behaviors/ai-tools.d.mts +84 -0
- package/dist/behaviors/ai-tools.mjs +1762 -0
- package/dist/behaviors/back-top.d.mts +20 -0
- package/dist/behaviors/back-top.mjs +64 -32
- package/dist/behaviors/carousel.d.mts +7 -0
- package/dist/behaviors/carousel.mjs +72 -27
- package/dist/behaviors/charts.d.mts +296 -0
- package/dist/behaviors/charts.mjs +1364 -440
- package/dist/behaviors/command-palette.d.mts +42 -0
- package/dist/behaviors/command-palette.mjs +167 -27
- package/dist/behaviors/context-menu.d.mts +53 -0
- package/dist/behaviors/context-menu.mjs +127 -39
- package/dist/behaviors/controls.d.mts +59 -0
- package/dist/behaviors/controls.mjs +601 -0
- package/dist/behaviors/copy.d.mts +7 -0
- package/dist/behaviors/copy.mjs +41 -15
- package/dist/behaviors/datatable.d.mts +90 -0
- package/dist/behaviors/datatable.mjs +279 -53
- package/dist/behaviors/date-picker.d.mts +4 -0
- package/dist/behaviors/date-picker.mjs +276 -69
- package/dist/behaviors/dropdown.d.mts +8 -0
- package/dist/behaviors/dropdown.mjs +171 -28
- package/dist/behaviors/events.d.mts +330 -0
- package/dist/behaviors/events.mjs +208 -0
- package/dist/behaviors/input.d.mts +4 -0
- package/dist/behaviors/input.mjs +52 -32
- package/dist/behaviors/modal.d.mts +30 -0
- package/dist/behaviors/modal.mjs +87 -16
- package/dist/behaviors/nav.d.mts +7 -0
- package/dist/behaviors/nav.mjs +57 -15
- package/dist/behaviors/notification.d.mts +188 -0
- package/dist/behaviors/notification.mjs +274 -140
- package/dist/behaviors/popover.d.mts +82 -0
- package/dist/behaviors/popover.mjs +92 -5
- package/dist/behaviors/select.d.mts +6 -0
- package/dist/behaviors/select.mjs +287 -45
- package/dist/behaviors/sidebar.d.mts +7 -0
- package/dist/behaviors/sidebar.mjs +44 -13
- package/dist/behaviors/slider.d.mts +7 -0
- package/dist/behaviors/slider.mjs +55 -43
- package/dist/behaviors/split-pane.d.mts +8 -0
- package/dist/behaviors/split-pane.mjs +70 -29
- package/dist/behaviors/tabs.d.mts +9 -0
- package/dist/behaviors/tabs.mjs +102 -26
- package/dist/behaviors/tag-input.d.mts +7 -0
- package/dist/behaviors/tag-input.mjs +66 -17
- package/dist/behaviors/theme.d.mts +36 -0
- package/dist/behaviors/theme.mjs +51 -5
- package/dist/behaviors/toast.d.mts +48 -0
- package/dist/behaviors/toast.mjs +98 -25
- package/dist/behaviors/tree.d.mts +7 -0
- package/dist/behaviors/tree.mjs +39 -15
- package/dist/behaviors/upload.d.mts +4 -0
- package/dist/behaviors/upload.mjs +79 -44
- package/dist/cli.mjs +1 -1
- 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 +5 -9
- package/dist/components/breadcrumb.css +0 -36
- 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 +4 -0
- package/dist/components/chart-gauge.css +1 -1
- package/dist/components/chart-heatmap.css +40 -0
- package/dist/components/chart-pie.css +0 -6
- package/dist/components/chart-scatter.css +24 -0
- package/dist/components/chart-scatter.mjs +4 -0
- package/dist/components/charts.css +87 -2
- package/dist/components/charts.mjs +1 -0
- package/dist/components/command-palette.css +2 -2
- package/dist/components/content.css +11 -25
- package/dist/components/copy.css +8 -8
- package/dist/components/datatable.css +5 -9
- package/dist/components/date-picker.css +4 -8
- 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 +27 -28
- package/dist/components/pagination.css +4 -2
- package/dist/components/pagination.mjs +1 -0
- package/dist/components/panel.css +16 -16
- package/dist/components/pill.css +15 -15
- package/dist/components/rating.mjs +1 -0
- package/dist/components/segmented.css +14 -6
- package/dist/components/segmented.mjs +1 -0
- package/dist/components/select.css +79 -24
- 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 +15 -5
- package/dist/components/steps.mjs +1 -0
- package/dist/components/switch.css +11 -13
- package/dist/components/switch.mjs +1 -0
- package/dist/components/table.css +46 -47
- package/dist/components/table.mjs +1 -0
- package/dist/components/tabs.css +57 -45
- package/dist/components/tag-input.css +5 -10
- package/dist/components/toast.css +16 -13
- package/dist/components/tooltip.css +2 -2
- package/dist/components/upload.css +0 -15
- package/dist/index.d.mts +39 -0
- package/dist/index.mjs +11243 -3312
- package/dist/registry.json +147 -9
- package/dist/retro-effects.css +23 -32
- package/dist/tokens/colors.css +4 -1
- package/dist/tokens/retro-effects.css +23 -32
- package/dist/tokens/style-profiles.css +24 -2
- package/dist/tokens/typography.css +2 -0
- package/dist/tokens.css +30 -3
- package/dist/ui.css +3420 -645
- package/docs/spec/ai-native.md +662 -0
- package/package.json +15 -5
|
@@ -1,3 +1,10 @@
|
|
|
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/upload.ts
|
|
2
9
|
function formatSize(bytes) {
|
|
3
10
|
if (bytes < 1024) return `${bytes} B`;
|
|
@@ -18,18 +25,32 @@ function isAccepted(file, accept) {
|
|
|
18
25
|
}
|
|
19
26
|
var fileIconSvg = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>';
|
|
20
27
|
var xIconSvg = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6 6 18M6 6l12 12"/></svg>';
|
|
28
|
+
function parseSvg(source) {
|
|
29
|
+
try {
|
|
30
|
+
const doc = new DOMParser().parseFromString(source, "image/svg+xml");
|
|
31
|
+
const svg = doc.documentElement;
|
|
32
|
+
if (!svg || svg.tagName.toLowerCase() !== "svg") return null;
|
|
33
|
+
return document.importNode(svg, true);
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function sameFile(a, b) {
|
|
39
|
+
return a.name === b.name && a.size === b.size && a.lastModified === b.lastModified;
|
|
40
|
+
}
|
|
21
41
|
function setup(zone) {
|
|
22
42
|
const el = zone;
|
|
23
43
|
if (el.__icenUploadInit) return;
|
|
24
|
-
el.__icenUploadInit = true;
|
|
25
44
|
const input = zone.querySelector('input[type="file"]');
|
|
26
45
|
if (!input) return;
|
|
46
|
+
el.__icenUploadInit = true;
|
|
27
47
|
const hasList = zone.hasAttribute("data-upload-list");
|
|
28
48
|
const maxSize = Number(zone.getAttribute("data-max-size")) || 0;
|
|
29
49
|
const maxFiles = Number(zone.getAttribute("data-max-files")) || 0;
|
|
30
50
|
const accept = input.getAttribute("accept") ?? "";
|
|
31
51
|
let currentFiles = [];
|
|
32
52
|
const urlMap = /* @__PURE__ */ new Map();
|
|
53
|
+
let syncing = false;
|
|
33
54
|
function getListEl() {
|
|
34
55
|
if (!hasList) return null;
|
|
35
56
|
let list = zone.querySelector(".upload-list");
|
|
@@ -43,8 +64,12 @@ function setup(zone) {
|
|
|
43
64
|
function renderList() {
|
|
44
65
|
const list = getListEl();
|
|
45
66
|
if (!list) return;
|
|
46
|
-
urlMap.forEach((url) =>
|
|
47
|
-
|
|
67
|
+
urlMap.forEach((url, f) => {
|
|
68
|
+
if (!currentFiles.includes(f)) {
|
|
69
|
+
URL.revokeObjectURL(url);
|
|
70
|
+
urlMap.delete(f);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
48
73
|
list.textContent = "";
|
|
49
74
|
if (currentFiles.length === 0) {
|
|
50
75
|
list.hidden = true;
|
|
@@ -55,25 +80,29 @@ function setup(zone) {
|
|
|
55
80
|
const li = document.createElement("li");
|
|
56
81
|
li.className = "upload-list-item";
|
|
57
82
|
const isImage = file.type.startsWith("image/");
|
|
83
|
+
let thumbUrl;
|
|
58
84
|
if (isImage) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
} catch {
|
|
68
|
-
const icon = document.createElement("span");
|
|
69
|
-
icon.className = "upload-list-item-icon";
|
|
70
|
-
icon.innerHTML = fileIconSvg;
|
|
71
|
-
li.appendChild(icon);
|
|
85
|
+
thumbUrl = urlMap.get(file);
|
|
86
|
+
if (!thumbUrl) {
|
|
87
|
+
try {
|
|
88
|
+
thumbUrl = URL.createObjectURL(file);
|
|
89
|
+
urlMap.set(file, thumbUrl);
|
|
90
|
+
} catch {
|
|
91
|
+
thumbUrl = void 0;
|
|
92
|
+
}
|
|
72
93
|
}
|
|
94
|
+
}
|
|
95
|
+
if (thumbUrl) {
|
|
96
|
+
const img = document.createElement("img");
|
|
97
|
+
img.className = "upload-list-item-thumb";
|
|
98
|
+
img.src = thumbUrl;
|
|
99
|
+
img.alt = file.name;
|
|
100
|
+
li.appendChild(img);
|
|
73
101
|
} else {
|
|
74
102
|
const icon = document.createElement("span");
|
|
75
103
|
icon.className = "upload-list-item-icon";
|
|
76
|
-
|
|
104
|
+
const svg = parseSvg(fileIconSvg);
|
|
105
|
+
if (svg) icon.appendChild(svg);
|
|
77
106
|
li.appendChild(icon);
|
|
78
107
|
}
|
|
79
108
|
const info = document.createElement("div");
|
|
@@ -91,11 +120,12 @@ function setup(zone) {
|
|
|
91
120
|
removeBtn.className = "upload-list-item-x";
|
|
92
121
|
removeBtn.type = "button";
|
|
93
122
|
removeBtn.setAttribute("aria-label", `\u79FB\u9664 ${file.name}`);
|
|
94
|
-
|
|
123
|
+
const xSvg = parseSvg(xIconSvg);
|
|
124
|
+
if (xSvg) removeBtn.appendChild(xSvg);
|
|
95
125
|
removeBtn.addEventListener("click", (ev) => {
|
|
96
126
|
ev.stopPropagation();
|
|
97
127
|
currentFiles = currentFiles.filter((f) => f !== file);
|
|
98
|
-
zone
|
|
128
|
+
emitIcen(zone, "icen:upload-remove", { file });
|
|
99
129
|
renderList();
|
|
100
130
|
syncInputFiles();
|
|
101
131
|
});
|
|
@@ -103,15 +133,21 @@ function setup(zone) {
|
|
|
103
133
|
list.appendChild(li);
|
|
104
134
|
}
|
|
105
135
|
}
|
|
106
|
-
function
|
|
136
|
+
function setInputFiles(files) {
|
|
107
137
|
try {
|
|
108
138
|
const dt = new DataTransfer();
|
|
109
|
-
for (const f of
|
|
139
|
+
for (const f of files) dt.items.add(f);
|
|
140
|
+
syncing = true;
|
|
110
141
|
input.files = dt.files;
|
|
111
142
|
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
112
143
|
} catch {
|
|
144
|
+
} finally {
|
|
145
|
+
syncing = false;
|
|
113
146
|
}
|
|
114
147
|
}
|
|
148
|
+
function syncInputFiles() {
|
|
149
|
+
setInputFiles(currentFiles);
|
|
150
|
+
}
|
|
115
151
|
function validate(file) {
|
|
116
152
|
if (accept && !isAccepted(file, accept)) return "\u6587\u4EF6\u7C7B\u578B\u4E0D\u652F\u6301";
|
|
117
153
|
if (maxSize > 0 && file.size > maxSize) return `\u6587\u4EF6\u8D85\u8FC7 ${formatSize(maxSize)}`;
|
|
@@ -122,29 +158,34 @@ function setup(zone) {
|
|
|
122
158
|
for (const file of files) {
|
|
123
159
|
const reason = validate(file);
|
|
124
160
|
if (reason) {
|
|
125
|
-
zone
|
|
161
|
+
emitIcen(zone, "icen:upload-error", { file, reason });
|
|
126
162
|
continue;
|
|
127
163
|
}
|
|
128
164
|
valid.push(file);
|
|
129
165
|
}
|
|
130
166
|
if (valid.length === 0) return;
|
|
131
167
|
if (hasList) {
|
|
168
|
+
const fresh = [];
|
|
169
|
+
for (const f of valid) {
|
|
170
|
+
if (currentFiles.some((c) => sameFile(c, f)) || fresh.some((c) => sameFile(c, f))) continue;
|
|
171
|
+
fresh.push(f);
|
|
172
|
+
}
|
|
173
|
+
if (fresh.length === 0) return;
|
|
132
174
|
if (maxFiles > 0) {
|
|
133
175
|
const remaining = maxFiles - currentFiles.length;
|
|
134
176
|
if (remaining <= 0) {
|
|
135
|
-
zone
|
|
136
|
-
bubbles: true,
|
|
137
|
-
detail: { reason: `\u6700\u591A ${maxFiles} \u4E2A\u6587\u4EF6` }
|
|
138
|
-
}));
|
|
177
|
+
emitIcen(zone, "icen:upload-error", { file: null, reason: `\u6700\u591A ${maxFiles} \u4E2A\u6587\u4EF6` });
|
|
139
178
|
return;
|
|
140
179
|
}
|
|
141
|
-
|
|
180
|
+
if (fresh.length > remaining) fresh.length = remaining;
|
|
142
181
|
}
|
|
143
|
-
currentFiles.push(...
|
|
182
|
+
currentFiles.push(...fresh);
|
|
144
183
|
renderList();
|
|
145
184
|
syncInputFiles();
|
|
185
|
+
emitIcen(zone, "icen:upload", { files: fresh });
|
|
186
|
+
return;
|
|
146
187
|
}
|
|
147
|
-
zone
|
|
188
|
+
emitIcen(zone, "icen:upload", { files: valid });
|
|
148
189
|
}
|
|
149
190
|
const openPicker = () => input.click();
|
|
150
191
|
zone.addEventListener("click", (ev) => {
|
|
@@ -155,19 +196,20 @@ function setup(zone) {
|
|
|
155
196
|
});
|
|
156
197
|
zone.addEventListener("keydown", (ev) => {
|
|
157
198
|
if (ev.key === "Enter" || ev.key === " ") {
|
|
199
|
+
const tgt = ev.target;
|
|
200
|
+
if (tgt instanceof Element && tgt.closest(".upload-list")) return;
|
|
158
201
|
ev.preventDefault();
|
|
159
202
|
openPicker();
|
|
160
203
|
}
|
|
161
204
|
});
|
|
162
205
|
input.addEventListener("change", () => {
|
|
206
|
+
if (syncing) return;
|
|
163
207
|
if (!input.files) return;
|
|
164
208
|
const files = Array.from(input.files);
|
|
165
209
|
if (hasList) {
|
|
166
|
-
handleFiles(files);
|
|
167
210
|
input.value = "";
|
|
168
|
-
} else {
|
|
169
|
-
handleFiles(files);
|
|
170
211
|
}
|
|
212
|
+
handleFiles(files);
|
|
171
213
|
});
|
|
172
214
|
zone.addEventListener("dragover", (ev) => {
|
|
173
215
|
ev.preventDefault();
|
|
@@ -182,22 +224,15 @@ function setup(zone) {
|
|
|
182
224
|
const files = ev.dataTransfer?.files;
|
|
183
225
|
if (!files || files.length === 0) return;
|
|
184
226
|
handleFiles(Array.from(files));
|
|
185
|
-
if (!hasList)
|
|
186
|
-
try {
|
|
187
|
-
const dt = new DataTransfer();
|
|
188
|
-
for (const f of Array.from(files)) dt.items.add(f);
|
|
189
|
-
input.files = dt.files;
|
|
190
|
-
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
191
|
-
} catch {
|
|
192
|
-
}
|
|
193
|
-
}
|
|
227
|
+
if (!hasList) setInputFiles(Array.from(files));
|
|
194
228
|
});
|
|
195
229
|
}
|
|
196
|
-
function initUpload(root
|
|
230
|
+
function initUpload(root) {
|
|
197
231
|
if (typeof document === "undefined") return;
|
|
232
|
+
const scope = root ?? document;
|
|
198
233
|
const zones = [];
|
|
199
|
-
if (
|
|
200
|
-
zones.push(...Array.from(
|
|
234
|
+
if (scope instanceof HTMLElement && scope.matches(".upload")) zones.push(scope);
|
|
235
|
+
zones.push(...Array.from(scope.querySelectorAll(".upload")));
|
|
201
236
|
for (const z of zones) setup(z);
|
|
202
237
|
}
|
|
203
238
|
export {
|
package/dist/cli.mjs
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 用法:
|
|
8
8
|
* bunx --bun @icen.ai/ui add <slug...> 打印引入行(多个 slug 空格分隔)
|
|
9
9
|
* bunx --bun @icen.ai/ui list 列出全部组件 slug
|
|
10
|
-
* 数据源:同目录 registry.json(构建期由 scripts/slugs.mjs
|
|
10
|
+
* 数据源:同目录 registry.json(构建期由 scripts/build-css.ts 写入,映射数据源为 scripts/slugs.mjs)。
|
|
11
11
|
*/
|
|
12
12
|
import { readFileSync } from 'node:fs';
|
|
13
13
|
|