dsh-token-pulse 0.4.2 → 0.4.5

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/lib/client.js CHANGED
@@ -440,29 +440,7 @@ window.__ModuleLoader__.load({
440
440
  if (day.totalTokens === 0) return `${day.dayKey}: No usage`;
441
441
  return `${day.dayKey}: ${formatTokensShort$1(day.totalTokens)} tokens`;
442
442
  }
443
- function HeatCell({ day, level, size = 14 }) {
444
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
445
- title: tooltipTotal(day),
446
- "aria-label": tooltipTotal(day),
447
- style: {
448
- width: size,
449
- height: size,
450
- borderRadius: 3,
451
- background: {
452
- 0: "var(--dsw-alias-bg-layer-2, #ebedf0)",
453
- 1: "#c6e48b",
454
- 2: "#7bc96f",
455
- 3: "#239a3b",
456
- 4: "#196127"
457
- }[level],
458
- border: level === 0 ? "1px solid var(--dsw-alias-border-l2, #e5e7eb)" : "1px solid transparent",
459
- boxSizing: "border-box",
460
- cursor: "pointer",
461
- flexShrink: 0
462
- }
463
- });
464
- }
465
- function HourStrip({ day, hourLevels }) {
443
+ function HourStrip({ day, hourLevels, baseDelay = 0 }) {
466
444
  const hourly = day.hourlyTokens ?? new Array(24).fill(0);
467
445
  const colors = {
468
446
  0: "var(--dsw-alias-bg-layer-2, #ebedf0)",
@@ -483,9 +461,11 @@ window.__ModuleLoader__.load({
483
461
  const lv = hourLevels[h] ?? 0;
484
462
  const hh = v === 0 ? `${String(h).padStart(2, "0")}:00 — No usage` : `${String(h).padStart(2, "0")}:00 — ${formatTokensShort$1(v)} tokens`;
485
463
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
464
+ className: "dtp-hm-bar",
486
465
  title: hh,
487
466
  "aria-label": hh,
488
467
  style: {
468
+ animationDelay: `${baseDelay + h * 12}ms`,
489
469
  flex: 1,
490
470
  height: 6 + lv * 4,
491
471
  borderRadius: 1,
@@ -527,49 +507,6 @@ window.__ModuleLoader__.load({
527
507
  }, v))
528
508
  });
529
509
  }
530
- function MonthHeader({ weeks, cellSize }) {
531
- const seen = /* @__PURE__ */ new Set();
532
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
533
- style: {
534
- display: "flex",
535
- gap: 3,
536
- paddingLeft: cellSize + 20,
537
- marginBottom: 2
538
- },
539
- children: weeks.map((week, i) => {
540
- const m = week.find((d) => d.totalTokens !== -1)?.dayKey.slice(0, 7) ?? "";
541
- let label = "";
542
- if (m && !seen.has(m)) {
543
- seen.add(m);
544
- label = [
545
- "Jan",
546
- "Feb",
547
- "Mar",
548
- "Apr",
549
- "May",
550
- "Jun",
551
- "Jul",
552
- "Aug",
553
- "Sep",
554
- "Oct",
555
- "Nov",
556
- "Dec"
557
- ][Number(m.slice(5, 7)) - 1];
558
- }
559
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
560
- style: {
561
- width: cellSize,
562
- fontSize: 10,
563
- color: "var(--dsw-alias-label-tertiary)",
564
- lineHeight: "12px",
565
- whiteSpace: "nowrap",
566
- overflow: "visible"
567
- },
568
- children: label
569
- }, i);
570
- })
571
- });
572
- }
573
510
  function GitHubGrid({ days, t, cellSize, twoRows, isEn, dayLevels }) {
574
511
  const weeks = [];
575
512
  let cur = [];
@@ -613,191 +550,207 @@ window.__ModuleLoader__.load({
613
550
  });
614
551
  weeks.push(cur);
615
552
  }
