@icen.ai/ui 0.1.1 → 0.2.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/README.md +11 -0
- package/dist/base.css +72 -0
- package/dist/behaviors/accordion.mjs +34 -0
- package/dist/behaviors/carousel.mjs +50 -0
- package/dist/behaviors/charts.mjs +267 -0
- package/dist/behaviors/context-menu.mjs +223 -0
- package/dist/behaviors/dropdown.mjs +136 -0
- package/dist/behaviors/input.mjs +102 -0
- package/dist/behaviors/modal.mjs +107 -0
- package/dist/behaviors/nav.mjs +29 -0
- package/dist/behaviors/popover.mjs +164 -0
- package/dist/behaviors/select.mjs +132 -0
- package/dist/behaviors/sidebar.mjs +27 -0
- package/dist/behaviors/slider.mjs +72 -0
- package/dist/behaviors/tag-input.mjs +74 -0
- package/dist/behaviors/tree.mjs +44 -0
- package/dist/behaviors/upload.mjs +55 -0
- package/dist/components/breadcrumb.css +31 -0
- package/dist/components/card.css +56 -0
- package/dist/components/charts.css +95 -0
- package/dist/components/content.css +195 -0
- package/dist/components/empty.css +25 -0
- package/dist/components/feedback.css +106 -0
- package/dist/components/input.css +82 -0
- package/dist/components/layout.css +109 -0
- package/dist/components/media.css +133 -0
- package/dist/components/menu.css +85 -0
- package/dist/components/modal.css +107 -0
- package/dist/components/nav.css +59 -0
- package/dist/components/pagination.css +32 -0
- package/dist/components/popover.css +25 -0
- package/dist/components/segmented.css +112 -0
- package/dist/components/select.css +79 -0
- package/dist/components/sidebar.css +54 -0
- package/dist/components/slider.css +69 -0
- package/dist/components/steps.css +61 -0
- package/dist/components/switch.css +95 -0
- package/dist/components/tag-input.css +48 -0
- package/dist/components/toast.css +1 -1
- package/dist/components/tooltip.css +56 -0
- package/dist/components/upload.css +43 -0
- package/dist/index.mjs +1502 -1
- package/dist/registry.json +41 -3
- package/dist/ui.css +1899 -1
- package/package.json +1 -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.2.1",
|
|
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",
|
|
@@ -261,6 +261,1481 @@ function initCopy(root = document) {
|
|
|
261
261
|
root.addEventListener("click", (ev) => onClick(root, ev));
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
// src/behaviors/input.ts
|
|
265
|
+
function setupClear(wrap) {
|
|
266
|
+
const btn = wrap.querySelector(".input-clear");
|
|
267
|
+
const field = wrap.querySelector("input.input");
|
|
268
|
+
if (!btn || !field) return;
|
|
269
|
+
btn.addEventListener("click", () => {
|
|
270
|
+
field.value = "";
|
|
271
|
+
field.dispatchEvent(new Event("input", { bubbles: true }));
|
|
272
|
+
field.focus();
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
function setupPasswordToggle(wrap) {
|
|
276
|
+
const btn = wrap.querySelector(".input-pw-toggle");
|
|
277
|
+
const field = wrap.querySelector("input.input");
|
|
278
|
+
if (!btn || !field) return;
|
|
279
|
+
const iconShow = btn.querySelector('[data-icon="show"]');
|
|
280
|
+
const iconHide = btn.querySelector('[data-icon="hide"]');
|
|
281
|
+
btn.setAttribute("aria-label", "\u663E\u793A\u5BC6\u7801");
|
|
282
|
+
btn.addEventListener("click", () => {
|
|
283
|
+
const show2 = field.type === "password";
|
|
284
|
+
field.type = show2 ? "text" : "password";
|
|
285
|
+
if (iconShow) iconShow.hidden = show2;
|
|
286
|
+
if (iconHide) iconHide.hidden = !show2;
|
|
287
|
+
btn.setAttribute("aria-label", show2 ? "\u9690\u85CF\u5BC6\u7801" : "\u663E\u793A\u5BC6\u7801");
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
function resizeTextarea(ta) {
|
|
291
|
+
ta.style.height = "auto";
|
|
292
|
+
const cs = getComputedStyle(ta);
|
|
293
|
+
const lineHeight = parseFloat(cs.lineHeight) || 20;
|
|
294
|
+
const padY = (parseFloat(cs.paddingTop) || 0) + (parseFloat(cs.paddingBottom) || 0);
|
|
295
|
+
const minRows = Number(ta.getAttribute("data-min-rows") ?? "3") || 3;
|
|
296
|
+
const maxAttr = ta.getAttribute("data-max-rows");
|
|
297
|
+
const maxRows = maxAttr ? Number(maxAttr) || 0 : 0;
|
|
298
|
+
const minH = minRows * lineHeight + padY;
|
|
299
|
+
const maxH = maxRows > 0 ? maxRows * lineHeight + padY : Infinity;
|
|
300
|
+
ta.style.height = `${Math.max(minH, Math.min(ta.scrollHeight, maxH))}px`;
|
|
301
|
+
}
|
|
302
|
+
function setupAutosize(ta) {
|
|
303
|
+
const el = ta;
|
|
304
|
+
if (el.__icenInputInit) return;
|
|
305
|
+
el.__icenInputInit = true;
|
|
306
|
+
ta.addEventListener("input", () => resizeTextarea(ta));
|
|
307
|
+
resizeTextarea(ta);
|
|
308
|
+
}
|
|
309
|
+
function setupOtp(group) {
|
|
310
|
+
const el = group;
|
|
311
|
+
if (el.__icenInputInit) return;
|
|
312
|
+
el.__icenInputInit = true;
|
|
313
|
+
const cells = Array.from(group.querySelectorAll(".otp-cell"));
|
|
314
|
+
cells.forEach((cell, i) => {
|
|
315
|
+
cell.addEventListener("input", () => {
|
|
316
|
+
const ch = cell.value.slice(-1);
|
|
317
|
+
cell.value = ch;
|
|
318
|
+
if (ch && i < cells.length - 1) cells[i + 1].focus();
|
|
319
|
+
});
|
|
320
|
+
cell.addEventListener("keydown", (ev) => {
|
|
321
|
+
if (ev.key === "Backspace" && !cell.value && i > 0) cells[i - 1].focus();
|
|
322
|
+
if (ev.key === "ArrowLeft" && i > 0) {
|
|
323
|
+
ev.preventDefault();
|
|
324
|
+
cells[i - 1].focus();
|
|
325
|
+
}
|
|
326
|
+
if (ev.key === "ArrowRight" && i < cells.length - 1) {
|
|
327
|
+
ev.preventDefault();
|
|
328
|
+
cells[i + 1].focus();
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
cell.addEventListener("paste", (ev) => {
|
|
332
|
+
ev.preventDefault();
|
|
333
|
+
const text = (ev.clipboardData?.getData("text") ?? "").replace(/\s/g, "").slice(0, cells.length - i);
|
|
334
|
+
for (let k = 0; k < text.length; k++) {
|
|
335
|
+
const target = cells[i + k];
|
|
336
|
+
target.value = text[k] ?? "";
|
|
337
|
+
}
|
|
338
|
+
cells[Math.min(i + text.length, cells.length - 1)].focus();
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
function initInput(root = document) {
|
|
343
|
+
if (typeof document === "undefined") return;
|
|
344
|
+
const wraps = [];
|
|
345
|
+
if (root instanceof Element && root.matches(".input-wrap")) wraps.push(root);
|
|
346
|
+
wraps.push(...Array.from(root.querySelectorAll(".input-wrap")));
|
|
347
|
+
for (const w of wraps) {
|
|
348
|
+
const el = w;
|
|
349
|
+
if (el.__icenInputInit) continue;
|
|
350
|
+
el.__icenInputInit = true;
|
|
351
|
+
setupClear(w);
|
|
352
|
+
setupPasswordToggle(w);
|
|
353
|
+
}
|
|
354
|
+
const textareas = [];
|
|
355
|
+
if (root instanceof HTMLTextAreaElement && root.matches("[data-autosize]")) textareas.push(root);
|
|
356
|
+
textareas.push(...Array.from(root.querySelectorAll("textarea[data-autosize]")));
|
|
357
|
+
for (const ta of textareas) setupAutosize(ta);
|
|
358
|
+
const otps = [];
|
|
359
|
+
if (root instanceof Element && root.matches(".otp")) otps.push(root);
|
|
360
|
+
otps.push(...Array.from(root.querySelectorAll(".otp")));
|
|
361
|
+
for (const g of otps) setupOtp(g);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// src/behaviors/select.ts
|
|
365
|
+
function setup2(container) {
|
|
366
|
+
const el = container;
|
|
367
|
+
if (el.__icenSelectInit) return;
|
|
368
|
+
el.__icenSelectInit = true;
|
|
369
|
+
const triggerEl = container.querySelector(".select-trigger");
|
|
370
|
+
const panelEl = container.querySelector(".select-panel");
|
|
371
|
+
const valueEl = container.querySelector(".select-value");
|
|
372
|
+
const hiddenInput = container.querySelector("input[data-select-value]");
|
|
373
|
+
if (!triggerEl || !panelEl) return;
|
|
374
|
+
const trigger = triggerEl;
|
|
375
|
+
const panel2 = panelEl;
|
|
376
|
+
const options = () => Array.from(panel2.querySelectorAll(".select-option"));
|
|
377
|
+
const isOpen = () => !panel2.hidden;
|
|
378
|
+
let focusIdx = -1;
|
|
379
|
+
function setFocus(idx) {
|
|
380
|
+
const opts = options();
|
|
381
|
+
focusIdx = Math.max(-1, Math.min(idx, opts.length - 1));
|
|
382
|
+
opts.forEach((o, i) => o.classList.toggle("is-focused", i === focusIdx));
|
|
383
|
+
const cur = focusIdx >= 0 ? opts[focusIdx] : void 0;
|
|
384
|
+
cur?.scrollIntoView({ block: "nearest" });
|
|
385
|
+
}
|
|
386
|
+
function firstEnabled() {
|
|
387
|
+
return options().findIndex((o) => !o.classList.contains("is-disabled"));
|
|
388
|
+
}
|
|
389
|
+
function moveFocus(delta) {
|
|
390
|
+
const opts = options();
|
|
391
|
+
if (opts.length === 0) return;
|
|
392
|
+
let n = focusIdx;
|
|
393
|
+
for (let guard = 0; guard < opts.length; guard++) {
|
|
394
|
+
n = (n + delta + opts.length) % opts.length;
|
|
395
|
+
const opt = opts[n];
|
|
396
|
+
if (opt && !opt.classList.contains("is-disabled")) break;
|
|
397
|
+
}
|
|
398
|
+
setFocus(n);
|
|
399
|
+
}
|
|
400
|
+
function openPanel() {
|
|
401
|
+
if (isOpen()) return;
|
|
402
|
+
panel2.hidden = false;
|
|
403
|
+
container.classList.add("is-open");
|
|
404
|
+
trigger.setAttribute("aria-expanded", "true");
|
|
405
|
+
const sel = options().findIndex((o) => o.classList.contains("is-selected"));
|
|
406
|
+
setFocus(sel >= 0 ? sel : firstEnabled());
|
|
407
|
+
}
|
|
408
|
+
function closePanel() {
|
|
409
|
+
if (!isOpen()) return;
|
|
410
|
+
panel2.hidden = true;
|
|
411
|
+
container.classList.remove("is-open");
|
|
412
|
+
trigger.setAttribute("aria-expanded", "false");
|
|
413
|
+
setFocus(-1);
|
|
414
|
+
}
|
|
415
|
+
function choose(opt) {
|
|
416
|
+
if (opt.classList.contains("is-disabled")) return;
|
|
417
|
+
options().forEach((o) => o.classList.toggle("is-selected", o === opt));
|
|
418
|
+
if (valueEl) {
|
|
419
|
+
valueEl.textContent = opt.textContent ?? "";
|
|
420
|
+
valueEl.classList.remove("is-empty");
|
|
421
|
+
}
|
|
422
|
+
const v = opt.getAttribute("data-value") ?? "";
|
|
423
|
+
container.setAttribute("data-value", v);
|
|
424
|
+
if (hiddenInput) {
|
|
425
|
+
hiddenInput.value = v;
|
|
426
|
+
hiddenInput.dispatchEvent(new Event("change", { bubbles: true }));
|
|
427
|
+
}
|
|
428
|
+
closePanel();
|
|
429
|
+
trigger.focus();
|
|
430
|
+
}
|
|
431
|
+
trigger.addEventListener("click", () => isOpen() ? closePanel() : openPanel());
|
|
432
|
+
trigger.addEventListener("keydown", (ev) => {
|
|
433
|
+
if (!isOpen()) {
|
|
434
|
+
if (ev.key === "ArrowDown" || ev.key === "ArrowUp" || ev.key === "Enter" || ev.key === " ") {
|
|
435
|
+
ev.preventDefault();
|
|
436
|
+
openPanel();
|
|
437
|
+
}
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
switch (ev.key) {
|
|
441
|
+
case "ArrowDown":
|
|
442
|
+
ev.preventDefault();
|
|
443
|
+
moveFocus(1);
|
|
444
|
+
break;
|
|
445
|
+
case "ArrowUp":
|
|
446
|
+
ev.preventDefault();
|
|
447
|
+
moveFocus(-1);
|
|
448
|
+
break;
|
|
449
|
+
case "Enter":
|
|
450
|
+
case " ": {
|
|
451
|
+
ev.preventDefault();
|
|
452
|
+
const opt = options()[focusIdx];
|
|
453
|
+
if (opt) choose(opt);
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
case "Escape":
|
|
457
|
+
ev.preventDefault();
|
|
458
|
+
closePanel();
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
panel2.addEventListener("click", (ev) => {
|
|
463
|
+
const t = ev.target;
|
|
464
|
+
if (!(t instanceof Element)) return;
|
|
465
|
+
const opt = t.closest(".select-option");
|
|
466
|
+
if (opt instanceof HTMLButtonElement && panel2.contains(opt)) choose(opt);
|
|
467
|
+
});
|
|
468
|
+
panel2.addEventListener("mouseover", (ev) => {
|
|
469
|
+
const t = ev.target;
|
|
470
|
+
if (!(t instanceof Element)) return;
|
|
471
|
+
const opt = t.closest(".select-option");
|
|
472
|
+
if (!opt) return;
|
|
473
|
+
const idx = options().indexOf(opt);
|
|
474
|
+
if (idx >= 0 && !opt.classList.contains("is-disabled")) setFocus(idx);
|
|
475
|
+
});
|
|
476
|
+
document.addEventListener("pointerdown", (ev) => {
|
|
477
|
+
if (isOpen() && ev.target instanceof Node && !container.contains(ev.target)) closePanel();
|
|
478
|
+
});
|
|
479
|
+
document.addEventListener("keydown", (ev) => {
|
|
480
|
+
if (ev.key === "Escape" && isOpen()) {
|
|
481
|
+
closePanel();
|
|
482
|
+
trigger.focus();
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
function initSelect(root = document) {
|
|
487
|
+
if (typeof document === "undefined") return;
|
|
488
|
+
const containers = [];
|
|
489
|
+
if (root instanceof Element && root.matches("[data-select]")) containers.push(root);
|
|
490
|
+
containers.push(...Array.from(root.querySelectorAll("[data-select]")));
|
|
491
|
+
for (const c of containers) setup2(c);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// src/behaviors/slider.ts
|
|
495
|
+
function pct(input) {
|
|
496
|
+
const min = Number(input.min || "0");
|
|
497
|
+
const max = Number(input.max || "100");
|
|
498
|
+
const range = max - min;
|
|
499
|
+
if (range <= 0) return 0;
|
|
500
|
+
return (Number(input.value) - min) / range * 100;
|
|
501
|
+
}
|
|
502
|
+
function setupSingle(slider, native) {
|
|
503
|
+
const fill = slider.querySelector(".slider-fill");
|
|
504
|
+
const thumb = slider.querySelector(".slider-thumb");
|
|
505
|
+
const sync = () => {
|
|
506
|
+
const p = pct(native);
|
|
507
|
+
if (fill) fill.style.width = `${p}%`;
|
|
508
|
+
if (thumb) thumb.style.left = `calc(${p}% - 8px)`;
|
|
509
|
+
};
|
|
510
|
+
native.addEventListener("input", sync);
|
|
511
|
+
sync();
|
|
512
|
+
}
|
|
513
|
+
function setupDual(slider, natives) {
|
|
514
|
+
const lo = natives.find((n) => n.getAttribute("data-thumb") === "lo") ?? natives[0];
|
|
515
|
+
const hi = natives.find((n) => n.getAttribute("data-thumb") === "hi") ?? natives[1];
|
|
516
|
+
if (!lo || !hi || lo === hi) return;
|
|
517
|
+
const range = slider.querySelector(".slider-range");
|
|
518
|
+
const thumbLo = slider.querySelector(".slider-thumb--lo");
|
|
519
|
+
const thumbHi = slider.querySelector(".slider-thumb--hi");
|
|
520
|
+
const sync = (changed) => {
|
|
521
|
+
const step = Number(lo.step || "1") || 1;
|
|
522
|
+
let vLo = Number(lo.value);
|
|
523
|
+
let vHi = Number(hi.value);
|
|
524
|
+
if (changed === lo && vLo > vHi - step) {
|
|
525
|
+
vLo = vHi - step;
|
|
526
|
+
lo.value = String(vLo);
|
|
527
|
+
}
|
|
528
|
+
if (changed === hi && vHi < vLo + step) {
|
|
529
|
+
vHi = vLo + step;
|
|
530
|
+
hi.value = String(vHi);
|
|
531
|
+
}
|
|
532
|
+
const pLo = pct(lo);
|
|
533
|
+
const pHi = pct(hi);
|
|
534
|
+
if (range) {
|
|
535
|
+
range.style.left = `${pLo}%`;
|
|
536
|
+
range.style.right = `${100 - pHi}%`;
|
|
537
|
+
}
|
|
538
|
+
if (thumbLo) thumbLo.style.left = `calc(${pLo}% - 8px)`;
|
|
539
|
+
if (thumbHi) thumbHi.style.left = `calc(${pHi}% - 8px)`;
|
|
540
|
+
lo.style.clipPath = `inset(0 ${100 - pLo}% 0 0)`;
|
|
541
|
+
hi.style.clipPath = `inset(0 0 0 ${pHi}%)`;
|
|
542
|
+
};
|
|
543
|
+
lo.addEventListener("input", () => sync(lo));
|
|
544
|
+
hi.addEventListener("input", () => sync(hi));
|
|
545
|
+
sync(null);
|
|
546
|
+
}
|
|
547
|
+
function setup3(slider) {
|
|
548
|
+
const el = slider;
|
|
549
|
+
if (el.__icenSliderInit) return;
|
|
550
|
+
el.__icenSliderInit = true;
|
|
551
|
+
const natives = Array.from(slider.querySelectorAll(".slider-native"));
|
|
552
|
+
if (natives.length === 0) return;
|
|
553
|
+
if (slider.classList.contains("slider--dual")) setupDual(slider, natives);
|
|
554
|
+
else setupSingle(slider, natives[0]);
|
|
555
|
+
}
|
|
556
|
+
function initSlider(root = document) {
|
|
557
|
+
if (typeof document === "undefined") return;
|
|
558
|
+
const containers = [];
|
|
559
|
+
if (root instanceof Element && root.matches(".slider")) containers.push(root);
|
|
560
|
+
containers.push(...Array.from(root.querySelectorAll(".slider")));
|
|
561
|
+
for (const c of containers) setup3(c);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// src/behaviors/tag-input.ts
|
|
565
|
+
function setup4(wrap) {
|
|
566
|
+
const el = wrap;
|
|
567
|
+
if (el.__icenTagInit) return;
|
|
568
|
+
el.__icenTagInit = true;
|
|
569
|
+
const fieldEl = wrap.querySelector(".tag-input-field");
|
|
570
|
+
if (!fieldEl) return;
|
|
571
|
+
const field = fieldEl;
|
|
572
|
+
const maxAttr = wrap.getAttribute("data-max");
|
|
573
|
+
const max = maxAttr ? Number(maxAttr) || Infinity : Infinity;
|
|
574
|
+
const placeholder = field.placeholder;
|
|
575
|
+
let tags = (wrap.getAttribute("data-tags") ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
576
|
+
function sync() {
|
|
577
|
+
wrap.setAttribute("data-tags", tags.join(","));
|
|
578
|
+
wrap.classList.toggle("is-max", tags.length >= max);
|
|
579
|
+
field.placeholder = tags.length === 0 ? placeholder : "";
|
|
580
|
+
}
|
|
581
|
+
function remove(index) {
|
|
582
|
+
tags.splice(index, 1);
|
|
583
|
+
render();
|
|
584
|
+
}
|
|
585
|
+
function chip(tag, index) {
|
|
586
|
+
const el2 = document.createElement("span");
|
|
587
|
+
el2.className = "tag-chip";
|
|
588
|
+
const label = document.createElement("span");
|
|
589
|
+
label.textContent = tag;
|
|
590
|
+
const x = document.createElement("button");
|
|
591
|
+
x.type = "button";
|
|
592
|
+
x.className = "tag-chip-x focus-ring";
|
|
593
|
+
x.textContent = "\xD7";
|
|
594
|
+
x.setAttribute("aria-label", `\u79FB\u9664 ${tag}`);
|
|
595
|
+
x.addEventListener("mousedown", (ev) => ev.preventDefault());
|
|
596
|
+
x.addEventListener("click", () => remove(index));
|
|
597
|
+
el2.append(label, x);
|
|
598
|
+
return el2;
|
|
599
|
+
}
|
|
600
|
+
function render() {
|
|
601
|
+
wrap.querySelectorAll(".tag-chip").forEach((c) => c.remove());
|
|
602
|
+
tags.forEach((t, i) => wrap.insertBefore(chip(t, i), field));
|
|
603
|
+
sync();
|
|
604
|
+
}
|
|
605
|
+
function commit() {
|
|
606
|
+
const v = field.value.trim();
|
|
607
|
+
if (!v) return;
|
|
608
|
+
if (tags.length >= max) return;
|
|
609
|
+
if (tags.includes(v)) {
|
|
610
|
+
field.value = "";
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
tags.push(v);
|
|
614
|
+
field.value = "";
|
|
615
|
+
render();
|
|
616
|
+
}
|
|
617
|
+
field.addEventListener("keydown", (ev) => {
|
|
618
|
+
if (ev.key === "Enter" || ev.key === ",") {
|
|
619
|
+
ev.preventDefault();
|
|
620
|
+
commit();
|
|
621
|
+
} else if (ev.key === "Backspace" && !field.value && tags.length > 0) {
|
|
622
|
+
remove(tags.length - 1);
|
|
623
|
+
}
|
|
624
|
+
});
|
|
625
|
+
field.addEventListener("blur", commit);
|
|
626
|
+
render();
|
|
627
|
+
}
|
|
628
|
+
function initTagInput(root = document) {
|
|
629
|
+
if (typeof document === "undefined") return;
|
|
630
|
+
const containers = [];
|
|
631
|
+
if (root instanceof HTMLElement && root.matches(".tag-input[data-tags]")) containers.push(root);
|
|
632
|
+
containers.push(...Array.from(root.querySelectorAll(".tag-input[data-tags]")));
|
|
633
|
+
for (const c of containers) setup4(c);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// src/behaviors/upload.ts
|
|
637
|
+
function setup5(zone) {
|
|
638
|
+
const el = zone;
|
|
639
|
+
if (el.__icenUploadInit) return;
|
|
640
|
+
el.__icenUploadInit = true;
|
|
641
|
+
const input = zone.querySelector('input[type="file"]');
|
|
642
|
+
if (!input) return;
|
|
643
|
+
const openPicker = () => input.click();
|
|
644
|
+
zone.addEventListener("click", (ev) => {
|
|
645
|
+
if (ev.target === input) return;
|
|
646
|
+
openPicker();
|
|
647
|
+
});
|
|
648
|
+
zone.addEventListener("keydown", (ev) => {
|
|
649
|
+
if (ev.key === "Enter" || ev.key === " ") {
|
|
650
|
+
ev.preventDefault();
|
|
651
|
+
openPicker();
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
zone.addEventListener("dragover", (ev) => {
|
|
655
|
+
ev.preventDefault();
|
|
656
|
+
zone.classList.add("is-drag");
|
|
657
|
+
});
|
|
658
|
+
zone.addEventListener("dragleave", () => zone.classList.remove("is-drag"));
|
|
659
|
+
zone.addEventListener("drop", (ev) => {
|
|
660
|
+
ev.preventDefault();
|
|
661
|
+
zone.classList.remove("is-drag");
|
|
662
|
+
const files = ev.dataTransfer?.files;
|
|
663
|
+
if (!files || files.length === 0) return;
|
|
664
|
+
let assigned = false;
|
|
665
|
+
try {
|
|
666
|
+
const dt = new DataTransfer();
|
|
667
|
+
for (const f of Array.from(files)) dt.items.add(f);
|
|
668
|
+
input.files = dt.files;
|
|
669
|
+
assigned = true;
|
|
670
|
+
} catch {
|
|
671
|
+
assigned = false;
|
|
672
|
+
}
|
|
673
|
+
if (assigned) {
|
|
674
|
+
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
|
+
}
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
function initUpload(root = document) {
|
|
682
|
+
if (typeof document === "undefined") return;
|
|
683
|
+
const zones = [];
|
|
684
|
+
if (root instanceof HTMLElement && root.matches(".upload")) zones.push(root);
|
|
685
|
+
zones.push(...Array.from(root.querySelectorAll(".upload")));
|
|
686
|
+
for (const z of zones) setup5(z);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// src/behaviors/modal.ts
|
|
690
|
+
var FOCUSABLE = 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
691
|
+
var openId = null;
|
|
692
|
+
var restoreFocusTo = null;
|
|
693
|
+
var prevBodyOverflow = "";
|
|
694
|
+
var keyHandler = null;
|
|
695
|
+
var initialized2 = false;
|
|
696
|
+
function findBackdrop(id) {
|
|
697
|
+
return document.querySelector(`.modal-backdrop[data-modal="${CSS.escape(id)}"]`);
|
|
698
|
+
}
|
|
699
|
+
function portal(backdrop) {
|
|
700
|
+
if (backdrop.parentElement !== document.body) document.body.appendChild(backdrop);
|
|
701
|
+
}
|
|
702
|
+
function openModal(id) {
|
|
703
|
+
if (typeof document === "undefined") return;
|
|
704
|
+
if (openId === id) return;
|
|
705
|
+
if (openId) closeModal(openId);
|
|
706
|
+
const backdrop = findBackdrop(id);
|
|
707
|
+
if (!backdrop) return;
|
|
708
|
+
const panel2 = backdrop.querySelector(".modal");
|
|
709
|
+
if (!panel2) return;
|
|
710
|
+
portal(backdrop);
|
|
711
|
+
restoreFocusTo = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
712
|
+
openId = id;
|
|
713
|
+
backdrop.hidden = false;
|
|
714
|
+
prevBodyOverflow = document.body.style.overflow;
|
|
715
|
+
document.body.style.overflow = "hidden";
|
|
716
|
+
panel2.setAttribute("role", "dialog");
|
|
717
|
+
panel2.setAttribute("aria-modal", "true");
|
|
718
|
+
if (!panel2.hasAttribute("tabindex")) panel2.tabIndex = -1;
|
|
719
|
+
const closeOnEscape = backdrop.dataset.closeOnEscape !== "false";
|
|
720
|
+
keyHandler = (e) => {
|
|
721
|
+
if (e.key === "Escape") {
|
|
722
|
+
if (closeOnEscape) {
|
|
723
|
+
e.preventDefault();
|
|
724
|
+
closeModal(id);
|
|
725
|
+
}
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
if (e.key !== "Tab") return;
|
|
729
|
+
const els = panel2.querySelectorAll(FOCUSABLE);
|
|
730
|
+
if (els.length === 0) {
|
|
731
|
+
e.preventDefault();
|
|
732
|
+
panel2.focus();
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
const first = els[0];
|
|
736
|
+
const last = els[els.length - 1];
|
|
737
|
+
if (e.shiftKey) {
|
|
738
|
+
if (document.activeElement === first || !panel2.contains(document.activeElement)) {
|
|
739
|
+
e.preventDefault();
|
|
740
|
+
last.focus();
|
|
741
|
+
}
|
|
742
|
+
} else if (document.activeElement === last || !panel2.contains(document.activeElement)) {
|
|
743
|
+
e.preventDefault();
|
|
744
|
+
first.focus();
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
window.addEventListener("keydown", keyHandler);
|
|
748
|
+
const focusable = panel2.querySelector(FOCUSABLE);
|
|
749
|
+
(focusable ?? panel2).focus();
|
|
750
|
+
}
|
|
751
|
+
function closeModal(id) {
|
|
752
|
+
if (typeof document === "undefined") return;
|
|
753
|
+
const backdrop = findBackdrop(id);
|
|
754
|
+
if (!backdrop) return;
|
|
755
|
+
backdrop.hidden = true;
|
|
756
|
+
if (openId !== id) return;
|
|
757
|
+
openId = null;
|
|
758
|
+
if (keyHandler) {
|
|
759
|
+
window.removeEventListener("keydown", keyHandler);
|
|
760
|
+
keyHandler = null;
|
|
761
|
+
}
|
|
762
|
+
document.body.style.overflow = prevBodyOverflow;
|
|
763
|
+
if (restoreFocusTo && restoreFocusTo.isConnected) restoreFocusTo.focus();
|
|
764
|
+
restoreFocusTo = null;
|
|
765
|
+
}
|
|
766
|
+
function initModal(root = document) {
|
|
767
|
+
if (typeof document === "undefined") return;
|
|
768
|
+
root.querySelectorAll("[data-modal-open]").forEach((el) => el.setAttribute("aria-haspopup", "dialog"));
|
|
769
|
+
if (initialized2) return;
|
|
770
|
+
initialized2 = true;
|
|
771
|
+
document.addEventListener("click", (e) => {
|
|
772
|
+
const target = e.target instanceof Element ? e.target : null;
|
|
773
|
+
if (!target) return;
|
|
774
|
+
const opener = target.closest("[data-modal-open]");
|
|
775
|
+
if (opener?.dataset.modalOpen) {
|
|
776
|
+
opener.setAttribute("aria-haspopup", "dialog");
|
|
777
|
+
openModal(opener.dataset.modalOpen);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
const closer = target.closest("[data-modal-close]");
|
|
781
|
+
if (closer) {
|
|
782
|
+
const backdrop = closer.closest("[data-modal]");
|
|
783
|
+
if (backdrop?.dataset.modal) closeModal(backdrop.dataset.modal);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
if (target instanceof HTMLElement && target.classList.contains("modal-backdrop") && target.dataset.modal && target.dataset.closeOnOverlay !== "false") {
|
|
787
|
+
closeModal(target.dataset.modal);
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// src/behaviors/dropdown.ts
|
|
793
|
+
var VIEWPORT_MARGIN = 12;
|
|
794
|
+
var SIDE_OFFSET = 4;
|
|
795
|
+
var active = null;
|
|
796
|
+
var initialized3 = false;
|
|
797
|
+
function clamp(value, min, max) {
|
|
798
|
+
if (max < min) return min;
|
|
799
|
+
return Math.min(max, Math.max(min, value));
|
|
800
|
+
}
|
|
801
|
+
function menuItems(panel2) {
|
|
802
|
+
return Array.from(panel2.querySelectorAll(".menu-item:not(.is-disabled):not(:disabled)"));
|
|
803
|
+
}
|
|
804
|
+
function setHighlight(next) {
|
|
805
|
+
if (!active) return;
|
|
806
|
+
active.items.forEach((el, i) => el.classList.toggle("is-highlighted", i === next));
|
|
807
|
+
active.highlight = next;
|
|
808
|
+
}
|
|
809
|
+
function place() {
|
|
810
|
+
if (!active) return;
|
|
811
|
+
const { trigger, panel: panel2 } = active;
|
|
812
|
+
const rect = trigger.getBoundingClientRect();
|
|
813
|
+
const vw = window.innerWidth;
|
|
814
|
+
const vh = window.innerHeight;
|
|
815
|
+
const pw = panel2.offsetWidth;
|
|
816
|
+
const ph = panel2.offsetHeight;
|
|
817
|
+
const left = clamp(rect.left, VIEWPORT_MARGIN, vw - VIEWPORT_MARGIN - pw);
|
|
818
|
+
let top = rect.bottom + SIDE_OFFSET;
|
|
819
|
+
let side = "bottom";
|
|
820
|
+
if (top + ph > vh - VIEWPORT_MARGIN && rect.top - SIDE_OFFSET - ph >= VIEWPORT_MARGIN) {
|
|
821
|
+
top = rect.top - SIDE_OFFSET - ph;
|
|
822
|
+
side = "top";
|
|
823
|
+
}
|
|
824
|
+
top = clamp(top, VIEWPORT_MARGIN, vh - VIEWPORT_MARGIN - ph);
|
|
825
|
+
panel2.style.left = `${left}px`;
|
|
826
|
+
panel2.style.top = `${top}px`;
|
|
827
|
+
panel2.dataset.side = side;
|
|
828
|
+
}
|
|
829
|
+
function openDropdown(trigger, highlightFirst = false) {
|
|
830
|
+
closeDropdown();
|
|
831
|
+
const root = trigger.closest("[data-dropdown]");
|
|
832
|
+
const template = root?.querySelector("[data-dropdown-menu]");
|
|
833
|
+
if (!root || !template) return;
|
|
834
|
+
const panel2 = document.createElement("div");
|
|
835
|
+
panel2.className = "menu";
|
|
836
|
+
panel2.setAttribute("role", "menu");
|
|
837
|
+
panel2.style.visibility = "hidden";
|
|
838
|
+
while (template.firstChild) panel2.appendChild(template.firstChild);
|
|
839
|
+
panel2.querySelectorAll(".menu-item").forEach((el) => el.setAttribute("role", "menuitem"));
|
|
840
|
+
document.body.appendChild(panel2);
|
|
841
|
+
active = { trigger, template, panel: panel2, items: menuItems(panel2), highlight: -1 };
|
|
842
|
+
trigger.setAttribute("aria-expanded", "true");
|
|
843
|
+
place();
|
|
844
|
+
panel2.style.visibility = "";
|
|
845
|
+
if (highlightFirst && active.items.length > 0) setHighlight(0);
|
|
846
|
+
document.addEventListener("pointerdown", onOutsidePointerDown, true);
|
|
847
|
+
document.addEventListener("scroll", onScroll, true);
|
|
848
|
+
window.addEventListener("keydown", onKeyDown);
|
|
849
|
+
}
|
|
850
|
+
function closeDropdown(restoreFocus = false) {
|
|
851
|
+
if (!active) return;
|
|
852
|
+
const { trigger, template, panel: panel2 } = active;
|
|
853
|
+
active = null;
|
|
854
|
+
trigger.setAttribute("aria-expanded", "false");
|
|
855
|
+
while (panel2.firstChild) template.appendChild(panel2.firstChild);
|
|
856
|
+
panel2.remove();
|
|
857
|
+
document.removeEventListener("pointerdown", onOutsidePointerDown, true);
|
|
858
|
+
document.removeEventListener("scroll", onScroll, true);
|
|
859
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
860
|
+
if (restoreFocus) trigger.focus();
|
|
861
|
+
}
|
|
862
|
+
function onOutsidePointerDown(e) {
|
|
863
|
+
if (!active) return;
|
|
864
|
+
const target = e.target;
|
|
865
|
+
if (!(target instanceof Node)) return;
|
|
866
|
+
if (active.panel.contains(target) || active.trigger.contains(target)) return;
|
|
867
|
+
closeDropdown();
|
|
868
|
+
}
|
|
869
|
+
function onScroll() {
|
|
870
|
+
closeDropdown();
|
|
871
|
+
}
|
|
872
|
+
function onKeyDown(e) {
|
|
873
|
+
if (!active) return;
|
|
874
|
+
const { items } = active;
|
|
875
|
+
if (e.key === "Escape") {
|
|
876
|
+
e.preventDefault();
|
|
877
|
+
closeDropdown(true);
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
if (items.length === 0) return;
|
|
881
|
+
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
882
|
+
e.preventDefault();
|
|
883
|
+
const offset = e.key === "ArrowDown" ? 1 : -1;
|
|
884
|
+
const next = active.highlight < 0 ? offset > 0 ? 0 : items.length - 1 : (active.highlight + offset + items.length) % items.length;
|
|
885
|
+
setHighlight(next);
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
if (e.key === "Enter" && active.highlight >= 0) {
|
|
889
|
+
e.preventDefault();
|
|
890
|
+
const item = items[active.highlight];
|
|
891
|
+
closeDropdown(true);
|
|
892
|
+
item.click();
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
function initDropdown(root = document) {
|
|
896
|
+
if (typeof document === "undefined") return;
|
|
897
|
+
root.querySelectorAll("[data-dropdown-trigger]").forEach((el) => el.setAttribute("aria-expanded", "false"));
|
|
898
|
+
if (initialized3) return;
|
|
899
|
+
initialized3 = true;
|
|
900
|
+
document.addEventListener("click", (e) => {
|
|
901
|
+
const target = e.target instanceof Element ? e.target : null;
|
|
902
|
+
if (!target) return;
|
|
903
|
+
if (active && target.closest(".menu-item") && active.panel.contains(target)) {
|
|
904
|
+
const item = target.closest(".menu-item");
|
|
905
|
+
if (item) {
|
|
906
|
+
closeDropdown();
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
const trigger = target.closest("[data-dropdown-trigger]");
|
|
911
|
+
if (!trigger) return;
|
|
912
|
+
if (active && active.trigger === trigger) closeDropdown();
|
|
913
|
+
else openDropdown(trigger);
|
|
914
|
+
});
|
|
915
|
+
document.addEventListener("keydown", (e) => {
|
|
916
|
+
if (e.key !== "ArrowDown") return;
|
|
917
|
+
if (active) return;
|
|
918
|
+
const target = e.target instanceof Element ? e.target : null;
|
|
919
|
+
const trigger = target?.closest("[data-dropdown-trigger]");
|
|
920
|
+
if (!trigger) return;
|
|
921
|
+
e.preventDefault();
|
|
922
|
+
openDropdown(trigger, true);
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// src/behaviors/context-menu.ts
|
|
927
|
+
var LONG_PRESS_DELAY = 520;
|
|
928
|
+
var LONG_PRESS_MOVE_LIMIT = 12;
|
|
929
|
+
var FLIP_MARGIN = 10;
|
|
930
|
+
var CLAMP_MARGIN = 8;
|
|
931
|
+
var registry = /* @__PURE__ */ new Map();
|
|
932
|
+
var fallbackItems = null;
|
|
933
|
+
var initialized4 = false;
|
|
934
|
+
var panel = null;
|
|
935
|
+
var focusableItems = [];
|
|
936
|
+
var focusedIndex = -1;
|
|
937
|
+
var longPress = null;
|
|
938
|
+
function registerContextMenu(id, items) {
|
|
939
|
+
registry.set(id, items);
|
|
940
|
+
}
|
|
941
|
+
function defaultItems() {
|
|
942
|
+
return [
|
|
943
|
+
{
|
|
944
|
+
label: "\u540E\u9000",
|
|
945
|
+
shortcut: "Alt \u2190",
|
|
946
|
+
disabled: window.history.length <= 1,
|
|
947
|
+
onClick: () => window.history.back()
|
|
948
|
+
},
|
|
949
|
+
{ label: "\u524D\u8FDB", shortcut: "Alt \u2192", onClick: () => window.history.forward() },
|
|
950
|
+
{ type: "separator" },
|
|
951
|
+
{ label: "\u5237\u65B0", shortcut: "Ctrl R", onClick: () => window.location.reload() },
|
|
952
|
+
{
|
|
953
|
+
label: "\u590D\u5236\u9875\u9762\u94FE\u63A5",
|
|
954
|
+
onClick: () => {
|
|
955
|
+
void navigator.clipboard?.writeText(window.location.href);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
];
|
|
959
|
+
}
|
|
960
|
+
function resolveItems(target) {
|
|
961
|
+
const host = target?.closest("[data-context-menu]");
|
|
962
|
+
const id = host?.dataset.contextMenu;
|
|
963
|
+
if (id) {
|
|
964
|
+
const items = registry.get(id);
|
|
965
|
+
if (items) return items;
|
|
966
|
+
}
|
|
967
|
+
return fallbackItems ?? defaultItems();
|
|
968
|
+
}
|
|
969
|
+
function isEditable(target) {
|
|
970
|
+
return Boolean(target?.closest('input, textarea, [contenteditable="true"]'));
|
|
971
|
+
}
|
|
972
|
+
function buildItem(item) {
|
|
973
|
+
if ("type" in item && item.type === "separator") {
|
|
974
|
+
const sep = document.createElement("div");
|
|
975
|
+
sep.className = "menu-separator";
|
|
976
|
+
sep.setAttribute("role", "separator");
|
|
977
|
+
return sep;
|
|
978
|
+
}
|
|
979
|
+
if ("type" in item && item.type === "header") {
|
|
980
|
+
const header = document.createElement("div");
|
|
981
|
+
header.className = "menu-header";
|
|
982
|
+
const label2 = document.createElement("span");
|
|
983
|
+
label2.className = "menu-header-label";
|
|
984
|
+
label2.textContent = item.label;
|
|
985
|
+
header.appendChild(label2);
|
|
986
|
+
if (item.description) {
|
|
987
|
+
const desc = document.createElement("span");
|
|
988
|
+
desc.className = "menu-header-desc";
|
|
989
|
+
desc.textContent = item.description;
|
|
990
|
+
header.appendChild(desc);
|
|
991
|
+
}
|
|
992
|
+
return header;
|
|
993
|
+
}
|
|
994
|
+
const entry = item;
|
|
995
|
+
const btn = document.createElement("button");
|
|
996
|
+
btn.type = "button";
|
|
997
|
+
btn.className = entry.danger ? "menu-item pressable menu-item--danger" : "menu-item pressable";
|
|
998
|
+
if (entry.disabled) btn.disabled = true;
|
|
999
|
+
btn.setAttribute("role", "menuitem");
|
|
1000
|
+
btn.tabIndex = -1;
|
|
1001
|
+
const icon = document.createElement("span");
|
|
1002
|
+
icon.className = "menu-item-icon";
|
|
1003
|
+
if (entry.icon) icon.innerHTML = entry.icon;
|
|
1004
|
+
btn.appendChild(icon);
|
|
1005
|
+
const label = document.createElement("span");
|
|
1006
|
+
label.className = "menu-item-label";
|
|
1007
|
+
label.textContent = entry.label;
|
|
1008
|
+
if (entry.subLabel) {
|
|
1009
|
+
const sub = document.createElement("span");
|
|
1010
|
+
sub.className = "menu-item-sub";
|
|
1011
|
+
sub.textContent = entry.subLabel;
|
|
1012
|
+
label.appendChild(sub);
|
|
1013
|
+
}
|
|
1014
|
+
btn.appendChild(label);
|
|
1015
|
+
if (entry.shortcut) {
|
|
1016
|
+
const shortcut = document.createElement("span");
|
|
1017
|
+
shortcut.className = "menu-item-shortcut";
|
|
1018
|
+
shortcut.textContent = entry.shortcut;
|
|
1019
|
+
btn.appendChild(shortcut);
|
|
1020
|
+
}
|
|
1021
|
+
btn.addEventListener("click", () => {
|
|
1022
|
+
entry.onClick?.();
|
|
1023
|
+
closeMenu();
|
|
1024
|
+
});
|
|
1025
|
+
return btn;
|
|
1026
|
+
}
|
|
1027
|
+
function openContextMenu(x, y, items) {
|
|
1028
|
+
if (typeof document === "undefined") return;
|
|
1029
|
+
closeMenu();
|
|
1030
|
+
panel = document.createElement("div");
|
|
1031
|
+
panel.className = "menu menu--context";
|
|
1032
|
+
panel.setAttribute("role", "menu");
|
|
1033
|
+
panel.style.visibility = "hidden";
|
|
1034
|
+
for (const item of items) panel.appendChild(buildItem(item));
|
|
1035
|
+
document.body.appendChild(panel);
|
|
1036
|
+
const rect = panel.getBoundingClientRect();
|
|
1037
|
+
const vw = window.innerWidth;
|
|
1038
|
+
const vh = window.innerHeight;
|
|
1039
|
+
let nx = x;
|
|
1040
|
+
let ny = y;
|
|
1041
|
+
if (x + rect.width > vw - FLIP_MARGIN) nx = x - rect.width;
|
|
1042
|
+
if (y + rect.height > vh - FLIP_MARGIN) ny = y - rect.height;
|
|
1043
|
+
if (nx < CLAMP_MARGIN) nx = CLAMP_MARGIN;
|
|
1044
|
+
if (ny < CLAMP_MARGIN) ny = CLAMP_MARGIN;
|
|
1045
|
+
panel.style.left = `${nx}px`;
|
|
1046
|
+
panel.style.top = `${ny}px`;
|
|
1047
|
+
panel.style.visibility = "";
|
|
1048
|
+
focusableItems = Array.from(panel.querySelectorAll(".menu-item:not(:disabled)"));
|
|
1049
|
+
focusedIndex = -1;
|
|
1050
|
+
requestAnimationFrame(() => {
|
|
1051
|
+
if (!panel || focusableItems.length === 0) return;
|
|
1052
|
+
setFocused(0);
|
|
1053
|
+
});
|
|
1054
|
+
document.addEventListener("pointerdown", onOutsidePointerDown2, true);
|
|
1055
|
+
document.addEventListener("scroll", onCloseSignal, true);
|
|
1056
|
+
window.addEventListener("resize", onCloseSignal);
|
|
1057
|
+
window.addEventListener("keydown", onKeyDown2);
|
|
1058
|
+
}
|
|
1059
|
+
function closeMenu() {
|
|
1060
|
+
if (!panel) return;
|
|
1061
|
+
panel.remove();
|
|
1062
|
+
panel = null;
|
|
1063
|
+
focusableItems = [];
|
|
1064
|
+
focusedIndex = -1;
|
|
1065
|
+
document.removeEventListener("pointerdown", onOutsidePointerDown2, true);
|
|
1066
|
+
document.removeEventListener("scroll", onCloseSignal, true);
|
|
1067
|
+
window.removeEventListener("resize", onCloseSignal);
|
|
1068
|
+
window.removeEventListener("keydown", onKeyDown2);
|
|
1069
|
+
}
|
|
1070
|
+
function setFocused(next) {
|
|
1071
|
+
if (!panel || focusableItems.length === 0) return;
|
|
1072
|
+
focusedIndex = (next % focusableItems.length + focusableItems.length) % focusableItems.length;
|
|
1073
|
+
focusableItems[focusedIndex].focus();
|
|
1074
|
+
}
|
|
1075
|
+
function onOutsidePointerDown2(e) {
|
|
1076
|
+
if (!panel) return;
|
|
1077
|
+
const target = e.target;
|
|
1078
|
+
if (!(target instanceof Node) || !panel.contains(target)) closeMenu();
|
|
1079
|
+
}
|
|
1080
|
+
function onCloseSignal() {
|
|
1081
|
+
closeMenu();
|
|
1082
|
+
}
|
|
1083
|
+
function onKeyDown2(e) {
|
|
1084
|
+
if (!panel) return;
|
|
1085
|
+
if (e.key === "Escape") {
|
|
1086
|
+
e.preventDefault();
|
|
1087
|
+
closeMenu();
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
if (focusableItems.length === 0) return;
|
|
1091
|
+
if (e.key === "ArrowDown") {
|
|
1092
|
+
e.preventDefault();
|
|
1093
|
+
setFocused(focusedIndex + 1);
|
|
1094
|
+
} else if (e.key === "ArrowUp") {
|
|
1095
|
+
e.preventDefault();
|
|
1096
|
+
setFocused(focusedIndex - 1);
|
|
1097
|
+
} else if (e.key === "Enter" && focusedIndex >= 0) {
|
|
1098
|
+
e.preventDefault();
|
|
1099
|
+
focusableItems[focusedIndex].click();
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
function clearLongPress() {
|
|
1103
|
+
if (!longPress) return;
|
|
1104
|
+
window.clearTimeout(longPress.timer);
|
|
1105
|
+
longPress = null;
|
|
1106
|
+
}
|
|
1107
|
+
function openForTarget(target, x, y) {
|
|
1108
|
+
openContextMenu(x, y, resolveItems(target));
|
|
1109
|
+
}
|
|
1110
|
+
function initContextMenu(opts = {}) {
|
|
1111
|
+
if (typeof document === "undefined") return;
|
|
1112
|
+
if (opts.items) fallbackItems = opts.items;
|
|
1113
|
+
if (initialized4) return;
|
|
1114
|
+
initialized4 = true;
|
|
1115
|
+
document.addEventListener("contextmenu", (e) => {
|
|
1116
|
+
const target = e.target instanceof Element ? e.target : null;
|
|
1117
|
+
if (isEditable(target)) return;
|
|
1118
|
+
e.preventDefault();
|
|
1119
|
+
openForTarget(target, e.clientX, e.clientY);
|
|
1120
|
+
});
|
|
1121
|
+
document.addEventListener("pointerdown", (e) => {
|
|
1122
|
+
if (e.pointerType !== "touch") return;
|
|
1123
|
+
const target = e.target instanceof Element ? e.target : null;
|
|
1124
|
+
if (!target || isEditable(target)) return;
|
|
1125
|
+
clearLongPress();
|
|
1126
|
+
const startX = e.clientX;
|
|
1127
|
+
const startY = e.clientY;
|
|
1128
|
+
const timer = window.setTimeout(() => {
|
|
1129
|
+
longPress = null;
|
|
1130
|
+
openForTarget(target, startX, startY);
|
|
1131
|
+
}, LONG_PRESS_DELAY);
|
|
1132
|
+
longPress = { timer, x: startX, y: startY, target };
|
|
1133
|
+
});
|
|
1134
|
+
document.addEventListener("pointermove", (e) => {
|
|
1135
|
+
if (!longPress) return;
|
|
1136
|
+
if (Math.hypot(e.clientX - longPress.x, e.clientY - longPress.y) > LONG_PRESS_MOVE_LIMIT) {
|
|
1137
|
+
clearLongPress();
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
1140
|
+
document.addEventListener("pointerup", clearLongPress);
|
|
1141
|
+
document.addEventListener("pointercancel", clearLongPress);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// src/behaviors/popover.ts
|
|
1145
|
+
var DEFAULT_MIN_WIDTH = 220;
|
|
1146
|
+
var DEFAULT_MAX_WIDTH = 420;
|
|
1147
|
+
var DEFAULT_MIN_HEIGHT = 120;
|
|
1148
|
+
var DEFAULT_MAX_HEIGHT = 420;
|
|
1149
|
+
function clamp2(value, min, max) {
|
|
1150
|
+
if (max < min) return min;
|
|
1151
|
+
return Math.min(max, Math.max(min, value));
|
|
1152
|
+
}
|
|
1153
|
+
function chooseSide(preferredSide, availableTop, availableBottom, desiredHeight) {
|
|
1154
|
+
const preferredSpace = preferredSide === "top" ? availableTop : availableBottom;
|
|
1155
|
+
const oppositeSpace = preferredSide === "top" ? availableBottom : availableTop;
|
|
1156
|
+
if (preferredSpace >= desiredHeight) return preferredSide;
|
|
1157
|
+
if (oppositeSpace >= desiredHeight) return preferredSide === "top" ? "bottom" : "top";
|
|
1158
|
+
if (preferredSpace >= oppositeSpace) return preferredSide;
|
|
1159
|
+
return preferredSide === "top" ? "bottom" : "top";
|
|
1160
|
+
}
|
|
1161
|
+
function horizontalAnchor(anchor, width, align) {
|
|
1162
|
+
if (align === "center") return anchor.left + anchor.width / 2 - width / 2;
|
|
1163
|
+
if (align === "end") return anchor.right - width;
|
|
1164
|
+
return anchor.left;
|
|
1165
|
+
}
|
|
1166
|
+
function computePopoverLayout(anchor, viewport, opts = {}) {
|
|
1167
|
+
const margin = opts.margin ?? 12;
|
|
1168
|
+
const offset = opts.offset ?? 8;
|
|
1169
|
+
const availableWidth = Math.max(80, viewport.width - margin * 2);
|
|
1170
|
+
const requestedMinWidth = Math.max(1, opts.minWidth ?? DEFAULT_MIN_WIDTH);
|
|
1171
|
+
const requestedMaxWidth = Math.max(requestedMinWidth, opts.maxWidth ?? DEFAULT_MAX_WIDTH);
|
|
1172
|
+
const maxWidth = Math.min(requestedMaxWidth, availableWidth);
|
|
1173
|
+
const minWidth = Math.min(requestedMinWidth, maxWidth);
|
|
1174
|
+
const preferredSide = opts.side ?? "top";
|
|
1175
|
+
const align = opts.align ?? "start";
|
|
1176
|
+
const availableTop = Math.max(0, anchor.top - margin - offset);
|
|
1177
|
+
const availableBottom = Math.max(0, viewport.height - anchor.bottom - margin - offset);
|
|
1178
|
+
const availableHeight = Math.max(80, viewport.height - margin * 2);
|
|
1179
|
+
const minHeight = Math.min(Math.max(80, opts.minHeight ?? DEFAULT_MIN_HEIGHT), availableHeight);
|
|
1180
|
+
const requestedMaxHeight = Math.max(minHeight, opts.maxHeight ?? DEFAULT_MAX_HEIGHT);
|
|
1181
|
+
const fallbackHeight = Math.min(260, requestedMaxHeight);
|
|
1182
|
+
const contentHeight = Math.max(opts.contentHeightHint ?? 0, fallbackHeight);
|
|
1183
|
+
const desiredHeight = clamp2(contentHeight, minHeight, Math.min(requestedMaxHeight, availableHeight));
|
|
1184
|
+
const side = chooseSide(preferredSide, availableTop, availableBottom, desiredHeight);
|
|
1185
|
+
const sideSpace = side === "top" ? availableTop : availableBottom;
|
|
1186
|
+
const maxHeight = Math.min(
|
|
1187
|
+
requestedMaxHeight,
|
|
1188
|
+
Math.max(80, sideSpace || Math.max(availableTop, availableBottom)),
|
|
1189
|
+
availableHeight
|
|
1190
|
+
);
|
|
1191
|
+
const positionedHeight = clamp2(desiredHeight, 40, maxHeight);
|
|
1192
|
+
const width = align === "stretch" ? clamp2(anchor.width, minWidth, maxWidth) : maxWidth;
|
|
1193
|
+
const left = clamp2(horizontalAnchor(anchor, width, align), margin, viewport.width - margin - width);
|
|
1194
|
+
const desiredTop = side === "top" ? anchor.top - offset - positionedHeight : anchor.bottom + offset;
|
|
1195
|
+
const top = clamp2(desiredTop, margin, viewport.height - margin - positionedHeight);
|
|
1196
|
+
return { side, left, top, width, maxHeight };
|
|
1197
|
+
}
|
|
1198
|
+
var openPanels = /* @__PURE__ */ new Map();
|
|
1199
|
+
function rectFromElement(el) {
|
|
1200
|
+
const r = el.getBoundingClientRect();
|
|
1201
|
+
return { left: r.left, top: r.top, right: r.right, bottom: r.bottom, width: r.width, height: r.height };
|
|
1202
|
+
}
|
|
1203
|
+
function resolveAnchor(state) {
|
|
1204
|
+
if (state.anchor instanceof Element) {
|
|
1205
|
+
if (!state.anchor.isConnected) return null;
|
|
1206
|
+
return rectFromElement(state.anchor);
|
|
1207
|
+
}
|
|
1208
|
+
return state.anchor;
|
|
1209
|
+
}
|
|
1210
|
+
function relayout(state) {
|
|
1211
|
+
const anchor = resolveAnchor(state);
|
|
1212
|
+
if (!anchor) {
|
|
1213
|
+
closePopover(state.panel);
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
const layout = computePopoverLayout(
|
|
1217
|
+
anchor,
|
|
1218
|
+
{ width: window.innerWidth, height: window.innerHeight },
|
|
1219
|
+
state.opts
|
|
1220
|
+
);
|
|
1221
|
+
const { panel: panel2 } = state;
|
|
1222
|
+
panel2.style.left = `${layout.left}px`;
|
|
1223
|
+
panel2.style.top = `${layout.top}px`;
|
|
1224
|
+
panel2.style.width = `${layout.width}px`;
|
|
1225
|
+
panel2.style.maxHeight = `${layout.maxHeight}px`;
|
|
1226
|
+
panel2.dataset.side = layout.side;
|
|
1227
|
+
panel2.style.visibility = state.prevVisibility;
|
|
1228
|
+
}
|
|
1229
|
+
function onWindowChange() {
|
|
1230
|
+
openPanels.forEach(relayout);
|
|
1231
|
+
}
|
|
1232
|
+
function onOutsidePointerDown3(e) {
|
|
1233
|
+
const target = e.target;
|
|
1234
|
+
if (!(target instanceof Node)) return;
|
|
1235
|
+
openPanels.forEach((state) => {
|
|
1236
|
+
if (state.panel.contains(target)) return;
|
|
1237
|
+
if (state.anchor instanceof Element && state.anchor.contains(target)) return;
|
|
1238
|
+
closePopover(state.panel);
|
|
1239
|
+
});
|
|
1240
|
+
}
|
|
1241
|
+
function onKeyDown3(e) {
|
|
1242
|
+
if (e.key !== "Escape") return;
|
|
1243
|
+
const states = Array.from(openPanels.values());
|
|
1244
|
+
const top = states[states.length - 1];
|
|
1245
|
+
if (top) {
|
|
1246
|
+
e.preventDefault();
|
|
1247
|
+
closePopover(top.panel);
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
function openPopover(panel2, options) {
|
|
1251
|
+
if (typeof document === "undefined") return;
|
|
1252
|
+
if (openPanels.has(panel2)) closePopover(panel2);
|
|
1253
|
+
const { anchor, onClose, ...layoutOpts } = options;
|
|
1254
|
+
const placeholder = document.createComment("popover-anchor");
|
|
1255
|
+
panel2.parentNode?.insertBefore(placeholder, panel2);
|
|
1256
|
+
const wasHidden = panel2.hidden;
|
|
1257
|
+
const prevVisibility = panel2.style.visibility;
|
|
1258
|
+
const state = {
|
|
1259
|
+
panel: panel2,
|
|
1260
|
+
anchor,
|
|
1261
|
+
opts: layoutOpts,
|
|
1262
|
+
onClose,
|
|
1263
|
+
placeholder,
|
|
1264
|
+
wasHidden,
|
|
1265
|
+
prevVisibility
|
|
1266
|
+
};
|
|
1267
|
+
openPanels.set(panel2, state);
|
|
1268
|
+
document.body.appendChild(panel2);
|
|
1269
|
+
panel2.hidden = false;
|
|
1270
|
+
panel2.style.visibility = "hidden";
|
|
1271
|
+
relayout(state);
|
|
1272
|
+
if (openPanels.size === 1) {
|
|
1273
|
+
window.addEventListener("resize", onWindowChange);
|
|
1274
|
+
window.addEventListener("scroll", onWindowChange, true);
|
|
1275
|
+
document.addEventListener("pointerdown", onOutsidePointerDown3, true);
|
|
1276
|
+
window.addEventListener("keydown", onKeyDown3);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
function closePopover(panel2) {
|
|
1280
|
+
if (typeof document === "undefined") return;
|
|
1281
|
+
const state = openPanels.get(panel2);
|
|
1282
|
+
if (!state) return;
|
|
1283
|
+
openPanels.delete(panel2);
|
|
1284
|
+
panel2.style.visibility = state.prevVisibility;
|
|
1285
|
+
panel2.hidden = state.wasHidden;
|
|
1286
|
+
panel2.style.left = "";
|
|
1287
|
+
panel2.style.top = "";
|
|
1288
|
+
panel2.style.width = "";
|
|
1289
|
+
panel2.style.maxHeight = "";
|
|
1290
|
+
delete panel2.dataset.side;
|
|
1291
|
+
if (state.placeholder.parentNode) {
|
|
1292
|
+
state.placeholder.parentNode.insertBefore(panel2, state.placeholder);
|
|
1293
|
+
state.placeholder.remove();
|
|
1294
|
+
}
|
|
1295
|
+
if (openPanels.size === 0) {
|
|
1296
|
+
window.removeEventListener("resize", onWindowChange);
|
|
1297
|
+
window.removeEventListener("scroll", onWindowChange, true);
|
|
1298
|
+
document.removeEventListener("pointerdown", onOutsidePointerDown3, true);
|
|
1299
|
+
window.removeEventListener("keydown", onKeyDown3);
|
|
1300
|
+
}
|
|
1301
|
+
state.onClose?.();
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// src/behaviors/accordion.ts
|
|
1305
|
+
function setup6(container) {
|
|
1306
|
+
const el = container;
|
|
1307
|
+
if (el.__icenAccordionInit) return;
|
|
1308
|
+
el.__icenAccordionInit = true;
|
|
1309
|
+
const single = container.hasAttribute("data-single");
|
|
1310
|
+
container.addEventListener("click", (ev) => {
|
|
1311
|
+
const target = ev.target;
|
|
1312
|
+
if (!(target instanceof Element)) return;
|
|
1313
|
+
const trigger = target.closest(".accordion-trigger");
|
|
1314
|
+
if (!trigger) return;
|
|
1315
|
+
const item = trigger.closest(".accordion-item");
|
|
1316
|
+
if (!item || trigger.closest(".accordion") !== container) return;
|
|
1317
|
+
const open = item.classList.toggle("is-open");
|
|
1318
|
+
trigger.setAttribute("aria-expanded", String(open));
|
|
1319
|
+
if (single && open) {
|
|
1320
|
+
container.querySelectorAll(".accordion-item.is-open").forEach((other) => {
|
|
1321
|
+
if (other === item) return;
|
|
1322
|
+
other.classList.remove("is-open");
|
|
1323
|
+
other.querySelector(".accordion-trigger")?.setAttribute("aria-expanded", "false");
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
});
|
|
1327
|
+
}
|
|
1328
|
+
function initAccordion(root = document) {
|
|
1329
|
+
if (typeof document === "undefined") return;
|
|
1330
|
+
const containers = [];
|
|
1331
|
+
if (root instanceof Element && root.matches(".accordion")) containers.push(root);
|
|
1332
|
+
containers.push(...Array.from(root.querySelectorAll(".accordion")));
|
|
1333
|
+
for (const c of containers) setup6(c);
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
// src/behaviors/tree.ts
|
|
1337
|
+
function setup7(tree) {
|
|
1338
|
+
const el = tree;
|
|
1339
|
+
if (el.__icenTreeInit) return;
|
|
1340
|
+
el.__icenTreeInit = true;
|
|
1341
|
+
tree.addEventListener("click", (ev) => {
|
|
1342
|
+
const target = ev.target;
|
|
1343
|
+
if (!(target instanceof Element)) return;
|
|
1344
|
+
const node = target.closest(".tree-node");
|
|
1345
|
+
if (!node || node.closest(".tree") !== tree) return;
|
|
1346
|
+
if (node.classList.contains("is-disabled")) return;
|
|
1347
|
+
const li = node.closest("li");
|
|
1348
|
+
if (!li) return;
|
|
1349
|
+
const childUl = li.querySelector(":scope > ul");
|
|
1350
|
+
if (target.closest(".tree-toggle")) {
|
|
1351
|
+
if (!childUl) return;
|
|
1352
|
+
const toggle = node.querySelector(".tree-toggle");
|
|
1353
|
+
const collapsed = childUl.hasAttribute("hidden");
|
|
1354
|
+
if (collapsed) {
|
|
1355
|
+
childUl.removeAttribute("hidden");
|
|
1356
|
+
toggle?.classList.remove("is-collapsed");
|
|
1357
|
+
toggle?.setAttribute("aria-expanded", "true");
|
|
1358
|
+
} else {
|
|
1359
|
+
childUl.setAttribute("hidden", "");
|
|
1360
|
+
toggle?.classList.add("is-collapsed");
|
|
1361
|
+
toggle?.setAttribute("aria-expanded", "false");
|
|
1362
|
+
}
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
if (childUl) return;
|
|
1366
|
+
tree.querySelectorAll(".tree-node.is-selected").forEach((n) => n.classList.remove("is-selected"));
|
|
1367
|
+
node.classList.add("is-selected");
|
|
1368
|
+
});
|
|
1369
|
+
}
|
|
1370
|
+
function initTree(root = document) {
|
|
1371
|
+
if (typeof document === "undefined") return;
|
|
1372
|
+
const trees = [];
|
|
1373
|
+
if (root instanceof Element && root.matches(".tree")) trees.push(root);
|
|
1374
|
+
trees.push(...Array.from(root.querySelectorAll(".tree")));
|
|
1375
|
+
for (const t of trees) setup7(t);
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// src/behaviors/carousel.ts
|
|
1379
|
+
function setup8(carousel) {
|
|
1380
|
+
const el = carousel;
|
|
1381
|
+
if (el.__icenCarouselInit) return;
|
|
1382
|
+
el.__icenCarouselInit = true;
|
|
1383
|
+
const track = carousel.querySelector(":scope > .carousel-track");
|
|
1384
|
+
if (!track) return;
|
|
1385
|
+
const slides = Array.from(track.querySelectorAll(":scope > .carousel-slide"));
|
|
1386
|
+
const count = slides.length;
|
|
1387
|
+
if (count === 0) return;
|
|
1388
|
+
const prev = carousel.querySelector(".carousel-arrow--prev");
|
|
1389
|
+
const next = carousel.querySelector(".carousel-arrow--next");
|
|
1390
|
+
const dotsBox = carousel.querySelector(".carousel-dots");
|
|
1391
|
+
let index = 0;
|
|
1392
|
+
const dots = [];
|
|
1393
|
+
if (dotsBox && count > 1) {
|
|
1394
|
+
for (let i = 0; i < count; i++) {
|
|
1395
|
+
const dot = document.createElement("button");
|
|
1396
|
+
dot.type = "button";
|
|
1397
|
+
dot.className = "carousel-dot";
|
|
1398
|
+
dot.setAttribute("aria-label", `\u7B2C ${i + 1} \u5F20`);
|
|
1399
|
+
dot.addEventListener("click", () => go(i));
|
|
1400
|
+
dotsBox.appendChild(dot);
|
|
1401
|
+
dots.push(dot);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
function render() {
|
|
1405
|
+
if (!track) return;
|
|
1406
|
+
track.style.transform = `translateX(${-index * 100}%)`;
|
|
1407
|
+
slides.forEach((s, i) => s.setAttribute("aria-hidden", String(i !== index)));
|
|
1408
|
+
dots.forEach((d, i) => d.classList.toggle("active", i === index));
|
|
1409
|
+
}
|
|
1410
|
+
function go(i) {
|
|
1411
|
+
index = (i % count + count) % count;
|
|
1412
|
+
render();
|
|
1413
|
+
}
|
|
1414
|
+
prev?.addEventListener("click", () => go(index - 1));
|
|
1415
|
+
next?.addEventListener("click", () => go(index + 1));
|
|
1416
|
+
render();
|
|
1417
|
+
}
|
|
1418
|
+
function initCarousel(root = document) {
|
|
1419
|
+
if (typeof document === "undefined") return;
|
|
1420
|
+
const containers = [];
|
|
1421
|
+
if (root instanceof Element && root.matches(".carousel")) containers.push(root);
|
|
1422
|
+
containers.push(...Array.from(root.querySelectorAll(".carousel")));
|
|
1423
|
+
for (const c of containers) setup8(c);
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// src/behaviors/charts.ts
|
|
1427
|
+
var SVG_NS = "http://www.w3.org/2000/svg";
|
|
1428
|
+
function toneVar(tone = "accent") {
|
|
1429
|
+
switch (tone) {
|
|
1430
|
+
case "success":
|
|
1431
|
+
return "var(--token-success)";
|
|
1432
|
+
case "warning":
|
|
1433
|
+
return "var(--token-warning)";
|
|
1434
|
+
case "error":
|
|
1435
|
+
return "var(--token-error)";
|
|
1436
|
+
case "muted":
|
|
1437
|
+
return "var(--token-text-faint)";
|
|
1438
|
+
case "accent":
|
|
1439
|
+
default:
|
|
1440
|
+
return "var(--token-accent)";
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
function fmt(opts, value) {
|
|
1444
|
+
return opts.formatValue ? opts.formatValue(value) : String(value);
|
|
1445
|
+
}
|
|
1446
|
+
function svgEl(tag, attrs) {
|
|
1447
|
+
const el = document.createElementNS(SVG_NS, tag);
|
|
1448
|
+
for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, v);
|
|
1449
|
+
return el;
|
|
1450
|
+
}
|
|
1451
|
+
function renderEmpty(el, label) {
|
|
1452
|
+
const box = document.createElement("div");
|
|
1453
|
+
box.className = "chart-empty";
|
|
1454
|
+
box.textContent = label ?? "\u6682\u65E0\u6570\u636E";
|
|
1455
|
+
el.appendChild(box);
|
|
1456
|
+
}
|
|
1457
|
+
function legendItem(label, valueText, tone) {
|
|
1458
|
+
const row = document.createElement("div");
|
|
1459
|
+
row.className = "chart-legend-item";
|
|
1460
|
+
const dot = document.createElement("span");
|
|
1461
|
+
dot.className = "chart-legend-dot";
|
|
1462
|
+
if (tone) dot.style.setProperty("--chart-tone", toneVar(tone));
|
|
1463
|
+
const name = document.createElement("span");
|
|
1464
|
+
name.className = "chart-legend-label";
|
|
1465
|
+
name.textContent = label;
|
|
1466
|
+
const val = document.createElement("span");
|
|
1467
|
+
val.className = "chart-legend-value";
|
|
1468
|
+
val.textContent = valueText;
|
|
1469
|
+
row.append(dot, name, val);
|
|
1470
|
+
return row;
|
|
1471
|
+
}
|
|
1472
|
+
function labelsRow(labels) {
|
|
1473
|
+
const row = document.createElement("div");
|
|
1474
|
+
row.className = "chart-labels";
|
|
1475
|
+
for (const label of labels) {
|
|
1476
|
+
const span = document.createElement("span");
|
|
1477
|
+
span.textContent = label;
|
|
1478
|
+
row.appendChild(span);
|
|
1479
|
+
}
|
|
1480
|
+
return row;
|
|
1481
|
+
}
|
|
1482
|
+
function renderVBar(el, opts) {
|
|
1483
|
+
if (typeof document === "undefined") return;
|
|
1484
|
+
el.textContent = "";
|
|
1485
|
+
const n = Math.min(opts.labels.length, opts.values.length);
|
|
1486
|
+
if (n === 0) {
|
|
1487
|
+
renderEmpty(el, opts.emptyLabel);
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
const labels = opts.labels.slice(0, n);
|
|
1491
|
+
const values = opts.values.slice(0, n);
|
|
1492
|
+
const max = Math.max(...values, 1);
|
|
1493
|
+
const wrap = document.createElement("div");
|
|
1494
|
+
wrap.className = "chart-vbar";
|
|
1495
|
+
if (opts.tone) wrap.style.setProperty("--chart-tone", toneVar(opts.tone));
|
|
1496
|
+
values.forEach((value, i) => {
|
|
1497
|
+
const track = document.createElement("div");
|
|
1498
|
+
track.className = "chart-vbar-track";
|
|
1499
|
+
const bar = document.createElement("div");
|
|
1500
|
+
bar.className = "chart-vbar-bar";
|
|
1501
|
+
bar.style.height = `${Math.max(6, value / max * 100)}%`;
|
|
1502
|
+
bar.setAttribute("title", `${labels[i]}: ${fmt(opts, value)}`);
|
|
1503
|
+
track.appendChild(bar);
|
|
1504
|
+
wrap.appendChild(track);
|
|
1505
|
+
});
|
|
1506
|
+
el.append(wrap, labelsRow(labels));
|
|
1507
|
+
}
|
|
1508
|
+
function renderHBar(el, opts) {
|
|
1509
|
+
if (typeof document === "undefined") return;
|
|
1510
|
+
el.textContent = "";
|
|
1511
|
+
const n = Math.min(opts.labels.length, opts.values.length);
|
|
1512
|
+
if (n === 0) {
|
|
1513
|
+
renderEmpty(el, opts.emptyLabel);
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
const labels = opts.labels.slice(0, n);
|
|
1517
|
+
const values = opts.values.slice(0, n);
|
|
1518
|
+
const max = Math.max(...values, 1);
|
|
1519
|
+
const wrap = document.createElement("div");
|
|
1520
|
+
wrap.className = "chart-hbar";
|
|
1521
|
+
if (opts.tone) wrap.style.setProperty("--chart-tone", toneVar(opts.tone));
|
|
1522
|
+
values.forEach((value, i) => {
|
|
1523
|
+
const row = document.createElement("div");
|
|
1524
|
+
row.className = "chart-hbar-row";
|
|
1525
|
+
const label = document.createElement("span");
|
|
1526
|
+
label.className = "chart-hbar-label";
|
|
1527
|
+
label.textContent = labels[i] ?? "";
|
|
1528
|
+
const track = document.createElement("div");
|
|
1529
|
+
track.className = "chart-hbar-track";
|
|
1530
|
+
const bar = document.createElement("div");
|
|
1531
|
+
bar.className = "chart-hbar-bar";
|
|
1532
|
+
bar.style.width = `${Math.max(2, value / max * 100)}%`;
|
|
1533
|
+
const val = document.createElement("span");
|
|
1534
|
+
val.className = "chart-hbar-value";
|
|
1535
|
+
val.textContent = fmt(opts, value);
|
|
1536
|
+
track.appendChild(bar);
|
|
1537
|
+
row.append(label, track, val);
|
|
1538
|
+
wrap.appendChild(row);
|
|
1539
|
+
});
|
|
1540
|
+
el.appendChild(wrap);
|
|
1541
|
+
}
|
|
1542
|
+
function renderStack(el, opts) {
|
|
1543
|
+
if (typeof document === "undefined") return;
|
|
1544
|
+
el.textContent = "";
|
|
1545
|
+
const total = opts.segments.reduce((sum, s) => sum + s.value, 0);
|
|
1546
|
+
if (opts.segments.length === 0 || total <= 0) {
|
|
1547
|
+
renderEmpty(el, opts.emptyLabel);
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
const bar = document.createElement("div");
|
|
1551
|
+
bar.className = "chart-stack";
|
|
1552
|
+
for (const seg of opts.segments) {
|
|
1553
|
+
const s = document.createElement("div");
|
|
1554
|
+
s.className = "chart-stack-seg";
|
|
1555
|
+
s.style.width = `${seg.value / total * 100}%`;
|
|
1556
|
+
if (seg.tone) s.style.setProperty("--chart-tone", toneVar(seg.tone));
|
|
1557
|
+
s.setAttribute("title", `${seg.label}: ${fmt(opts, seg.value)}`);
|
|
1558
|
+
bar.appendChild(s);
|
|
1559
|
+
}
|
|
1560
|
+
const legend = document.createElement("div");
|
|
1561
|
+
legend.className = "chart-legend";
|
|
1562
|
+
for (const seg of opts.segments) {
|
|
1563
|
+
legend.appendChild(legendItem(seg.label, fmt(opts, seg.value), seg.tone));
|
|
1564
|
+
}
|
|
1565
|
+
el.append(bar, legend);
|
|
1566
|
+
}
|
|
1567
|
+
function renderDonut(el, opts) {
|
|
1568
|
+
if (typeof document === "undefined") return;
|
|
1569
|
+
el.textContent = "";
|
|
1570
|
+
const total = opts.segments.reduce((sum, s) => sum + s.value, 0);
|
|
1571
|
+
if (opts.segments.length === 0 || total <= 0) {
|
|
1572
|
+
renderEmpty(el, opts.emptyLabel);
|
|
1573
|
+
return;
|
|
1574
|
+
}
|
|
1575
|
+
const radius = 42;
|
|
1576
|
+
const circumference = 2 * Math.PI * radius;
|
|
1577
|
+
const wrap = document.createElement("div");
|
|
1578
|
+
wrap.className = "chart-donut";
|
|
1579
|
+
const svg = svgEl("svg", { viewBox: "0 0 120 120", role: "img" });
|
|
1580
|
+
svg.style.transform = "rotate(-90deg)";
|
|
1581
|
+
svg.appendChild(svgEl("circle", {
|
|
1582
|
+
cx: "60",
|
|
1583
|
+
cy: "60",
|
|
1584
|
+
r: String(radius),
|
|
1585
|
+
fill: "none",
|
|
1586
|
+
stroke: "var(--token-line-soft)",
|
|
1587
|
+
"stroke-width": "14"
|
|
1588
|
+
}));
|
|
1589
|
+
let offset = 0;
|
|
1590
|
+
for (const seg of opts.segments) {
|
|
1591
|
+
const dash = seg.value / total * circumference;
|
|
1592
|
+
svg.appendChild(svgEl("circle", {
|
|
1593
|
+
cx: "60",
|
|
1594
|
+
cy: "60",
|
|
1595
|
+
r: String(radius),
|
|
1596
|
+
fill: "none",
|
|
1597
|
+
stroke: toneVar(seg.tone),
|
|
1598
|
+
"stroke-width": "14",
|
|
1599
|
+
"stroke-dasharray": `${dash} ${circumference - dash}`,
|
|
1600
|
+
"stroke-dashoffset": String(-offset),
|
|
1601
|
+
"stroke-linecap": "butt"
|
|
1602
|
+
}));
|
|
1603
|
+
offset += dash;
|
|
1604
|
+
}
|
|
1605
|
+
const legend = document.createElement("div");
|
|
1606
|
+
legend.className = "chart-legend";
|
|
1607
|
+
for (const seg of opts.segments) {
|
|
1608
|
+
legend.appendChild(legendItem(seg.label, `${Math.round(seg.value / total * 100)}%`, seg.tone));
|
|
1609
|
+
}
|
|
1610
|
+
wrap.append(svg, legend);
|
|
1611
|
+
el.appendChild(wrap);
|
|
1612
|
+
}
|
|
1613
|
+
var lineGradientSeq = 0;
|
|
1614
|
+
function renderLine(el, opts) {
|
|
1615
|
+
if (typeof document === "undefined") return;
|
|
1616
|
+
el.textContent = "";
|
|
1617
|
+
const n = Math.min(opts.labels.length, opts.values.length);
|
|
1618
|
+
if (n === 0) {
|
|
1619
|
+
renderEmpty(el, opts.emptyLabel);
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
const labels = opts.labels.slice(0, n);
|
|
1623
|
+
const values = opts.values.slice(0, n);
|
|
1624
|
+
const width = 360;
|
|
1625
|
+
const height = 150;
|
|
1626
|
+
const pad = 14;
|
|
1627
|
+
const max = Math.max(...values, 1);
|
|
1628
|
+
const tone = toneVar(opts.tone);
|
|
1629
|
+
const points = values.map((value, i) => ({
|
|
1630
|
+
x: values.length <= 1 ? width / 2 : pad + i / (values.length - 1) * (width - pad * 2),
|
|
1631
|
+
y: height - pad - value / max * (height - pad * 2),
|
|
1632
|
+
value,
|
|
1633
|
+
label: labels[i] ?? ""
|
|
1634
|
+
}));
|
|
1635
|
+
const line = points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(" ");
|
|
1636
|
+
const last = points[points.length - 1];
|
|
1637
|
+
const first = points[0];
|
|
1638
|
+
const area = `${line} L${last.x.toFixed(1)},${height - pad} L${first.x.toFixed(1)},${height - pad} Z`;
|
|
1639
|
+
const gradientId = `icen-chart-line-${++lineGradientSeq}`;
|
|
1640
|
+
const wrap = document.createElement("div");
|
|
1641
|
+
wrap.className = "chart-line";
|
|
1642
|
+
const svg = svgEl("svg", { viewBox: `0 0 ${width} ${height}`, role: "img" });
|
|
1643
|
+
const defs = svgEl("defs", {});
|
|
1644
|
+
const gradient = svgEl("linearGradient", { id: gradientId, x1: "0", x2: "0", y1: "0", y2: "1" });
|
|
1645
|
+
gradient.appendChild(svgEl("stop", { offset: "0%", "stop-color": tone, "stop-opacity": "0.32" }));
|
|
1646
|
+
gradient.appendChild(svgEl("stop", { offset: "100%", "stop-color": tone, "stop-opacity": "0.02" }));
|
|
1647
|
+
defs.appendChild(gradient);
|
|
1648
|
+
svg.appendChild(defs);
|
|
1649
|
+
for (let i = 0; i < 4; i++) {
|
|
1650
|
+
const y = pad + i * (height - pad * 2) / 3;
|
|
1651
|
+
svg.appendChild(svgEl("line", {
|
|
1652
|
+
x1: String(pad),
|
|
1653
|
+
x2: String(width - pad),
|
|
1654
|
+
y1: String(y),
|
|
1655
|
+
y2: String(y),
|
|
1656
|
+
stroke: "var(--token-line-soft)",
|
|
1657
|
+
"stroke-width": "1"
|
|
1658
|
+
}));
|
|
1659
|
+
}
|
|
1660
|
+
svg.appendChild(svgEl("path", { d: area, fill: `url(#${gradientId})` }));
|
|
1661
|
+
svg.appendChild(svgEl("path", {
|
|
1662
|
+
d: line,
|
|
1663
|
+
fill: "none",
|
|
1664
|
+
stroke: tone,
|
|
1665
|
+
"stroke-width": "2.5",
|
|
1666
|
+
"stroke-linecap": "round",
|
|
1667
|
+
"stroke-linejoin": "round"
|
|
1668
|
+
}));
|
|
1669
|
+
for (const p of points) {
|
|
1670
|
+
const dot = svgEl("circle", {
|
|
1671
|
+
cx: p.x.toFixed(1),
|
|
1672
|
+
cy: p.y.toFixed(1),
|
|
1673
|
+
r: "3",
|
|
1674
|
+
fill: "var(--token-card)",
|
|
1675
|
+
stroke: tone,
|
|
1676
|
+
"stroke-width": "2"
|
|
1677
|
+
});
|
|
1678
|
+
const title = svgEl("title", {});
|
|
1679
|
+
title.textContent = `${p.label}: ${fmt(opts, p.value)}`;
|
|
1680
|
+
dot.appendChild(title);
|
|
1681
|
+
svg.appendChild(dot);
|
|
1682
|
+
}
|
|
1683
|
+
wrap.append(svg, labelsRow(labels.slice(-7)));
|
|
1684
|
+
el.appendChild(wrap);
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
// src/behaviors/nav.ts
|
|
1688
|
+
function setup9(nav) {
|
|
1689
|
+
const el = nav;
|
|
1690
|
+
if (el.__icenNavInit) return;
|
|
1691
|
+
el.__icenNavInit = true;
|
|
1692
|
+
let ticking = false;
|
|
1693
|
+
const update = () => {
|
|
1694
|
+
ticking = false;
|
|
1695
|
+
el.classList.toggle("is-scrolled", window.scrollY > 12);
|
|
1696
|
+
};
|
|
1697
|
+
const onScroll2 = () => {
|
|
1698
|
+
if (ticking) return;
|
|
1699
|
+
ticking = true;
|
|
1700
|
+
window.requestAnimationFrame(update);
|
|
1701
|
+
};
|
|
1702
|
+
window.addEventListener("scroll", onScroll2, { passive: true });
|
|
1703
|
+
update();
|
|
1704
|
+
}
|
|
1705
|
+
function initNav(root = document) {
|
|
1706
|
+
if (typeof document === "undefined") return;
|
|
1707
|
+
if (typeof window === "undefined") return;
|
|
1708
|
+
const navs = [];
|
|
1709
|
+
if (root instanceof Element && root.matches(".nav[data-scroll-reactive]")) navs.push(root);
|
|
1710
|
+
navs.push(...Array.from(root.querySelectorAll(".nav[data-scroll-reactive]")));
|
|
1711
|
+
for (const n of navs) setup9(n);
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
// src/behaviors/sidebar.ts
|
|
1715
|
+
function setup10(sidebar) {
|
|
1716
|
+
const el = sidebar;
|
|
1717
|
+
if (el.__icenSidebarInit) return;
|
|
1718
|
+
el.__icenSidebarInit = true;
|
|
1719
|
+
el.addEventListener("click", (ev) => {
|
|
1720
|
+
const target = ev.target;
|
|
1721
|
+
if (!(target instanceof Element)) return;
|
|
1722
|
+
const title = target.closest(".sidebar-group-title");
|
|
1723
|
+
if (!(title instanceof HTMLElement)) return;
|
|
1724
|
+
if (title.closest(".sidebar") !== el) return;
|
|
1725
|
+
const group = title.closest(".sidebar-group");
|
|
1726
|
+
if (!group) return;
|
|
1727
|
+
const open = group.classList.toggle("is-open");
|
|
1728
|
+
title.setAttribute("aria-expanded", String(open));
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
function initSidebar(root = document) {
|
|
1732
|
+
if (typeof document === "undefined") return;
|
|
1733
|
+
const sidebars = [];
|
|
1734
|
+
if (root instanceof Element && root.matches(".sidebar")) sidebars.push(root);
|
|
1735
|
+
sidebars.push(...Array.from(root.querySelectorAll(".sidebar")));
|
|
1736
|
+
for (const s of sidebars) setup10(s);
|
|
1737
|
+
}
|
|
1738
|
+
|
|
264
1739
|
// src/index.ts
|
|
265
1740
|
var VERSION = package_default.version;
|
|
266
1741
|
export {
|
|
@@ -269,11 +1744,37 @@ export {
|
|
|
269
1744
|
STYLES,
|
|
270
1745
|
VERSION,
|
|
271
1746
|
applyTheme,
|
|
1747
|
+
closeMenu,
|
|
1748
|
+
closeModal,
|
|
1749
|
+
closePopover,
|
|
1750
|
+
computePopoverLayout,
|
|
272
1751
|
getDefaultTheme,
|
|
273
1752
|
getTheme,
|
|
1753
|
+
initAccordion,
|
|
1754
|
+
initCarousel,
|
|
1755
|
+
initContextMenu,
|
|
274
1756
|
initCopy,
|
|
1757
|
+
initDropdown,
|
|
1758
|
+
initInput,
|
|
1759
|
+
initModal,
|
|
1760
|
+
initNav,
|
|
1761
|
+
initSelect,
|
|
1762
|
+
initSidebar,
|
|
1763
|
+
initSlider,
|
|
275
1764
|
initTabs,
|
|
1765
|
+
initTagInput,
|
|
276
1766
|
initTheme,
|
|
1767
|
+
initTree,
|
|
1768
|
+
initUpload,
|
|
1769
|
+
openContextMenu,
|
|
1770
|
+
openModal,
|
|
1771
|
+
openPopover,
|
|
1772
|
+
registerContextMenu,
|
|
1773
|
+
renderDonut,
|
|
1774
|
+
renderHBar,
|
|
1775
|
+
renderLine,
|
|
1776
|
+
renderStack,
|
|
1777
|
+
renderVBar,
|
|
277
1778
|
setTheme,
|
|
278
1779
|
toast,
|
|
279
1780
|
toggleDark
|