@icen.ai/ui 0.2.1 → 0.3.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/README.md +29 -16
- package/dist/base.css +389 -48
- package/dist/behaviors/accordion.mjs +20 -0
- package/dist/behaviors/carousel.mjs +30 -1
- package/dist/behaviors/charts.mjs +168 -0
- package/dist/behaviors/copy.mjs +4 -3
- package/dist/behaviors/datatable.mjs +748 -0
- package/dist/behaviors/input.mjs +20 -1
- package/dist/behaviors/modal.mjs +14 -0
- package/dist/behaviors/nav.mjs +31 -4
- package/dist/behaviors/sidebar.mjs +12 -0
- package/dist/behaviors/slider.mjs +35 -3
- package/dist/behaviors/tabs.mjs +100 -9
- package/dist/behaviors/tag-input.mjs +11 -0
- package/dist/behaviors/theme.mjs +26 -0
- package/dist/behaviors/toast.mjs +83 -15
- package/dist/behaviors/tree.mjs +57 -1
- package/dist/behaviors/upload.mjs +2 -3
- package/dist/cli.mjs +74 -0
- package/dist/components/accordion.mjs +3 -0
- package/dist/components/alert.mjs +2 -0
- package/dist/components/avatar.mjs +2 -0
- package/dist/components/breadcrumb.css +91 -18
- package/dist/components/breadcrumb.mjs +2 -0
- package/dist/components/btn.css +174 -14
- package/dist/components/btn.mjs +2 -0
- package/dist/components/card.css +118 -18
- package/dist/components/card.mjs +2 -0
- package/dist/components/carousel.mjs +3 -0
- package/dist/components/charts.css +225 -33
- package/dist/components/charts.mjs +3 -0
- package/dist/components/content.css +256 -74
- package/dist/components/context-menu.mjs +3 -0
- package/dist/components/datatable.css +343 -0
- package/dist/components/datatable.mjs +4 -0
- package/dist/components/desc.mjs +2 -0
- package/dist/components/dropdown.mjs +3 -0
- package/dist/components/empty.css +50 -10
- package/dist/components/empty.mjs +2 -0
- package/dist/components/feedback.css +158 -47
- package/dist/components/form.css +139 -22
- package/dist/components/input.css +165 -45
- package/dist/components/input.mjs +3 -0
- package/dist/components/kbd.mjs +2 -0
- package/dist/components/layout.mjs +2 -0
- package/dist/components/list.mjs +2 -0
- package/dist/components/media-card.mjs +2 -0
- package/dist/components/media.css +187 -40
- package/dist/components/menu.css +159 -32
- package/dist/components/modal.css +140 -34
- package/dist/components/modal.mjs +3 -0
- package/dist/components/nav.css +134 -32
- package/dist/components/nav.mjs +3 -0
- package/dist/components/pagination.css +105 -19
- package/dist/components/pagination.mjs +2 -0
- package/dist/components/panel.css +61 -6
- package/dist/components/panel.mjs +2 -0
- package/dist/components/pill.css +74 -14
- package/dist/components/pill.mjs +2 -0
- package/dist/components/popover.css +109 -7
- package/dist/components/popover.mjs +3 -0
- package/dist/components/progress.mjs +2 -0
- package/dist/components/rating.mjs +2 -0
- package/dist/components/result.mjs +2 -0
- package/dist/components/segmented.css +199 -50
- package/dist/components/segmented.mjs +2 -0
- package/dist/components/select.css +140 -39
- package/dist/components/select.mjs +3 -0
- package/dist/components/sidebar.css +162 -34
- package/dist/components/sidebar.mjs +3 -0
- package/dist/components/skeleton.mjs +2 -0
- package/dist/components/slider.css +169 -31
- package/dist/components/slider.mjs +3 -0
- package/dist/components/spinner.mjs +2 -0
- package/dist/components/stat.css +103 -8
- package/dist/components/stat.mjs +2 -0
- package/dist/components/steps.css +105 -31
- package/dist/components/steps.mjs +2 -0
- package/dist/components/switch.css +171 -54
- package/dist/components/switch.mjs +2 -0
- package/dist/components/table.css +114 -12
- package/dist/components/table.mjs +2 -0
- package/dist/components/tabs.css +104 -17
- package/dist/components/tabs.mjs +3 -0
- package/dist/components/tag-input.css +119 -23
- package/dist/components/tag-input.mjs +3 -0
- package/dist/components/timeline.mjs +2 -0
- package/dist/components/toast.css +85 -12
- package/dist/components/toast.mjs +3 -0
- package/dist/components/tooltip.css +86 -26
- package/dist/components/tooltip.mjs +2 -0
- package/dist/components/tree.mjs +3 -0
- package/dist/components/upload.css +144 -17
- package/dist/components/upload.mjs +3 -0
- package/dist/index.mjs +623 -42
- package/dist/registry.json +197 -2
- package/dist/retro-effects.css +264 -0
- package/dist/tokens/colors.css +18 -90
- package/dist/tokens/retro-effects.css +264 -0
- package/dist/tokens/style-profiles.css +47 -47
- package/dist/tokens.css +65 -137
- package/dist/ui.css +4861 -1019
- package/package.json +9 -1
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// package.json
|
|
2
2
|
var package_default = {
|
|
3
3
|
name: "@icen.ai/ui",
|
|
4
|
-
version: "0.
|
|
4
|
+
version: "0.3.0",
|
|
5
5
|
description: "Icen Design System \u2014 \u8BBE\u8BA1 tokens\uFF086 \u9884\u8BBE \xD7 \u660E\u6697\uFF09\u3001\u65E0\u6846\u67B6\u7EC4\u4EF6 CSS \u4E0E\u884C\u4E3A JS",
|
|
6
6
|
repository: {
|
|
7
7
|
type: "git",
|
|
@@ -12,13 +12,21 @@ var package_default = {
|
|
|
12
12
|
exports: {
|
|
13
13
|
".": "./dist/index.mjs",
|
|
14
14
|
"./behaviors/*": "./dist/behaviors/*.mjs",
|
|
15
|
+
"./kit/*": "./dist/components/*.mjs",
|
|
15
16
|
"./tokens.css": "./dist/tokens.css",
|
|
16
17
|
"./ui.css": "./dist/ui.css",
|
|
17
18
|
"./base.css": "./dist/base.css",
|
|
19
|
+
"./retro-effects.css": "./dist/retro-effects.css",
|
|
18
20
|
"./tokens/*": "./dist/tokens/*",
|
|
19
21
|
"./components/*": "./dist/components/*",
|
|
20
22
|
"./registry.json": "./dist/registry.json"
|
|
21
23
|
},
|
|
24
|
+
bin: {
|
|
25
|
+
"icen-ui": "./dist/cli.mjs"
|
|
26
|
+
},
|
|
27
|
+
sideEffects: [
|
|
28
|
+
"**/*.css"
|
|
29
|
+
],
|
|
22
30
|
files: [
|
|
23
31
|
"dist"
|
|
24
32
|
],
|
|
@@ -104,64 +112,188 @@ function toggleDark() {
|
|
|
104
112
|
function initTheme() {
|
|
105
113
|
const t = getTheme();
|
|
106
114
|
applyTheme(t);
|
|
115
|
+
if (isBrowser() && typeof window.matchMedia === "function" && !hasPersistedPreference()) {
|
|
116
|
+
let lastDark = t.dark;
|
|
117
|
+
try {
|
|
118
|
+
const mql = window.matchMedia("(prefers-color-scheme: dark)");
|
|
119
|
+
const onChange = (ev) => {
|
|
120
|
+
if (hasPersistedPreference()) return;
|
|
121
|
+
if (ev.matches === lastDark) return;
|
|
122
|
+
lastDark = ev.matches;
|
|
123
|
+
applyTheme({ ...getTheme(), dark: ev.matches });
|
|
124
|
+
};
|
|
125
|
+
if (typeof mql.addEventListener === "function") {
|
|
126
|
+
mql.addEventListener("change", onChange);
|
|
127
|
+
} else {
|
|
128
|
+
mql.addListener(onChange);
|
|
129
|
+
}
|
|
130
|
+
} catch {
|
|
131
|
+
}
|
|
132
|
+
}
|
|
107
133
|
return t;
|
|
108
134
|
}
|
|
135
|
+
function hasPersistedPreference() {
|
|
136
|
+
if (!isBrowser()) return false;
|
|
137
|
+
try {
|
|
138
|
+
return window.localStorage.getItem(STORAGE_KEY) !== null;
|
|
139
|
+
} catch {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
109
143
|
|
|
110
144
|
// src/behaviors/toast.ts
|
|
145
|
+
var DEFAULT_MS = 2600;
|
|
146
|
+
var ACTION_MS = 6e3;
|
|
147
|
+
var maxStack = 5;
|
|
148
|
+
function isBrowser2() {
|
|
149
|
+
return typeof document !== "undefined";
|
|
150
|
+
}
|
|
111
151
|
function ensureContainer() {
|
|
112
152
|
const existing = document.body.querySelector(".toast-container");
|
|
113
153
|
if (existing) return existing;
|
|
114
154
|
const el = document.createElement("div");
|
|
115
155
|
el.className = "toast-container";
|
|
156
|
+
el.setAttribute("role", "status");
|
|
157
|
+
el.setAttribute("aria-live", "polite");
|
|
158
|
+
el.setAttribute("aria-atomic", "false");
|
|
116
159
|
document.body.appendChild(el);
|
|
117
160
|
return el;
|
|
118
161
|
}
|
|
119
|
-
function
|
|
120
|
-
|
|
162
|
+
function trimStack(container) {
|
|
163
|
+
for (; ; ) {
|
|
164
|
+
const items = Array.from(container.querySelectorAll(":scope > .toast"));
|
|
165
|
+
if (items.length <= maxStack) return;
|
|
166
|
+
const oldest = items[0];
|
|
167
|
+
if (!oldest) return;
|
|
168
|
+
oldest.classList.add("leaving");
|
|
169
|
+
let done = false;
|
|
170
|
+
const finish = () => {
|
|
171
|
+
if (done) return;
|
|
172
|
+
done = true;
|
|
173
|
+
oldest.remove();
|
|
174
|
+
};
|
|
175
|
+
oldest.addEventListener("transitionend", finish, { once: true });
|
|
176
|
+
window.setTimeout(finish, 400);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function show(message, kind = "info", opts = {}) {
|
|
180
|
+
if (!isBrowser2()) return;
|
|
181
|
+
const { ms, actionLabel, onAction } = opts;
|
|
182
|
+
const container = ensureContainer();
|
|
121
183
|
const el = document.createElement("div");
|
|
122
184
|
el.className = kind === "info" ? "toast" : `toast ${kind}`;
|
|
123
|
-
el.
|
|
124
|
-
|
|
185
|
+
el.setAttribute("role", kind === "err" ? "alert" : "status");
|
|
186
|
+
const msgNode = document.createElement("span");
|
|
187
|
+
msgNode.className = "toast-msg";
|
|
188
|
+
msgNode.textContent = message;
|
|
189
|
+
el.appendChild(msgNode);
|
|
190
|
+
let actionBtn = null;
|
|
191
|
+
if (actionLabel) {
|
|
192
|
+
actionBtn = document.createElement("button");
|
|
193
|
+
actionBtn.type = "button";
|
|
194
|
+
actionBtn.className = "toast-action";
|
|
195
|
+
actionBtn.textContent = actionLabel;
|
|
196
|
+
el.appendChild(actionBtn);
|
|
197
|
+
}
|
|
198
|
+
container.appendChild(el);
|
|
199
|
+
trimStack(container);
|
|
125
200
|
let removed = false;
|
|
126
201
|
const remove = () => {
|
|
127
202
|
if (removed) return;
|
|
128
203
|
removed = true;
|
|
129
|
-
el.remove();
|
|
130
|
-
};
|
|
131
|
-
window.setTimeout(() => {
|
|
132
204
|
el.classList.add("leaving");
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
205
|
+
let done = false;
|
|
206
|
+
const finish = () => {
|
|
207
|
+
if (done) return;
|
|
208
|
+
done = true;
|
|
209
|
+
el.remove();
|
|
210
|
+
};
|
|
211
|
+
el.addEventListener("transitionend", finish, { once: true });
|
|
212
|
+
window.setTimeout(finish, 400);
|
|
213
|
+
};
|
|
214
|
+
const duration = ms ?? (actionLabel ? ACTION_MS : DEFAULT_MS);
|
|
215
|
+
const timer = window.setTimeout(remove, duration);
|
|
216
|
+
if (actionBtn && onAction) {
|
|
217
|
+
actionBtn.addEventListener("click", () => {
|
|
218
|
+
window.clearTimeout(timer);
|
|
219
|
+
const ret = onAction();
|
|
220
|
+
if (ret === false) return;
|
|
221
|
+
remove();
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
var toast = Object.assign(
|
|
226
|
+
(message, kind = "info", ms) => show(message, kind, { ms }),
|
|
227
|
+
{
|
|
228
|
+
ok: (message, ms) => show(message, "ok", { ms }),
|
|
229
|
+
err: (message, ms) => show(message, "err", { ms }),
|
|
230
|
+
warn: (message, ms) => show(message, "warn", { ms }),
|
|
231
|
+
info: (message, ms) => show(message, "info", { ms }),
|
|
232
|
+
action: (message, actionLabel, onAction, ms) => show(message, "info", { actionLabel, onAction, ms }),
|
|
233
|
+
dismiss: () => {
|
|
234
|
+
if (!isBrowser2()) return;
|
|
235
|
+
const c = document.body.querySelector(".toast-container");
|
|
236
|
+
if (!c) return;
|
|
237
|
+
c.querySelectorAll(".toast").forEach((t) => t.remove());
|
|
238
|
+
},
|
|
239
|
+
config: (opts) => {
|
|
240
|
+
if (typeof opts.maxStack === "number" && opts.maxStack > 0) maxStack = opts.maxStack;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
);
|
|
142
244
|
|
|
143
245
|
// src/behaviors/tabs.ts
|
|
246
|
+
function tabButtons(container) {
|
|
247
|
+
return Array.from(container.querySelectorAll("[data-tab]"));
|
|
248
|
+
}
|
|
144
249
|
function tabNames(container) {
|
|
145
|
-
return
|
|
250
|
+
return tabButtons(container).map((t) => t.getAttribute("data-tab")).filter((n) => n !== null);
|
|
146
251
|
}
|
|
147
252
|
function currentHash() {
|
|
148
253
|
return window.location.hash.replace(/^#/, "");
|
|
149
254
|
}
|
|
150
255
|
function activate(container, name, writeHash) {
|
|
256
|
+
const buttons = tabButtons(container);
|
|
151
257
|
let matched = false;
|
|
152
|
-
|
|
258
|
+
buttons.forEach((t) => {
|
|
153
259
|
const on = t.getAttribute("data-tab") === name;
|
|
154
260
|
if (on) matched = true;
|
|
155
261
|
t.classList.toggle("active", on);
|
|
262
|
+
t.setAttribute("aria-selected", String(on));
|
|
263
|
+
t.tabIndex = on ? 0 : -1;
|
|
156
264
|
});
|
|
157
265
|
if (!matched) return;
|
|
158
266
|
container.querySelectorAll("[data-tab-panel]").forEach((p) => {
|
|
159
|
-
|
|
267
|
+
const isOn = p.getAttribute("data-tab-panel") === name;
|
|
268
|
+
p.hidden = !isOn;
|
|
269
|
+
if (isOn && !p.getAttribute("aria-labelledby")) {
|
|
270
|
+
const btn = buttons.find((b) => b.getAttribute("data-tab") === name);
|
|
271
|
+
if (btn?.id) p.setAttribute("aria-labelledby", btn.id);
|
|
272
|
+
}
|
|
160
273
|
});
|
|
161
274
|
if (writeHash && typeof window !== "undefined") {
|
|
162
275
|
window.history.replaceState(null, "", `#${name}`);
|
|
163
276
|
}
|
|
164
277
|
}
|
|
278
|
+
function isDisabled(btn) {
|
|
279
|
+
return btn.hasAttribute("disabled") || btn.getAttribute("aria-disabled") === "true" || btn.classList.contains("is-disabled");
|
|
280
|
+
}
|
|
281
|
+
function focusTab(container, idx) {
|
|
282
|
+
const buttons = tabButtons(container);
|
|
283
|
+
const target = buttons[idx];
|
|
284
|
+
if (!target || isDisabled(target)) return;
|
|
285
|
+
target.focus();
|
|
286
|
+
}
|
|
287
|
+
function nextEnabled(container, from, dir) {
|
|
288
|
+
const buttons = tabButtons(container);
|
|
289
|
+
if (buttons.length === 0) return -1;
|
|
290
|
+
let idx = from;
|
|
291
|
+
for (let i = 0; i < buttons.length; i++) {
|
|
292
|
+
idx = (idx + dir + buttons.length) % buttons.length;
|
|
293
|
+
if (!isDisabled(buttons[idx])) return idx;
|
|
294
|
+
}
|
|
295
|
+
return -1;
|
|
296
|
+
}
|
|
165
297
|
function setup(container) {
|
|
166
298
|
const el = container;
|
|
167
299
|
if (el.__icenTabsInit) return;
|
|
@@ -169,14 +301,69 @@ function setup(container) {
|
|
|
169
301
|
const useHash = container.hasAttribute("data-tabs-hash");
|
|
170
302
|
const names = tabNames(container);
|
|
171
303
|
if (names.length === 0) return;
|
|
172
|
-
|
|
304
|
+
const buttons = tabButtons(container);
|
|
305
|
+
buttons.forEach((b) => {
|
|
306
|
+
if (!b.getAttribute("role")) b.setAttribute("role", "tab");
|
|
307
|
+
if (!b.hasAttribute("aria-selected")) b.setAttribute("aria-selected", "false");
|
|
308
|
+
});
|
|
309
|
+
const tablist = buttons[0]?.parentElement;
|
|
310
|
+
if (tablist && !tablist.getAttribute("role")) tablist.setAttribute("role", "tablist");
|
|
311
|
+
const onClick2 = (ev) => {
|
|
173
312
|
const target = ev.target;
|
|
174
313
|
if (!(target instanceof Element)) return;
|
|
175
314
|
const btn = target.closest("[data-tab]");
|
|
176
315
|
if (!btn || btn.closest("[data-tabs]") !== container) return;
|
|
316
|
+
if (btn instanceof HTMLElement && isDisabled(btn)) return;
|
|
177
317
|
const name = btn.getAttribute("data-tab");
|
|
178
318
|
if (name) activate(container, name, useHash);
|
|
179
|
-
}
|
|
319
|
+
};
|
|
320
|
+
const onKeyDown4 = (ev) => {
|
|
321
|
+
const kev = ev;
|
|
322
|
+
const target = kev.target;
|
|
323
|
+
if (!(target instanceof Element)) return;
|
|
324
|
+
const btn = target.closest("[data-tab]");
|
|
325
|
+
if (!btn || btn.closest("[data-tabs]") !== container) return;
|
|
326
|
+
const buttonsList = tabButtons(container);
|
|
327
|
+
const currentIdx = buttonsList.indexOf(btn);
|
|
328
|
+
if (currentIdx === -1) return;
|
|
329
|
+
switch (kev.key) {
|
|
330
|
+
case "ArrowRight":
|
|
331
|
+
case "ArrowDown": {
|
|
332
|
+
kev.preventDefault();
|
|
333
|
+
const next = nextEnabled(container, currentIdx, 1);
|
|
334
|
+
if (next >= 0) focusTab(container, next);
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
case "ArrowLeft":
|
|
338
|
+
case "ArrowUp": {
|
|
339
|
+
kev.preventDefault();
|
|
340
|
+
const prev = nextEnabled(container, currentIdx, -1);
|
|
341
|
+
if (prev >= 0) focusTab(container, prev);
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
case "Home": {
|
|
345
|
+
kev.preventDefault();
|
|
346
|
+
focusTab(container, 0);
|
|
347
|
+
break;
|
|
348
|
+
}
|
|
349
|
+
case "End": {
|
|
350
|
+
kev.preventDefault();
|
|
351
|
+
focusTab(container, buttonsList.length - 1);
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
case "Enter":
|
|
355
|
+
case " ": {
|
|
356
|
+
if (btn instanceof HTMLElement && !isDisabled(btn)) {
|
|
357
|
+
kev.preventDefault();
|
|
358
|
+
const name = btn.getAttribute("data-tab");
|
|
359
|
+
if (name) activate(container, name, useHash);
|
|
360
|
+
}
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
container.addEventListener("click", onClick2);
|
|
366
|
+
container.addEventListener("keydown", onKeyDown4);
|
|
180
367
|
let initial;
|
|
181
368
|
if (useHash) {
|
|
182
369
|
const h = currentHash();
|
|
@@ -186,12 +373,18 @@ function setup(container) {
|
|
|
186
373
|
if (n && names.includes(n)) initial = n;
|
|
187
374
|
}
|
|
188
375
|
activate(container, initial ?? names[0], false);
|
|
376
|
+
const onHash = () => {
|
|
377
|
+
const h = currentHash();
|
|
378
|
+
if (names.includes(h)) activate(container, h, false);
|
|
379
|
+
};
|
|
189
380
|
if (useHash && typeof window !== "undefined") {
|
|
190
|
-
window.addEventListener("hashchange",
|
|
191
|
-
const h = currentHash();
|
|
192
|
-
if (names.includes(h)) activate(container, h, false);
|
|
193
|
-
});
|
|
381
|
+
window.addEventListener("hashchange", onHash);
|
|
194
382
|
}
|
|
383
|
+
el.__icenTabsCleanup = () => {
|
|
384
|
+
container.removeEventListener("click", onClick2);
|
|
385
|
+
container.removeEventListener("keydown", onKeyDown4);
|
|
386
|
+
window.removeEventListener("hashchange", onHash);
|
|
387
|
+
};
|
|
195
388
|
}
|
|
196
389
|
function initTabs(root = document) {
|
|
197
390
|
if (typeof document === "undefined") return;
|
|
@@ -234,11 +427,12 @@ async function copyText(text) {
|
|
|
234
427
|
function flashDone(btn) {
|
|
235
428
|
const prev = flashing.get(btn);
|
|
236
429
|
if (prev) window.clearTimeout(prev.timer);
|
|
237
|
-
const
|
|
238
|
-
btn.textContent
|
|
430
|
+
const iconOnly = btn.hasAttribute("data-copy-icon");
|
|
431
|
+
const original = prev ? prev.original : iconOnly ? null : btn.textContent;
|
|
432
|
+
if (!iconOnly) btn.textContent = "\u5DF2\u590D\u5236";
|
|
239
433
|
btn.classList.add("done");
|
|
240
434
|
const timer = window.setTimeout(() => {
|
|
241
|
-
btn.textContent = original;
|
|
435
|
+
if (!iconOnly) btn.textContent = original;
|
|
242
436
|
btn.classList.remove("done");
|
|
243
437
|
flashing.delete(btn);
|
|
244
438
|
}, 1400);
|
|
@@ -311,14 +505,25 @@ function setupOtp(group) {
|
|
|
311
505
|
if (el.__icenInputInit) return;
|
|
312
506
|
el.__icenInputInit = true;
|
|
313
507
|
const cells = Array.from(group.querySelectorAll(".otp-cell"));
|
|
508
|
+
const syncFilled = (cell) => {
|
|
509
|
+
cell.classList.toggle("is-filled", cell.value.length > 0);
|
|
510
|
+
};
|
|
314
511
|
cells.forEach((cell, i) => {
|
|
512
|
+
if (!cell.hasAttribute("aria-label")) {
|
|
513
|
+
cell.setAttribute("aria-label", `\u7B2C ${i + 1} \u4F4D\uFF0C\u5171 ${cells.length} \u4F4D`);
|
|
514
|
+
}
|
|
515
|
+
syncFilled(cell);
|
|
315
516
|
cell.addEventListener("input", () => {
|
|
316
517
|
const ch = cell.value.slice(-1);
|
|
317
518
|
cell.value = ch;
|
|
519
|
+
syncFilled(cell);
|
|
318
520
|
if (ch && i < cells.length - 1) cells[i + 1].focus();
|
|
319
521
|
});
|
|
320
522
|
cell.addEventListener("keydown", (ev) => {
|
|
321
|
-
if (ev.key === "Backspace" && !cell.value && i > 0)
|
|
523
|
+
if (ev.key === "Backspace" && !cell.value && i > 0) {
|
|
524
|
+
ev.preventDefault();
|
|
525
|
+
cells[i - 1].focus();
|
|
526
|
+
}
|
|
322
527
|
if (ev.key === "ArrowLeft" && i > 0) {
|
|
323
528
|
ev.preventDefault();
|
|
324
529
|
cells[i - 1].focus();
|
|
@@ -334,10 +539,18 @@ function setupOtp(group) {
|
|
|
334
539
|
for (let k = 0; k < text.length; k++) {
|
|
335
540
|
const target = cells[i + k];
|
|
336
541
|
target.value = text[k] ?? "";
|
|
542
|
+
syncFilled(target);
|
|
337
543
|
}
|
|
338
544
|
cells[Math.min(i + text.length, cells.length - 1)].focus();
|
|
339
545
|
});
|
|
340
546
|
});
|
|
547
|
+
const fireComplete = () => {
|
|
548
|
+
const code = cells.map((c) => c.value).join("");
|
|
549
|
+
if (code.length === cells.length && cells.every((c) => c.value.length > 0)) {
|
|
550
|
+
group.dispatchEvent(new CustomEvent("otp:complete", { detail: { code }, bubbles: true }));
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
cells.forEach((cell) => cell.addEventListener("input", fireComplete));
|
|
341
554
|
}
|
|
342
555
|
function initInput(root = document) {
|
|
343
556
|
if (typeof document === "undefined") return;
|
|
@@ -507,7 +720,19 @@ function setupSingle(slider, native) {
|
|
|
507
720
|
if (fill) fill.style.width = `${p}%`;
|
|
508
721
|
if (thumb) thumb.style.left = `calc(${p}% - 8px)`;
|
|
509
722
|
};
|
|
510
|
-
native.addEventListener("input",
|
|
723
|
+
native.addEventListener("input", () => {
|
|
724
|
+
sync();
|
|
725
|
+
slider.dispatchEvent(new CustomEvent("icen:slider-input", {
|
|
726
|
+
bubbles: true,
|
|
727
|
+
detail: { value: Number(native.value) }
|
|
728
|
+
}));
|
|
729
|
+
});
|
|
730
|
+
native.addEventListener("change", () => {
|
|
731
|
+
slider.dispatchEvent(new CustomEvent("icen:slider-change", {
|
|
732
|
+
bubbles: true,
|
|
733
|
+
detail: { value: Number(native.value) }
|
|
734
|
+
}));
|
|
735
|
+
});
|
|
511
736
|
sync();
|
|
512
737
|
}
|
|
513
738
|
function setupDual(slider, natives) {
|
|
@@ -540,8 +765,28 @@ function setupDual(slider, natives) {
|
|
|
540
765
|
lo.style.clipPath = `inset(0 ${100 - pLo}% 0 0)`;
|
|
541
766
|
hi.style.clipPath = `inset(0 0 0 ${pHi}%)`;
|
|
542
767
|
};
|
|
543
|
-
lo.addEventListener("input", () =>
|
|
544
|
-
|
|
768
|
+
lo.addEventListener("input", () => {
|
|
769
|
+
sync(lo);
|
|
770
|
+
slider.dispatchEvent(new CustomEvent("icen:slider-input", {
|
|
771
|
+
bubbles: true,
|
|
772
|
+
detail: { lo: Number(lo.value), hi: Number(hi.value) }
|
|
773
|
+
}));
|
|
774
|
+
});
|
|
775
|
+
hi.addEventListener("input", () => {
|
|
776
|
+
sync(hi);
|
|
777
|
+
slider.dispatchEvent(new CustomEvent("icen:slider-input", {
|
|
778
|
+
bubbles: true,
|
|
779
|
+
detail: { lo: Number(lo.value), hi: Number(hi.value) }
|
|
780
|
+
}));
|
|
781
|
+
});
|
|
782
|
+
const onChange = () => {
|
|
783
|
+
slider.dispatchEvent(new CustomEvent("icen:slider-change", {
|
|
784
|
+
bubbles: true,
|
|
785
|
+
detail: { lo: Number(lo.value), hi: Number(hi.value) }
|
|
786
|
+
}));
|
|
787
|
+
};
|
|
788
|
+
lo.addEventListener("change", onChange);
|
|
789
|
+
hi.addEventListener("change", onChange);
|
|
545
790
|
sync(null);
|
|
546
791
|
}
|
|
547
792
|
function setup3(slider) {
|
|
@@ -577,10 +822,20 @@ function setup4(wrap) {
|
|
|
577
822
|
wrap.setAttribute("data-tags", tags.join(","));
|
|
578
823
|
wrap.classList.toggle("is-max", tags.length >= max);
|
|
579
824
|
field.placeholder = tags.length === 0 ? placeholder : "";
|
|
825
|
+
if (!wrap.hasAttribute("aria-label")) {
|
|
826
|
+
wrap.setAttribute("aria-label", `\u6807\u7B7E\u8F93\u5165\uFF0C\u5171 ${tags.length} \u4E2A`);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
function emit() {
|
|
830
|
+
wrap.dispatchEvent(new CustomEvent("icen:tags-change", {
|
|
831
|
+
bubbles: true,
|
|
832
|
+
detail: { tags: [...tags] }
|
|
833
|
+
}));
|
|
580
834
|
}
|
|
581
835
|
function remove(index) {
|
|
582
836
|
tags.splice(index, 1);
|
|
583
837
|
render();
|
|
838
|
+
emit();
|
|
584
839
|
}
|
|
585
840
|
function chip(tag, index) {
|
|
586
841
|
const el2 = document.createElement("span");
|
|
@@ -613,6 +868,7 @@ function setup4(wrap) {
|
|
|
613
868
|
tags.push(v);
|
|
614
869
|
field.value = "";
|
|
615
870
|
render();
|
|
871
|
+
emit();
|
|
616
872
|
}
|
|
617
873
|
field.addEventListener("keydown", (ev) => {
|
|
618
874
|
if (ev.key === "Enter" || ev.key === ",") {
|
|
@@ -672,10 +928,9 @@ function setup5(zone) {
|
|
|
672
928
|
}
|
|
673
929
|
if (assigned) {
|
|
674
930
|
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
675
|
-
} else {
|
|
676
|
-
const list = Array.from(files);
|
|
677
|
-
zone.dispatchEvent(new CustomEvent("icn:upload", { bubbles: true, detail: { files: list } }));
|
|
678
931
|
}
|
|
932
|
+
const list = Array.from(files);
|
|
933
|
+
zone.dispatchEvent(new CustomEvent("icen:upload", { bubbles: true, detail: { files: list } }));
|
|
679
934
|
});
|
|
680
935
|
}
|
|
681
936
|
function initUpload(root = document) {
|
|
@@ -716,6 +971,20 @@ function openModal(id) {
|
|
|
716
971
|
panel2.setAttribute("role", "dialog");
|
|
717
972
|
panel2.setAttribute("aria-modal", "true");
|
|
718
973
|
if (!panel2.hasAttribute("tabindex")) panel2.tabIndex = -1;
|
|
974
|
+
if (!panel2.hasAttribute("aria-labelledby")) {
|
|
975
|
+
const title = panel2.querySelector(".modal-title");
|
|
976
|
+
if (title) {
|
|
977
|
+
if (!title.id) title.id = `modal-title-${CSS.escape(id)}`;
|
|
978
|
+
panel2.setAttribute("aria-labelledby", title.id);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
if (!panel2.hasAttribute("aria-describedby")) {
|
|
982
|
+
const desc = panel2.querySelector(".modal-description");
|
|
983
|
+
if (desc) {
|
|
984
|
+
if (!desc.id) desc.id = `modal-desc-${CSS.escape(id)}`;
|
|
985
|
+
panel2.setAttribute("aria-describedby", desc.id);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
719
988
|
const closeOnEscape = backdrop.dataset.closeOnEscape !== "false";
|
|
720
989
|
keyHandler = (e) => {
|
|
721
990
|
if (e.key === "Escape") {
|
|
@@ -1314,6 +1583,7 @@ function setup6(container) {
|
|
|
1314
1583
|
if (!trigger) return;
|
|
1315
1584
|
const item = trigger.closest(".accordion-item");
|
|
1316
1585
|
if (!item || trigger.closest(".accordion") !== container) return;
|
|
1586
|
+
if (item.classList.contains("is-disabled")) return;
|
|
1317
1587
|
const open = item.classList.toggle("is-open");
|
|
1318
1588
|
trigger.setAttribute("aria-expanded", String(open));
|
|
1319
1589
|
if (single && open) {
|
|
@@ -1324,6 +1594,25 @@ function setup6(container) {
|
|
|
1324
1594
|
});
|
|
1325
1595
|
}
|
|
1326
1596
|
});
|
|
1597
|
+
container.addEventListener("keydown", (ev) => {
|
|
1598
|
+
const kev = ev;
|
|
1599
|
+
const target = kev.target;
|
|
1600
|
+
if (!(target instanceof Element)) return;
|
|
1601
|
+
const trigger = target.closest(".accordion-trigger");
|
|
1602
|
+
if (!trigger || trigger.closest(".accordion") !== container) return;
|
|
1603
|
+
const triggers = Array.from(
|
|
1604
|
+
container.querySelectorAll(".accordion-item:not(.is-disabled) > .accordion-trigger")
|
|
1605
|
+
);
|
|
1606
|
+
const idx = triggers.indexOf(trigger);
|
|
1607
|
+
if (idx === -1) return;
|
|
1608
|
+
if (kev.key === "Home") {
|
|
1609
|
+
kev.preventDefault();
|
|
1610
|
+
triggers[0]?.focus();
|
|
1611
|
+
} else if (kev.key === "End") {
|
|
1612
|
+
kev.preventDefault();
|
|
1613
|
+
triggers[triggers.length - 1]?.focus();
|
|
1614
|
+
}
|
|
1615
|
+
});
|
|
1327
1616
|
}
|
|
1328
1617
|
function initAccordion(root = document) {
|
|
1329
1618
|
if (typeof document === "undefined") return;
|
|
@@ -1338,6 +1627,9 @@ function setup7(tree) {
|
|
|
1338
1627
|
const el = tree;
|
|
1339
1628
|
if (el.__icenTreeInit) return;
|
|
1340
1629
|
el.__icenTreeInit = true;
|
|
1630
|
+
tree.querySelectorAll(".tree-node").forEach((node) => {
|
|
1631
|
+
if (!node.hasAttribute("tabindex")) node.tabIndex = 0;
|
|
1632
|
+
});
|
|
1341
1633
|
tree.addEventListener("click", (ev) => {
|
|
1342
1634
|
const target = ev.target;
|
|
1343
1635
|
if (!(target instanceof Element)) return;
|
|
@@ -1363,8 +1655,61 @@ function setup7(tree) {
|
|
|
1363
1655
|
return;
|
|
1364
1656
|
}
|
|
1365
1657
|
if (childUl) return;
|
|
1366
|
-
tree.querySelectorAll(
|
|
1658
|
+
tree.querySelectorAll('.tree-node.is-selected, .tree-node[aria-selected="true"]').forEach((n) => {
|
|
1659
|
+
n.classList.remove("is-selected");
|
|
1660
|
+
n.removeAttribute("aria-selected");
|
|
1661
|
+
});
|
|
1367
1662
|
node.classList.add("is-selected");
|
|
1663
|
+
node.setAttribute("aria-selected", "true");
|
|
1664
|
+
tree.dispatchEvent(new CustomEvent("icen:tree-select", {
|
|
1665
|
+
bubbles: true,
|
|
1666
|
+
detail: { node }
|
|
1667
|
+
}));
|
|
1668
|
+
});
|
|
1669
|
+
tree.addEventListener("keydown", (ev) => {
|
|
1670
|
+
const kev = ev;
|
|
1671
|
+
const target = kev.target;
|
|
1672
|
+
if (!(target instanceof Element)) return;
|
|
1673
|
+
const node = target.closest(".tree-node");
|
|
1674
|
+
if (!node || node.closest(".tree") !== tree) return;
|
|
1675
|
+
if (node.classList.contains("is-disabled")) return;
|
|
1676
|
+
const allNodes = Array.from(tree.querySelectorAll(".tree-node:not(.is-disabled)"));
|
|
1677
|
+
const idx = allNodes.indexOf(node);
|
|
1678
|
+
if (idx === -1) return;
|
|
1679
|
+
switch (kev.key) {
|
|
1680
|
+
case "ArrowDown": {
|
|
1681
|
+
kev.preventDefault();
|
|
1682
|
+
allNodes[(idx + 1) % allNodes.length]?.focus();
|
|
1683
|
+
break;
|
|
1684
|
+
}
|
|
1685
|
+
case "ArrowUp": {
|
|
1686
|
+
kev.preventDefault();
|
|
1687
|
+
allNodes[(idx - 1 + allNodes.length) % allNodes.length]?.focus();
|
|
1688
|
+
break;
|
|
1689
|
+
}
|
|
1690
|
+
case "ArrowRight": {
|
|
1691
|
+
const li = node.closest("li");
|
|
1692
|
+
const childUl = li?.querySelector(":scope > ul");
|
|
1693
|
+
if (childUl?.hasAttribute("hidden")) {
|
|
1694
|
+
kev.preventDefault();
|
|
1695
|
+
childUl.removeAttribute("hidden");
|
|
1696
|
+
node.querySelector(".tree-toggle")?.setAttribute("aria-expanded", "true");
|
|
1697
|
+
node.querySelector(".tree-toggle")?.classList.remove("is-collapsed");
|
|
1698
|
+
}
|
|
1699
|
+
break;
|
|
1700
|
+
}
|
|
1701
|
+
case "ArrowLeft": {
|
|
1702
|
+
const li = node.closest("li");
|
|
1703
|
+
const childUl = li?.querySelector(":scope > ul");
|
|
1704
|
+
if (childUl && !childUl.hasAttribute("hidden")) {
|
|
1705
|
+
kev.preventDefault();
|
|
1706
|
+
childUl.setAttribute("hidden", "");
|
|
1707
|
+
node.querySelector(".tree-toggle")?.setAttribute("aria-expanded", "false");
|
|
1708
|
+
node.querySelector(".tree-toggle")?.classList.add("is-collapsed");
|
|
1709
|
+
}
|
|
1710
|
+
break;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1368
1713
|
});
|
|
1369
1714
|
}
|
|
1370
1715
|
function initTree(root = document) {
|
|
@@ -1405,7 +1750,15 @@ function setup8(carousel) {
|
|
|
1405
1750
|
if (!track) return;
|
|
1406
1751
|
track.style.transform = `translateX(${-index * 100}%)`;
|
|
1407
1752
|
slides.forEach((s, i) => s.setAttribute("aria-hidden", String(i !== index)));
|
|
1408
|
-
dots.forEach((d, i) =>
|
|
1753
|
+
dots.forEach((d, i) => {
|
|
1754
|
+
d.classList.toggle("active", i === index);
|
|
1755
|
+
if (i === index) d.setAttribute("aria-current", "true");
|
|
1756
|
+
else d.removeAttribute("aria-current");
|
|
1757
|
+
});
|
|
1758
|
+
carousel.dispatchEvent(new CustomEvent("icen:carousel-change", {
|
|
1759
|
+
bubbles: true,
|
|
1760
|
+
detail: { index, count }
|
|
1761
|
+
}));
|
|
1409
1762
|
}
|
|
1410
1763
|
function go(i) {
|
|
1411
1764
|
index = (i % count + count) % count;
|
|
@@ -1413,6 +1766,27 @@ function setup8(carousel) {
|
|
|
1413
1766
|
}
|
|
1414
1767
|
prev?.addEventListener("click", () => go(index - 1));
|
|
1415
1768
|
next?.addEventListener("click", () => go(index + 1));
|
|
1769
|
+
carousel.addEventListener("keydown", (ev) => {
|
|
1770
|
+
const kev = ev;
|
|
1771
|
+
switch (kev.key) {
|
|
1772
|
+
case "ArrowLeft":
|
|
1773
|
+
kev.preventDefault();
|
|
1774
|
+
go(index - 1);
|
|
1775
|
+
break;
|
|
1776
|
+
case "ArrowRight":
|
|
1777
|
+
kev.preventDefault();
|
|
1778
|
+
go(index + 1);
|
|
1779
|
+
break;
|
|
1780
|
+
case "Home":
|
|
1781
|
+
kev.preventDefault();
|
|
1782
|
+
go(0);
|
|
1783
|
+
break;
|
|
1784
|
+
case "End":
|
|
1785
|
+
kev.preventDefault();
|
|
1786
|
+
go(count - 1);
|
|
1787
|
+
break;
|
|
1788
|
+
}
|
|
1789
|
+
});
|
|
1416
1790
|
render();
|
|
1417
1791
|
}
|
|
1418
1792
|
function initCarousel(root = document) {
|
|
@@ -1683,24 +2057,216 @@ function renderLine(el, opts) {
|
|
|
1683
2057
|
wrap.append(svg, labelsRow(labels.slice(-7)));
|
|
1684
2058
|
el.appendChild(wrap);
|
|
1685
2059
|
}
|
|
2060
|
+
function isoDate(d) {
|
|
2061
|
+
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
2062
|
+
const day = String(d.getDate()).padStart(2, "0");
|
|
2063
|
+
return `${d.getFullYear()}-${m}-${day}`;
|
|
2064
|
+
}
|
|
2065
|
+
function renderHeatmap(el, opts) {
|
|
2066
|
+
if (typeof document === "undefined") return;
|
|
2067
|
+
el.textContent = "";
|
|
2068
|
+
let entries;
|
|
2069
|
+
if (opts.data && opts.data.length > 0) {
|
|
2070
|
+
entries = opts.data;
|
|
2071
|
+
} else if (opts.values && opts.values.length > 0) {
|
|
2072
|
+
const today = /* @__PURE__ */ new Date();
|
|
2073
|
+
entries = opts.values.map((value, i) => {
|
|
2074
|
+
const d = new Date(today);
|
|
2075
|
+
d.setDate(d.getDate() - (opts.values.length - 1 - i));
|
|
2076
|
+
return { date: isoDate(d), value };
|
|
2077
|
+
});
|
|
2078
|
+
} else {
|
|
2079
|
+
renderEmpty(el, opts.emptyLabel);
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
2082
|
+
const weeks = opts.weeks ?? Math.ceil(entries.length / 7);
|
|
2083
|
+
if (entries.length > weeks * 7) entries = entries.slice(entries.length - weeks * 7);
|
|
2084
|
+
const max = Math.max(...entries.map((e) => e.value), 1);
|
|
2085
|
+
const levelOf = (v) => v <= 0 ? 0 : Math.min(4, Math.ceil(v / max * 4));
|
|
2086
|
+
const wrap = document.createElement("div");
|
|
2087
|
+
wrap.className = "chart-heatmap";
|
|
2088
|
+
if (opts.tone) wrap.style.setProperty("--chart-tone", toneVar(opts.tone));
|
|
2089
|
+
const grid = document.createElement("div");
|
|
2090
|
+
grid.className = "chart-heatmap-grid";
|
|
2091
|
+
grid.setAttribute("role", "img");
|
|
2092
|
+
const first = /* @__PURE__ */ new Date(`${entries[0].date}T00:00:00`);
|
|
2093
|
+
const pad = (first.getDay() + 6) % 7;
|
|
2094
|
+
for (let i = 0; i < pad; i++) {
|
|
2095
|
+
const blank = document.createElement("span");
|
|
2096
|
+
blank.className = "chart-heatmap-cell is-blank";
|
|
2097
|
+
grid.appendChild(blank);
|
|
2098
|
+
}
|
|
2099
|
+
for (const entry of entries) {
|
|
2100
|
+
const cell = document.createElement("span");
|
|
2101
|
+
cell.className = "chart-heatmap-cell";
|
|
2102
|
+
cell.dataset.level = String(levelOf(entry.value));
|
|
2103
|
+
cell.setAttribute("title", `${entry.date}\uFF1A${fmt(opts, entry.value)}`);
|
|
2104
|
+
grid.appendChild(cell);
|
|
2105
|
+
}
|
|
2106
|
+
const legend = document.createElement("div");
|
|
2107
|
+
legend.className = "chart-heatmap-legend";
|
|
2108
|
+
const less = document.createElement("span");
|
|
2109
|
+
less.textContent = "\u5C11";
|
|
2110
|
+
const more = document.createElement("span");
|
|
2111
|
+
more.textContent = "\u591A";
|
|
2112
|
+
legend.appendChild(less);
|
|
2113
|
+
for (let lv = 0; lv <= 4; lv++) {
|
|
2114
|
+
const cell = document.createElement("span");
|
|
2115
|
+
cell.className = "chart-heatmap-cell";
|
|
2116
|
+
cell.dataset.level = String(lv);
|
|
2117
|
+
legend.appendChild(cell);
|
|
2118
|
+
}
|
|
2119
|
+
legend.appendChild(more);
|
|
2120
|
+
wrap.append(grid, legend);
|
|
2121
|
+
el.appendChild(wrap);
|
|
2122
|
+
}
|
|
2123
|
+
function renderSparkline(el, opts) {
|
|
2124
|
+
if (typeof document === "undefined") return;
|
|
2125
|
+
el.textContent = "";
|
|
2126
|
+
const values = opts.values;
|
|
2127
|
+
if (values.length === 0) {
|
|
2128
|
+
renderEmpty(el, opts.emptyLabel);
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
2131
|
+
const width = 96;
|
|
2132
|
+
const height = 28;
|
|
2133
|
+
const pad = 3;
|
|
2134
|
+
const max = Math.max(...values, 1);
|
|
2135
|
+
const tone = toneVar(opts.tone);
|
|
2136
|
+
const points = values.map((value, i) => ({
|
|
2137
|
+
x: values.length <= 1 ? width / 2 : pad + i / (values.length - 1) * (width - pad * 2),
|
|
2138
|
+
y: height - pad - value / max * (height - pad * 2),
|
|
2139
|
+
value
|
|
2140
|
+
}));
|
|
2141
|
+
const line = points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(" ");
|
|
2142
|
+
const last = points[points.length - 1];
|
|
2143
|
+
const first = points[0];
|
|
2144
|
+
const area = `${line} L${last.x.toFixed(1)},${height - pad} L${first.x.toFixed(1)},${height - pad} Z`;
|
|
2145
|
+
const gradientId = `icen-chart-line-${++lineGradientSeq}`;
|
|
2146
|
+
const wrap = document.createElement("div");
|
|
2147
|
+
wrap.className = "chart-sparkline";
|
|
2148
|
+
const svg = svgEl("svg", { viewBox: `0 0 ${width} ${height}`, role: "img" });
|
|
2149
|
+
const defs = svgEl("defs", {});
|
|
2150
|
+
const gradient = svgEl("linearGradient", { id: gradientId, x1: "0", x2: "0", y1: "0", y2: "1" });
|
|
2151
|
+
gradient.appendChild(svgEl("stop", { offset: "0%", "stop-color": tone, "stop-opacity": "0.28" }));
|
|
2152
|
+
gradient.appendChild(svgEl("stop", { offset: "100%", "stop-color": tone, "stop-opacity": "0.02" }));
|
|
2153
|
+
defs.appendChild(gradient);
|
|
2154
|
+
svg.appendChild(defs);
|
|
2155
|
+
svg.appendChild(svgEl("path", { d: area, fill: `url(#${gradientId})` }));
|
|
2156
|
+
svg.appendChild(svgEl("path", {
|
|
2157
|
+
d: line,
|
|
2158
|
+
fill: "none",
|
|
2159
|
+
stroke: tone,
|
|
2160
|
+
"stroke-width": "1.8",
|
|
2161
|
+
"stroke-linecap": "round",
|
|
2162
|
+
"stroke-linejoin": "round"
|
|
2163
|
+
}));
|
|
2164
|
+
const dot = svgEl("circle", {
|
|
2165
|
+
cx: last.x.toFixed(1),
|
|
2166
|
+
cy: last.y.toFixed(1),
|
|
2167
|
+
r: "2.4",
|
|
2168
|
+
fill: "var(--token-card)",
|
|
2169
|
+
stroke: tone,
|
|
2170
|
+
"stroke-width": "1.6"
|
|
2171
|
+
});
|
|
2172
|
+
const title = svgEl("title", {});
|
|
2173
|
+
title.textContent = fmt(opts, last.value);
|
|
2174
|
+
dot.appendChild(title);
|
|
2175
|
+
svg.appendChild(dot);
|
|
2176
|
+
wrap.appendChild(svg);
|
|
2177
|
+
el.appendChild(wrap);
|
|
2178
|
+
}
|
|
2179
|
+
function renderGauge(el, opts) {
|
|
2180
|
+
if (typeof document === "undefined") return;
|
|
2181
|
+
el.textContent = "";
|
|
2182
|
+
const max = opts.max ?? 100;
|
|
2183
|
+
const ratio = max > 0 ? Math.min(1, Math.max(0, opts.value / max)) : 0;
|
|
2184
|
+
const radius = 42;
|
|
2185
|
+
const circumference = 2 * Math.PI * radius;
|
|
2186
|
+
const wrap = document.createElement("div");
|
|
2187
|
+
wrap.className = "chart-gauge";
|
|
2188
|
+
if (opts.tone) wrap.style.setProperty("--chart-tone", toneVar(opts.tone));
|
|
2189
|
+
const svg = svgEl("svg", { viewBox: "0 0 120 120", role: "img" });
|
|
2190
|
+
svg.style.transform = "rotate(-90deg)";
|
|
2191
|
+
svg.appendChild(svgEl("circle", {
|
|
2192
|
+
cx: "60",
|
|
2193
|
+
cy: "60",
|
|
2194
|
+
r: String(radius),
|
|
2195
|
+
fill: "none",
|
|
2196
|
+
stroke: "var(--token-line-soft)",
|
|
2197
|
+
"stroke-width": "10"
|
|
2198
|
+
}));
|
|
2199
|
+
const dash = ratio * circumference;
|
|
2200
|
+
svg.appendChild(svgEl("circle", {
|
|
2201
|
+
cx: "60",
|
|
2202
|
+
cy: "60",
|
|
2203
|
+
r: String(radius),
|
|
2204
|
+
fill: "none",
|
|
2205
|
+
stroke: toneVar(opts.tone),
|
|
2206
|
+
"stroke-width": "10",
|
|
2207
|
+
"stroke-dasharray": `${dash} ${circumference - dash}`,
|
|
2208
|
+
"stroke-linecap": "round"
|
|
2209
|
+
}));
|
|
2210
|
+
const center = document.createElement("div");
|
|
2211
|
+
center.className = "chart-gauge-center";
|
|
2212
|
+
const val = document.createElement("span");
|
|
2213
|
+
val.className = "chart-gauge-value";
|
|
2214
|
+
val.textContent = opts.formatValue ? opts.formatValue(opts.value) : `${Math.round(ratio * 100)}%`;
|
|
2215
|
+
center.appendChild(val);
|
|
2216
|
+
if (opts.label) {
|
|
2217
|
+
const lab = document.createElement("span");
|
|
2218
|
+
lab.className = "chart-gauge-label";
|
|
2219
|
+
lab.textContent = opts.label;
|
|
2220
|
+
center.appendChild(lab);
|
|
2221
|
+
}
|
|
2222
|
+
wrap.append(svg, center);
|
|
2223
|
+
el.appendChild(wrap);
|
|
2224
|
+
}
|
|
1686
2225
|
|
|
1687
2226
|
// src/behaviors/nav.ts
|
|
2227
|
+
function prefersReducedMotion() {
|
|
2228
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
2229
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
2230
|
+
}
|
|
1688
2231
|
function setup9(nav) {
|
|
1689
2232
|
const el = nav;
|
|
1690
2233
|
if (el.__icenNavInit) return;
|
|
1691
2234
|
el.__icenNavInit = true;
|
|
2235
|
+
const threshold = Number(nav.dataset.navThreshold ?? "12") || 12;
|
|
2236
|
+
const hideOnScroll = nav.hasAttribute("data-nav-hide-on-scroll");
|
|
1692
2237
|
let ticking = false;
|
|
1693
|
-
|
|
2238
|
+
let lastY = window.scrollY;
|
|
2239
|
+
let hidden = false;
|
|
2240
|
+
const apply = () => {
|
|
1694
2241
|
ticking = false;
|
|
1695
|
-
|
|
2242
|
+
const y = window.scrollY;
|
|
2243
|
+
nav.classList.toggle("is-scrolled", y > threshold);
|
|
2244
|
+
if (hideOnScroll && !prefersReducedMotion()) {
|
|
2245
|
+
const dy = y - lastY;
|
|
2246
|
+
if (y < 8) {
|
|
2247
|
+
hidden = false;
|
|
2248
|
+
} else if (dy > 6 && !hidden) {
|
|
2249
|
+
hidden = true;
|
|
2250
|
+
} else if (dy < -6 && hidden) {
|
|
2251
|
+
hidden = false;
|
|
2252
|
+
}
|
|
2253
|
+
nav.classList.toggle("is-nav-hidden", hidden);
|
|
2254
|
+
}
|
|
2255
|
+
lastY = y;
|
|
1696
2256
|
};
|
|
1697
2257
|
const onScroll2 = () => {
|
|
1698
2258
|
if (ticking) return;
|
|
1699
2259
|
ticking = true;
|
|
1700
|
-
window.requestAnimationFrame(
|
|
2260
|
+
window.requestAnimationFrame(apply);
|
|
2261
|
+
};
|
|
2262
|
+
const onResize = () => {
|
|
2263
|
+
if (ticking) return;
|
|
2264
|
+
ticking = true;
|
|
2265
|
+
window.requestAnimationFrame(apply);
|
|
1701
2266
|
};
|
|
1702
2267
|
window.addEventListener("scroll", onScroll2, { passive: true });
|
|
1703
|
-
|
|
2268
|
+
window.addEventListener("resize", onResize, { passive: true });
|
|
2269
|
+
apply();
|
|
1704
2270
|
}
|
|
1705
2271
|
function initNav(root = document) {
|
|
1706
2272
|
if (typeof document === "undefined") return;
|
|
@@ -1716,6 +2282,14 @@ function setup10(sidebar) {
|
|
|
1716
2282
|
const el = sidebar;
|
|
1717
2283
|
if (el.__icenSidebarInit) return;
|
|
1718
2284
|
el.__icenSidebarInit = true;
|
|
2285
|
+
sidebar.querySelectorAll(".sidebar-group-title").forEach((title) => {
|
|
2286
|
+
const group = title.closest(".sidebar-group");
|
|
2287
|
+
const panel2 = group?.querySelector(".sidebar-group-panel");
|
|
2288
|
+
if (panel2 && !title.hasAttribute("aria-controls")) {
|
|
2289
|
+
if (!panel2.id) panel2.id = `sb-panel-${Math.random().toString(36).slice(2, 9)}`;
|
|
2290
|
+
title.setAttribute("aria-controls", panel2.id);
|
|
2291
|
+
}
|
|
2292
|
+
});
|
|
1719
2293
|
el.addEventListener("click", (ev) => {
|
|
1720
2294
|
const target = ev.target;
|
|
1721
2295
|
if (!(target instanceof Element)) return;
|
|
@@ -1726,6 +2300,10 @@ function setup10(sidebar) {
|
|
|
1726
2300
|
if (!group) return;
|
|
1727
2301
|
const open = group.classList.toggle("is-open");
|
|
1728
2302
|
title.setAttribute("aria-expanded", String(open));
|
|
2303
|
+
sidebar.dispatchEvent(new CustomEvent("icen:sidebar-toggle", {
|
|
2304
|
+
bubbles: true,
|
|
2305
|
+
detail: { group, open }
|
|
2306
|
+
}));
|
|
1729
2307
|
});
|
|
1730
2308
|
}
|
|
1731
2309
|
function initSidebar(root = document) {
|
|
@@ -1771,8 +2349,11 @@ export {
|
|
|
1771
2349
|
openPopover,
|
|
1772
2350
|
registerContextMenu,
|
|
1773
2351
|
renderDonut,
|
|
2352
|
+
renderGauge,
|
|
1774
2353
|
renderHBar,
|
|
2354
|
+
renderHeatmap,
|
|
1775
2355
|
renderLine,
|
|
2356
|
+
renderSparkline,
|
|
1776
2357
|
renderStack,
|
|
1777
2358
|
renderVBar,
|
|
1778
2359
|
setTheme,
|