616
- const renderWeeks = (ws) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
617
- style: {
618
- display: "flex",
619
- gap: cellSize >= 22 ? 10 : 5
620
- },
621
- children: ws.map((week, wi) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
622
- style: {
623
- display: "flex",
624
- flexDirection: "column",
625
- gap: 3
626
- },
627
- children: week.map((d) => d.totalTokens === -1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
628
- width: cellSize,
629
- height: cellSize
630
- } }, d.dayKey) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HeatCell, {
631
- day: d,
632
- level: dayLevels.get(d.dayKey) ?? 0,
633
- size: cellSize
634
- }, d.dayKey))
635
- }, wi))
636
- });
637
- if (twoRows && weeks.length > 26) {
553
+ const specHistoryRef = react.useRef(/* @__PURE__ */ new Map());
554
+ const labelHistoryRef = react.useRef(/* @__PURE__ */ new Set());
555
+ const gap = 3;
556
+ const colGap = cellSize >= 22 ? 10 : 5;
557
+ const headerH = 16;
558
+ const blockH = 7 * cellSize + 18;
559
+ const rowGap = 34;
560
+ const twoRowsSplit = !!(twoRows && weeks.length > 26);
561
+ const labelW = cellSize + 24;
562
+ const dayByKey = new Map(days.map((d) => [d.dayKey, d]));
563
+ const specs = /* @__PURE__ */ new Map();
564
+ const monthLabels = [];
565
+ const colX = (wi) => labelW + wi * (cellSize + colGap);
566
+ const place = (ws, yBase) => {
567
+ const seen = /* @__PURE__ */ new Set();
568
+ ws.forEach((week, wi) => {
569
+ const m = week.find((d) => d.totalTokens !== -1)?.dayKey.slice(0, 7) ?? "";
570
+ if (m && !seen.has(m)) {
571
+ seen.add(m);
572
+ monthLabels.push({
573
+ key: m,
574
+ x: colX(wi),
575
+ y: yBase - 14
576
+ });
577
+ }
578
+ week.forEach((d, r) => {
579
+ if (d.totalTokens === -1) return;
580
+ specs.set(d.dayKey, {
581
+ x: colX(wi),
582
+ y: yBase + r * (cellSize + gap)
583
+ });
584
+ });
585
+ });
586
+ };
587
+ let ncols;
588
+ if (twoRowsSplit) {
638
589
  const mid = Math.ceil(weeks.length / 2);
639
590
  const top = weeks.slice(0, mid);
640
591
  const bot = weeks.slice(mid);
641
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
642
- style: {
643
- display: "flex",
644
- flexDirection: "column",
645
- gap: 16,
646
- alignItems: "center"
647
- },
648
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MonthHeader, {
649
- weeks: top,
650
- cellSize
651
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
652
- style: {
653
- display: "flex",
654
- gap: 3
655
- },
656
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
657
- style: {
658
- display: "flex",
659
- flexDirection: "column",
660
- gap: 3
661
- },
662
- children: [
663
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
664
- style: {
665
- height: cellSize,
666
- fontSize: cellSize >= 22 ? 13 : 10,
667
- color: "var(--dsw-alias-label-tertiary)",
668
- lineHeight: `${cellSize}px`
669
- },
670
- children: isEn ? "Sun" : "Mon"
671
- }),
672
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { height: cellSize } }),
673
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
674
- style: {
675
- height: cellSize,
676
- fontSize: cellSize >= 22 ? 13 : 10,
677
- color: "var(--dsw-alias-label-tertiary)",
678
- lineHeight: `${cellSize}px`
679
- },
680
- children: isEn ? "Tue" : "Wed"
681
- }),
682
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { height: cellSize } }),
683
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
684
- style: {
685
- height: cellSize,
686
- fontSize: cellSize >= 22 ? 13 : 10,
687
- color: "var(--dsw-alias-label-tertiary)",
688
- lineHeight: `${cellSize}px`
689
- },
690
- children: isEn ? "Thu" : "Fri"
691
- }),
692
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { height: cellSize } }),
693
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { height: cellSize } })
694
- ]
695
- }), renderWeeks(top)]
696
- })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MonthHeader, {
697
- weeks: bot,
698
- cellSize
699
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
700
- style: {
701
- display: "flex",
702
- gap: 3
703
- },
704
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
705
- style: {
706
- display: "flex",
707
- flexDirection: "column",
708
- gap: 3
709
- },
710
- children: [
711
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
712
- style: {
713
- height: cellSize,
714
- fontSize: cellSize >= 22 ? 13 : 10,
715
- color: "var(--dsw-alias-label-tertiary)",
716
- lineHeight: `${cellSize}px`
717
- },
718
- children: isEn ? "Sun" : "Mon"
719
- }),
720
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { height: cellSize } }),
721
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
722
- style: {
723
- height: cellSize,
724
- fontSize: cellSize >= 22 ? 13 : 10,
725
- color: "var(--dsw-alias-label-tertiary)",
726
- lineHeight: `${cellSize}px`
727
- },
728
- children: isEn ? "Tue" : "Wed"
729
- }),
730
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { height: cellSize } }),
731
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
732
- style: {
733
- height: cellSize,
734
- fontSize: cellSize >= 22 ? 13 : 10,
735
- color: "var(--dsw-alias-label-tertiary)",
736
- lineHeight: `${cellSize}px`
737
- },
738
- children: isEn ? "Thu" : "Fri"
739
- }),
740
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { height: cellSize } }),
741
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { height: cellSize } })
742
- ]
743
- }), renderWeeks(bot)]
744
- })] })]
745
- });
592
+ place(top, headerH);
593
+ place(bot, headerH + blockH + rowGap);
594
+ ncols = Math.max(top.length, bot.length);
595
+ } else {
596
+ place(weeks, headerH);
597
+ ncols = weeks.length;
746
598
  }
