@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.
- package/dist/behaviors/back-top.mjs +84 -0
- package/dist/behaviors/charts.mjs +188 -0
- package/dist/behaviors/command-palette.mjs +177 -0
- package/dist/behaviors/datatable.mjs +244 -24
- package/dist/behaviors/date-picker.mjs +228 -0
- package/dist/behaviors/input.mjs +170 -1
- package/dist/behaviors/notification.mjs +754 -0
- package/dist/behaviors/select.mjs +151 -22
- package/dist/behaviors/split-pane.mjs +102 -0
- package/dist/behaviors/upload.mjs +165 -14
- package/dist/components/back-top.css +88 -0
- package/dist/components/back-top.mjs +3 -0
- package/dist/components/badge.css +131 -0
- package/dist/components/badge.mjs +2 -0
- package/dist/components/breadcrumb.css +57 -0
- package/dist/components/card.css +68 -0
- package/dist/components/chart-area.css +15 -0
- package/dist/components/chart-area.mjs +4 -0
- package/dist/components/chart-bar.css +79 -0
- package/dist/components/chart-bar.mjs +4 -0
- package/dist/components/chart-gauge.css +38 -0
- package/dist/components/chart-gauge.mjs +4 -0
- package/dist/components/chart-heatmap.css +52 -0
- package/dist/components/chart-heatmap.mjs +4 -0
- package/dist/components/chart-line.css +15 -0
- package/dist/components/chart-line.mjs +4 -0
- package/dist/components/chart-pie.css +26 -0
- package/dist/components/chart-pie.mjs +4 -0
- package/dist/components/chart-radar.css +19 -0
- package/dist/components/chart-radar.mjs +4 -0
- package/dist/components/chart-sparkline.css +14 -0
- package/dist/components/chart-sparkline.mjs +4 -0
- package/dist/components/chart-stack.css +30 -0
- package/dist/components/chart-stack.mjs +4 -0
- package/dist/components/charts.css +7 -207
- package/dist/components/charts.mjs +9 -0
- package/dist/components/command-palette.css +246 -0
- package/dist/components/command-palette.mjs +3 -0
- package/dist/components/content.css +214 -0
- package/dist/components/copy.css +93 -0
- package/dist/components/copy.mjs +3 -0
- package/dist/components/datatable.css +86 -0
- package/dist/components/date-picker.css +244 -0
- package/dist/components/date-picker.mjs +3 -0
- package/dist/components/empty.css +73 -3
- package/dist/components/feedback.css +239 -1
- package/dist/components/form.css +119 -0
- package/dist/components/form.mjs +2 -0
- package/dist/components/input.css +24 -1
- package/dist/components/media.css +144 -1
- package/dist/components/nav.css +99 -0
- package/dist/components/notification.css +387 -0
- package/dist/components/notification.mjs +3 -0
- package/dist/components/panel.css +100 -5
- package/dist/components/pill.css +70 -0
- package/dist/components/scroll-area.css +112 -0
- package/dist/components/scroll-area.mjs +2 -0
- package/dist/components/segmented.css +75 -0
- package/dist/components/select.css +97 -2
- package/dist/components/sidebar.css +118 -0
- package/dist/components/slider.css +113 -0
- package/dist/components/split-pane.css +112 -0
- package/dist/components/split-pane.mjs +3 -0
- package/dist/components/stat.css +129 -6
- package/dist/components/steps.css +97 -0
- package/dist/components/switch.css +142 -0
- package/dist/components/table.css +86 -3
- package/dist/components/tabs.css +122 -1
- package/dist/components/tag.css +164 -0
- package/dist/components/tag.mjs +2 -0
- package/dist/components/toast.css +114 -0
- package/dist/components/toolbar.css +101 -0
- package/dist/components/toolbar.mjs +2 -0
- package/dist/components/upload.css +61 -4
- package/dist/index.mjs +2039 -62
- package/dist/registry.json +200 -24
- package/dist/ui.css +4865 -622
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// package.json
|
|
2
2
|
var package_default = {
|
|
3
3
|
name: "@icen.ai/ui",
|
|
4
|
-
version: "0.
|
|
4
|
+
version: "0.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
|
|
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 || !
|
|
755
|
+
if (!triggerEl || !panelEl2) return;
|
|
587
756
|
const trigger = triggerEl;
|
|
588
|
-
const panel2 =
|
|
589
|
-
const
|
|
590
|
-
const
|
|
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 =
|
|
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
|
|
786
|
+
return visibleOptions().findIndex((o) => !o.classList.contains("is-disabled"));
|
|
601
787
|
}
|
|
602
788
|
function moveFocus(delta) {
|
|
603
|
-
const opts =
|
|
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
|
-
|
|
619
|
-
|
|
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
|
|
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
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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))
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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", () =>
|
|
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
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
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
|
|
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 :
|
|
2005
|
-
y: height -
|
|
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 -
|
|
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 =
|
|
2473
|
+
const y = pad2 + i * (height - pad2 * 2) / 3;
|
|
2025
2474
|
svg.appendChild(svgEl("line", {
|
|
2026
|
-
x1: String(
|
|
2027
|
-
x2: String(width -
|
|
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
|
|
2094
|
-
for (let i = 0; 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
|
|
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 :
|
|
2138
|
-
y: height -
|
|
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 -
|
|
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,
|