@icen.ai/ui 0.3.0 → 0.5.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.
Files changed (78) hide show
  1. package/dist/behaviors/back-top.mjs +84 -0
  2. package/dist/behaviors/charts.mjs +188 -0
  3. package/dist/behaviors/command-palette.mjs +177 -0
  4. package/dist/behaviors/datatable.mjs +244 -24
  5. package/dist/behaviors/date-picker.mjs +228 -0
  6. package/dist/behaviors/input.mjs +170 -1
  7. package/dist/behaviors/notification.mjs +754 -0
  8. package/dist/behaviors/select.mjs +151 -22
  9. package/dist/behaviors/split-pane.mjs +102 -0
  10. package/dist/behaviors/upload.mjs +165 -14
  11. package/dist/components/back-top.css +88 -0
  12. package/dist/components/back-top.mjs +3 -0
  13. package/dist/components/badge.css +131 -0
  14. package/dist/components/badge.mjs +2 -0
  15. package/dist/components/breadcrumb.css +57 -0
  16. package/dist/components/card.css +68 -0
  17. package/dist/components/chart-area.css +15 -0
  18. package/dist/components/chart-area.mjs +4 -0
  19. package/dist/components/chart-bar.css +79 -0
  20. package/dist/components/chart-bar.mjs +4 -0
  21. package/dist/components/chart-gauge.css +38 -0
  22. package/dist/components/chart-gauge.mjs +4 -0
  23. package/dist/components/chart-heatmap.css +52 -0
  24. package/dist/components/chart-heatmap.mjs +4 -0
  25. package/dist/components/chart-line.css +15 -0
  26. package/dist/components/chart-line.mjs +4 -0
  27. package/dist/components/chart-pie.css +26 -0
  28. package/dist/components/chart-pie.mjs +4 -0
  29. package/dist/components/chart-radar.css +19 -0
  30. package/dist/components/chart-radar.mjs +4 -0
  31. package/dist/components/chart-sparkline.css +14 -0
  32. package/dist/components/chart-sparkline.mjs +4 -0
  33. package/dist/components/chart-stack.css +30 -0
  34. package/dist/components/chart-stack.mjs +4 -0
  35. package/dist/components/charts.css +7 -207
  36. package/dist/components/charts.mjs +9 -0
  37. package/dist/components/command-palette.css +246 -0
  38. package/dist/components/command-palette.mjs +3 -0
  39. package/dist/components/content.css +214 -0
  40. package/dist/components/copy.css +93 -0
  41. package/dist/components/copy.mjs +3 -0
  42. package/dist/components/datatable.css +86 -0
  43. package/dist/components/date-picker.css +244 -0
  44. package/dist/components/date-picker.mjs +3 -0
  45. package/dist/components/empty.css +73 -3
  46. package/dist/components/feedback.css +239 -1
  47. package/dist/components/form.css +119 -0
  48. package/dist/components/form.mjs +2 -0
  49. package/dist/components/input.css +24 -1
  50. package/dist/components/media.css +144 -1
  51. package/dist/components/nav.css +99 -0
  52. package/dist/components/notification.css +387 -0
  53. package/dist/components/notification.mjs +3 -0
  54. package/dist/components/panel.css +100 -5
  55. package/dist/components/pill.css +70 -0
  56. package/dist/components/scroll-area.css +112 -0
  57. package/dist/components/scroll-area.mjs +2 -0
  58. package/dist/components/segmented.css +75 -0
  59. package/dist/components/select.css +97 -2
  60. package/dist/components/sidebar.css +118 -0
  61. package/dist/components/slider.css +113 -0
  62. package/dist/components/split-pane.css +112 -0
  63. package/dist/components/split-pane.mjs +3 -0
  64. package/dist/components/stat.css +129 -6
  65. package/dist/components/steps.css +97 -0
  66. package/dist/components/switch.css +142 -0
  67. package/dist/components/table.css +86 -3
  68. package/dist/components/tabs.css +122 -1
  69. package/dist/components/tag.css +164 -0
  70. package/dist/components/tag.mjs +2 -0
  71. package/dist/components/toast.css +114 -0
  72. package/dist/components/toolbar.css +101 -0
  73. package/dist/components/toolbar.mjs +2 -0
  74. package/dist/components/upload.css +61 -4
  75. package/dist/index.mjs +2039 -62
  76. package/dist/registry.json +200 -24
  77. package/dist/ui.css +4865 -622
  78. 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.3.0",
4
+ version: "0.5.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",
@@ -456,9 +456,18 @@ function initCopy(root = document) {
456
456
  }
457
457
 
458
458
  // src/behaviors/input.ts