747
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
599
+ const width = labelW + ncols * cellSize + Math.max(0, ncols - 1) * colGap;
600
+ const height = twoRowsSplit ? 32 + blockH * 2 + rowGap : headerH + blockH;
601
+ const prevSpecs = specHistoryRef.current;
602
+ const items = [...specs.entries()].map(([k, s]) => ({
603
+ k,
604
+ s,
605
+ isNew: !prevSpecs.has(k)
606
+ }));
607
+ specHistoryRef.current = new Map(specs);
608
+ const prevLabelKeys = labelHistoryRef.current;
609
+ const labelIsNew = (k) => !prevLabelKeys.has(k);
610
+ labelHistoryRef.current = new Set(monthLabels.map((m) => m.key));
611
+ const [ghosts, setGhosts] = react.useState([]);
612
+ react.useEffect(() => {
613
+ const removed = [];
614
+ for (const [k, s] of prevSpecs) if (!specs.has(k)) removed.push({
615
+ k,
616
+ x: s.x,
617
+ y: s.y
618
+ });
619
+ if (removed.length) {
620
+ setGhosts(removed);
621
+ const id = setTimeout(() => setGhosts([]), 300);
622
+ return () => clearTimeout(id);
623
+ }
624
+ });
625
+ const colors = {
626
+ 0: "var(--dsw-alias-bg-layer-2, #ebedf0)",
627
+ 1: "#c6e48b",
628
+ 2: "#7bc96f",
629
+ 3: "#239a3b",
630
+ 4: "#196127"
631
+ };
632
+ const dayNames = isEn ? [
633
+ "Sun",
634
+ "Mon",
635
+ "Tue",
636
+ "Wed",
637
+ "Thu",
638
+ "Fri",
639
+ "Sat"
640
+ ] : [
641
+ "Mon",
642
+ "Tue",
643
+ "Wed",
644
+ "Thu",
645
+ "Fri",
646
+ "Sat",
647
+ "Sun"
648
+ ];
649
+ const wdIdx = twoRowsSplit ? [
650
+ 0,
651
+ 2,
652
+ 4
653
+ ] : [
654
+ 0,
655
+ 1,
656
+ 2,
657
+ 3,
658
+ 4,
659
+ 5,
660
+ 6
661
+ ];
662
+ const wdFontSize = cellSize >= 22 ? 13 : 10;
663
+ const monthName = (m) => [
664
+ "Jan",
665
+ "Feb",
666
+ "Mar",
667
+ "Apr",
668
+ "May",
669
+ "Jun",
670
+ "Jul",
671
+ "Aug",
672
+ "Sep",
673
+ "Oct",
674
+ "Nov",
675
+ "Dec"
676
+ ][Number(m.slice(5, 7)) - 1];
677
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
748
678
  style: {
749
- overflowX: "auto",
679
+ overflowX: twoRowsSplit ? "auto" : "hidden",
750
680
  paddingBottom: 4,
751
681
  display: "flex",
752
- flexDirection: "column",
753
- alignItems: "center",
754
- gap: 6
682
+ justifyContent: "center"
755
683
  },
756
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MonthHeader, {
757
- weeks,
758
- cellSize
759
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
684
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
760
685
  style: {
761
- display: "flex",
762
- gap: cellSize >= 22 ? 10 : 5,
763
- justifyContent: "center"
686
+ position: "relative",
687
+ width,
688
+ height,
689
+ flexShrink: 0
764
690
  },
765
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
766
- style: {
767
- display: "flex",
768
- flexDirection: "column",
769
- gap: 3,
770
- fontSize: cellSize >= 22 ? 13 : 10,
771
- color: "var(--dsw-alias-label-tertiary)"
772
- },
773
- children: (isEn ? [
774
- "Sun",
775
- "Mon",
776
- "Tue",
777
- "Wed",
778
- "Thu",
779
- "Fri",
780
- "Sat"
781
- ] : [
782
- "Mon",
783
- "Tue",
784
- "Wed",
785
- "Thu",
786
- "Fri",
787
- "Sat",
788
- "Sun"
789
- ]).map((w, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
691
+ children: [
692
+ monthLabels.map((m) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
693
+ className: `dtp-hm-label${labelIsNew(m.key) ? " dtp-hm-new" : ""}`,
790
694
  style: {
791
- height: cellSize,
792
- lineHeight: `${cellSize}px`,
695
+ transform: `translate(${m.x}px, ${m.y}px)`,
696
+ fontSize: 10,
697
+ color: "var(--dsw-alias-label-tertiary)",
698
+ lineHeight: "12px"
699
+ },
700
+ children: monthName(m.key)
701
+ }, m.key)),
702
+ wdIdx.map((i) => [{
703
+ y: headerH + i * (cellSize + gap),
704
+ k: `top-${i}`
705
+ }, ...twoRowsSplit ? [{
706
+ y: headerH + blockH + rowGap + i * (cellSize + gap),
707
+ k: `bot-${i}`
708
+ }] : []].map(({ y, k }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
709
+ className: "dtp-hm-label",
710
+ style: {
711
+ transform: `translate(0px, ${y}px)`,
712
+ fontSize: wdFontSize,
713
+ width: labelW - 6,
793
714
  textAlign: "right",
794
715
  paddingRight: 4,
716
+ color: "var(--dsw-alias-label-tertiary)",
717
+ lineHeight: `${cellSize}px`,
795
718
  fontWeight: 500
796
719
  },
797
- children: w
798
- }, w))
799
- }), renderWeeks(weeks)]
800
- })]
720
+ children: dayNames[i]
721
+ }, k))),
722
+ items.map(({ k, s, isNew }) => {
723
+ const d = dayByKey.get(k);
724
+ const lv = dayLevels.get(k) ?? 0;
725
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
726
+ title: tooltipTotal(d),
727
+ "aria-label": tooltipTotal(d),
728
+ className: `dtp-hm-cell${isNew ? " dtp-hm-new" : ""}`,
729
+ style: {
730
+ transform: `translate(${s.x}px, ${s.y}px)`,
731
+ width: cellSize,
732
+ height: cellSize,
733
+ borderRadius: 3,
734
+ background: colors[lv],
735
+ border: lv === 0 ? "1px solid var(--dsw-alias-border-l2, #e5e7eb)" : "1px solid transparent"
736
+ }
737
+ }, k);
738
+ }),
739
+ ghosts.map((g) => {
740
+ const lv = dayByKey.get(g.k) ? dayLevels.get(g.k) ?? 0 : 0;
741
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
742
+ className: "dtp-hm-cell dtp-hm-ghost",
743
+ style: {
744
+ transform: `translate(${g.x}px, ${g.y}px)`,
745
+ width: cellSize,
746
+ height: cellSize,
747
+ borderRadius: 3,
748
+ background: colors[lv]
749
+ }
750
+ }, g.k);
751
+ })
752
+ ]
753
+ })
801
754
  });