459
+ var isComposing = false;
460
+ if (typeof document !== "undefined") {
461
+ document.addEventListener("compositionstart", () => {
462
+ isComposing = true;
463
+ });
464
+ document.addEventListener("compositionend", () => {
465
+ isComposing = false;
466
+ });
467
+ }
459
468
  function setupClear(wrap) {
460
469
  const btn = wrap.querySelector(".input-clear");
461
- const field = wrap.querySelector("input.input");
470
+ const field = wrap.querySelector("input.input, textarea.textarea");
462
471
  if (!btn || !field) return;
463
472
  btn.addEventListener("click", () => {
464
473
  field.value = "";
@@ -500,6 +509,140 @@ function setupAutosize(ta) {
500
509
  ta.addEventListener("input", () => resizeTextarea(ta));
501
510
  resizeTextarea(ta);
502
511
  }
512
+ function setupCounter(field) {
513
+ const maxAttr = field.getAttribute("maxlength");
514
+ const dataCount = field.getAttribute("data-count");
515
+ if (!maxAttr && dataCount === null) return;
516
+ const max = maxAttr ? Number(maxAttr) : 0;
517
+ let counter = null;
518
+ const wrap = field.closest(".input-wrap, .textarea-wrap");
519
+ if (wrap) {
520
+ counter = wrap.querySelector(".input-counter");
521
+ if (!counter) {
522
+ counter = document.createElement("span");
523
+ counter.className = "input-counter";
524
+ wrap.appendChild(counter);
525
+ if (wrap.classList.contains("input-wrap")) {
526
+ wrap.classList.add("input-wrap--has-counter");
527
+ }
528
+ }
529
+ }
530
+ const counterEl = counter;
531
+ if (!counterEl) return;
532
+ function update() {
533
+ const len = field.value.length;
534
+ counterEl.textContent = max > 0 ? `${len} / ${max}` : `${len}`;
535
+ counterEl.classList.remove("is-warn", "is-error");
536
+ if (max > 0) {
537
+ const ratio = len / max;
538
+ if (ratio >= 1) counterEl.classList.add("is-error");
539
+ else if (ratio >= 0.8) counterEl.classList.add("is-warn");
540
+ }
541
+ }
542
+ field.addEventListener("input", () => {
543
+ if (!isComposing) update();
544
+ });
545
+ update();
546
+ }
547
+ function setupMask(field) {
548
+ const mask = field.getAttribute("data-mask");
549
+ if (!mask) return;
550
+ const maskChars = [];
551
+ for (const ch of mask) maskChars.push(ch);
552
+ function isInputSlot(ch) {
553
+ return ch === "#" || ch === "A" || ch === "*";
554
+ }
555
+ function validate(rawChar, slotType) {
556
+ if (slotType === "#") return /[0-9]/.test(rawChar);
557
+ if (slotType === "A") return /[a-zA-Z]/.test(rawChar);
558
+ return true;
559
+ }
560
+ function applyMask(input) {
561
+ let result = "";
562
+ let rawIdx = 0;
563
+ const rawChars = input.split("");
564
+ for (let i = 0; i < maskChars.length && rawIdx < rawChars.length; i++) {
565
+ const slot = maskChars[i];
566
+ if (isInputSlot(slot)) {
567
+ while (rawIdx < rawChars.length && !validate(rawChars[rawIdx], slot)) {
568
+ rawIdx++;
569
+ }
570
+ if (rawIdx < rawChars.length) {
571
+ result += rawChars[rawIdx];
572
+ rawIdx++;
573
+ }
574
+ } else {
575
+ result += slot;
576
+ }
577
+ }
578
+ return result;
579
+ }
580
+ function extractRaw(formatted) {
581
+ let raw = "";
582
+ for (let i = 0; i < formatted.length && i < maskChars.length; i++) {
583
+ if (isInputSlot(maskChars[i])) raw += formatted[i];
584
+ }
585
+ return raw;
586
+ }
587
+ let lastFormatted = applyMask(field.value);
588
+ field.value = lastFormatted;
589
+ field.setAttribute("data-raw-value", extractRaw(lastFormatted));
590
+ field.addEventListener("input", () => {
591
+ if (isComposing) return;
592
+ const cursorPos = field.selectionStart ?? 0;
593
+ const oldRaw = extractRaw(lastFormatted);
594
+ const newFull = field.value;
595
+ const newRaw = extractRaw(newFull);
596
+ const formatted = applyMask(newRaw);
597
+ field.value = formatted;
598
+ field.setAttribute("data-raw-value", extractRaw(formatted));
599
+ lastFormatted = formatted;
600
+ const rawDelta = newRaw.length - oldRaw.length;
601
+ let targetCursor = cursorPos;
602
+ if (rawDelta > 0) {
603
+ let count = 0;
604
+ for (let i = 0; i < formatted.length; i++) {
605
+ if (isInputSlot(maskChars[i])) {
606
+ count++;
607
+ if (count >= newRaw.length) {
608
+ targetCursor = i + 1;
609
+ break;
610
+ }
611
+ }
612
+ }
613
+ } else if (rawDelta < 0) {
614
+ targetCursor = Math.min(cursorPos, formatted.length);
615
+ }
616
+ field.setSelectionRange(targetCursor, targetCursor);
617
+ });
618
+ field.addEventListener("keydown", (ev) => {
619
+ if (isComposing) return;
620
+ if (ev.ctrlKey || ev.metaKey || ev.altKey) return;
621
+ if (["Backspace", "Delete", "Tab", "Escape", "ArrowLeft", "ArrowRight", "Home", "End", "Enter"].includes(ev.key)) return;
622
+ const currentRaw = extractRaw(field.value);
623
+ const nextSlotIdx = maskChars.findIndex((ch, i) => {
624
+ if (!isInputSlot(ch)) return false;
625
+ return i >= field.value.length || !isInputSlot(maskChars[Math.min(field.value.length, maskChars.length - 1)]);
626
+ });
627
+ const nextSlot = maskChars.find((ch, i) => {
628
+ const filledSlots = maskChars.slice(0, i).filter(isInputSlot).length;
629
+ return isInputSlot(ch) && filledSlots >= currentRaw.length;
630
+ });
631
+ if (nextSlot && !validate(ev.key, nextSlot)) {
632
+ ev.preventDefault();
633
+ }
634
+ });
635
+ }
636
+ function setupTrim(field) {
637
+ if (!field.hasAttribute("data-trim")) return;
638
+ field.addEventListener("blur", () => {
639
+ const trimmed = field.value.trim();
640
+ if (trimmed !== field.value) {
641
+ field.value = trimmed;
642
+ field.dispatchEvent(new Event("input", { bubbles: true }));
643
+ }
644
+ });
645
+ }
503
646
  function setupOtp(group) {
504
647
  const el = group;
505
648
  if (el.__icenInputInit) return;
@@ -514,6 +657,7 @@ function setupOtp(group) {
514
657
  }
515
658
  syncFilled(cell);
516
659
  cell.addEventListener("input", () => {
660
+ if (isComposing) return;
517
661
  const ch = cell.value.slice(-1);
518
662
  cell.value = ch;
519
663
  syncFilled(cell);
@@ -568,6 +712,31 @@ function initInput(root = document) {
568
712
  if (root instanceof HTMLTextAreaElement && root.matches("[data-autosize]")) textareas.push(root);
569
713
  textareas.push(...Array.from(root.querySelectorAll("textarea[data-autosize]")));
570
714
  for (const ta of textareas) setupAutosize(ta);
715
+ const countedFields = [];
716
+ if (root instanceof Element) {
717
+ const fields = Array.from(root.querySelectorAll(
718
+ "input[maxlength], input[data-count], textarea[maxlength], textarea[data-count]"
719
+ ));
720
+ countedFields.push(...fields);
721
+ if (root instanceof HTMLInputElement || root instanceof HTMLTextAreaElement) {
722
+ if (root.hasAttribute("maxlength") || root.hasAttribute("data-count")) countedFields.push(root);
723
+ }
724
+ }
725
+ for (const f of countedFields) setupCounter(f);
726
+ const maskedInputs = [];
727
+ if (root instanceof Element) {
728
+ maskedInputs.push(...Array.from(root.querySelectorAll("input[data-mask]")));
729
+ if (root instanceof HTMLInputElement && root.hasAttribute("data-mask")) maskedInputs.push(root);
730
+ }
731
+ for (const inp of maskedInputs) setupMask(inp);
732
+ const trimFields = [];
733
+ if (root instanceof Element) {
734
+ trimFields.push(...Array.from(root.querySelectorAll("[data-trim]")));
735
+ if ((root instanceof HTMLInputElement || root instanceof HTMLTextAreaElement) && root.hasAttribute("data-trim")) {
736
+ trimFields.push(root);
737
+ }
738
+ }
739
+ for (const f of trimFields) setupTrim(f);
571
740
  const otps = [];
572
741
  if (root instanceof Element && root.matches(".otp")) otps.push(root);
573
742
  otps.push(...Array.from(root.querySelectorAll(".otp")));
@@ -580,27 +749,44 @@ function setup2(container) {
580
749
  if (el.__icenSelectInit) return;
581
750
  el.__icenSelectInit = true;
582
751
  const triggerEl = container.querySelector(".select-trigger");
583
- const panelEl = container.querySelector(".select-panel");
752
+ const panelEl2 = container.querySelector(".select-panel");
584
753
  const valueEl = container.querySelector(".select-value");
585
754
  const hiddenInput = container.querySelector("input[data-select-value]");
586
- if (!triggerEl || !panelEl) return;
755
+ if (!triggerEl || !panelEl2) return;
587
756
  const trigger = triggerEl;
588
- const panel2 = panelEl;
589
- const options = () => Array.from(panel2.querySelectorAll(".select-option"));
590
- const isOpen = () => !panel2.hidden;
757
+ const panel2 = panelEl2;
758
+ const isMultiple = container.hasAttribute("data-select-multiple");
759
+ const isSearchable = container.hasAttribute("data-select-search");
760
+ const maxSelect = Number(container.getAttribute("data-select-max")) || 0;
761
+ const placeholder = container.getAttribute("data-select-placeholder") ?? (valueEl?.classList.contains("is-empty") ? valueEl?.textContent ?? "" : "");
762
+ let searchInput = null;
591
763
  let focusIdx = -1;
764
+ if (isSearchable && !panel2.querySelector(".select-search-input")) {
765
+ const searchWrap = document.createElement("div");
766
+ searchWrap.className = "select-search";
767
+ const input = document.createElement("input");
768
+ input.className = "select-search-input";
769
+ input.type = "search";
770
+ input.placeholder = "\u641C\u7D22\u2026";
771
+ searchWrap.appendChild(input);
772
+ panel2.insertBefore(searchWrap, panel2.firstChild);
773
+ }
774
+ searchInput = panel2.querySelector(".select-search-input");
775
+ const allOptions = () => Array.from(panel2.querySelectorAll(".select-option"));
776
+ const visibleOptions = () => allOptions().filter((o) => !o.hidden);
777
+ const isOpen = () => !panel2.hidden;
592
778
  function setFocus(idx) {
593
- const opts = options();
779
+ const opts = visibleOptions();
594
780
  focusIdx = Math.max(-1, Math.min(idx, opts.length - 1));
595
781
  opts.forEach((o, i) => o.classList.toggle("is-focused", i === focusIdx));
596
782
  const cur = focusIdx >= 0 ? opts[focusIdx] : void 0;
597
783
  cur?.scrollIntoView({ block: "nearest" });
598
784
  }
599
785
  function firstEnabled() {
600
- return options().findIndex((o) => !o.classList.contains("is-disabled"));
786
+ return visibleOptions().findIndex((o) => !o.classList.contains("is-disabled"));
601
787
  }
602
788
  function moveFocus(delta) {
603
- const opts = options();
789
+ const opts = visibleOptions();
604
790
  if (opts.length === 0) return;
605
791
  let n = focusIdx;
606
792
  for (let guard = 0; guard < opts.length; guard++) {
@@ -610,38 +796,135 @@ function setup2(container) {
610
796
  }
611
797
  setFocus(n);
612
798
  }
799
+ let emptyEl = panel2.querySelector(".select-empty");
800
+ if (!emptyEl) {
801
+ emptyEl = document.createElement("div");
802
+ emptyEl.className = "select-empty";
803
+ emptyEl.textContent = "\u65E0\u5339\u914D\u9879";
804
+ emptyEl.hidden = true;
805
+ panel2.appendChild(emptyEl);
806
+ }
807
+ function updateEmptyState() {
808
+ const hasVisible = visibleOptions().length > 0;
809
+ if (emptyEl) emptyEl.hidden = hasVisible;
810
+ }
811
+ function filterOptions(query) {
812
+ const q = query.trim().toLowerCase();
813
+ const groups = Array.from(panel2.querySelectorAll(".select-group"));
814
+ for (const opt of allOptions()) {
815
+ const text = (opt.textContent ?? "").toLowerCase();
816
+ const kw = (opt.getAttribute("data-keywords") ?? "").toLowerCase();
817
+ opt.hidden = q !== "" && !text.includes(q) && !kw.includes(q);
818
+ }
819
+ for (const g of groups) {
820
+ const hasVisible = Array.from(g.querySelectorAll(".select-option")).some((o) => !o.hidden);
821
+ g.hidden = !hasVisible;
822
+ }
823
+ updateEmptyState();
824
+ setFocus(firstEnabled());
825
+ }
826
+ if (searchInput) {
827
+ searchInput.addEventListener("input", () => filterOptions(searchInput.value));
828
+ searchInput.addEventListener("keydown", (ev) => {
829
+ if (ev.key === "Escape") {
830
+ ev.stopPropagation();
831
+ searchInput.value = "";
832
+ filterOptions("");
833
+ searchInput.focus();
834
+ }
835
+ });
836
+ }
613
837
  function openPanel() {
614
838
  if (isOpen()) return;
615
839
  panel2.hidden = false;
616
840
  container.classList.add("is-open");
617
841
  trigger.setAttribute("aria-expanded", "true");
618
- const sel = options().findIndex((o) => o.classList.contains("is-selected"));
619
- setFocus(sel >= 0 ? sel : firstEnabled());
842
+ if (searchInput) {
843
+ searchInput.value = "";
844
+ filterOptions("");
845
+ setTimeout(() => searchInput?.focus(), 0);
846
+ } else {
847
+ const sel = allOptions().findIndex((o) => o.classList.contains("is-selected"));
848
+ setFocus(sel >= 0 ? sel : firstEnabled());
849
+ }
620
850
  }
621
- function closePanel() {
851
+ function closePanel2() {
622
852
  if (!isOpen()) return;
623
853
  panel2.hidden = true;
624
854
  container.classList.remove("is-open");
625
855
  trigger.setAttribute("aria-expanded", "false");
626
856
  setFocus(-1);
627
857
  }
628
- function choose(opt) {
629
- if (opt.classList.contains("is-disabled")) return;
630
- options().forEach((o) => o.classList.toggle("is-selected", o === opt));
631
- if (valueEl) {
632
- valueEl.textContent = opt.textContent ?? "";
858
+ function getSelectedValues() {
859
+ return allOptions().filter((o) => o.classList.contains("is-selected")).map((o) => o.getAttribute("data-value") ?? "");
860
+ }
861
+ function updateMultiTrigger() {
862
+ if (!valueEl) return;
863
+ const selected = allOptions().filter((o) => o.classList.contains("is-selected"));
864
+ if (selected.length === 0) {
865
+ valueEl.textContent = placeholder;
866
+ valueEl.classList.add("is-empty");
867
+ container.removeAttribute("data-value");
868
+ } else {
633
869
  valueEl.classList.remove("is-empty");
870
+ const maxLabel = Number(container.getAttribute("data-select-max-label")) || 2;
871
+ if (selected.length <= maxLabel) {
872
+ valueEl.textContent = selected.map((o) => o.textContent ?? "").join("\u3001");
873
+ } else {
874
+ valueEl.textContent = `\u5DF2\u9009 ${selected.length} \u9879`;
875
+ }
634
876
  }
635
- const v = opt.getAttribute("data-value") ?? "";
636
- container.setAttribute("data-value", v);
637
- if (hiddenInput) {
638
- hiddenInput.value = v;
639
- hiddenInput.dispatchEvent(new Event("change", { bubbles: true }));
877
+ if (maxSelect > 0) {
878
+ const atMax = selected.length >= maxSelect;
879
+ allOptions().forEach((o) => {
880
+ if (!o.classList.contains("is-selected")) {
881
+ o.classList.toggle("is-disabled", atMax);
882
+ }
883
+ });
640
884
  }
641
- closePanel();
642
- trigger.focus();
643
885
  }
644
- trigger.addEventListener("click", () => isOpen() ? closePanel() : openPanel());
886
+ function syncHiddenInput() {
887
+ if (!hiddenInput) return;
888
+ const values = getSelectedValues();
889
+ hiddenInput.value = isMultiple ? values.join(",") : values[0] ?? "";
890
+ hiddenInput.dispatchEvent(new Event("change", { bubbles: true }));
891
+ }
892
+ function choose(opt) {
893
+ if (opt.classList.contains("is-disabled")) return;
894
+ if (isMultiple) {
895
+ opt.classList.toggle("is-selected");
896
+ container.setAttribute(
897
+ "data-value",
898
+ getSelectedValues().join(",")
899
+ );
900
+ updateMultiTrigger();
901
+ syncHiddenInput();
902
+ if (searchInput) searchInput.focus();
903
+ else opt.focus();
904
+ } else {
905
+ allOptions().forEach((o) => o.classList.toggle("is-selected", o === opt));
906
+ if (valueEl) {
907
+ valueEl.textContent = opt.textContent ?? "";
908
+ valueEl.classList.remove("is-empty");
909
+ }
910
+ const v = opt.getAttribute("data-value") ?? "";
911
+ container.setAttribute("data-value", v);
912
+ syncHiddenInput();
913
+ closePanel2();
914
+ trigger.focus();
915
+ }
916
+ }
917
+ const clearBtn = container.querySelector(".select-clear");
918
+ if (clearBtn && isMultiple) {
919
+ clearBtn.addEventListener("click", (ev) => {
920
+ ev.stopPropagation();
921
+ allOptions().forEach((o) => o.classList.remove("is-selected"));
922
+ updateMultiTrigger();
923
+ syncHiddenInput();
924
+ });
925
+ clearBtn.addEventListener("pointerdown", (ev) => ev.stopPropagation());
926
+ }
927
+ trigger.addEventListener("click", () => isOpen() ? closePanel2() : openPanel());
645
928
  trigger.addEventListener("keydown", (ev) => {
646
929
  if (!isOpen()) {
647
930
  if (ev.key === "ArrowDown" || ev.key === "ArrowUp" || ev.key === "Enter" || ev.key === " ") {
@@ -650,6 +933,20 @@ function setup2(container) {
650
933
  }
651
934
  return;
652
935
  }
936
+ if (searchInput && document.activeElement === searchInput) {
937
+ if (ev.key === "ArrowDown") {
938
+ ev.preventDefault();
939
+ moveFocus(1);
940
+ } else if (ev.key === "ArrowUp") {
941
+ ev.preventDefault();
942
+ moveFocus(-1);
943
+ } else if (ev.key === "Enter") {
944
+ ev.preventDefault();
945
+ const opt = visibleOptions()[focusIdx];
946
+ if (opt) choose(opt);
947
+ }
948
+ return;
949
+ }
653
950
  switch (ev.key) {
654
951
  case "ArrowDown":
655
952
  ev.preventDefault();
@@ -662,13 +959,13 @@ function setup2(container) {
662
959
  case "Enter":
663
960
  case " ": {
664
961
  ev.preventDefault();
665
- const opt = options()[focusIdx];
962
+ const opt = visibleOptions()[focusIdx];
666
963
  if (opt) choose(opt);
667
964
  break;
668
965
  }
669
966
  case "Escape":
670
967
  ev.preventDefault();
671
- closePanel();
968
+ closePanel2();
672
969
  break;
673
970
  }
674
971
  });
@@ -682,19 +979,20 @@ function setup2(container) {
682
979
  const t = ev.target;
683
980
  if (!(t instanceof Element)) return;
684
981
  const opt = t.closest(".select-option");
685
- if (!opt) return;
686
- const idx = options().indexOf(opt);
982
+ if (!opt || opt.hidden) return;
983
+ const idx = visibleOptions().indexOf(opt);
687
984
  if (idx >= 0 && !opt.classList.contains("is-disabled")) setFocus(idx);
688
985
  });
689
986
  document.addEventListener("pointerdown", (ev) => {
690
- if (isOpen() && ev.target instanceof Node && !container.contains(ev.target)) closePanel();
987
+ if (isOpen() && ev.target instanceof Node && !container.contains(ev.target)) closePanel2();
691
988
  });
692
989
  document.addEventListener("keydown", (ev) => {
693
990
  if (ev.key === "Escape" && isOpen()) {
694
- closePanel();
991
+ closePanel2();
695
992
  trigger.focus();
696
993
  }
697
994
  });
995
+ if (isMultiple) updateMultiTrigger();
698
996
  }
699
997
  function initSelect(root = document) {
700
998
  if (typeof document === "undefined") return;
@@ -890,47 +1188,198 @@ function initTagInput(root = document) {
890
1188
  }
891
1189
 
892
1190
  // src/behaviors/upload.ts
1191
+ function formatSize(bytes) {
1192
+ if (bytes < 1024) return `${bytes} B`;
1193
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1194
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1195
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
1196
+ }
1197
+ function isAccepted(file, accept) {
1198
+ if (!accept.trim()) return true;
1199
+ const types = accept.split(",").map((t) => t.trim().toLowerCase());
1200
+ const fileName = file.name.toLowerCase();
1201
+ const mime = file.type.toLowerCase();
1202
+ return types.some((t) => {
1203
+ if (t.startsWith(".")) return fileName.endsWith(t);
1204
+ if (t.endsWith("/*")) return mime.startsWith(t.slice(0, -1));
1205
+ return mime === t;
1206
+ });
1207
+ }
1208
+ var fileIconSvg = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>';
1209
+ var xIconSvg = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6 6 18M6 6l12 12"/></svg>';
893
1210
  function setup5(zone) {
894
1211
  const el = zone;
895
1212
  if (el.__icenUploadInit) return;
896
1213
  el.__icenUploadInit = true;
897
1214
  const input = zone.querySelector('input[type="file"]');
898
1215
  if (!input) return;
899
- const openPicker = () => input.click();
1216
+ const hasList = zone.hasAttribute("data-upload-list");
1217
+ const maxSize = Number(zone.getAttribute("data-max-size")) || 0;
1218
+ const maxFiles = Number(zone.getAttribute("data-max-files")) || 0;
1219
+ const accept = input.getAttribute("accept") ?? "";
1220
+ let currentFiles = [];
1221
+ const urlMap = /* @__PURE__ */ new Map();
1222
+ function getListEl() {
1223
+ if (!hasList) return null;
1224
+ let list = zone.querySelector(".upload-list");
1225
+ if (!list) {
1226
+ list = document.createElement("ul");
1227
+ list.className = "upload-list";
1228
+ zone.appendChild(list);
1229
+ }
1230
+ return list;
1231
+ }
1232
+ function renderList() {
1233
+ const list = getListEl();
1234
+ if (!list) return;
1235
+ urlMap.forEach((url) => URL.revokeObjectURL(url));
1236
+ urlMap.clear();
1237
+ list.textContent = "";
1238
+ if (currentFiles.length === 0) {
1239
+ list.hidden = true;
1240
+ return;
1241
+ }
1242
+ list.hidden = false;
1243
+ for (const file of currentFiles) {
1244
+ const li = document.createElement("li");
1245
+ li.className = "upload-list-item";
1246
+ const isImage = file.type.startsWith("image/");
1247
+ if (isImage) {
1248
+ try {
1249
+ const url = URL.createObjectURL(file);
1250
+ urlMap.set(file, url);
1251
+ const img = document.createElement("img");
1252
+ img.className = "upload-list-item-thumb";
1253
+ img.src = url;
1254
+ img.alt = file.name;
1255
+ li.appendChild(img);
1256
+ } catch {
1257
+ const icon = document.createElement("span");
1258
+ icon.className = "upload-list-item-icon";
1259
+ icon.innerHTML = fileIconSvg;
1260
+ li.appendChild(icon);
1261
+ }
1262
+ } else {
1263
+ const icon = document.createElement("span");
1264
+ icon.className = "upload-list-item-icon";
1265
+ icon.innerHTML = fileIconSvg;
1266
+ li.appendChild(icon);
1267
+ }
1268
+ const info = document.createElement("div");
1269
+ info.className = "upload-list-item-info";
1270
+ const name = document.createElement("span");
1271
+ name.className = "upload-list-item-name";
1272
+ name.textContent = file.name;
1273
+ const size = document.createElement("span");
1274
+ size.className = "upload-list-item-size";
1275
+ size.textContent = formatSize(file.size);
1276
+ info.appendChild(name);
1277
+ info.appendChild(size);
1278
+ li.appendChild(info);
1279
+ const removeBtn = document.createElement("button");
1280
+ removeBtn.className = "upload-list-item-x";
1281
+ removeBtn.type = "button";
1282
+ removeBtn.setAttribute("aria-label", `\u79FB\u9664 ${file.name}`);
1283
+ removeBtn.innerHTML = xIconSvg;
1284
+ removeBtn.addEventListener("click", (ev) => {
1285
+ ev.stopPropagation();
1286
+ currentFiles = currentFiles.filter((f) => f !== file);
1287
+ zone.dispatchEvent(new CustomEvent("icen:upload-remove", { bubbles: true, detail: { file } }));
1288
+ renderList();
1289
+ syncInputFiles();
1290
+ });
1291
+ li.appendChild(removeBtn);
1292
+ list.appendChild(li);
1293
+ }
1294
+ }
1295
+ function syncInputFiles() {
1296
+ try {
1297
+ const dt = new DataTransfer();
1298
+ for (const f of currentFiles) dt.items.add(f);
1299
+ input.files = dt.files;
1300
+ input.dispatchEvent(new Event("change", { bubbles: true }));
1301
+ } catch {
1302
+ }
1303
+ }
1304
+ function validate(file) {
1305
+ if (accept && !isAccepted(file, accept)) return "\u6587\u4EF6\u7C7B\u578B\u4E0D\u652F\u6301";
1306
+ if (maxSize > 0 && file.size > maxSize) return `\u6587\u4EF6\u8D85\u8FC7 ${formatSize(maxSize)}`;
1307
+ return null;
1308
+ }
1309
+ function handleFiles(files) {
1310
+ const valid = [];
1311
+ for (const file of files) {
1312
+ const reason = validate(file);
1313
+ if (reason) {
1314
+ zone.dispatchEvent(new CustomEvent("icen:upload-error", { bubbles: true, detail: { file, reason } }));
1315
+ continue;
1316
+ }
1317
+ valid.push(file);
1318
+ }
1319
+ if (valid.length === 0) return;
1320
+ if (hasList) {
1321
+ if (maxFiles > 0) {
1322
+ const remaining = maxFiles - currentFiles.length;
1323
+ if (remaining <= 0) {
1324
+ zone.dispatchEvent(new CustomEvent("icen:upload-error", {
1325
+ bubbles: true,
1326
+ detail: { reason: `\u6700\u591A ${maxFiles} \u4E2A\u6587\u4EF6` }
1327
+ }));
1328
+ return;
1329
+ }
1330
+ valid.splice(remaining);
1331
+ }
1332
+ currentFiles.push(...valid);
1333
+ renderList();
1334
+ syncInputFiles();
1335
+ }
1336
+ zone.dispatchEvent(new CustomEvent("icen:upload", { bubbles: true, detail: { files: valid } }));
1337
+ }
1338
+ const openPicker2 = () => input.click();
900
1339
  zone.addEventListener("click", (ev) => {
901
1340
  if (ev.target === input) return;
902
- openPicker();
1341
+ const tgt = ev.target;
1342
+ if (tgt instanceof Element && tgt.closest(".upload-list")) return;
1343
+ openPicker2();
903
1344
  });
904
1345
  zone.addEventListener("keydown", (ev) => {
905
1346
  if (ev.key === "Enter" || ev.key === " ") {
906
1347
  ev.preventDefault();
907
- openPicker();
1348
+ openPicker2();
1349
+ }
1350
+ });
1351
+ input.addEventListener("change", () => {
1352
+ if (!input.files) return;
1353
+ const files = Array.from(input.files);
1354
+ if (hasList) {
1355
+ handleFiles(files);
1356
+ input.value = "";
1357
+ } else {
1358
+ handleFiles(files);
908
1359
  }
909
1360
  });
910
1361
  zone.addEventListener("dragover", (ev) => {
911
1362
  ev.preventDefault();
912
1363
  zone.classList.add("is-drag");
913
1364
  });
914
- zone.addEventListener("dragleave", () => zone.classList.remove("is-drag"));
1365
+ zone.addEventListener("dragleave", (ev) => {
1366
+ if (ev.target === zone) zone.classList.remove("is-drag");
1367
+ });
915
1368
  zone.addEventListener("drop", (ev) => {
916
1369
  ev.preventDefault();
917
1370
  zone.classList.remove("is-drag");
918
1371
  const files = ev.dataTransfer?.files;
919
1372
  if (!files || files.length === 0) return;
920
- let assigned = false;
921
- try {
922
- const dt = new DataTransfer();
923
- for (const f of Array.from(files)) dt.items.add(f);
924
- input.files = dt.files;
925
- assigned = true;
926
- } catch {
927
- assigned = false;
928
- }
929
- if (assigned) {
930
- input.dispatchEvent(new Event("change", { bubbles: true }));
1373
+ handleFiles(Array.from(files));
1374
+ if (!hasList) {
1375
+ try {
1376
+ const dt = new DataTransfer();
1377
+ for (const f of Array.from(files)) dt.items.add(f);
1378
+ input.files = dt.files;
1379
+ input.dispatchEvent(new Event("change", { bubbles: true }));
1380
+ } catch {
1381
+ }
931
1382
  }
932
- const list = Array.from(files);
933
- zone.dispatchEvent(new CustomEvent("icen:upload", { bubbles: true, detail: { files: list } }));
934
1383
  });
935
1384
  }
936
1385
  function initUpload(root = document) {
@@ -1997,19 +2446,19 @@ function renderLine(el, opts) {
1997
2446
  const values = opts.values.slice(0, n);
1998
2447
  const width = 360;
1999
2448
  const height = 150;
2000
- const pad = 14;
2449
+ const pad2 = 14;
2001
2450
  const max = Math.max(...values, 1);
2002
2451
  const tone = toneVar(opts.tone);
2003
2452
  const points = values.map((value, i) => ({
2004
- x: values.length <= 1 ? width / 2 : pad + i / (values.length - 1) * (width - pad * 2),
2005
- y: height - pad - value / max * (height - pad * 2),
2453
+ x: values.length <= 1 ? width / 2 : pad2 + i / (values.length - 1) * (width - pad2 * 2),
2454
+ y: height - pad2 - value / max * (height - pad2 * 2),
2006
2455
  value,
2007
2456
  label: labels[i] ?? ""
2008
2457
  }));
2009
2458
  const line = points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(" ");
2010
2459
  const last = points[points.length - 1];
2011
2460
  const first = points[0];
2012
- const area = `${line} L${last.x.toFixed(1)},${height - pad} L${first.x.toFixed(1)},${height - pad} Z`;
2461
+ const area = `${line} L${last.x.toFixed(1)},${height - pad2} L${first.x.toFixed(1)},${height - pad2} Z`;
2013
2462
  const gradientId = `icen-chart-line-${++lineGradientSeq}`;
2014
2463
  const wrap = document.createElement("div");
2015
2464
  wrap.className = "chart-line";
@@ -2021,10 +2470,10 @@ function renderLine(el, opts) {
2021
2470
  defs.appendChild(gradient);
2022
2471
  svg.appendChild(defs);
2023
2472
  for (let i = 0; i < 4; i++) {
2024
- const y = pad + i * (height - pad * 2) / 3;
2473
+ const y = pad2 + i * (height - pad2 * 2) / 3;
2025
2474
  svg.appendChild(svgEl("line", {
2026
- x1: String(pad),
2027
- x2: String(width - pad),
2475
+ x1: String(pad2),
2476
+ x2: String(width - pad2),
2028
2477
  y1: String(y),
2029
2478
  y2: String(y),
2030
2479
  stroke: "var(--token-line-soft)",
@@ -2057,6 +2506,192 @@ function renderLine(el, opts) {
2057
2506
  wrap.append(svg, labelsRow(labels.slice(-7)));
2058
2507
  el.appendChild(wrap);
2059
2508
  }
2509
+ function renderArea(el, opts) {
2510
+ if (typeof document === "undefined") return;
2511
+ el.textContent = "";
2512
+ const n = Math.min(opts.labels.length, opts.values.length);
2513
+ if (n === 0) {
2514
+ renderEmpty(el, opts.emptyLabel);
2515
+ return;
2516
+ }
2517
+ const labels = opts.labels.slice(0, n);
2518
+ const values = opts.values.slice(0, n);
2519
+ const width = 360;
2520
+ const height = 150;
2521
+ const pad2 = 14;
2522
+ const max = Math.max(...values, 1);
2523
+ const tone = toneVar(opts.tone);
2524
+ const points = values.map((value, i) => ({
2525
+ x: values.length <= 1 ? width / 2 : pad2 + i / (values.length - 1) * (width - pad2 * 2),
2526
+ y: height - pad2 - value / max * (height - pad2 * 2),
2527
+ value,
2528
+ label: labels[i] ?? ""
2529
+ }));
2530
+ const line = points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(" ");
2531
+ const last = points[points.length - 1];
2532
+ const first = points[0];
2533
+ const area = `${line} L${last.x.toFixed(1)},${height - pad2} L${first.x.toFixed(1)},${height - pad2} Z`;
2534
+ const gradientId = `icen-chart-area-${++lineGradientSeq}`;
2535
+ const wrap = document.createElement("div");
2536
+ wrap.className = "chart-area";
2537
+ const svg = svgEl("svg", { viewBox: `0 0 ${width} ${height}`, role: "img" });
2538
+ const defs = svgEl("defs", {});
2539
+ const gradient = svgEl("linearGradient", { id: gradientId, x1: "0", x2: "0", y1: "0", y2: "1" });
2540
+ gradient.appendChild(svgEl("stop", { offset: "0%", "stop-color": tone, "stop-opacity": "0.5" }));
2541
+ gradient.appendChild(svgEl("stop", { offset: "100%", "stop-color": tone, "stop-opacity": "0.05" }));
2542
+ defs.appendChild(gradient);
2543
+ svg.appendChild(defs);
2544
+ for (let i = 0; i < 4; i++) {
2545
+ const y = pad2 + i * (height - pad2 * 2) / 3;
2546
+ svg.appendChild(svgEl("line", {
2547
+ x1: String(pad2),
2548
+ x2: String(width - pad2),
2549
+ y1: String(y),
2550
+ y2: String(y),
2551
+ stroke: "var(--token-line-soft)",
2552
+ "stroke-width": "1"
2553
+ }));
2554
+ }
2555
+ svg.appendChild(svgEl("path", { d: area, fill: `url(#${gradientId})` }));
2556
+ svg.appendChild(svgEl("path", {
2557
+ d: line,
2558
+ fill: "none",
2559
+ stroke: tone,
2560
+ "stroke-width": "1.5",
2561
+ "stroke-linecap": "round",
2562
+ "stroke-linejoin": "round"
2563
+ }));
2564
+ for (const p of points) {
2565
+ const title = svgEl("title", {});
2566
+ title.textContent = `${p.label}: ${fmt(opts, p.value)}`;
2567
+ const inv = svgEl("circle", {
2568
+ cx: p.x.toFixed(1),
2569
+ cy: p.y.toFixed(1),
2570
+ r: "2",
2571
+ fill: "transparent"
2572
+ });
2573
+ inv.appendChild(title);
2574
+ svg.appendChild(inv);
2575
+ }
2576
+ wrap.append(svg, labelsRow(labels.slice(-7)));
2577
+ el.appendChild(wrap);
2578
+ }
2579
+ function renderRadar(el, opts) {
2580
+ if (typeof document === "undefined") return;
2581
+ el.textContent = "";
2582
+ const axes = opts.axes;
2583
+ const n = axes.length;
2584
+ if (n < 3 || opts.series.length === 0) {
2585
+ renderEmpty(el, opts.emptyLabel);
2586
+ return;
2587
+ }
2588
+ let maxVal = opts.max ?? 0;
2589
+ if (opts.max == null) {
2590
+ for (const s of opts.series) {
2591
+ for (let i = 0; i < n; i++) maxVal = Math.max(maxVal, s.values[i] ?? 0);
2592
+ }
2593
+ maxVal = niceCeil(maxVal);
2594
+ }
2595
+ if (maxVal <= 0) maxVal = 1;
2596
+ const size = 200;
2597
+ const cx = size / 2;
2598
+ const cy = size / 2;
2599
+ const radius = 78;
2600
+ const levels = Math.max(2, opts.levels ?? 4);
2601
+ const angleOf = (i) => -Math.PI / 2 + i * 2 * Math.PI / n;
2602
+ const pointAt = (i, r) => ({
2603
+ x: cx + r * Math.cos(angleOf(i)),
2604
+ y: cy + r * Math.sin(angleOf(i))
2605
+ });
2606
+ const wrap = document.createElement("div");
2607
+ wrap.className = "chart-radar";
2608
+ const svg = svgEl("svg", { viewBox: `0 0 ${size} ${size}`, role: "img" });
2609
+ for (let lv = 1; lv <= levels; lv++) {
2610
+ const r = radius * lv / levels;
2611
+ const pts = Array.from({ length: n }, (_, i) => {
2612
+ const p = pointAt(i, r);
2613
+ return `${p.x.toFixed(1)},${p.y.toFixed(1)}`;
2614
+ }).join(" ");
2615
+ svg.appendChild(svgEl("polygon", {
2616
+ points: pts,
2617
+ fill: "none",
2618
+ stroke: "var(--token-line-soft)",
2619
+ "stroke-width": "1",
2620
+ "stroke-dasharray": lv === levels ? "0" : "3 2"
2621
+ }));
2622
+ }
2623
+ for (let i = 0; i < n; i++) {
2624
+ const edge = pointAt(i, radius);
2625
+ svg.appendChild(svgEl("line", {
2626
+ x1: String(cx),
2627
+ y1: String(cy),
2628
+ x2: edge.x.toFixed(1),
2629
+ y2: edge.y.toFixed(1),
2630
+ stroke: "var(--token-line-soft)",
2631
+ "stroke-width": "1"
2632
+ }));
2633
+ const labelP = pointAt(i, radius + 14);
2634
+ const txt = svgEl("text", {
2635
+ x: labelP.x.toFixed(1),
2636
+ y: labelP.y.toFixed(1),
2637
+ "text-anchor": "middle",
2638
+ "dominant-baseline": "middle",
2639
+ "font-size": "9",
2640
+ fill: "var(--token-text-muted)"
2641
+ });
2642
+ txt.textContent = axes[i] ?? "";
2643
+ svg.appendChild(txt);
2644
+ }
2645
+ const legend = document.createElement("div");
2646
+ legend.className = "chart-legend";
2647
+ for (const s of opts.series) {
2648
+ const tone = toneVar(s.tone);
2649
+ const pts = Array.from({ length: n }, (_, i) => {
2650
+ const v = Math.max(0, Math.min(maxVal, s.values[i] ?? 0));
2651
+ const p = pointAt(i, v / maxVal * radius);
2652
+ return `${p.x.toFixed(1)},${p.y.toFixed(1)}`;
2653
+ }).join(" ");
2654
+ const polyFill = svgEl("polygon", {
2655
+ points: pts,
2656
+ fill: tone,
2657
+ "fill-opacity": "0.16",
2658
+ stroke: tone,
2659
+ "stroke-width": "2",
2660
+ "stroke-linejoin": "round"
2661
+ });
2662
+ const title = svgEl("title", {});
2663
+ title.textContent = `${s.name}: ${s.values.map((v) => fmt(opts, v)).join(" / ")}`;
2664
+ polyFill.appendChild(title);
2665
+ svg.appendChild(polyFill);
2666
+ for (let i = 0; i < n; i++) {
2667
+ const v = Math.max(0, Math.min(maxVal, s.values[i] ?? 0));
2668
+ const p = pointAt(i, v / maxVal * radius);
2669
+ svg.appendChild(svgEl("circle", {
2670
+ cx: p.x.toFixed(1),
2671
+ cy: p.y.toFixed(1),
2672
+ r: "2.5",
2673
+ fill: "var(--token-card)",
2674
+ stroke: tone,
2675
+ "stroke-width": "1.5"
2676
+ }));
2677
+ }
2678
+ legend.appendChild(legendItem(s.name, "", s.tone));
2679
+ }
2680
+ wrap.append(svg, legend);
2681
+ el.appendChild(wrap);
2682
+ }
2683
+ function niceCeil(v) {
2684
+ if (v <= 0) return 1;
2685
+ const exp = Math.floor(Math.log10(v));
2686
+ const base = Math.pow(10, exp);
2687
+ const frac = v / base;
2688
+ let nice;
2689
+ if (frac <= 1) nice = 1;
2690
+ else if (frac <= 2) nice = 2;
2691
+ else if (frac <= 5) nice = 5;
2692
+ else nice = 10;
2693
+ return nice * base;
2694
+ }
2060
2695
  function isoDate(d) {
2061
2696
  const m = String(d.getMonth() + 1).padStart(2, "0");
2062
2697
  const day = String(d.getDate()).padStart(2, "0");
@@ -2090,8 +2725,8 @@ function renderHeatmap(el, opts) {
2090
2725
  grid.className = "chart-heatmap-grid";
2091
2726
  grid.setAttribute("role", "img");
2092
2727
  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++) {
2728
+ const pad2 = (first.getDay() + 6) % 7;
2729
+ for (let i = 0; i < pad2; i++) {
2095
2730
  const blank = document.createElement("span");
2096
2731
  blank.className = "chart-heatmap-cell is-blank";
2097
2732
  grid.appendChild(blank);
@@ -2130,18 +2765,18 @@ function renderSparkline(el, opts) {
2130
2765
  }
2131
2766
  const width = 96;
2132
2767
  const height = 28;
2133
- const pad = 3;
2768
+ const pad2 = 3;
2134
2769
  const max = Math.max(...values, 1);
2135
2770
  const tone = toneVar(opts.tone);
2136
2771
  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),
2772
+ x: values.length <= 1 ? width / 2 : pad2 + i / (values.length - 1) * (width - pad2 * 2),
2773
+ y: height - pad2 - value / max * (height - pad2 * 2),
2139
2774
  value
2140
2775
  }));
2141
2776
  const line = points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(" ");
2142
2777
  const last = points[points.length - 1];
2143
2778
  const first = points[0];
2144
- const area = `${line} L${last.x.toFixed(1)},${height - pad} L${first.x.toFixed(1)},${height - pad} Z`;
2779
+ const area = `${line} L${last.x.toFixed(1)},${height - pad2} L${first.x.toFixed(1)},${height - pad2} Z`;
2145
2780
  const gradientId = `icen-chart-line-${++lineGradientSeq}`;
2146
2781
  const wrap = document.createElement("div");
2147
2782
  wrap.className = "chart-sparkline";
@@ -2314,6 +2949,1337 @@ function initSidebar(root = document) {
2314
2949
  for (const s of sidebars) setup10(s);
2315
2950
  }
2316
2951
 
2952
+ // src/behaviors/notification.ts
2953
+ var KIND_ICON = {
2954
+ success: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><path d="m9 11 3 3L22 4"/></svg>',
2955
+ warning: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>',
2956
+ error: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m15 9-6 6"/><path d="m9 9 6 6"/></svg>',
2957
+ info: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>'
2958
+ };
2959
+ var CLOSE_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>';
2960
+ var PROGRESS_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" x2="12" y1="15" y2="3"/></svg>';
2961
+ var cfg = {
2962
+ position: "top-right",
2963
+ maxStack: 5,
2964
+ pauseOnHover: true,
2965
+ persist: false,
2966
+ sound: false
2967
+ };
2968
+ var store = /* @__PURE__ */ new Map();
2969
+ var globalInit = { done: false };
2970
+ function isBrowser3() {
2971
+ return typeof document !== "undefined" && typeof window !== "undefined";
2972
+ }
2973
+ function genId() {
2974
+ return "notif-" + Math.random().toString(36).slice(2, 10);
2975
+ }
2976
+ function ensureContainer2() {
2977
+ const sel = `.notification-container[data-notif-default]`;
2978
+ const existing = document.body.querySelector(sel);
2979
+ if (existing) {
2980
+ syncContainerPosition(existing);
2981
+ return existing;
2982
+ }
2983
+ const el = document.createElement("div");
2984
+ el.className = "notification-container";
2985
+ el.setAttribute("role", "region");
2986
+ el.setAttribute("aria-label", "\u901A\u77E5");
2987
+ el.dataset.notifDefault = "";
2988
+ syncContainerPosition(el);
2989
+ document.body.appendChild(el);
2990
+ return el;
2991
+ }
2992
+ function syncContainerPosition(el) {
2993
+ el.dataset.position = cfg.position;
2994
+ }
2995
+ var DEFAULT_PERSIST_KEY = "icen.ui.notifications";
2996
+ function persistKey() {
2997
+ if (cfg.persist === true) return DEFAULT_PERSIST_KEY;
2998
+ if (typeof cfg.persist === "string") return cfg.persist;
2999
+ return null;
3000
+ }
3001
+ function persistSave() {
3002
+ const key = persistKey();
3003
+ if (!key) return;
3004
+ try {
3005
+ const items = Array.from(store.values()).filter((r) => !r.opts.duration || r.opts.duration === 0).map((r) => ({
3006
+ id: r.id,
3007
+ title: r.title,
3008
+ kind: r.kind,
3009
+ description: r.opts.description ?? "",
3010
+ tag: r.opts.tag,
3011
+ unread: r.opts.unread,
3012
+ priority: r.opts.priority,
3013
+ progress: r.opts.progress,
3014
+ progressLabel: r.opts.progressLabel,
3015
+ link: r.opts.link,
3016
+ linkLabel: r.opts.linkLabel,
3017
+ data: r.opts.data,
3018
+ createdAt: r.createdAt
3019
+ }));
3020
+ localStorage.setItem(key, JSON.stringify(items));
3021
+ } catch {
3022
+ }
3023
+ }
3024
+ function persistLoad() {
3025
+ const key = persistKey();
3026
+ if (!key) return [];
3027
+ try {
3028
+ const raw = localStorage.getItem(key);
3029
+ if (!raw) return [];
3030
+ const arr = JSON.parse(raw);
3031
+ return Array.isArray(arr) ? arr : [];
3032
+ } catch {
3033
+ return [];
3034
+ }
3035
+ }
3036
+ var audioCtx = null;
3037
+ function playSound() {
3038
+ if (!cfg.sound) return;
3039
+ try {
3040
+ if (typeof cfg.sound === "string") {
3041
+ const audio = new Audio(cfg.sound);
3042
+ void audio.play();
3043
+ return;
3044
+ }
3045
+ if (!audioCtx) {
3046
+ const Ctor = window.AudioContext ?? window.webkitAudioContext;
3047
+ if (!Ctor) return;
3048
+ audioCtx = new Ctor();
3049
+ }
3050
+ if (audioCtx.state === "suspended") void audioCtx.resume();
3051
+ const now = audioCtx.currentTime;
3052
+ [880, 1320].forEach((freq, i) => {
3053
+ const osc = audioCtx.createOscillator();
3054
+ const gain = audioCtx.createGain();
3055
+ osc.connect(gain);
3056
+ gain.connect(audioCtx.destination);
3057
+ osc.frequency.value = freq;
3058
+ osc.type = "sine";
3059
+ const t = now + i * 0.08;
3060
+ gain.gain.setValueAtTime(0, t);
3061
+ gain.gain.linearRampToValueAtTime(0.08, t + 0.01);
3062
+ gain.gain.exponentialRampToValueAtTime(1e-3, t + 0.12);
3063
+ osc.start(t);
3064
+ osc.stop(t + 0.13);
3065
+ });
3066
+ } catch {
3067
+ }
3068
+ }
3069
+ function buildIcon(record) {
3070
+ const wrap = document.createElement("span");
3071
+ wrap.className = "notification-icon";
3072
+ wrap.setAttribute("aria-hidden", "true");
3073
+ const { icon, avatar } = record.opts;
3074
+ if (avatar) {
3075
+ const img = document.createElement("img");
3076
+ img.className = "notification-avatar";
3077
+ img.src = avatar;
3078
+ img.alt = "";
3079
+ img.loading = "lazy";
3080
+ wrap.appendChild(img);
3081
+ return wrap;
3082
+ }
3083
+ if (icon === false) {
3084
+ wrap.style.display = "none";
3085
+ return wrap;
3086
+ }
3087
+ if (typeof icon === "string" && icon.length > 0) {
3088
+ if (/^https?:\/\/|^data:image\//.test(icon) || icon.endsWith(".png") || icon.endsWith(".jpg") || icon.endsWith(".svg") || icon.endsWith(".webp")) {
3089
+ const img = document.createElement("img");
3090
+ img.className = "notification-avatar";
3091
+ img.src = icon;
3092
+ img.alt = "";
3093
+ wrap.appendChild(img);
3094
+ } else {
3095
+ wrap.innerHTML = icon;
3096
+ }
3097
+ return wrap;
3098
+ }
3099
+ if (record.opts.progress !== void 0 && record.opts.progress >= 0) {
3100
+ wrap.innerHTML = PROGRESS_ICON;
3101
+ } else {
3102
+ wrap.innerHTML = KIND_ICON[record.kind];
3103
+ }
3104
+ return wrap;
3105
+ }
3106
+ function buildTitle(title) {
3107
+ const el = document.createElement("p");
3108
+ el.className = "notification-title";
3109
+ el.textContent = title;
3110
+ return el;
3111
+ }
3112
+ function buildDescription(text) {
3113
+ const el = document.createElement("p");
3114
+ el.className = "notification-message";
3115
+ el.style.whiteSpace = "pre-wrap";
3116
+ el.textContent = text;
3117
+ return el;
3118
+ }
3119
+ function buildActions(record, getHandle) {
3120
+ const actions = record.opts.actions?.length ? record.opts.actions : record.opts.actionLabel ? [{ label: record.opts.actionLabel, onClick: record.opts.onAction }] : null;
3121
+ if (!actions || actions.length === 0) return null;
3122
+ const wrap = document.createElement("div");
3123
+ wrap.className = "notification-actions";
3124
+ for (const a of actions) {
3125
+ const btn = document.createElement("button");
3126
+ btn.type = "button";
3127
+ btn.className = "notification-action";
3128
+ if (a.type === "primary") btn.classList.add("notification-action--primary");
3129
+ else if (a.type === "danger") btn.classList.add("notification-action--danger");
3130
+ btn.textContent = a.label;
3131
+ btn.addEventListener("click", (ev) => {
3132
+ ev.stopPropagation();
3133
+ const h = getHandle();
3134
+ if (a.onClick) {
3135
+ const ret = a.onClick();
3136
+ if (ret === false) return;
3137
+ }
3138
+ if (!a.keepOpen) dismissRecord(record.id);
3139
+ else void h;
3140
+ });
3141
+ wrap.appendChild(btn);
3142
+ }
3143
+ return wrap;
3144
+ }
3145
+ function buildLink(record) {
3146
+ const { link, linkLabel } = record.opts;
3147
+ if (!link) return null;
3148
+ const wrap = document.createElement("div");
3149
+ wrap.className = "notification-link-wrap";
3150
+ const a = document.createElement("a");
3151
+ a.className = "notification-link";
3152
+ a.href = link;
3153
+ a.textContent = linkLabel ?? "\u67E5\u770B\u8BE6\u60C5";
3154
+ a.addEventListener("click", (ev) => ev.stopPropagation());
3155
+ wrap.appendChild(a);
3156
+ return wrap;
3157
+ }
3158
+ function buildProgressBar(record) {
3159
+ const { progress, progressLabel } = record.opts;
3160
+ if (progress === void 0 || progress < 0) return null;
3161
+ const wrap = document.createElement("div");
3162
+ wrap.className = "notification-progress";
3163
+ const track = document.createElement("div");
3164
+ track.className = "notification-progress-track";
3165
+ const fill = document.createElement("div");
3166
+ fill.className = "notification-progress-bar";
3167
+ const pct3 = Math.min(100, Math.max(0, progress * 100));
3168
+ fill.style.width = pct3 + "%";
3169
+ track.appendChild(fill);
3170
+ wrap.appendChild(track);
3171
+ if (progressLabel) {
3172
+ const lbl = document.createElement("span");
3173
+ lbl.className = "notification-progress-label";
3174
+ lbl.textContent = progressLabel;
3175
+ wrap.appendChild(lbl);
3176
+ } else if (progress > 0) {
3177
+ const lbl = document.createElement("span");
3178
+ lbl.className = "notification-progress-label";
3179
+ lbl.textContent = Math.round(pct3) + "%";
3180
+ wrap.appendChild(lbl);
3181
+ }
3182
+ return wrap;
3183
+ }
3184
+ function buildCountdownBar(record) {
3185
+ if (!record.duration || record.duration <= 0) return null;
3186
+ if (record.opts.showCountdown === false) return null;
3187
+ const bar = document.createElement("div");
3188
+ bar.className = "notification-countdown";
3189
+ bar.setAttribute("aria-hidden", "true");
3190
+ const fill = document.createElement("div");
3191
+ fill.className = "notification-countdown-fill";
3192
+ fill.style.animationDuration = record.duration + "ms";
3193
+ fill.style.animationPlayState = "running";
3194
+ bar.appendChild(fill);
3195
+ return bar;
3196
+ }
3197
+ function buildClose(record) {
3198
+ if (record.opts.closable === false) return null;
3199
+ const btn = document.createElement("button");
3200
+ btn.type = "button";
3201
+ btn.className = "notification-close";
3202
+ btn.setAttribute("aria-label", "\u5173\u95ED");
3203
+ btn.innerHTML = CLOSE_SVG;
3204
+ btn.addEventListener("click", (ev) => {
3205
+ ev.stopPropagation();
3206
+ dismissRecord(record.id);
3207
+ });
3208
+ return btn;
3209
+ }
3210
+ function renderNotification(record, container, getHandle) {
3211
+ const el = document.createElement("div");
3212
+ let cls = record.kind === "info" ? "notification" : `notification notif--${record.kind}`;
3213
+ if (record.opts.priority === "high") cls += " is-priority";
3214
+ if (record.opts.unread) cls += " is-unread";
3215
+ if (record.opts.progress !== void 0 && record.opts.progress >= 0) cls += " is-progress";
3216
+ if (record.opts.avatar) cls += " has-avatar";
3217
+ if (record.opts.onClick) {
3218
+ cls += " is-clickable";
3219
+ el.setAttribute("role", "button");
3220
+ el.tabIndex = 0;
3221
+ } else {
3222
+ el.setAttribute("role", "alert");
3223
+ }
3224
+ el.className = cls;
3225
+ el.id = record.id;
3226
+ const icon = buildIcon(record);
3227
+ el.appendChild(icon);
3228
+ const content = document.createElement("div");
3229
+ content.className = "notification-content";
3230
+ content.appendChild(buildTitle(record.title));
3231
+ if (record.opts.description) content.appendChild(buildDescription(record.opts.description));
3232
+ const actions = buildActions(record, getHandle);
3233
+ if (actions) content.appendChild(actions);
3234
+ const link = buildLink(record);
3235
+ if (link) content.appendChild(link);
3236
+ const progress = buildProgressBar(record);
3237
+ if (progress) content.appendChild(progress);
3238
+ el.appendChild(content);
3239
+ const close = buildClose(record);
3240
+ if (close) el.appendChild(close);
3241
+ const countdown = buildCountdownBar(record);
3242
+ if (countdown) el.appendChild(countdown);
3243
+ if (record.opts.onClick) {
3244
+ const onClick2 = record.opts.onClick;
3245
+ const handleClick = () => onClick2(getHandle());
3246
+ el.addEventListener("click", handleClick);
3247
+ el.addEventListener("keydown", (ev) => {
3248
+ if (ev.key === "Enter" || ev.key === " ") {
3249
+ ev.preventDefault();
3250
+ handleClick();
3251
+ }
3252
+ });
3253
+ }
3254
+ record.el = el;
3255
+ return el;
3256
+ }
3257
+ function pushToContainer(container, el, priority) {
3258
+ if (priority === "high") {
3259
+ const firstNormal = Array.from(container.children).find(
3260
+ (c) => c instanceof HTMLElement && !c.classList.contains("is-priority")
3261
+ );
3262
+ if (firstNormal) container.insertBefore(el, firstNormal);
3263
+ else container.appendChild(el);
3264
+ } else {
3265
+ container.appendChild(el);
3266
+ }
3267
+ }
3268
+ function removeOne(el, onClose) {
3269
+ if (el.classList.contains("leaving")) return;
3270
+ el.classList.add("leaving");
3271
+ let done = false;
3272
+ const finish = () => {
3273
+ if (done) return;
3274
+ done = true;
3275
+ el.remove();
3276
+ onClose?.();
3277
+ };
3278
+ el.addEventListener("transitionend", finish, { once: true });
3279
+ window.setTimeout(finish, 360);
3280
+ }
3281
+ function trimStack2(container) {
3282
+ for (; ; ) {
3283
+ const items = Array.from(container.querySelectorAll(":scope > .notification"));
3284
+ if (items.length <= cfg.maxStack) return;
3285
+ const target = items.find((it) => !it.classList.contains("is-priority")) ?? items[0];
3286
+ if (!target) return;
3287
+ removeOne(target);
3288
+ }
3289
+ }
3290
+ function attachTimer(record, handle) {
3291
+ if (!record.duration || record.duration <= 0) return;
3292
+ const el = record.el;
3293
+ if (!el) return;
3294
+ const start = () => {
3295
+ if (record.timer) window.clearTimeout(record.timer);
3296
+ record.timer = window.setTimeout(() => dismissRecord(record.id), record.remaining);
3297
+ record.paused = false;
3298
+ };
3299
+ const pause = () => {
3300
+ if (record.paused) return;
3301
+ if (record.timer) {
3302
+ window.clearTimeout(record.timer);
3303
+ record.timer = 0;
3304
+ }
3305
+ record.paused = true;
3306
+ el.classList.add("is-paused");
3307
+ const fill = el.querySelector(".notification-countdown-fill");
3308
+ if (fill) fill.style.animationPlayState = "paused";
3309
+ };
3310
+ const resume = () => {
3311
+ if (!record.paused) return;
3312
+ el.classList.remove("is-paused");
3313
+ const fill = el.querySelector(".notification-countdown-fill");
3314
+ if (fill) fill.style.animationPlayState = "running";
3315
+ start();
3316
+ };
3317
+ const pauseOnHover = record.opts.pauseOnHover ?? cfg.pauseOnHover;
3318
+ if (pauseOnHover) {
3319
+ el.addEventListener("mouseenter", () => handle.pause());
3320
+ el.addEventListener("mouseleave", () => handle.resume());
3321
+ }
3322
+ handle.pause = pause;
3323
+ handle.resume = resume;
3324
+ start();
3325
+ }
3326
+ function makeHandle(record) {
3327
+ const handle = {
3328
+ id: record.id,
3329
+ el: record.el ?? document.createElement("div"),
3330
+ dismiss: () => dismissRecord(record.id),
3331
+ update: (patch) => updateRecord(record.id, patch),
3332
+ setProgress: (value, label) => updateRecord(record.id, {
3333
+ progress: Math.min(1, Math.max(0, value)),
3334
+ progressLabel: label
3335
+ }),
3336
+ markRead: () => updateRecord(record.id, { unread: false }),
3337
+ pause: () => {
3338
+ },
3339
+ resume: () => {
3340
+ }
3341
+ };
3342
+ return handle;
3343
+ }
3344
+ function showRecord(title, kind, opts) {
3345
+ if (!isBrowser3()) return null;
3346
+ const container = ensureContainer2();
3347
+ const id = opts.id ?? genId();
3348
+ if (store.has(id)) dismissRecord(id);
3349
+ const duration = opts.duration ?? 0;
3350
+ const record = {
3351
+ id,
3352
+ title,
3353
+ kind,
3354
+ opts: { unread: opts.unread ?? false, ...opts, id },
3355
+ el: null,
3356
+ createdAt: Date.now(),
3357
+ duration,
3358
+ remaining: duration,
3359
+ paused: false,
3360
+ timer: 0
3361
+ };
3362
+ const handle = makeHandle(record);
3363
+ const el = renderNotification(record, container, () => handle);
3364
+ record.el = el;
3365
+ handle.el = el;
3366
+ pushToContainer(container, el, record.opts.priority ?? "normal");
3367
+ store.set(id, record);
3368
+ attachTimer(record, handle);
3369
+ trimStack2(container);
3370
+ persistSave();
3371
+ playSound();
3372
+ opts.onShow?.(handle);
3373
+ return handle;
3374
+ }
3375
+ function dismissRecord(id) {
3376
+ const record = store.get(id);
3377
+ if (!record) {
3378
+ const el = document.getElementById(id);
3379
+ if (el && el.classList.contains("notification")) removeOne(el);
3380
+ return;
3381
+ }
3382
+ if (record.timer) window.clearTimeout(record.timer);
3383
+ const handle = makeHandle(record);
3384
+ const onClose = record.opts.onClose;
3385
+ store.delete(id);
3386
+ if (record.el) removeOne(record.el, () => onClose?.(handle));
3387
+ else onClose?.(handle);
3388
+ persistSave();
3389
+ }
3390
+ function updateRecord(id, patch) {
3391
+ const record = store.get(id);
3392
+ if (!record) return;
3393
+ if (patch.title !== void 0) record.title = patch.title;
3394
+ if (patch.description !== void 0) record.opts.description = patch.description;
3395
+ if (patch.duration !== void 0) {
3396
+ record.duration = patch.duration;
3397
+ record.remaining = patch.duration;
3398
+ }
3399
+ if (patch.unread !== void 0) record.opts.unread = patch.unread;
3400
+ if (patch.progress !== void 0) record.opts.progress = patch.progress;
3401
+ if (patch.progressLabel !== void 0) record.opts.progressLabel = patch.progressLabel;
3402
+ if (patch.actions !== void 0) record.opts.actions = patch.actions;
3403
+ if (patch.tag !== void 0) record.opts.tag = patch.tag;
3404
+ if (patch.priority !== void 0) record.opts.priority = patch.priority;
3405
+ if (patch.icon !== void 0) record.opts.icon = patch.icon;
3406
+ if (patch.avatar !== void 0) record.opts.avatar = patch.avatar;
3407
+ if (patch.link !== void 0) record.opts.link = patch.link;
3408
+ if (patch.linkLabel !== void 0) record.opts.linkLabel = patch.linkLabel;
3409
+ const el = record.el;
3410
+ if (!el) return;
3411
+ if (patch.title !== void 0) {
3412
+ const t = el.querySelector(".notification-title");
3413
+ if (t) t.textContent = patch.title;
3414
+ }
3415
+ if (patch.description !== void 0) {
3416
+ let m = el.querySelector(".notification-message");
3417
+ if (patch.description) {
3418
+ if (!m) {
3419
+ m = document.createElement("p");
3420
+ m.className = "notification-message";
3421
+ m.style.whiteSpace = "pre-wrap";
3422
+ const content = el.querySelector(".notification-content");
3423
+ const titleEl = content?.querySelector(".notification-title");
3424
+ if (titleEl && titleEl.nextSibling) content?.insertBefore(m, titleEl.nextSibling);
3425
+ else content?.appendChild(m);
3426
+ }
3427
+ m.textContent = patch.description;
3428
+ } else if (m) {
3429
+ m.remove();
3430
+ }
3431
+ }
3432
+ if (patch.unread !== void 0) {
3433
+ el.classList.toggle("is-unread", patch.unread);
3434
+ }
3435
+ if (patch.priority !== void 0) {
3436
+ el.classList.toggle("is-priority", patch.priority === "high");
3437
+ }
3438
+ if (patch.progress !== void 0 || patch.progressLabel !== void 0) {
3439
+ el.classList.add("is-progress");
3440
+ let progWrap = el.querySelector(".notification-progress");
3441
+ const pct3 = Math.min(100, Math.max(0, (record.opts.progress ?? 0) * 100));
3442
+ if (!progWrap) {
3443
+ progWrap = document.createElement("div");
3444
+ progWrap.className = "notification-progress";
3445
+ const track = document.createElement("div");
3446
+ track.className = "notification-progress-track";
3447
+ const fill2 = document.createElement("div");
3448
+ fill2.className = "notification-progress-bar";
3449
+ track.appendChild(fill2);
3450
+ progWrap.appendChild(track);
3451
+ el.querySelector(".notification-content")?.appendChild(progWrap);
3452
+ }
3453
+ const fill = progWrap.querySelector(".notification-progress-bar");
3454
+ if (fill) fill.style.width = pct3 + "%";
3455
+ let lbl = progWrap.querySelector(".notification-progress-label");
3456
+ const labelText = record.opts.progressLabel ?? Math.round(pct3) + "%";
3457
+ if (!lbl) {
3458
+ lbl = document.createElement("span");
3459
+ lbl.className = "notification-progress-label";
3460
+ progWrap.appendChild(lbl);
3461
+ }
3462
+ lbl.textContent = labelText;
3463
+ }
3464
+ persistSave();
3465
+ }
3466
+ function confirmDialog(title, opts = {}) {
3467
+ return new Promise((resolve) => {
3468
+ if (!isBrowser3()) {
3469
+ resolve(false);
3470
+ return;
3471
+ }
3472
+ let resolved = false;
3473
+ const settle = (val) => {
3474
+ if (resolved) return;
3475
+ resolved = true;
3476
+ resolve(val);
3477
+ };
3478
+ const confirmLabel = opts.confirmLabel ?? "\u786E\u8BA4";
3479
+ const cancelLabel = opts.cancelLabel ?? "\u53D6\u6D88";
3480
+ const actions = [
3481
+ {
3482
+ label: cancelLabel,
3483
+ type: "ghost",
3484
+ onClick: () => settle(false)
3485
+ },
3486
+ {
3487
+ label: confirmLabel,
3488
+ type: "primary",
3489
+ onClick: () => settle(true)
3490
+ }
3491
+ ];
3492
+ const handle = showRecord(title, "info", {
3493
+ ...opts,
3494
+ actions,
3495
+ closable: false,
3496
+ onClose: () => settle(false)
3497
+ });
3498
+ if (!handle) settle(false);
3499
+ });
3500
+ }
3501
+ function createNotificationCenter(parent = document.body, opts = {}) {
3502
+ if (!isBrowser3()) {
3503
+ return {
3504
+ push: () => null,
3505
+ dismiss() {
3506
+ },
3507
+ clear() {
3508
+ },
3509
+ markAllRead() {
3510
+ },
3511
+ get: () => null,
3512
+ getAll: () => [],
3513
+ config() {
3514
+ },
3515
+ el: {}
3516
+ };
3517
+ }
3518
+ const local = {
3519
+ position: opts.position ?? "top-right",
3520
+ maxStack: opts.maxStack ?? 5,
3521
+ pauseOnHover: opts.pauseOnHover ?? true,
3522
+ persist: opts.persist ?? false,
3523
+ sound: opts.sound ?? false
3524
+ };
3525
+ const container = document.createElement("div");
3526
+ container.className = "notification-container";
3527
+ container.setAttribute("role", "region");
3528
+ container.setAttribute("aria-label", "\u901A\u77E5");
3529
+ container.dataset.position = local.position;
3530
+ parent.appendChild(container);
3531
+ const localStore = /* @__PURE__ */ new Map();
3532
+ function push(input) {
3533
+ const kind = input.kind ?? "info";
3534
+ const opts2 = input.options ?? {};
3535
+ const id = opts2.id ?? genId();
3536
+ const duration = opts2.duration ?? 0;
3537
+ const record = {
3538
+ id,
3539
+ title: input.title,
3540
+ kind,
3541
+ opts: { unread: opts2.unread ?? false, ...opts2, id },
3542
+ el: null,
3543
+ createdAt: Date.now(),
3544
+ duration,
3545
+ remaining: duration,
3546
+ paused: false,
3547
+ timer: 0
3548
+ };
3549
+ const handle = makeHandle(record);
3550
+ const el = renderNotification(record, container, () => handle);
3551
+ record.el = el;
3552
+ handle.el = el;
3553
+ pushToContainer(container, el, record.opts.priority ?? "normal");
3554
+ localStore.set(id, record);
3555
+ store.set(id, record);
3556
+ attachTimer(record, handle);
3557
+ for (; ; ) {
3558
+ const items = Array.from(container.querySelectorAll(":scope > .notification"));
3559
+ if (items.length <= local.maxStack) break;
3560
+ const target = items.find((it) => !it.classList.contains("is-priority")) ?? items[0];
3561
+ if (!target) break;
3562
+ const tid = target.id;
3563
+ removeOne(target);
3564
+ const rec = tid ? localStore.get(tid) : null;
3565
+ if (rec) {
3566
+ localStore.delete(tid);
3567
+ store.delete(tid);
3568
+ }
3569
+ }
3570
+ opts2.onShow?.(handle);
3571
+ return handle;
3572
+ }
3573
+ function dismiss(id) {
3574
+ if (id) {
3575
+ const rec = localStore.get(id);
3576
+ if (!rec) return;
3577
+ if (rec.timer) window.clearTimeout(rec.timer);
3578
+ const handle = makeHandle(rec);
3579
+ localStore.delete(id);
3580
+ store.delete(id);
3581
+ if (rec.el) removeOne(rec.el, () => rec.opts.onClose?.(handle));
3582
+ return;
3583
+ }
3584
+ Array.from(localStore.keys()).forEach((k) => dismiss(k));
3585
+ }
3586
+ function clear() {
3587
+ dismiss();
3588
+ }
3589
+ function markAllRead() {
3590
+ localStore.forEach((rec) => {
3591
+ rec.opts.unread = false;
3592
+ rec.el?.classList.remove("is-unread");
3593
+ });
3594
+ }
3595
+ return {
3596
+ push,
3597
+ dismiss,
3598
+ clear,
3599
+ markAllRead,
3600
+ get: (id) => localStore.get(id) ?? null,
3601
+ getAll: () => Array.from(localStore.values()),
3602
+ config: (next) => {
3603
+ if (next.position) {
3604
+ local.position = next.position;
3605
+ container.dataset.position = next.position;
3606
+ }
3607
+ if (typeof next.maxStack === "number" && next.maxStack > 0) local.maxStack = next.maxStack;
3608
+ if (typeof next.pauseOnHover === "boolean") local.pauseOnHover = next.pauseOnHover;
3609
+ if (next.persist !== void 0) local.persist = next.persist;
3610
+ if (next.sound !== void 0) local.sound = next.sound;
3611
+ },
3612
+ el: container
3613
+ };
3614
+ }
3615
+ var notify = Object.assign(
3616
+ (title, opts) => showRecord(title, "info", opts ?? {}),
3617
+ {
3618
+ success: (title, opts) => showRecord(title, "success", opts ?? {}),
3619
+ warn: (title, opts) => showRecord(title, "warning", opts ?? {}),
3620
+ error: (title, opts) => showRecord(title, "error", opts ?? {}),
3621
+ info: (title, opts) => showRecord(title, "info", opts ?? {}),
3622
+ progress: (title, opts) => showRecord(title, "info", { progress: opts?.progress ?? 0, ...opts }),
3623
+ confirm: (title, opts) => confirmDialog(title, opts),
3624
+ dismiss: (id) => {
3625
+ if (!isBrowser3()) return;
3626
+ if (id) {
3627
+ dismissRecord(id);
3628
+ return;
3629
+ }
3630
+ Array.from(store.keys()).forEach((k) => dismissRecord(k));
3631
+ },
3632
+ clear: () => {
3633
+ if (!isBrowser3()) return;
3634
+ Array.from(store.keys()).forEach((k) => dismissRecord(k));
3635
+ },
3636
+ markRead: (id) => {
3637
+ if (!isBrowser3()) return;
3638
+ if (id) {
3639
+ updateRecord(id, { unread: false });
3640
+ return;
3641
+ }
3642
+ store.forEach((_, k) => updateRecord(k, { unread: false }));
3643
+ },
3644
+ update: (id, patch) => updateRecord(id, patch),
3645
+ get: (id) => store.get(id) ?? null,
3646
+ getAll: () => Array.from(store.values()),
3647
+ config: (opts) => {
3648
+ if (opts.position) {
3649
+ cfg.position = opts.position;
3650
+ const c = document.body.querySelector(".notification-container[data-notif-default]");
3651
+ if (c) c.dataset.position = opts.position;
3652
+ }
3653
+ if (typeof opts.maxStack === "number" && opts.maxStack > 0) cfg.maxStack = opts.maxStack;
3654
+ if (typeof opts.pauseOnHover === "boolean") cfg.pauseOnHover = opts.pauseOnHover;
3655
+ if (opts.persist !== void 0) cfg.persist = opts.persist;
3656
+ if (opts.sound !== void 0) cfg.sound = opts.sound;
3657
+ }
3658
+ }
3659
+ );
3660
+ function initNotification() {
3661
+ if (!isBrowser3()) return;
3662
+ if (globalInit.done) return;
3663
+ globalInit.done = true;
3664
+ ensureContainer2();
3665
+ if (cfg.persist) {
3666
+ const items = persistLoad();
3667
+ const container = ensureContainer2();
3668
+ for (const item of items) {
3669
+ const record = {
3670
+ id: item.id,
3671
+ title: item.title,
3672
+ kind: item.kind,
3673
+ opts: {
3674
+ id: item.id,
3675
+ description: item.description,
3676
+ tag: item.tag,
3677
+ unread: item.unread,
3678
+ priority: item.priority,
3679
+ progress: item.progress,
3680
+ progressLabel: item.progressLabel,
3681
+ link: item.link,
3682
+ linkLabel: item.linkLabel,
3683
+ data: item.data
3684
+ },
3685
+ el: null,
3686
+ createdAt: item.createdAt ?? Date.now(),
3687
+ duration: 0,
3688
+ remaining: 0,
3689
+ paused: false,
3690
+ timer: 0
3691
+ };
3692
+ const handle = makeHandle(record);
3693
+ const el = renderNotification(record, container, () => handle);
3694
+ record.el = el;
3695
+ handle.el = el;
3696
+ pushToContainer(container, el, record.opts.priority ?? "normal");
3697
+ store.set(record.id, record);
3698
+ }
3699
+ }
3700
+ }
3701
+
3702
+ // src/behaviors/back-top.ts
3703
+ var AUTO_BTN_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg>';
3704
+ function prefersReducedMotion2() {
3705
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
3706
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
3707
+ }
3708
+ function getScrollTarget(target) {
3709
+ if (!target) return { el: null, isWindow: true };
3710
+ const el = document.querySelector(target);
3711
+ return { el, isWindow: false };
3712
+ }
3713
+ function getScrollY(target) {
3714
+ if (!target) return window.scrollY;
3715
+ const el = document.querySelector(target);
3716
+ return el ? el.scrollTop : window.scrollY;
3717
+ }
3718
+ function scrollToTop(target, offset = 0) {
3719
+ const reduce = prefersReducedMotion2();
3720
+ const behavior = reduce ? "auto" : "smooth";
3721
+ if (!target) {
3722
+ window.scrollTo({ top: offset, behavior });
3723
+ return;
3724
+ }
3725
+ const el = document.querySelector(target);
3726
+ if (el) el.scrollTo({ top: offset, behavior });
3727
+ }
3728
+ function setup11(btn, opts = {}) {
3729
+ const el = btn;
3730
+ if (el.__icenBackTopInit) return;
3731
+ el.__icenBackTopInit = true;
3732
+ const threshold = Number(btn.dataset.backTopThreshold ?? opts.threshold ?? 320) || 320;
3733
+ const target = btn.dataset.backTopTarget ?? opts.target;
3734
+ const offset = Number(btn.dataset.backTopOffset ?? opts.offset ?? 0) || 0;
3735
+ let ticking = false;
3736
+ const apply = () => {
3737
+ ticking = false;
3738
+ const y = getScrollY(target);
3739
+ btn.classList.toggle("is-shown", y > threshold);
3740
+ };
3741
+ const onScroll2 = () => {
3742
+ if (ticking) return;
3743
+ ticking = true;
3744
+ window.requestAnimationFrame(apply);
3745
+ };
3746
+ const { el: targetEl, isWindow } = getScrollTarget(target);
3747
+ if (isWindow) {
3748
+ window.addEventListener("scroll", onScroll2, { passive: true });
3749
+ } else if (targetEl) {
3750
+ targetEl.addEventListener("scroll", onScroll2, { passive: true });
3751
+ }
3752
+ window.addEventListener("resize", onScroll2, { passive: true });
3753
+ btn.addEventListener("click", () => scrollToTop(target, offset));
3754
+ apply();
3755
+ }
3756
+ function initBackTop(opts = {}) {
3757
+ if (typeof document === "undefined") return;
3758
+ if (typeof window === "undefined") return;
3759
+ const root = opts.root ?? document;
3760
+ if (opts.autoCreate) {
3761
+ let btn = document.body.querySelector(".back-top[data-back-top-auto]");
3762
+ if (!btn) {
3763
+ btn = document.createElement("button");
3764
+ btn.type = "button";
3765
+ btn.className = "back-top";
3766
+ btn.setAttribute("data-back-top", "");
3767
+ btn.setAttribute("data-back-top-auto", "");
3768
+ btn.setAttribute("aria-label", "\u56DE\u5230\u9876\u90E8");
3769
+ btn.innerHTML = AUTO_BTN_SVG;
3770
+ document.body.appendChild(btn);
3771
+ }
3772
+ setup11(btn, opts);
3773
+ }
3774
+ const candidates = [];
3775
+ if (root instanceof Element && root.matches(".back-top[data-back-top]")) candidates.push(root);
3776
+ candidates.push(...Array.from(root.querySelectorAll(".back-top[data-back-top]")));
3777
+ for (const c of candidates) {
3778
+ const el = c;
3779
+ if (el.hasAttribute("data-back-top-auto") && opts.autoCreate) continue;
3780
+ setup11(el, opts);
3781
+ }
3782
+ }
3783
+
3784
+ // src/behaviors/command-palette.ts
3785
+ var FOCUSABLE2 = 'button:not([disabled]), [href], input:not([disabled]), [tabindex]:not([tabindex="-1"])';
3786
+ var openId2 = null;
3787
+ var restoreFocusTo2 = null;
3788
+ var prevBodyOverflow2 = "";
3789
+ var keyHandler2 = null;
3790
+ var globalKeyHandler = null;
3791
+ var initialized5 = false;
3792
+ function findBackdrop2(id) {
3793
+ return document.querySelector(
3794
+ `.command-palette-backdrop[data-command-palette="${CSS.escape(id)}"]`
3795
+ );
3796
+ }
3797
+ function getVisibleItems(panel2) {
3798
+ return Array.from(
3799
+ panel2.querySelectorAll(".command-palette-item:not([hidden]):not([disabled])")
3800
+ ).filter((el) => el.offsetParent !== null || el.getClientRects().length > 0);
3801
+ }
3802
+ function isDisabledItem(el) {
3803
+ return el.hasAttribute("disabled") || el.getAttribute("aria-disabled") === "true";
3804
+ }
3805
+ function setActive(panel2, idx) {
3806
+ const items = getVisibleItems(panel2);
3807
+ items.forEach((el, i) => el.classList.toggle("is-active", i === idx));
3808
+ const active2 = items[idx];
3809
+ if (active2 && typeof active2.scrollIntoView === "function") {
3810
+ active2.scrollIntoView({ block: "nearest" });
3811
+ }
3812
+ }
3813
+ function filterPanel(panel2, query) {
3814
+ const q = query.trim().toLowerCase();
3815
+ let visibleCount = 0;
3816
+ panel2.querySelectorAll(".command-palette-item").forEach((item) => {
3817
+ if (isDisabledItem(item)) return;
3818
+ const label = (item.querySelector(".command-palette-item-label")?.textContent ?? "").toLowerCase();
3819
+ const hint = (item.querySelector(".command-palette-item-hint")?.textContent ?? "").toLowerCase();
3820
+ const keyword = (item.dataset.commandPaletteKeyword ?? "").toLowerCase();
3821
+ const match = !q || label.includes(q) || hint.includes(q) || keyword.includes(q);
3822
+ item.hidden = !match;
3823
+ item.classList.remove("is-active");
3824
+ if (match) visibleCount++;
3825
+ });
3826
+ panel2.querySelectorAll(".command-palette-group").forEach((group) => {
3827
+ const hasVisible = Array.from(group.querySelectorAll(".command-palette-item")).some(
3828
+ (it) => !it.hidden
3829
+ );
3830
+ group.hidden = !hasVisible;
3831
+ });
3832
+ const empty = panel2.querySelector(".command-palette-empty");
3833
+ if (empty) empty.hidden = visibleCount !== 0;
3834
+ if (visibleCount > 0) setActive(panel2, 0);
3835
+ }
3836
+ function openCommandPalette(id) {
3837
+ if (typeof document === "undefined") return;
3838
+ if (openId2 === id) return;
3839
+ if (openId2) closeCommandPalette(openId2);
3840
+ const backdrop = findBackdrop2(id);
3841
+ if (!backdrop) return;
3842
+ const panel2 = backdrop.querySelector(".command-palette");
3843
+ if (!panel2) return;
3844
+ if (backdrop.parentElement !== document.body) document.body.appendChild(backdrop);
3845
+ restoreFocusTo2 = document.activeElement instanceof HTMLElement ? document.activeElement : null;
3846
+ openId2 = id;
3847
+ backdrop.hidden = false;
3848
+ prevBodyOverflow2 = document.body.style.overflow;
3849
+ document.body.style.overflow = "hidden";
3850
+ const input = panel2.querySelector(".command-palette-input");
3851
+ if (input) {
3852
+ input.value = "";
3853
+ input.focus();
3854
+ } else {
3855
+ panel2.focus();
3856
+ }
3857
+ filterPanel(panel2, "");
3858
+ keyHandler2 = (e) => {
3859
+ if (e.key === "Escape") {
3860
+ e.preventDefault();
3861
+ closeCommandPalette(id);
3862
+ return;
3863
+ }
3864
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
3865
+ e.preventDefault();
3866
+ const items = getVisibleItems(panel2);
3867
+ if (items.length === 0) return;
3868
+ const cur = items.findIndex((el) => el.classList.contains("is-active"));
3869
+ const next = e.key === "ArrowDown" ? (cur + 1) % items.length : (cur - 1 + items.length) % items.length;
3870
+ setActive(panel2, next);
3871
+ return;
3872
+ }
3873
+ if (e.key === "Enter") {
3874
+ const active2 = panel2.querySelector(".command-palette-item.is-active");
3875
+ if (active2 && !isDisabledItem(active2)) {
3876
+ e.preventDefault();
3877
+ active2.click();
3878
+ }
3879
+ return;
3880
+ }
3881
+ if (e.key === "Tab") {
3882
+ e.preventDefault();
3883
+ const els = Array.from(panel2.querySelectorAll(FOCUSABLE2)).filter(
3884
+ (el) => !el.hidden && el.offsetParent !== null
3885
+ );
3886
+ if (els.length === 0) return;
3887
+ const cur = els.findIndex((el) => el === document.activeElement);
3888
+ const next = e.shiftKey ? (cur - 1 + els.length) % els.length : (cur + 1) % els.length;
3889
+ els[next]?.focus();
3890
+ }
3891
+ };
3892
+ window.addEventListener("keydown", keyHandler2);
3893
+ }
3894
+ function closeCommandPalette(id) {
3895
+ if (typeof document === "undefined") return;
3896
+ const backdrop = findBackdrop2(id);
3897
+ if (!backdrop) return;
3898
+ backdrop.hidden = true;
3899
+ if (openId2 !== id) return;
3900
+ openId2 = null;
3901
+ if (keyHandler2) {
3902
+ window.removeEventListener("keydown", keyHandler2);
3903
+ keyHandler2 = null;
3904
+ }
3905
+ document.body.style.overflow = prevBodyOverflow2;
3906
+ if (restoreFocusTo2 && restoreFocusTo2.isConnected) restoreFocusTo2.focus();
3907
+ restoreFocusTo2 = null;
3908
+ }
3909
+ function initCommandPalette(root = document) {
3910
+ if (typeof document === "undefined") return;
3911
+ if (typeof window === "undefined") return;
3912
+ root.querySelectorAll("[data-command-palette-open]").forEach((el) => el.setAttribute("aria-haspopup", "dialog"));
3913
+ if (initialized5) return;
3914
+ initialized5 = true;
3915
+ document.addEventListener("click", (e) => {
3916
+ const target = e.target instanceof Element ? e.target : null;
3917
+ if (!target) return;
3918
+ const opener = target.closest("[data-command-palette-open]");
3919
+ if (opener?.dataset.commandPaletteOpen) {
3920
+ openCommandPalette(opener.dataset.commandPaletteOpen);
3921
+ return;
3922
+ }
3923
+ const item = target.closest(".command-palette-item");
3924
+ if (item) {
3925
+ const backdrop = item.closest("[data-command-palette]");
3926
+ if (backdrop?.dataset.commandPalette) {
3927
+ if (!item.hasAttribute("data-command-palette-keep-open")) {
3928
+ closeCommandPalette(backdrop.dataset.commandPalette);
3929
+ }
3930
+ }
3931
+ return;
3932
+ }
3933
+ if (target instanceof HTMLElement && target.classList.contains("command-palette-backdrop") && target.dataset.commandPalette) {
3934
+ closeCommandPalette(target.dataset.commandPalette);
3935
+ }
3936
+ });
3937
+ document.addEventListener("input", (e) => {
3938
+ if (!(e.target instanceof HTMLElement)) return;
3939
+ if (!e.target.matches(".command-palette-input")) return;
3940
+ const backdrop = e.target.closest("[data-command-palette]");
3941
+ const panel2 = backdrop?.querySelector(".command-palette");
3942
+ if (panel2) filterPanel(panel2, e.target.value);
3943
+ });
3944
+ globalKeyHandler = (e) => {
3945
+ if ((e.metaKey || e.ctrlKey) && (e.key === "k" || e.key === "K")) {
3946
+ e.preventDefault();
3947
+ const first = document.querySelector(".command-palette-backdrop[data-command-palette]");
3948
+ if (first?.dataset.commandPalette) {
3949
+ if (openId2 === first.dataset.commandPalette) closeCommandPalette(openId2);
3950
+ else openCommandPalette(first.dataset.commandPalette);
3951
+ }
3952
+ }
3953
+ };
3954
+ window.addEventListener("keydown", globalKeyHandler);
3955
+ }
3956
+
3957
+ // src/behaviors/date-picker.ts
3958
+ var openPicker = null;
3959
+ var panelEl = null;
3960
+ var outsideHandler = null;
3961
+ var escHandler = null;
3962
+ function pad(n) {
3963
+ return n < 10 ? "0" + n : String(n);
3964
+ }
3965
+ function toDateStr(d) {
3966
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
3967
+ }
3968
+ function parseDateStr(s) {
3969
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s.trim());
3970
+ if (!m) return null;
3971
+ const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
3972
+ return Number.isNaN(d.getTime()) ? null : d;
3973
+ }
3974
+ function format(d, fmt2) {
3975
+ if (!d) return "";
3976
+ return fmt2.replace(/yyyy/g, String(d.getFullYear())).replace(/yy/g, String(d.getFullYear()).slice(-2)).replace(/mm/g, pad(d.getMonth() + 1)).replace(/m/g, String(d.getMonth() + 1)).replace(/dd/g, pad(d.getDate())).replace(/d/g, String(d.getDate()));
3977
+ }
3978
+ function ensurePanel() {
3979
+ if (panelEl && panelEl.isConnected) return panelEl;
3980
+ const el = document.createElement("div");
3981
+ el.className = "date-picker-panel";
3982
+ el.setAttribute("role", "dialog");
3983
+ el.setAttribute("aria-label", "\u9009\u62E9\u65E5\u671F");
3984
+ el.hidden = true;
3985
+ document.body.appendChild(el);
3986
+ panelEl = el;
3987
+ return el;
3988
+ }
3989
+ function positionPanel(panel2, trigger) {
3990
+ const r = trigger.getBoundingClientRect();
3991
+ const pw = 280;
3992
+ const ph = panel2.offsetHeight || 320;
3993
+ let left = r.left;
3994
+ let top = r.bottom + 4;
3995
+ if (left + pw > window.innerWidth - 12) left = window.innerWidth - pw - 12;
3996
+ if (left < 12) left = 12;
3997
+ if (top + ph > window.innerHeight - 12) top = r.top - ph - 4;
3998
+ if (top < 12) top = 12;
3999
+ panel2.style.left = left + "px";
4000
+ panel2.style.top = top + "px";
4001
+ }
4002
+ var CAL_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" x2="16" y1="2" y2="6"/><line x1="8" x2="8" y1="2" y2="6"/><line x1="3" x2="21" y1="10" y2="10"/></svg>';
4003
+ var WEEK_LABELS_ZH = ["\u4E00", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D", "\u65E5"];
4004
+ var WEEK_LABELS_SUN = ["\u65E5", "\u4E00", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D"];
4005
+ function renderCalendar(panel2, ctx) {
4006
+ const { view, selected, min, max, weekStart, onPick } = ctx;
4007
+ const year = view.getFullYear();
4008
+ const month = view.getMonth();
4009
+ const labels = weekStart === 0 ? WEEK_LABELS_SUN : WEEK_LABELS_ZH;
4010
+ const firstDay = new Date(year, month, 1);
4011
+ const offsetRaw = firstDay.getDay();
4012
+ const leadDays = weekStart === 0 ? offsetRaw : (offsetRaw + 6) % 7;
4013
+ const gridStart = new Date(year, month, 1 - leadDays);
4014
+ const today = /* @__PURE__ */ new Date();
4015
+ today.setHours(0, 0, 0, 0);
4016
+ const cells = [];
4017
+ for (let i = 0; i < 42; i++) {
4018
+ cells.push(new Date(gridStart.getFullYear(), gridStart.getMonth(), gridStart.getDate() + i));
4019
+ }
4020
+ let html = `<div class="date-picker-head">
4021
+ <button class="date-picker-nav" data-dp-nav="-1" type="button" aria-label="\u4E0A\u4E00\u6708">\u2039</button>
4022
+ <span class="date-picker-title">${year} \u5E74 ${month + 1} \u6708</span>
4023
+ <button class="date-picker-nav" data-dp-nav="1" type="button" aria-label="\u4E0B\u4E00\u6708">\u203A</button>
4024
+ </div><div class="date-picker-weekdays">${labels.map((l) => `<span>${l}</span>`).join("")}</div><div class="date-picker-grid">`;
4025
+ for (const d of cells) {
4026
+ const isOutside = d.getMonth() !== month;
4027
+ const ds = toDateStr(d);
4028
+ const isToday = d.getTime() === today.getTime();
4029
+ const isSelected = selected && ds === toDateStr(selected);
4030
+ const disabled = min && d < min || max && d > max;
4031
+ const classes = ["date-picker-cell"];
4032
+ if (isOutside) classes.push("is-outside");
4033
+ if (isToday) classes.push("is-today");
4034
+ if (isSelected) classes.push("is-selected");
4035
+ if (disabled) classes.push("is-disabled");
4036
+ html += `<button type="button" class="${classes.join(" ")}" data-dp-date="${ds}"${disabled ? " disabled" : ""}>${d.getDate()}</button>`;
4037
+ }
4038
+ html += `</div><div class="date-picker-foot">
4039
+ <button type="button" class="date-picker-today" data-dp-today>\u4ECA\u5929</button>
4040
+ <button type="button" class="date-picker-clear" data-dp-clear>\u6E05\u9664</button>
4041
+ </div>`;
4042
+ panel2.innerHTML = html;
4043
+ panel2.querySelector('[data-dp-nav="-1"]')?.addEventListener("click", () => {
4044
+ ctx.view = new Date(year, month - 1, 15);
4045
+ renderCalendar(panel2, { ...ctx, view: ctx.view });
4046
+ });
4047
+ panel2.querySelector('[data-dp-nav="1"]')?.addEventListener("click", () => {
4048
+ ctx.view = new Date(year, month + 1, 15);
4049
+ renderCalendar(panel2, { ...ctx, view: ctx.view });
4050
+ });
4051
+ panel2.querySelectorAll("[data-dp-date]").forEach((btn) => {
4052
+ btn.addEventListener("click", () => {
4053
+ const ds = btn.dataset.dpDate;
4054
+ if (!ds) return;
4055
+ const d = parseDateStr(ds);
4056
+ if (d) onPick(d);
4057
+ });
4058
+ });
4059
+ panel2.querySelector("[data-dp-today]")?.addEventListener("click", () => {
4060
+ onPick(new Date(today.getFullYear(), today.getMonth(), today.getDate()));
4061
+ });
4062
+ panel2.querySelector("[data-dp-clear]")?.addEventListener("click", () => {
4063
+ onPick(null);
4064
+ });
4065
+ }
4066
+ function closePanel() {
4067
+ if (!panelEl) return;
4068
+ panelEl.hidden = true;
4069
+ openPicker?.querySelector(".date-picker-trigger")?.classList.remove("is-open");
4070
+ openPicker = null;
4071
+ if (outsideHandler) {
4072
+ document.removeEventListener("mousedown", outsideHandler);
4073
+ outsideHandler = null;
4074
+ }
4075
+ if (escHandler) {
4076
+ window.removeEventListener("keydown", escHandler);
4077
+ escHandler = null;
4078
+ }
4079
+ }
4080
+ function setupPicker(container) {
4081
+ const el = container;
4082
+ if (el.__icenDatePickerInit) return;
4083
+ el.__icenDatePickerInit = true;
4084
+ const fmt2 = el.dataset.datePickerFormat ?? "yyyy-mm-dd";
4085
+ const minStr = el.dataset.datePickerMin;
4086
+ const maxStr = el.dataset.datePickerMax;
4087
+ const weekStart = Number(el.dataset.datePickerWeekStart ?? "1");
4088
+ const placeholder = el.dataset.datePickerPlaceholder ?? "\u9009\u62E9\u65E5\u671F";
4089
+ const min = minStr ? parseDateStr(minStr) : null;
4090
+ const max = maxStr ? parseDateStr(maxStr) : null;
4091
+ const hidden = el.querySelector('input[type="hidden"]');
4092
+ const initialStr = hidden?.value ?? "";
4093
+ const initialDate = initialStr ? parseDateStr(initialStr) : null;
4094
+ let trigger = el.querySelector(".date-picker-trigger");
4095
+ if (!trigger) {
4096
+ trigger = document.createElement("button");
4097
+ trigger.type = "button";
4098
+ trigger.className = "date-picker-trigger";
4099
+ trigger.innerHTML = `<span class="date-picker-value${initialDate ? "" : " is-empty"}"></span><span class="date-picker-icon">${CAL_ICON}</span>`;
4100
+ el.prepend(trigger);
4101
+ }
4102
+ trigger.setAttribute("aria-haspopup", "dialog");
4103
+ const valueEl = trigger.querySelector(".date-picker-value");
4104
+ function paint() {
4105
+ const v = hidden?.value ?? "";
4106
+ const d = v ? parseDateStr(v) : null;
4107
+ if (valueEl) {
4108
+ valueEl.textContent = d ? format(d, fmt2) : placeholder;
4109
+ valueEl.classList.toggle("is-empty", !d);
4110
+ }
4111
+ }
4112
+ paint();
4113
+ if (hidden) {
4114
+ const obs = new MutationObserver(() => paint());
4115
+ obs.observe(hidden, { attributes: true, attributeFilter: ["value"] });
4116
+ }
4117
+ trigger.addEventListener("click", () => {
4118
+ if (openPicker === el) {
4119
+ closePanel();
4120
+ return;
4121
+ }
4122
+ closePanel();
4123
+ openPicker = el;
4124
+ trigger.classList.add("is-open");
4125
+ const panel2 = ensurePanel();
4126
+ panel2.hidden = false;
4127
+ const selected = hidden?.value ? parseDateStr(hidden.value) : null;
4128
+ const view = selected ?? /* @__PURE__ */ new Date();
4129
+ view.setDate(15);
4130
+ positionPanel(panel2, trigger);
4131
+ renderCalendar(panel2, {
4132
+ view,
4133
+ selected,
4134
+ min,
4135
+ max,
4136
+ weekStart,
4137
+ onPick: (d) => {
4138
+ if (d === null) {
4139
+ if (hidden) {
4140
+ hidden.value = "";
4141
+ hidden.dispatchEvent(new Event("change", { bubbles: true }));
4142
+ }
4143
+ } else {
4144
+ if (hidden) {
4145
+ hidden.value = toDateStr(d);
4146
+ hidden.dispatchEvent(new Event("change", { bubbles: true }));
4147
+ }
4148
+ }
4149
+ paint();
4150
+ closePanel();
4151
+ }
4152
+ });
4153
+ outsideHandler = (ev) => {
4154
+ const t = ev.target instanceof Node ? ev.target : null;
4155
+ if (!t) return;
4156
+ if (panel2.contains(t) || el.contains(t)) return;
4157
+ closePanel();
4158
+ };
4159
+ document.addEventListener("mousedown", outsideHandler);
4160
+ escHandler = (ev) => {
4161
+ if (ev.key === "Escape") {
4162
+ ev.preventDefault();
4163
+ closePanel();
4164
+ }
4165
+ };
4166
+ window.addEventListener("keydown", escHandler);
4167
+ const reposition = () => {
4168
+ if (openPicker === el && panel2 && !panel2.hidden) positionPanel(panel2, trigger);
4169
+ };
4170
+ window.addEventListener("scroll", reposition, { passive: true, once: false });
4171
+ window.addEventListener("resize", reposition, { passive: true });
4172
+ });
4173
+ }
4174
+ function initDatePicker(root = document) {
4175
+ if (typeof document === "undefined") return;
4176
+ if (typeof window === "undefined") return;
4177
+ const pickers = [];
4178
+ if (root instanceof Element && root.matches(".date-picker[data-date-picker]")) pickers.push(root);
4179
+ pickers.push(...Array.from(root.querySelectorAll(".date-picker[data-date-picker]")));
4180
+ for (const p of pickers) setupPicker(p);
4181
+ }
4182
+
4183
+ // src/behaviors/split-pane.ts
4184
+ var initialized6 = /* @__PURE__ */ new WeakSet();
4185
+ function pct2(value, min, max) {
4186
+ return Math.min(max, Math.max(min, value));
4187
+ }
4188
+ function setup12(container) {
4189
+ const el = container;
4190
+ if (el.__icenSplitPaneInit) return;
4191
+ el.__icenSplitPaneInit = true;
4192
+ const dividerQuery = el.querySelector(".split-pane-divider");
4193
+ if (!dividerQuery) return;
4194
+ const divider = dividerQuery;
4195
+ const min = Number(el.dataset.splitMin ?? "10") || 10;
4196
+ const max = Number(el.dataset.splitMax ?? "90") || 90;
4197
+ const step = Number(el.dataset.splitStep ?? "2") || 2;
4198
+ const vertical = el.classList.contains("split-pane--vertical");
4199
+ const orientation = vertical ? "vertical" : "horizontal";
4200
+ divider.setAttribute("role", "separator");
4201
+ divider.setAttribute("aria-orientation", orientation === "vertical" ? "horizontal" : "vertical");
4202
+ divider.setAttribute("aria-valuemin", String(min));
4203
+ divider.setAttribute("aria-valuemax", String(max));
4204
+ divider.tabIndex = 0;
4205
+ function readCurrent() {
4206
+ const v = parseFloat(getComputedStyle(el).getPropertyValue("--split"));
4207
+ return Number.isFinite(v) ? v : 50;
4208
+ }
4209
+ function write(p) {
4210
+ const v = pct2(p, min, max);
4211
+ el.style.setProperty("--split", v + "%");
4212
+ divider.setAttribute("aria-valuenow", String(Math.round(v)));
4213
+ }
4214
+ if (!el.style.getPropertyValue("--split")) write(50);
4215
+ divider.setAttribute("aria-valuenow", String(Math.round(readCurrent())));
4216
+ let dragging = false;
4217
+ divider.addEventListener("pointerdown", (ev) => {
4218
+ dragging = true;
4219
+ divider.classList.add("is-dragging");
4220
+ divider.setPointerCapture(ev.pointerId);
4221
+ el.style.userSelect = "none";
4222
+ });
4223
+ const onUp = (ev) => {
4224
+ if (!dragging) return;
4225
+ dragging = false;
4226
+ divider.classList.remove("is-dragging");
4227
+ try {
4228
+ divider.releasePointerCapture(ev.pointerId);
4229
+ } catch {
4230
+ }
4231
+ el.style.userSelect = "";
4232
+ };
4233
+ divider.addEventListener("pointerup", onUp);
4234
+ divider.addEventListener("pointercancel", () => {
4235
+ dragging = false;
4236
+ divider.classList.remove("is-dragging");
4237
+ el.style.userSelect = "";
4238
+ });
4239
+ divider.addEventListener("pointermove", (ev) => {
4240
+ if (!dragging) return;
4241
+ const rect = el.getBoundingClientRect();
4242
+ if (vertical) {
4243
+ if (rect.height <= 0) return;
4244
+ const p = (ev.clientY - rect.top) / rect.height * 100;
4245
+ write(p);
4246
+ } else {
4247
+ if (rect.width <= 0) return;
4248
+ const p = (ev.clientX - rect.left) / rect.width * 100;
4249
+ write(p);
4250
+ }
4251
+ });
4252
+ divider.addEventListener("dblclick", () => write(50));
4253
+ divider.addEventListener("keydown", (ev) => {
4254
+ const current = readCurrent();
4255
+ let next = null;
4256
+ if (vertical) {
4257
+ if (ev.key === "ArrowUp") next = current - step;
4258
+ else if (ev.key === "ArrowDown") next = current + step;
4259
+ } else {
4260
+ if (ev.key === "ArrowLeft") next = current - step;
4261
+ else if (ev.key === "ArrowRight") next = current + step;
4262
+ }
4263
+ if (ev.key === "Home") next = min;
4264
+ else if (ev.key === "End") next = max;
4265
+ if (next === null) return;
4266
+ ev.preventDefault();
4267
+ write(next);
4268
+ });
4269
+ }
4270
+ function initSplitPane(root = document) {
4271
+ if (typeof document === "undefined") return;
4272
+ const panes = [];
4273
+ if (root instanceof Element && root.matches(".split-pane[data-split-pane]")) panes.push(root);
4274
+ panes.push(...Array.from(root.querySelectorAll(".split-pane[data-split-pane]")));
4275
+ for (const p of panes) {
4276
+ const el = p;
4277
+ if (initialized6.has(el)) continue;
4278
+ initialized6.add(el);
4279
+ setup12(el);
4280
+ }
4281
+ }
4282
+
2317
4283
  // src/index.ts
2318
4284
  var VERSION = package_default.version;
2319
4285
  export {
@@ -2322,37 +4288,48 @@ export {
2322
4288
  STYLES,
2323
4289
  VERSION,
2324
4290
  applyTheme,
4291
+ closeCommandPalette,
2325
4292
  closeMenu,
2326
4293
  closeModal,
2327
4294
  closePopover,
2328
4295
  computePopoverLayout,
4296
+ createNotificationCenter,
2329
4297
  getDefaultTheme,
2330
4298
  getTheme,
2331
4299
  initAccordion,
4300
+ initBackTop,
2332
4301
  initCarousel,
4302
+ initCommandPalette,
2333
4303
  initContextMenu,
2334
4304
  initCopy,
4305
+ initDatePicker,
2335
4306
  initDropdown,
2336
4307
  initInput,
2337
4308
  initModal,
2338
4309
  initNav,
4310
+ initNotification,
2339
4311
  initSelect,
2340
4312
  initSidebar,
2341
4313
  initSlider,
4314
+ initSplitPane,
2342
4315
  initTabs,
2343
4316
  initTagInput,
2344
4317
  initTheme,
2345
4318
  initTree,
2346
4319
  initUpload,
4320
+ notify,
4321
+ openCommandPalette,
2347
4322
  openContextMenu,
2348
4323
  openModal,
2349
4324
  openPopover,
2350
4325
  registerContextMenu,
4326
+ renderArea,
2351
4327
  renderDonut,
2352
4328
  renderGauge,
2353
4329
  renderHBar,
2354
4330
  renderHeatmap,
2355
4331
  renderLine,
4332
+ renderRadar,
2356
4333
  renderSparkline,
2357
4334
  renderStack,
2358
4335
  renderVBar,