802
755
  }
803
756
  function CalendarGrid({ days, t, dayLevels, selectedKey, onSelect, isEn }) {
@@ -846,7 +799,7 @@ window.__ModuleLoader__.load({
846
799
  ] : header).map((w) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
847
800
  style: {
848
801
  textAlign: "center",
849
- fontSize: 11,
802
+ fontSize: 10,
850
803
  fontWeight: 500,
851
804
  color: "var(--dsw-alias-label-tertiary)"
852
805
  },
@@ -911,6 +864,20 @@ window.__ModuleLoader__.load({
911
864
  })]
912
865
  });
913
866
  }
867
+ const HM_ANIM_CSS = `
868
+ @keyframes dtp-hm-in { from { opacity: 0; transform: translateY(8px); } }
869
+ @keyframes dtp-hm-row { from { opacity: 0; transform: translateX(-14px); } }
870
+ @keyframes dtp-hm-grow { from { transform: scaleY(0); } }
871
+ @keyframes dtp-hm-fade { from { opacity: 0; } }
872
+ @keyframes dtp-hm-fadeout { to { opacity: 0; } }
873
+ .dtp-hm-view { animation: dtp-hm-in 0.4s ease; }
874
+ .dtp-hm-row { animation: dtp-hm-row 0.4s ease both; }
875
+ .dtp-hm-bar { transform-origin: bottom; animation: dtp-hm-grow 0.45s ease both; }
876
+ .dtp-hm-cell { position: absolute; box-sizing: border-box; transition: transform 0.45s ease, width 0.45s ease, height 0.45s ease, opacity 0.45s ease; }
877
+ .dtp-hm-new { animation: dtp-hm-fade 0.45s ease both; }
878
+ .dtp-hm-ghost { animation: dtp-hm-fadeout 0.25s ease forwards; }
879
+ .dtp-hm-label { position: absolute; white-space: nowrap; transition: transform 0.45s ease, opacity 0.45s ease; }
880
+ `;
914
881
  function HeatmapGrid({ t, days, view, onViewChange, selectedKey, onSelect, isEn, thresholds = DEFAULT_THRESHOLDS }) {
915
882
  const isWeek = view === "week";
916
883
  const hasAny = days.some((d) => d.totalTokens > 0);
@@ -928,6 +895,7 @@ window.__ModuleLoader__.load({
928
895
  gap: 6
929
896
  },
930
897
  children: [
898
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: HM_ANIM_CSS }),
931
899
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ViewSwitcher, {
932
900
  t,
933
901
  value: view,
@@ -950,7 +918,7 @@ window.__ModuleLoader__.load({
950
918
  flexDirection: "column",
951
919
  gap: 5
952
920
  },
953
- children: days.map((d) => {
921
+ children: days.map((d, di) => {
954
922
  const wd = (/* @__PURE__ */ new Date(d.dayKey + "T12:00:00+08:00")).getDay();
955
923
  const weekday = isEn ? [
956
924
  "Sun",
@@ -971,9 +939,11 @@ window.__ModuleLoader__.load({
971
939
  ][(wd + 6) % 7];
972
940
  const sel = d.dayKey === selectedKey;
973
941
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
942
+ className: "dtp-hm-row",
974
943
  onClick: () => onSelect?.(sel ? null : d.dayKey),
975
944
  title: tooltipTotal(d),
976
945
  style: {
946
+ animationDelay: `${di * 45}ms`,
977
947
  display: "flex",
978
948
  alignItems: "center",
979
949
  gap: 10,
@@ -1014,7 +984,8 @@ window.__ModuleLoader__.load({
1014
984
  }),
1015
985
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(HourStrip, {
1016
986
  day: d,
1017
- hourLevels: hourLevels.get(d.dayKey) ?? new Array(24).fill(0)
987
+ hourLevels: hourLevels.get(d.dayKey) ?? new Array(24).fill(0),
988
+ baseDelay: di * 45 + 140
1018
989
  }),
1019
990
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1020
991
  style: {
@@ -1029,27 +1000,27 @@ window.__ModuleLoader__.load({
1029
1000
  ]
1030
1001
  }, d.dayKey);
1031
1002
  })
1032
- }) : view === "month" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CalendarGrid, {
1033
- days,
1034
- t,
1035
- dayLevels,
1036
- selectedKey: selectedKey ?? null,
1037
- onSelect: onSelect ?? (() => {}),
1038
- isEn
1039
- }) : view === "year" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GitHubGrid, {
1040
- days,
1041
- t,
1042
- cellSize,
1043
- twoRows: true,
1044
- isEn,
1045
- dayLevels
1046
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GitHubGrid, {
1047
- days,
1048
- t,
1049
- cellSize,
1050
- isEn,
1051
- dayLevels
1052
- }),
1003
+ }, "week") : view === "month" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1004
+ className: "dtp-hm-view",
1005
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CalendarGrid, {
1006
+ days,
1007
+ t,
1008
+ dayLevels,
1009
+ selectedKey: selectedKey ?? null,
1010
+ onSelect: onSelect ?? (() => {}),
1011
+ isEn
1012
+ })
1013
+ }, "month") : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1014
+ className: "dtp-hm-view",
1015
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(GitHubGrid, {
1016
+ days,
1017
+ t,
1018
+ cellSize,
1019
+ twoRows: view === "year",
1020
+ isEn,
1021
+ dayLevels
1022
+ })
1023
+ }, "github"),
1053
1024
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1054
1025
  style: {
1055
1026
  display: "flex",
@@ -1443,9 +1414,13 @@ window.__ModuleLoader__.load({
1443
1414
  * has an identical command count no matter how many days it represents, so
1444
1415
  * CSS `transition: d` can morph lines across range switches too — where raw
1445
1416
  * bezier paths (M + n-1 C) have mismatched structures and would jump.
1446
- * Visual output is indistinguishable at this density.
1417
+ *
1418
+ * Default density 712 = 8 × 89 keeps the polyline visually identical to the
1419
+ * spline (≥ 8 samples per data interval for a 90-day series — the sparse 89
1420
+ * sample grid cut spike corners and left dots floating above the line) while
1421
+ * letting a 90-day series' sample xs land exactly ON its data points.
1447
1422
  */
1448
- function seriesPathFixed(points, samples = 89) {
1423
+ function seriesPathFixed(points, samples = 712) {
1449
1424
  if (points.length === 0) return "";
1450
1425
  const x0 = points[0][0];
1451
1426
  const x1 = points[points.length - 1][0];
@@ -1729,6 +1704,20 @@ window.__ModuleLoader__.load({
1729
1704
  color: active ? "var(--dsw-alias-bg-layer-3)" : "var(--dsw-alias-label-secondary)",
1730
1705
  transition: "background .25s ease, color .25s ease"
1731
1706
  });
1707
+ const linePaths = react.useMemo(() => {
1708
+ const m = /* @__PURE__ */ new Map();
1709
+ if (chartType === "line") for (const s of series) {
1710
+ const pts = s.points.map((v, i) => [xOf(i), yOf(v)]);
1711
+ m.set(s.model, seriesPathFixed(pts));
1712
+ }
1713
+ return m;
1714
+ }, [
1715
+ series,
1716
+ yMax,
1717
+ chartType,
1718
+ dayKeys,
1719
+ hidden
1720
+ ]);
1732
1721
  const labelsNode = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: tickIdx.map((i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
1733
1722
  x: 0,
1734
1723
  y: 0,
@@ -1780,7 +1769,7 @@ window.__ModuleLoader__.load({
1780
1769
  onAnimationEnd: onAnimEnd,
1781
1770
  style: { transition: T_FADE },
1782
1771
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1783
- d: seriesPathFixed(pts),
1772
+ d: linePaths.get(s.model) ?? "",
1784
1773
  fill: "none",
1785
1774
  stroke: color,
1786
1775
  strokeWidth: 2,
@@ -2607,7 +2596,7 @@ window.__ModuleLoader__.load({
2607
2596
  color: "var(--dsw-alias-label-tertiary)",
2608
2597
  verticalAlign: "middle"
2609
2598
  },
2610
- children: ["v", "0.4.2"]
2599
+ children: ["v", "0.4.5"]
2611
2600
  })]
2612
2601
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2613
2602
  style: {
@@ -2905,73 +2894,71 @@ window.__ModuleLoader__.load({
2905
2894
  }) : null
2906
2895
  ]
2907
2896
  }),
2908
- view === "week" || view === "month" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2909
- style: {
2910
- display: "flex",
2911
- alignItems: "center",
2912
- gap: 8,
2913
- justifyContent: "space-between"
2914
- },
2915
- children: [
2916
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2917
- onClick: () => setAnchor((a) => {
2918
- const d = /* @__PURE__ */ new Date(a + "T12:00:00+08:00");
2919
- if (view === "week") d.setDate(d.getDate() - 7);
2920
- else d.setMonth(d.getMonth() - 1);
2921
- const ms = d.getTime();
2922
- return new Date(ms).toLocaleDateString("en-CA", { timeZone: "Asia/Shanghai" });
2897
+ (() => {
2898
+ const dOf = (a) => /* @__PURE__ */ new Date(a + "T12:00:00+08:00");
2899
+ const btn = {
2900
+ padding: "4px 10px",
2901
+ fontSize: 12,
2902
+ borderRadius: 6,
2903
+ border: "1px solid var(--dsw-alias-border-l2)",
2904
+ background: "var(--dsw-alias-bg-layer-3)",
2905
+ cursor: "pointer"
2906
+ };
2907
+ const shiftDays = (n) => setAnchor((a) => {
2908
+ const d = dOf(a);
2909
+ d.setDate(d.getDate() + n);
2910
+ return new Date(d.getTime()).toLocaleDateString("en-CA", { timeZone: "Asia/Shanghai" });
2911
+ });
2912
+ const shiftMonth = (n) => setAnchor((a) => {
2913
+ const d = dOf(a);
2914
+ d.setMonth(d.getMonth() + n);
2915
+ return new Date(d.getTime()).toLocaleDateString("en-CA", { timeZone: "Asia/Shanghai" });
2916
+ });
2917
+ const aD = dOf(anchor);
2918
+ const fmtCn = (x) => `${x.getMonth() + 1}月${x.getDate()}日`;
2919
+ const prevLabel = view === "week" ? "‹ 上一周" : view === "month" ? "‹ 上一月" : view === "quarter" ? "‹ 上一季度" : "‹ 上一年";
2920
+ const nextLabel = view === "week" ? "下一周 ›" : view === "month" ? "下一月 ›" : view === "quarter" ? "下一季度 ›" : "下一年 ›";
2921
+ const prevLabelEn = view === "week" ? "‹ Prev Week" : view === "month" ? "‹ Prev Month" : view === "quarter" ? "‹ Prev Quarter" : "‹ Prev Year";
2922
+ const nextLabelEn = view === "week" ? "Next Week ›" : view === "month" ? "Next Month ›" : view === "quarter" ? "Next Quarter ›" : "Next Year ›";
2923
+ const label = view === "week" ? (() => {
2924
+ const d = aD;
2925
+ const dow = (d.getDay() + 6) % 7;
2926
+ const mon = new Date(d);
2927
+ mon.setDate(d.getDate() - dow);
2928
+ const sun = new Date(mon);
2929
+ sun.setDate(mon.getDate() + 6);
2930
+ const fmt = (x) => `${x.getMonth() + 1}/${x.getDate()}`;
2931
+ return lang === "en" ? `Week of ${fmt(mon)} - ${fmt(sun)}` : `${mon.getMonth() + 1}月${mon.getDate()}日 - ${sun.getMonth() + 1}月${sun.getDate()}日`;
2932
+ })() : view === "month" ? `${Number(anchor.slice(0, 4))}年${Number(anchor.slice(5, 7))}月` + (lang === "en" ? ` ${anchor.slice(0, 7)}` : "") : view === "quarter" ? lang === "en" ? `${(/* @__PURE__ */ new Date(aD.getTime() - 76896e5)).toLocaleDateString("en-CA")} - ${aD.toLocaleDateString("en-CA")}` : `${fmtCn(/* @__PURE__ */ new Date(aD.getTime() - 76896e5))} - ${fmtCn(aD)}` : lang === "en" ? `${aD.getFullYear()}` : `${aD.getFullYear()}年`;
2933
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2934
+ style: {
2935
+ display: "flex",
2936
+ alignItems: "center",
2937
+ gap: 8,
2938
+ justifyContent: "space-between"
2939
+ },
2940
+ children: [
2941
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2942
+ onClick: view === "month" ? () => shiftMonth(-1) : () => shiftDays(view === "week" ? -7 : view === "quarter" ? -90 : -365),
2943
+ style: btn,
2944
+ children: lang === "en" ? prevLabelEn : prevLabel
2923
2945
  }),
2924
- style: {
2925
- padding: "4px 10px",
2926
- fontSize: 12,
2927
- borderRadius: 6,
2928
- border: "1px solid var(--dsw-alias-border-l2)",
2929
- background: "var(--dsw-alias-bg-layer-3)",
2930
- cursor: "pointer"
2931
- },
2932
- children: [lang === "en" ? "‹ Prev" : "‹ 上一", view === "week" ? lang === "en" ? " Week" : "周" : lang === "en" ? " Month" : "月"]
2933
- }),
2934
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2935
- style: {
2936
- fontSize: 12,
2937
- color: "var(--dsw-alias-label-secondary)",
2938
- fontWeight: 500
2939
- },
2940
- children: [view === "week" ? (() => {
2941
- const d = /* @__PURE__ */ new Date(anchor + "T12:00:00+08:00");
2942
- const dow = (d.getDay() + 6) % 7;
2943
- const mon = new Date(d);
2944
- mon.setDate(d.getDate() - dow);
2945
- const sun = new Date(mon);
2946
- sun.setDate(mon.getDate() + 6);
2947
- const fmt = (x) => `${x.getMonth() + 1}/${x.getDate()}`;
2948
- return lang === "en" ? `Week of ${fmt(mon)} - ${fmt(sun)}` : `${mon.getMonth() + 1}月${mon.getDate()}日 - ${sun.getMonth() + 1}月${sun.getDate()}日`;
2949
- })() : `${Number(anchor.slice(0, 4))}年${Number(anchor.slice(5, 7))}月`, lang === "en" && view === "month" ? ` ${anchor.slice(0, 7)}` : ""]
2950
- }),
2951
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
2952
- onClick: () => setAnchor((a) => {
2953
- const d = /* @__PURE__ */ new Date(a + "T12:00:00+08:00");
2954
- if (view === "week") d.setDate(d.getDate() + 7);
2955
- else d.setMonth(d.getMonth() + 1);
2956
- const ms = d.getTime();
2957
- return new Date(ms).toLocaleDateString("en-CA", { timeZone: "Asia/Shanghai" });
2946
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2947
+ style: {
2948
+ fontSize: 12,
2949
+ color: "var(--dsw-alias-label-secondary)",
2950
+ fontWeight: 500
2951
+ },
2952
+ children: label
2958
2953
  }),
2959
- style: {
2960
- padding: "4px 10px",
2961
- fontSize: 12,
2962
- borderRadius: 6,
2963
- border: "1px solid var(--dsw-alias-border-l2)",
2964
- background: "var(--dsw-alias-bg-layer-3)",
2965
- cursor: "pointer"
2966
- },
2967
- children: [
2968
- lang === "en" ? "Next ›" : "下一",
2969
- view === "week" ? lang === "en" ? " Week" : "周" : lang === "en" ? " Month" : "月",
2970
- " ›"
2971
- ]
2972
- })
2973
- ]
2974
- }) : null,
2954
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2955
+ onClick: view === "month" ? () => shiftMonth(1) : () => shiftDays(view === "week" ? 7 : view === "quarter" ? 90 : 365),
2956
+ style: btn,
2957
+ children: lang === "en" ? nextLabelEn : nextLabel
2958
+ })
2959
+ ]
2960
+ });
2961
+ })(),
2975
2962
  totals ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatsCards, {
2976
2963
  t: lang === "en" ? (k, p) => {
2977
2964
  const enMap = {