@zendir/ui 0.1.14 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/index.js +6 -0
  3. package/dist/index.js.map +1 -1
  4. package/dist/react/astro/SimulationControls.js +3 -3
  5. package/dist/react/astro/SimulationControls.js.map +1 -1
  6. package/dist/react/astro/UnifiedTimeline.d.ts +70 -8
  7. package/dist/react/astro/UnifiedTimeline.js +886 -260
  8. package/dist/react/astro/UnifiedTimeline.js.map +1 -1
  9. package/dist/react/astro/index.d.ts +2 -1
  10. package/dist/react/charts/GroundTrackMap.d.ts +40 -1
  11. package/dist/react/charts/GroundTrackMap.js +98 -47
  12. package/dist/react/charts/GroundTrackMap.js.map +1 -1
  13. package/dist/react/charts/GroundTrackMapLeaflet.d.ts +11 -2
  14. package/dist/react/charts/GroundTrackMapLeaflet.js +128 -15
  15. package/dist/react/charts/GroundTrackMapLeaflet.js.map +1 -1
  16. package/dist/react/charts/index.d.ts +1 -1
  17. package/dist/react/charts/unified/theme.d.ts +7 -7
  18. package/dist/react/context/CategoryContext.d.ts +51 -0
  19. package/dist/react/context/CategoryContext.js +36 -0
  20. package/dist/react/context/CategoryContext.js.map +1 -0
  21. package/dist/react/context/index.d.ts +2 -0
  22. package/dist/react/index.d.ts +6 -4
  23. package/dist/react/types.d.ts +26 -0
  24. package/dist/react/types.js.map +1 -1
  25. package/dist/react/utils/categoryPalette.d.ts +43 -0
  26. package/dist/react/utils/categoryPalette.js +104 -0
  27. package/dist/react/utils/categoryPalette.js.map +1 -0
  28. package/dist/react/utils/index.d.ts +1 -0
  29. package/dist/react/utils/index.js.map +1 -1
  30. package/dist/react.js +6 -0
  31. package/dist/react.js.map +1 -1
  32. package/dist/style.css +49 -0
  33. package/package.json +1 -1
@@ -1,10 +1,140 @@
1
1
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
2
  import { memo, useState, useCallback, useMemo, useRef } from "react";
3
3
  import { classNames, safeAccentText } from "../utils/index.js";
4
+ import { useCategoryPalette } from "../context/CategoryContext.js";
4
5
  import { Icon } from "../core/Icon.js";
5
6
  import { Badge } from "../core/Badge.js";
6
7
  import { Tooltip } from "../core/Tooltip.js";
8
+ import { Button } from "../core/Button.js";
9
+ import { useBreakpoint } from "../core/layout/useBreakpoint.js";
7
10
  import { useTheme } from "../theme/ThemeProvider.js";
11
+ const Z = {
12
+ /** Base layer for track events / scatter dots */
13
+ trackEvent: 1,
14
+ /** Hovered track event raised above siblings */
15
+ trackEventHovered: 20,
16
+ /** Sticky column elements (playhead, track labels) */
17
+ sticky: 15,
18
+ /** Floating day-marker lines above track content */
19
+ dayMarker: 5,
20
+ /** Loading overlay covering chart content */
21
+ loadingOverlay: 100,
22
+ /** Floating tooltips — above all chart chrome */
23
+ tooltip: 1e3
24
+ };
25
+ const TIMELINE_FILTER_TEAM_NONE = "__zendir_team_none__";
26
+ const TIMELINE_FILTER_TEAM_LEGACY = "__zendir_team_legacy__";
27
+ function getTimelineTeamAccent(event) {
28
+ const t = event.team;
29
+ if (t == null ? void 0 : t.color) {
30
+ return { color: t.color, id: t.id, label: t.label };
31
+ }
32
+ if (event.color) {
33
+ return { color: event.color };
34
+ }
35
+ return null;
36
+ }
37
+ function getTimelineTeamDisplayLabel(event, accent) {
38
+ if (accent.label) return accent.label;
39
+ if (accent.id !== void 0 && accent.id !== "") return String(accent.id);
40
+ const args = event.arguments;
41
+ const argsTeam = args == null ? void 0 : args.Team;
42
+ if (argsTeam !== void 0 && argsTeam !== "") return String(argsTeam);
43
+ return event.badge || "Team";
44
+ }
45
+ function getTimelineTeamFilterKey(event) {
46
+ const accent = getTimelineTeamAccent(event);
47
+ if (!accent) return TIMELINE_FILTER_TEAM_NONE;
48
+ if (accent.id === void 0 || accent.id === "") return TIMELINE_FILTER_TEAM_LEGACY;
49
+ return String(accent.id);
50
+ }
51
+ const ALL_TIMELINE_STATUSES = [
52
+ "off",
53
+ "standby",
54
+ "normal",
55
+ "caution",
56
+ "serious",
57
+ "critical"
58
+ ];
59
+ function getTimelineEventDisplayStatus(event) {
60
+ if (event.badgeVariant && event.badgeVariant !== "default" && event.badgeVariant !== "primary") {
61
+ return event.badgeVariant;
62
+ }
63
+ return event.status ?? "normal";
64
+ }
65
+ function isTimelineFilterActive(filter) {
66
+ return Boolean(
67
+ filter.search && filter.search.trim() || filter.tracks && filter.tracks.length > 0 || filter.status && filter.status.length > 0 || filter.eventShape && filter.eventShape !== "all" || filter.teams && filter.teams.length > 0 || filter.teamColoredOnly
68
+ );
69
+ }
70
+ function normalizeTimelineFilter(f) {
71
+ var _a, _b, _c, _d;
72
+ const o = {};
73
+ const s = (_a = f.search) == null ? void 0 : _a.trim();
74
+ if (s) o.search = s;
75
+ if ((_b = f.tracks) == null ? void 0 : _b.length) o.tracks = [...f.tracks];
76
+ if ((_c = f.status) == null ? void 0 : _c.length) o.status = [...f.status];
77
+ if (f.eventShape && f.eventShape !== "all") o.eventShape = f.eventShape;
78
+ if ((_d = f.teams) == null ? void 0 : _d.length) o.teams = [...f.teams];
79
+ if (f.teamColoredOnly) o.teamColoredOnly = true;
80
+ return o;
81
+ }
82
+ function matchesTimelineFilter(event, filter) {
83
+ var _a, _b, _c, _d, _e, _f;
84
+ if (!isTimelineFilterActive(filter)) return true;
85
+ if ((_a = filter.tracks) == null ? void 0 : _a.length) {
86
+ const tid = event.track ?? "default";
87
+ if (!filter.tracks.includes(tid)) return false;
88
+ }
89
+ if ((_b = filter.status) == null ? void 0 : _b.length) {
90
+ const ds = getTimelineEventDisplayStatus(event);
91
+ if (!filter.status.includes(ds)) return false;
92
+ }
93
+ const q = (_c = filter.search) == null ? void 0 : _c.trim().toLowerCase();
94
+ if (q) {
95
+ const argStr = event.arguments ? Object.values(event.arguments).map((v) => String(v)).join(" ") : "";
96
+ const accent = getTimelineTeamAccent(event);
97
+ const teamHay = accent ? [accent.label, accent.id !== void 0 ? String(accent.id) : ""].filter(Boolean).join(" ") : "";
98
+ const hay = [
99
+ event.title,
100
+ event.subtitle,
101
+ event.badge,
102
+ event.track,
103
+ argStr,
104
+ teamHay
105
+ ].filter(Boolean).join(" ").toLowerCase();
106
+ if (!hay.includes(q)) return false;
107
+ }
108
+ if (filter.eventShape === "point") {
109
+ const endT = (_d = event.end) == null ? void 0 : _d.getTime();
110
+ const st = event.start.getTime();
111
+ if (endT != null && endT > st) return false;
112
+ }
113
+ if (filter.eventShape === "range") {
114
+ const endT = (_e = event.end) == null ? void 0 : _e.getTime();
115
+ const st = event.start.getTime();
116
+ if (endT == null || endT <= st) return false;
117
+ }
118
+ if ((_f = filter.teams) == null ? void 0 : _f.length) {
119
+ const teamKey = getTimelineTeamFilterKey(event);
120
+ if (!filter.teams.includes(teamKey)) return false;
121
+ }
122
+ if (filter.teamColoredOnly && !(filter.teams && filter.teams.length > 0) && !getTimelineTeamAccent(event)) {
123
+ return false;
124
+ }
125
+ return true;
126
+ }
127
+ function countTimelineFilterChips(filter) {
128
+ var _a, _b, _c;
129
+ let n = 0;
130
+ if ((_a = filter.search) == null ? void 0 : _a.trim()) n++;
131
+ if ((_b = filter.tracks) == null ? void 0 : _b.length) n++;
132
+ if ((_c = filter.status) == null ? void 0 : _c.length) n++;
133
+ if (filter.eventShape && filter.eventShape !== "all") n++;
134
+ if (filter.teams && filter.teams.length > 0) n++;
135
+ if (filter.teamColoredOnly && !(filter.teams && filter.teams.length > 0)) n++;
136
+ return n;
137
+ }
8
138
  function TimelineStatusMarker({
9
139
  status,
10
140
  fillColor,
@@ -44,6 +174,7 @@ const EventListItem = memo(function EventListItem2({
44
174
  ];
45
175
  const displayStatus = event.badgeVariant && event.badgeVariant !== "default" && event.badgeVariant !== "primary" ? event.badgeVariant : event.status ?? "normal";
46
176
  const markerColor = tokens.colors.status[displayStatus];
177
+ const teamAccent = getTimelineTeamAccent(event);
47
178
  return /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "16px" }, children: [
48
179
  /* @__PURE__ */ jsxs(
49
180
  "div",
@@ -81,7 +212,7 @@ const EventListItem = memo(function EventListItem2({
81
212
  position: "relative",
82
213
  flex: 1,
83
214
  marginBottom: "16px",
84
- padding: event.color ? "19px 20px 16px 20px" : "16px 20px",
215
+ padding: teamAccent ? "19px 20px 16px 20px" : "16px 20px",
85
216
  backgroundColor: tokens.colors.background.surface,
86
217
  border: `1px solid ${hovered ? tokens.colors.accent.primary : tokens.colors.border.muted}`,
87
218
  borderRadius: tokens.borderRadius.lg,
@@ -91,7 +222,7 @@ const EventListItem = memo(function EventListItem2({
91
222
  boxShadow: hovered ? `0 4px 20px ${tokens.colors.accent.primary}15, 0 0 0 1px ${tokens.colors.accent.primary}20` : "none"
92
223
  },
93
224
  children: [
94
- event.color && /* @__PURE__ */ jsx(
225
+ teamAccent && /* @__PURE__ */ jsx(
95
226
  "div",
96
227
  {
97
228
  style: {
@@ -100,7 +231,7 @@ const EventListItem = memo(function EventListItem2({
100
231
  left: 0,
101
232
  right: 0,
102
233
  height: 3,
103
- backgroundColor: event.color
234
+ backgroundColor: teamAccent.color
104
235
  }
105
236
  }
106
237
  ),
@@ -313,6 +444,7 @@ const ChartView = memo(function ChartView2({
313
444
  const [hoveredEvent, setHoveredEvent] = useState(null);
314
445
  const [tooltipPos, setTooltipPos] = useState(null);
315
446
  const scrollContainerRef = useRef(null);
447
+ const [scrollX, setScrollX] = useState(0);
316
448
  const totalDuration = end.getTime() - start.getTime();
317
449
  const trackLabelWidth = 130;
318
450
  const timeLabels = useMemo(() => {
@@ -416,6 +548,22 @@ const ChartView = memo(function ChartView2({
416
548
  if (!status) return tokens.colors.accent.primary;
417
549
  return tokens.colors.status[status] || tokens.colors.accent.primary;
418
550
  };
551
+ const isEventVisible = useCallback(
552
+ (event) => {
553
+ const container = scrollContainerRef.current;
554
+ if (!container) return true;
555
+ const viewportWidth = container.clientWidth - trackLabelWidth;
556
+ if (viewportWidth <= 0) return true;
557
+ const totalWidth = container.scrollWidth - trackLabelWidth;
558
+ const eventStartMs = event.start.getTime();
559
+ const eventEndMs = (event.end ?? new Date(eventStartMs + 3e5)).getTime();
560
+ const leftPx = (eventStartMs - start.getTime()) / totalDuration * totalWidth;
561
+ const rightPx = (eventEndMs - start.getTime()) / totalDuration * totalWidth;
562
+ const overscan = 50;
563
+ return rightPx >= scrollX - overscan && leftPx <= scrollX + viewportWidth + overscan;
564
+ },
565
+ [start, totalDuration, scrollX, trackLabelWidth]
566
+ );
419
567
  const getStatusLabel = (status) => {
420
568
  const labels = {
421
569
  normal: "Normal",
@@ -437,6 +585,7 @@ const ChartView = memo(function ChartView2({
437
585
  {
438
586
  ref: scrollContainerRef,
439
587
  className: "zendir-timeline-scroll",
588
+ onScroll: (e) => setScrollX(e.currentTarget.scrollLeft),
440
589
  style: {
441
590
  overflowX: "auto",
442
591
  overflowY: "hidden",
@@ -460,7 +609,7 @@ const ChartView = memo(function ChartView2({
460
609
  borderBottom: `1px solid ${tokens.colors.accent.primary}20`,
461
610
  position: "sticky",
462
611
  top: 0,
463
- zIndex: 20,
612
+ zIndex: Z.trackEventHovered,
464
613
  backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.surface,
465
614
  ...isTransparentTheme && { backdropFilter: "blur(12px)", WebkitBackdropFilter: "blur(12px)" }
466
615
  },
@@ -473,7 +622,7 @@ const ChartView = memo(function ChartView2({
473
622
  flexShrink: 0,
474
623
  position: "sticky",
475
624
  left: 0,
476
- zIndex: 25,
625
+ zIndex: Z.trackEventHovered + 5,
477
626
  backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.surface,
478
627
  ...isTransparentTheme && { backdropFilter: "blur(12px)", WebkitBackdropFilter: "blur(12px)" },
479
628
  borderRight: `1px solid ${tokens.colors.border.muted}30`,
@@ -502,7 +651,7 @@ const ChartView = memo(function ChartView2({
502
651
  display: "flex",
503
652
  flexDirection: "column",
504
653
  alignItems: "center",
505
- zIndex: 5
654
+ zIndex: Z.dayMarker
506
655
  },
507
656
  children: /* @__PURE__ */ jsx(
508
657
  "span",
@@ -562,7 +711,7 @@ const ChartView = memo(function ChartView2({
562
711
  flexShrink: 0,
563
712
  position: "sticky",
564
713
  left: 0,
565
- zIndex: 15,
714
+ zIndex: Z.sticky,
566
715
  display: "flex",
567
716
  alignItems: "center",
568
717
  paddingLeft: 12,
@@ -599,25 +748,28 @@ const ChartView = memo(function ChartView2({
599
748
  bottom: 0,
600
749
  width: 1,
601
750
  backgroundColor: `${tokens.colors.status.caution}25`,
602
- zIndex: 1,
751
+ zIndex: Z.trackEvent,
603
752
  pointerEvents: "none"
604
753
  }
605
754
  },
606
755
  `day-line-${index}`
607
756
  )),
608
- (eventsByTrack[track.id] || []).map((event) => {
757
+ (eventsByTrack[track.id] || []).filter(isEventVisible).map((event) => {
609
758
  const { left, width } = getEventPosition(event);
610
- const color = getStatusColor(event.status);
759
+ const displayStatus = event.badgeVariant && event.badgeVariant !== "default" && event.badgeVariant !== "primary" ? event.badgeVariant : event.status ?? "normal";
760
+ const color = getStatusColor(displayStatus);
611
761
  const isHovered = hoveredEvent === event.id;
612
762
  const overlap = eventOverlaps[event.id];
613
763
  const hasOverlap = overlap && overlap.overlapCount > 0;
614
- const cardHeight = hasOverlap ? 28 : 36;
764
+ const teamAccent = getTimelineTeamAccent(event);
765
+ const cardHeight = hasOverlap ? 30 : 38;
615
766
  const stackOffset = overlap ? overlap.stackIndex * 30 : 0;
616
767
  const baseTop = ((track.height || trackHeight) - cardHeight) / 2;
617
768
  return /* @__PURE__ */ jsxs(
618
769
  "button",
619
770
  {
620
771
  type: "button",
772
+ "aria-label": `${event.title}${event.status ? `, status: ${event.status}` : ""}`,
621
773
  onClick: () => onEventClick == null ? void 0 : onEventClick(event),
622
774
  onMouseEnter: (e) => {
623
775
  setHoveredEvent(event.id);
@@ -628,6 +780,15 @@ const ChartView = memo(function ChartView2({
628
780
  setHoveredEvent(null);
629
781
  setTooltipPos(null);
630
782
  },
783
+ onFocus: (e) => {
784
+ setHoveredEvent(event.id);
785
+ const rect = e.currentTarget.getBoundingClientRect();
786
+ setTooltipPos({ x: rect.left + rect.width / 2, y: rect.top - 8 });
787
+ },
788
+ onBlur: () => {
789
+ setHoveredEvent(null);
790
+ setTooltipPos(null);
791
+ },
631
792
  className: "zendir-timeline-region",
632
793
  style: {
633
794
  position: "absolute",
@@ -643,13 +804,13 @@ const ChartView = memo(function ChartView2({
643
804
  display: "flex",
644
805
  alignItems: "center",
645
806
  overflow: "hidden",
646
- zIndex: isHovered ? 100 : 1,
807
+ zIndex: isHovered ? Z.trackEventHovered : Z.trackEvent,
647
808
  boxShadow: isHovered ? `0 8px 24px rgba(0, 0, 0, 0.5), 0 0 0 1px ${color}60` : "0 2px 6px rgba(0, 0, 0, 0.25)",
648
809
  transition: "all 150ms cubic-bezier(0.4, 0, 0.2, 1)",
649
810
  transform: isHovered ? "translateY(-3px) scale(1.02)" : "none"
650
811
  },
651
812
  children: [
652
- event.color && /* @__PURE__ */ jsx(
813
+ teamAccent ? /* @__PURE__ */ jsx(
653
814
  "div",
654
815
  {
655
816
  style: {
@@ -657,57 +818,63 @@ const ChartView = memo(function ChartView2({
657
818
  top: 0,
658
819
  left: 0,
659
820
  right: 0,
660
- height: 3,
661
- backgroundColor: event.color,
662
- borderRadius: "4px 4px 0 0"
663
- }
664
- }
665
- ),
666
- /* @__PURE__ */ jsx(
667
- "div",
668
- {
669
- style: {
670
- width: 4,
671
- height: "100%",
672
- backgroundColor: color,
673
- flexShrink: 0,
674
- borderRadius: "2px 0 0 2px"
675
- }
821
+ height: 2,
822
+ backgroundColor: teamAccent.color,
823
+ borderRadius: "4px 4px 0 0",
824
+ zIndex: 1,
825
+ pointerEvents: "none"
826
+ },
827
+ "aria-hidden": true
676
828
  }
677
- ),
829
+ ) : null,
678
830
  /* @__PURE__ */ jsxs(
679
831
  "div",
680
832
  {
681
833
  style: {
682
834
  flex: 1,
683
- padding: "4px 8px",
835
+ padding: `${teamAccent ? 6 : 4}px 8px 4px 8px`,
684
836
  overflow: "hidden",
685
- minWidth: 0
837
+ minWidth: 0,
838
+ display: "flex",
839
+ flexDirection: "column",
840
+ justifyContent: "center"
686
841
  },
687
842
  children: [
688
- /* @__PURE__ */ jsx(
843
+ /* @__PURE__ */ jsxs(
689
844
  "div",
690
845
  {
691
846
  style: {
692
847
  display: "flex",
693
848
  alignItems: "center",
694
- gap: 4
849
+ gap: 6,
850
+ minWidth: 0
695
851
  },
696
- children: /* @__PURE__ */ jsx(
697
- "span",
698
- {
699
- style: {
700
- fontSize: "0.6875rem",
701
- fontWeight: 500,
702
- // AstroUXDS medium (was 600)
703
- color: "#fff",
704
- whiteSpace: "nowrap",
705
- overflow: "hidden",
706
- textOverflow: "ellipsis"
707
- },
708
- children: event.title
709
- }
710
- )
852
+ children: [
853
+ /* @__PURE__ */ jsx(
854
+ TimelineStatusMarker,
855
+ {
856
+ status: displayStatus,
857
+ fillColor: color,
858
+ size: hasOverlap ? 8 : 10
859
+ }
860
+ ),
861
+ /* @__PURE__ */ jsx(
862
+ "span",
863
+ {
864
+ style: {
865
+ fontSize: "0.6875rem",
866
+ fontWeight: 500,
867
+ // AstroUXDS medium (was 600)
868
+ color: "#fff",
869
+ whiteSpace: "nowrap",
870
+ overflow: "hidden",
871
+ textOverflow: "ellipsis",
872
+ minWidth: 0
873
+ },
874
+ children: event.title
875
+ }
876
+ )
877
+ ]
711
878
  }
712
879
  ),
713
880
  event.subtitle && !hasOverlap && /* @__PURE__ */ jsx(
@@ -719,7 +886,8 @@ const ChartView = memo(function ChartView2({
719
886
  whiteSpace: "nowrap",
720
887
  overflow: "hidden",
721
888
  textOverflow: "ellipsis",
722
- marginTop: 1
889
+ marginTop: 1,
890
+ paddingLeft: 16
723
891
  },
724
892
  children: event.subtitle
725
893
  }
@@ -745,7 +913,7 @@ const ChartView = memo(function ChartView2({
745
913
  fontWeight: 700,
746
914
  color: "#000",
747
915
  boxShadow: "0 2px 6px rgba(0, 0, 0, 0.3)",
748
- zIndex: 10
916
+ zIndex: Z.sticky - 5
749
917
  },
750
918
  title: `${overlap.overlapCount + 1} overlapping events`,
751
919
  children: [
@@ -777,7 +945,7 @@ const ChartView = memo(function ChartView2({
777
945
  width: 2,
778
946
  backgroundColor: tokens.colors.accent.primary,
779
947
  boxShadow: `0 0 12px ${tokens.colors.accent.primary}`,
780
- zIndex: 20,
948
+ zIndex: Z.trackEventHovered,
781
949
  pointerEvents: "none"
782
950
  },
783
951
  children: [
@@ -828,8 +996,8 @@ const ChartView = memo(function ChartView2({
828
996
  }
829
997
  ),
830
998
  hoveredEventData && tooltipPos && (() => {
831
- var _a;
832
999
  const tooltipColor = getStatusColor(hoveredEventData.status);
1000
+ const teamTip = getTimelineTeamAccent(hoveredEventData);
833
1001
  return /* @__PURE__ */ jsxs(
834
1002
  "div",
835
1003
  {
@@ -838,7 +1006,7 @@ const ChartView = memo(function ChartView2({
838
1006
  left: tooltipPos.x,
839
1007
  top: tooltipPos.y,
840
1008
  transform: "translate(-50%, -100%)",
841
- zIndex: 1e3,
1009
+ zIndex: Z.tooltip,
842
1010
  pointerEvents: "none",
843
1011
  animation: "zendir-tooltip-appear 150ms ease"
844
1012
  },
@@ -897,9 +1065,9 @@ const ChartView = memo(function ChartView2({
897
1065
  }
898
1066
  )
899
1067
  ] }),
900
- hoveredEventData.color && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 6 }, children: [
901
- /* @__PURE__ */ jsx("div", { style: { width: 8, height: 8, borderRadius: 2, backgroundColor: hoveredEventData.color, flexShrink: 0 } }),
902
- /* @__PURE__ */ jsx("span", { style: { fontSize: "0.6875rem", color: hoveredEventData.color, fontWeight: 500 }, children: ((_a = hoveredEventData.arguments) == null ? void 0 : _a.Team) || hoveredEventData.badge || "Team" })
1068
+ teamTip && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 6 }, children: [
1069
+ /* @__PURE__ */ jsx("div", { style: { width: 8, height: 8, borderRadius: 2, backgroundColor: teamTip.color, flexShrink: 0 } }),
1070
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "0.6875rem", color: teamTip.color, fontWeight: 500 }, children: getTimelineTeamDisplayLabel(hoveredEventData, teamTip) })
903
1071
  ] }),
904
1072
  /* @__PURE__ */ jsx(
905
1073
  "div",
@@ -1180,11 +1348,14 @@ const ScatterView = memo(function ScatterView2({
1180
1348
  const color = getStatusColor(event.status);
1181
1349
  const isHovered = hoveredEvent === event.id;
1182
1350
  const dotSize = isHovered ? 16 : 12;
1351
+ const hitboxPad = Math.max(0, (24 - dotSize) / 2);
1183
1352
  const effectiveStatus = event.status ?? "normal";
1353
+ const scatterTeamAccent = getTimelineTeamAccent(event);
1184
1354
  return /* @__PURE__ */ jsx(
1185
1355
  "button",
1186
1356
  {
1187
1357
  type: "button",
1358
+ "aria-label": `${event.title}${event.status ? `, status: ${event.status}` : ""}`,
1188
1359
  onClick: () => onEventClick == null ? void 0 : onEventClick(event),
1189
1360
  onMouseEnter: (e) => {
1190
1361
  setHoveredEvent(event.id);
@@ -1195,23 +1366,33 @@ const ScatterView = memo(function ScatterView2({
1195
1366
  setHoveredEvent(null);
1196
1367
  setTooltipPosition(null);
1197
1368
  },
1369
+ onFocus: (e) => {
1370
+ setHoveredEvent(event.id);
1371
+ const rect = e.currentTarget.getBoundingClientRect();
1372
+ setTooltipPosition({ x: rect.left + rect.width / 2, y: rect.top });
1373
+ },
1374
+ onBlur: () => {
1375
+ setHoveredEvent(null);
1376
+ setTooltipPosition(null);
1377
+ },
1198
1378
  style: {
1199
1379
  position: "absolute",
1200
1380
  left: `${leftPercent}%`,
1201
1381
  transform: "translateX(-50%)",
1382
+ /* visible size + transparent padding = ≥24px touch/click target */
1202
1383
  width: dotSize,
1203
1384
  height: dotSize,
1204
- padding: 0,
1385
+ padding: hitboxPad,
1205
1386
  background: "none",
1206
1387
  border: "none",
1207
1388
  cursor: "pointer",
1208
1389
  transition: "all 200ms cubic-bezier(0.34, 1.56, 0.64, 1)",
1209
- zIndex: isHovered ? 20 : 1,
1390
+ zIndex: isHovered ? Z.trackEventHovered : Z.trackEvent,
1210
1391
  lineHeight: 0,
1211
1392
  borderRadius: "50%",
1212
- boxShadow: event.color ? `0 0 0 2px ${event.color}` : void 0
1393
+ boxSizing: "content-box",
1394
+ boxShadow: scatterTeamAccent ? `0 0 0 2px ${scatterTeamAccent.color}` : void 0
1213
1395
  },
1214
- "aria-label": event.title,
1215
1396
  children: /* @__PURE__ */ jsx(TimelineStatusMarker, { status: effectiveStatus, fillColor: color, size: dotSize })
1216
1397
  },
1217
1398
  event.id
@@ -1228,8 +1409,8 @@ const ScatterView = memo(function ScatterView2({
1228
1409
  }
1229
1410
  ),
1230
1411
  hoveredEventData && tooltipPosition && (() => {
1231
- var _a;
1232
1412
  const scatterTipColor = getStatusColor(hoveredEventData.status);
1413
+ const scatterTeamTip = getTimelineTeamAccent(hoveredEventData);
1233
1414
  return /* @__PURE__ */ jsxs(
1234
1415
  "div",
1235
1416
  {
@@ -1245,7 +1426,7 @@ const ScatterView = memo(function ScatterView2({
1245
1426
  padding: "12px 16px",
1246
1427
  minWidth: 200,
1247
1428
  maxWidth: 320,
1248
- zIndex: 1e3,
1429
+ zIndex: Z.tooltip,
1249
1430
  boxShadow: `0 12px 40px rgba(0, 0, 0, 0.5), 0 0 0 1px ${scatterTipColor}20`,
1250
1431
  backdropFilter: "blur(12px)",
1251
1432
  animation: "zendir-tooltip-in 150ms cubic-bezier(0.34, 1.56, 0.64, 1)"
@@ -1305,9 +1486,9 @@ const ScatterView = memo(function ScatterView2({
1305
1486
  }
1306
1487
  )
1307
1488
  ] }),
1308
- hoveredEventData.color && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 6 }, children: [
1309
- /* @__PURE__ */ jsx("div", { style: { width: 8, height: 8, borderRadius: 2, backgroundColor: hoveredEventData.color, flexShrink: 0 } }),
1310
- /* @__PURE__ */ jsx("span", { style: { fontSize: "0.6875rem", color: hoveredEventData.color, fontWeight: 500 }, children: ((_a = hoveredEventData.arguments) == null ? void 0 : _a.Team) || hoveredEventData.badge || "Team" })
1489
+ scatterTeamTip && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 6, marginBottom: 6 }, children: [
1490
+ /* @__PURE__ */ jsx("div", { style: { width: 8, height: 8, borderRadius: 2, backgroundColor: scatterTeamTip.color, flexShrink: 0 } }),
1491
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "0.6875rem", color: scatterTeamTip.color, fontWeight: 500 }, children: getTimelineTeamDisplayLabel(hoveredEventData, scatterTeamTip) })
1311
1492
  ] }),
1312
1493
  /* @__PURE__ */ jsx(
1313
1494
  "div",
@@ -1404,6 +1585,243 @@ const ScatterView = memo(function ScatterView2({
1404
1585
  ` })
1405
1586
  ] });
1406
1587
  });
1588
+ function buildTeamFilterOptions(events, tokens, categoryLabel = "team") {
1589
+ const map = /* @__PURE__ */ new Map();
1590
+ for (const event of events) {
1591
+ const key = getTimelineTeamFilterKey(event);
1592
+ if (map.has(key)) continue;
1593
+ const accent = getTimelineTeamAccent(event);
1594
+ const isNone = key === TIMELINE_FILTER_TEAM_NONE;
1595
+ const isLegacy = key === TIMELINE_FILTER_TEAM_LEGACY;
1596
+ const label = isNone ? `No ${categoryLabel.toLowerCase()}` : isLegacy ? "Accent only" : accent ? getTimelineTeamDisplayLabel(event, accent) : key;
1597
+ const color = isNone ? "" : (accent == null ? void 0 : accent.color) ?? tokens.colors.text.tertiary;
1598
+ map.set(key, { key, label, color, isNoneBucket: isNone });
1599
+ }
1600
+ const list = [...map.values()];
1601
+ list.sort((a, b) => {
1602
+ if (a.isNoneBucket !== b.isNoneBucket) return a.isNoneBucket ? 1 : -1;
1603
+ if (a.key === TIMELINE_FILTER_TEAM_LEGACY) return b.key === TIMELINE_FILTER_TEAM_NONE ? -1 : 1;
1604
+ if (b.key === TIMELINE_FILTER_TEAM_LEGACY) return a.key === TIMELINE_FILTER_TEAM_NONE ? 1 : -1;
1605
+ return a.label.localeCompare(b.label, void 0, { sensitivity: "base" });
1606
+ });
1607
+ return list;
1608
+ }
1609
+ const TimelineFiltersPanel = memo(function TimelineFiltersPanel2({
1610
+ filter,
1611
+ onFilterChange,
1612
+ trackOptions,
1613
+ events,
1614
+ expanded,
1615
+ teamLabel
1616
+ }) {
1617
+ const { tokens, theme } = useTheme();
1618
+ const isTransparentTheme = theme === "transparent" || theme === "transparent-bold" || theme === "transparent-minimal";
1619
+ const accentColor = tokens.colors.accent.primary;
1620
+ const teamOptions = useMemo(() => buildTeamFilterOptions(events, tokens, teamLabel), [events, tokens, teamLabel]);
1621
+ const patch = useCallback(
1622
+ (partial) => {
1623
+ onFilterChange(normalizeTimelineFilter({ ...filter, ...partial }));
1624
+ },
1625
+ [filter, onFilterChange]
1626
+ );
1627
+ const clearAll = useCallback(() => {
1628
+ onFilterChange({});
1629
+ }, [onFilterChange]);
1630
+ const toggleTrack = useCallback(
1631
+ (id) => {
1632
+ const cur = filter.tracks ?? [];
1633
+ const next = cur.includes(id) ? cur.filter((t) => t !== id) : [...cur, id];
1634
+ patch({ tracks: next.length ? next : void 0 });
1635
+ },
1636
+ [filter.tracks, patch]
1637
+ );
1638
+ const toggleStatus = useCallback(
1639
+ (st) => {
1640
+ const cur = filter.status ?? [];
1641
+ const next = cur.includes(st) ? cur.filter((s) => s !== st) : [...cur, st];
1642
+ patch({ status: next.length ? next : void 0 });
1643
+ },
1644
+ [filter.status, patch]
1645
+ );
1646
+ const toggleTeam = useCallback(
1647
+ (teamKey) => {
1648
+ const cur = filter.teams ?? [];
1649
+ const next = cur.includes(teamKey) ? cur.filter((k) => k !== teamKey) : [...cur, teamKey];
1650
+ patch({ teams: next.length ? next : void 0 });
1651
+ },
1652
+ [filter.teams, patch]
1653
+ );
1654
+ const pillStyle = useCallback(
1655
+ (active, tintColor) => {
1656
+ const c = tintColor || accentColor;
1657
+ return {
1658
+ padding: "2px 8px",
1659
+ borderRadius: tokens.borderRadius.md,
1660
+ fontSize: tokens.typography.fontSize.xs,
1661
+ fontWeight: 500,
1662
+ textTransform: "uppercase",
1663
+ letterSpacing: "0.06em",
1664
+ lineHeight: 1.4,
1665
+ border: `1px solid ${active ? `${c}66` : tokens.colors.border.muted}`,
1666
+ backgroundColor: active ? `${c}22` : "transparent",
1667
+ color: active ? c : tokens.colors.text.secondary,
1668
+ cursor: "pointer",
1669
+ transition: tokens.animation.fast
1670
+ };
1671
+ },
1672
+ [accentColor, tokens]
1673
+ );
1674
+ const sectionHeader = (text) => /* @__PURE__ */ jsx(
1675
+ "div",
1676
+ {
1677
+ style: {
1678
+ fontSize: tokens.typography.fontSize.xs,
1679
+ fontWeight: 700,
1680
+ letterSpacing: "0.06em",
1681
+ textTransform: "uppercase",
1682
+ color: tokens.colors.text.tertiary,
1683
+ marginBottom: 8
1684
+ },
1685
+ children: text
1686
+ }
1687
+ );
1688
+ if (!expanded) return null;
1689
+ return /* @__PURE__ */ jsxs(
1690
+ "div",
1691
+ {
1692
+ id: "zendir-timeline-filters-panel",
1693
+ style: {
1694
+ padding: `${tokens.spacing.smd} ${tokens.spacing.md}`,
1695
+ borderBottom: `1px solid ${tokens.colors.border.muted}`,
1696
+ backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.surface,
1697
+ ...isTransparentTheme && { backdropFilter: "blur(8px)", WebkitBackdropFilter: "blur(8px)" },
1698
+ display: "flex",
1699
+ flexDirection: "column",
1700
+ gap: tokens.spacing.smd
1701
+ },
1702
+ children: [
1703
+ trackOptions.length > 0 ? /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": "Filter by track", children: [
1704
+ sectionHeader("Tracks"),
1705
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: tokens.spacing.xs }, children: trackOptions.map((t) => {
1706
+ var _a;
1707
+ const active = ((_a = filter.tracks) == null ? void 0 : _a.includes(t.id)) ?? false;
1708
+ return /* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": active, onClick: () => toggleTrack(t.id), style: pillStyle(active), children: t.label }, t.id);
1709
+ }) })
1710
+ ] }) : null,
1711
+ /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": "Filter by status", children: [
1712
+ sectionHeader("Status"),
1713
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: tokens.spacing.xs }, children: ALL_TIMELINE_STATUSES.map((st) => {
1714
+ var _a;
1715
+ const active = ((_a = filter.status) == null ? void 0 : _a.includes(st)) ?? false;
1716
+ const statusColor = st ? tokens.colors.status[st] : accentColor;
1717
+ return /* @__PURE__ */ jsxs(
1718
+ "button",
1719
+ {
1720
+ type: "button",
1721
+ "aria-pressed": active,
1722
+ onClick: () => toggleStatus(st),
1723
+ style: {
1724
+ ...pillStyle(active, statusColor),
1725
+ display: "inline-flex",
1726
+ alignItems: "center",
1727
+ gap: 6
1728
+ },
1729
+ children: [
1730
+ /* @__PURE__ */ jsx(
1731
+ "span",
1732
+ {
1733
+ style: {
1734
+ width: 8,
1735
+ height: 8,
1736
+ borderRadius: "50%",
1737
+ backgroundColor: active ? statusColor : `${statusColor}50`,
1738
+ flexShrink: 0,
1739
+ transition: tokens.animation.fast
1740
+ }
1741
+ }
1742
+ ),
1743
+ st
1744
+ ]
1745
+ },
1746
+ st
1747
+ );
1748
+ }) })
1749
+ ] }),
1750
+ /* @__PURE__ */ jsxs(
1751
+ "div",
1752
+ {
1753
+ style: {
1754
+ display: "flex",
1755
+ flexWrap: "wrap",
1756
+ gap: `${tokens.spacing.smd} ${tokens.spacing.lg}`,
1757
+ alignItems: "flex-start"
1758
+ },
1759
+ children: [
1760
+ /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": "Filter by duration", style: { minWidth: 0 }, children: [
1761
+ sectionHeader("Duration"),
1762
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: tokens.spacing.xs }, children: ["all", "point", "range"].map((key) => {
1763
+ const active = (filter.eventShape ?? "all") === key;
1764
+ return /* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": active, onClick: () => patch({ eventShape: key }), style: pillStyle(active), children: key === "all" ? "All" : key === "point" ? "Instant" : "Range" }, key);
1765
+ }) })
1766
+ ] }),
1767
+ teamOptions.length > 0 ? /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": `Filter by ${teamLabel.toLowerCase()}`, style: { flex: 1, minWidth: 180 }, children: [
1768
+ sectionHeader(teamLabel),
1769
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: tokens.spacing.xs }, children: teamOptions.map((opt) => {
1770
+ var _a, _b, _c;
1771
+ const active = ((_a = filter.teams) == null ? void 0 : _a.includes(opt.key)) ?? false;
1772
+ return /* @__PURE__ */ jsxs(
1773
+ "button",
1774
+ {
1775
+ type: "button",
1776
+ "aria-pressed": active,
1777
+ onClick: () => toggleTeam(opt.key),
1778
+ style: {
1779
+ ...pillStyle(active, opt.color || void 0),
1780
+ display: "inline-flex",
1781
+ alignItems: "center",
1782
+ gap: 6,
1783
+ maxWidth: "100%"
1784
+ },
1785
+ children: [
1786
+ opt.isNoneBucket ? /* @__PURE__ */ jsx(
1787
+ "span",
1788
+ {
1789
+ style: {
1790
+ width: 8,
1791
+ height: 8,
1792
+ borderRadius: "50%",
1793
+ border: `${((_c = (_b = tokens.borders) == null ? void 0 : _b.width) == null ? void 0 : _c.thick) ?? "2px"} solid ${tokens.colors.border.muted}`,
1794
+ flexShrink: 0,
1795
+ boxSizing: "border-box"
1796
+ }
1797
+ }
1798
+ ) : /* @__PURE__ */ jsx(
1799
+ "span",
1800
+ {
1801
+ style: {
1802
+ width: 8,
1803
+ height: 8,
1804
+ borderRadius: "50%",
1805
+ backgroundColor: opt.color,
1806
+ flexShrink: 0
1807
+ }
1808
+ }
1809
+ ),
1810
+ /* @__PURE__ */ jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: opt.label })
1811
+ ]
1812
+ },
1813
+ opt.key
1814
+ );
1815
+ }) })
1816
+ ] }) : null
1817
+ ]
1818
+ }
1819
+ ),
1820
+ isTimelineFilterActive(filter) ? /* @__PURE__ */ jsx("div", { style: { display: "flex", justifyContent: "flex-end" }, children: /* @__PURE__ */ jsx(Button, { variant: "borderless", size: "small", onClick: clearAll, children: "Clear all" }) }) : null
1821
+ ]
1822
+ }
1823
+ );
1824
+ });
1407
1825
  const UnifiedTimeline = memo(function UnifiedTimeline2({
1408
1826
  title = "Timeline",
1409
1827
  events,
@@ -1421,7 +1839,9 @@ const UnifiedTimeline = memo(function UnifiedTimeline2({
1421
1839
  timeFormat = "absolute",
1422
1840
  zoomable = false,
1423
1841
  initialZoom = 1,
1424
- showFilters: _showFilters = false,
1842
+ showFilters: _showFilters = true,
1843
+ filtersDefaultExpanded = false,
1844
+ hideEmptyTracksWhenFiltered = true,
1425
1845
  filter: _filter,
1426
1846
  onFilterChange: _onFilterChange,
1427
1847
  showCount: _showCount = true,
@@ -1430,13 +1850,34 @@ const UnifiedTimeline = memo(function UnifiedTimeline2({
1430
1850
  showPlayhead = true,
1431
1851
  playheadTime,
1432
1852
  onPlayheadChange: _onPlayheadChangeTimeline,
1433
- showDayMarkers = true
1853
+ showDayMarkers = true,
1854
+ teamLabel: teamLabelProp
1434
1855
  }) {
1435
1856
  const { tokens, theme } = useTheme();
1436
1857
  const isTransparentTheme = theme === "transparent" || theme === "transparent-bold" || theme === "transparent-minimal";
1858
+ const { isMobile } = useBreakpoint();
1859
+ const categoryCtx = useCategoryPalette();
1860
+ const teamLabel = teamLabelProp ?? (categoryCtx == null ? void 0 : categoryCtx.categoryLabel) ?? "Teams";
1437
1861
  const [internalViewMode, setInternalViewMode] = useState(defaultView);
1438
1862
  const [zoom, setZoom] = useState(initialZoom);
1439
1863
  const [_internalFilter, _setInternalFilter] = useState({});
1864
+ const [filtersExpanded, setFiltersExpanded] = useState(filtersDefaultExpanded);
1865
+ const filter = _filter !== void 0 ? _filter : _internalFilter;
1866
+ const filterChipCount = _showFilters ? countTimelineFilterChips(filter) : 0;
1867
+ const setFilter = useCallback(
1868
+ (next) => {
1869
+ const n = normalizeTimelineFilter(next);
1870
+ if (_filter === void 0) _setInternalFilter(n);
1871
+ _onFilterChange == null ? void 0 : _onFilterChange(n);
1872
+ },
1873
+ [_filter, _onFilterChange]
1874
+ );
1875
+ const patchFilter = useCallback(
1876
+ (partial) => {
1877
+ setFilter(normalizeTimelineFilter({ ...filter, ...partial }));
1878
+ },
1879
+ [filter, setFilter]
1880
+ );
1440
1881
  const viewMode = controlledViewMode ?? internalViewMode;
1441
1882
  const handleViewModeChange = useCallback((mode) => {
1442
1883
  setInternalViewMode(mode);
@@ -1481,9 +1922,28 @@ const UnifiedTimeline = memo(function UnifiedTimeline2({
1481
1922
  height: trackHeight
1482
1923
  }));
1483
1924
  }, [events, tracks, trackHeight]);
1925
+ const filteredEvents = useMemo(
1926
+ () => events.filter((e) => matchesTimelineFilter(e, filter)),
1927
+ [events, filter]
1928
+ );
1929
+ const effectiveTracksForViews = useMemo(() => {
1930
+ if (!hideEmptyTracksWhenFiltered || !isTimelineFilterActive(filter)) return effectiveTracks;
1931
+ const ids = new Set(filteredEvents.map((e) => e.track || "default"));
1932
+ return effectiveTracks.filter((t) => ids.has(t.id));
1933
+ }, [effectiveTracks, filteredEvents, filter, hideEmptyTracksWhenFiltered]);
1934
+ const trackOptionsForToolbar = useMemo(() => {
1935
+ if (tracks.length > 0) return tracks.map((t) => ({ id: t.id, label: t.label }));
1936
+ const u = /* @__PURE__ */ new Set();
1937
+ events.forEach((e) => {
1938
+ if (e.track) u.add(e.track);
1939
+ });
1940
+ if (u.size === 0) return [{ id: "default", label: "Events" }];
1941
+ return [...u].sort().map((id) => ({ id, label: id }));
1942
+ }, [events, tracks]);
1484
1943
  const sortedEvents = useMemo(() => {
1485
- return [...events].sort((a, b) => b.start.getTime() - a.start.getTime());
1486
- }, [events]);
1944
+ return [...filteredEvents].sort((a, b) => b.start.getTime() - a.start.getTime());
1945
+ }, [filteredEvents]);
1946
+ const countLabel = !_showCount ? title : `${title} (${filteredEvents.length}${filteredEvents.length !== events.length ? ` / ${events.length}` : ""})`;
1487
1947
  return /* @__PURE__ */ jsxs(
1488
1948
  "div",
1489
1949
  {
@@ -1501,202 +1961,323 @@ const UnifiedTimeline = memo(function UnifiedTimeline2({
1501
1961
  {
1502
1962
  style: {
1503
1963
  display: "flex",
1504
- alignItems: "center",
1505
- justifyContent: "space-between",
1506
- padding: "16px 20px",
1964
+ flexDirection: isMobile ? "column" : "row",
1965
+ alignItems: isMobile ? "stretch" : "center",
1966
+ padding: `${tokens.spacing.smd} ${tokens.spacing.md}`,
1967
+ gap: isMobile ? tokens.spacing.xs : tokens.spacing.sm,
1507
1968
  borderBottom: `1px solid ${tokens.colors.border.muted}`
1508
1969
  },
1509
1970
  children: [
1510
- /* @__PURE__ */ jsxs(
1511
- "h3",
1512
- {
1513
- style: {
1514
- margin: 0,
1515
- fontSize: "1rem",
1516
- fontWeight: 500,
1517
- // AstroUXDS medium (was 600)
1518
- color: tokens.colors.text.primary
1519
- },
1520
- children: [
1521
- title,
1522
- " (",
1523
- events.length,
1524
- ")"
1525
- ]
1526
- }
1527
- ),
1528
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
1529
- zoomable && (viewMode === "chart" || viewMode === "scatter") && /* @__PURE__ */ jsxs(
1530
- "div",
1971
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: tokens.spacing.sm }, children: [
1972
+ /* @__PURE__ */ jsx(
1973
+ "h3",
1531
1974
  {
1532
1975
  style: {
1533
- display: "flex",
1534
- alignItems: "center",
1535
- gap: 4,
1536
- paddingRight: 12,
1537
- borderRight: `1px solid ${tokens.colors.border.muted}`
1976
+ margin: 0,
1977
+ fontSize: "1rem",
1978
+ fontWeight: 500,
1979
+ // AstroUXDS medium
1980
+ color: tokens.colors.text.primary,
1981
+ whiteSpace: "nowrap",
1982
+ flexShrink: 0
1538
1983
  },
1539
- children: [
1540
- /* @__PURE__ */ jsx(Tooltip, { content: "Zoom out", children: /* @__PURE__ */ jsx(
1541
- "button",
1542
- {
1543
- type: "button",
1544
- "aria-label": "Zoom out",
1545
- onClick: () => setZoom(Math.max(zoom - 0.5, 1)),
1546
- disabled: zoom <= 1,
1547
- style: {
1548
- width: 28,
1549
- height: 28,
1550
- display: "flex",
1551
- alignItems: "center",
1552
- justifyContent: "center",
1553
- backgroundColor: zoom <= 1 ? "transparent" : tokens.colors.background.elevated,
1554
- border: `1px solid ${tokens.colors.border.muted}`,
1555
- borderRadius: tokens.borderRadius.sm,
1556
- color: zoom <= 1 ? tokens.colors.text.tertiary : tokens.colors.text.primary,
1557
- cursor: zoom <= 1 ? "not-allowed" : "pointer",
1558
- fontSize: "1rem",
1559
- fontWeight: 500,
1560
- // AstroUXDS medium (was 600)
1561
- opacity: zoom <= 1 ? 0.5 : 1
1562
- },
1563
- children: "−"
1564
- }
1565
- ) }),
1566
- /* @__PURE__ */ jsxs(
1567
- "span",
1568
- {
1569
- style: {
1570
- minWidth: 40,
1571
- textAlign: "center",
1572
- fontSize: "0.75rem",
1573
- fontFamily: tokens.typography.fontFamily.mono,
1574
- color: tokens.colors.text.secondary
1575
- },
1576
- children: [
1577
- Math.round(zoom * 100),
1578
- "%"
1579
- ]
1580
- }
1581
- ),
1582
- /* @__PURE__ */ jsx(Tooltip, { content: "Zoom in", children: /* @__PURE__ */ jsx(
1583
- "button",
1584
- {
1585
- type: "button",
1586
- "aria-label": "Zoom in",
1587
- onClick: () => setZoom(Math.min(zoom + 0.5, 4)),
1588
- disabled: zoom >= 4,
1589
- style: {
1590
- width: 28,
1591
- height: 28,
1592
- display: "flex",
1593
- alignItems: "center",
1594
- justifyContent: "center",
1595
- backgroundColor: zoom >= 4 ? "transparent" : tokens.colors.background.elevated,
1596
- border: `1px solid ${tokens.colors.border.muted}`,
1597
- borderRadius: tokens.borderRadius.sm,
1598
- color: zoom >= 4 ? tokens.colors.text.tertiary : tokens.colors.text.primary,
1599
- cursor: zoom >= 4 ? "not-allowed" : "pointer",
1600
- fontSize: "1rem",
1601
- fontWeight: 500,
1602
- // AstroUXDS medium (was 600)
1603
- opacity: zoom >= 4 ? 0.5 : 1
1604
- },
1605
- children: "+"
1606
- }
1607
- ) }),
1608
- /* @__PURE__ */ jsx(Tooltip, { content: "Reset zoom", children: /* @__PURE__ */ jsx(
1609
- "button",
1610
- {
1611
- type: "button",
1612
- "aria-label": "Reset zoom",
1613
- onClick: () => setZoom(1),
1614
- style: {
1615
- height: 28,
1616
- padding: "0 10px",
1617
- display: "flex",
1618
- alignItems: "center",
1619
- justifyContent: "center",
1620
- backgroundColor: tokens.colors.background.elevated,
1621
- border: `1px solid ${tokens.colors.border.muted}`,
1622
- borderRadius: tokens.borderRadius.sm,
1623
- color: tokens.colors.text.secondary,
1624
- cursor: "pointer",
1625
- fontSize: "0.6875rem",
1626
- fontWeight: 500
1627
- },
1628
- children: "FIT"
1629
- }
1630
- ) })
1631
- ]
1984
+ children: countLabel
1632
1985
  }
1633
1986
  ),
1634
- showViewToggle && /* @__PURE__ */ jsx(
1635
- "div",
1636
- {
1637
- style: {
1638
- display: "flex",
1639
- gap: "2px",
1640
- backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.elevated,
1641
- ...isTransparentTheme && { backdropFilter: "blur(12px)", WebkitBackdropFilter: "blur(12px)" },
1642
- padding: "4px",
1643
- borderRadius: tokens.borderRadius.lg
1644
- },
1645
- children: ["chart", "list", "scatter"].map((mode) => {
1646
- const isActive = viewMode === mode;
1647
- const icons = {
1648
- chart: "chart",
1649
- list: "list",
1650
- scatter: "timeline"
1651
- };
1652
- const labels = {
1653
- chart: "Gantt Chart",
1654
- list: "List View",
1655
- scatter: "Scatter View"
1656
- };
1657
- return /* @__PURE__ */ jsx(Tooltip, { content: labels[mode], children: /* @__PURE__ */ jsx(
1658
- "button",
1659
- {
1660
- type: "button",
1661
- "aria-label": labels[mode],
1662
- onClick: () => handleViewModeChange(mode),
1663
- style: {
1664
- display: "flex",
1665
- alignItems: "center",
1666
- justifyContent: "center",
1667
- width: "32px",
1668
- height: "32px",
1669
- backgroundColor: isActive ? tokens.colors.accent.primary : "transparent",
1670
- border: "none",
1671
- borderRadius: tokens.borderRadius.md,
1672
- color: isActive ? "#ffffff" : tokens.colors.text.secondary,
1673
- cursor: "pointer",
1674
- transition: "all 200ms cubic-bezier(0.4, 0, 0.2, 1)",
1675
- transform: isActive ? "scale(1)" : "scale(0.95)",
1676
- boxShadow: isActive ? `0 2px 8px ${tokens.colors.accent.primary}40` : "none"
1677
- },
1678
- onMouseEnter: (e) => {
1679
- if (!isActive) {
1680
- e.currentTarget.style.backgroundColor = `${tokens.colors.accent.primary}20`;
1681
- e.currentTarget.style.transform = "scale(1)";
1682
- }
1683
- },
1684
- onMouseLeave: (e) => {
1685
- if (!isActive) {
1686
- e.currentTarget.style.backgroundColor = "transparent";
1687
- e.currentTarget.style.transform = "scale(0.95)";
1688
- }
1689
- },
1690
- children: /* @__PURE__ */ jsx(Icon, { name: icons[mode], size: 16 })
1987
+ !isMobile && /* @__PURE__ */ jsx("div", { style: { flex: 1 } })
1988
+ ] }),
1989
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: tokens.spacing.sm, flex: isMobile ? void 0 : 1, justifyContent: isMobile ? "space-between" : "flex-end" }, children: [
1990
+ _showFilters && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: tokens.spacing.xs, flex: isMobile ? 1 : void 0, flexShrink: 0 }, children: [
1991
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: isMobile ? "100%" : 180 }, children: [
1992
+ /* @__PURE__ */ jsx(
1993
+ Icon,
1994
+ {
1995
+ name: "search",
1996
+ size: 14,
1997
+ style: {
1998
+ position: "absolute",
1999
+ left: 8,
2000
+ top: "50%",
2001
+ transform: "translateY(-50%)",
2002
+ color: tokens.colors.text.tertiary,
2003
+ pointerEvents: "none"
1691
2004
  }
1692
- ) }, mode);
1693
- })
1694
- }
1695
- )
1696
- ] })
2005
+ }
2006
+ ),
2007
+ /* @__PURE__ */ jsx(
2008
+ "input",
2009
+ {
2010
+ type: "search",
2011
+ value: filter.search ?? "",
2012
+ onChange: (e) => patchFilter({ search: e.target.value || void 0 }),
2013
+ placeholder: "Search…",
2014
+ "aria-label": "Search timeline events",
2015
+ style: {
2016
+ width: "100%",
2017
+ boxSizing: "border-box",
2018
+ height: tokens.elementSize.sm,
2019
+ paddingLeft: 28,
2020
+ paddingRight: 8,
2021
+ borderRadius: tokens.borderRadius.md,
2022
+ border: `1px solid ${tokens.colors.border.muted}`,
2023
+ backgroundColor: tokens.colors.background.base,
2024
+ color: tokens.colors.text.primary,
2025
+ fontSize: tokens.typography.fontSize.sm,
2026
+ fontFamily: "inherit",
2027
+ outline: "none",
2028
+ transition: "all 200ms cubic-bezier(0.4, 0, 0.2, 1)"
2029
+ },
2030
+ onFocus: (e) => {
2031
+ e.target.style.borderColor = tokens.colors.accent.primary;
2032
+ e.target.style.boxShadow = `0 0 0 3px ${tokens.colors.accent.primary}50, 0 0 20px ${tokens.colors.accent.primary}15`;
2033
+ },
2034
+ onBlur: (e) => {
2035
+ e.target.style.borderColor = tokens.colors.border.muted;
2036
+ e.target.style.boxShadow = "none";
2037
+ }
2038
+ }
2039
+ )
2040
+ ] }),
2041
+ /* @__PURE__ */ jsx(Tooltip, { content: filtersExpanded ? "Hide filters" : "Show filters", children: /* @__PURE__ */ jsxs(
2042
+ "button",
2043
+ {
2044
+ type: "button",
2045
+ "aria-label": filtersExpanded ? "Hide filters" : "Show filters",
2046
+ "aria-expanded": filtersExpanded,
2047
+ "aria-controls": "zendir-timeline-filters-panel",
2048
+ onClick: () => setFiltersExpanded((v) => !v),
2049
+ style: {
2050
+ position: "relative",
2051
+ display: "flex",
2052
+ alignItems: "center",
2053
+ justifyContent: "center",
2054
+ width: 28,
2055
+ height: 28,
2056
+ border: `1px solid ${filterChipCount > 0 ? `${tokens.colors.accent.primary}80` : tokens.colors.border.muted}`,
2057
+ borderRadius: tokens.borderRadius.sm,
2058
+ backgroundColor: filterChipCount > 0 ? `${tokens.colors.accent.primary}12` : "transparent",
2059
+ color: filterChipCount > 0 ? tokens.colors.accent.primary : tokens.colors.text.secondary,
2060
+ cursor: "pointer",
2061
+ transition: tokens.animation.fast
2062
+ },
2063
+ children: [
2064
+ /* @__PURE__ */ jsx(Icon, { name: "filter", size: 16 }),
2065
+ filterChipCount > 0 ? /* @__PURE__ */ jsx(
2066
+ "span",
2067
+ {
2068
+ style: {
2069
+ position: "absolute",
2070
+ top: -4,
2071
+ right: -4,
2072
+ minWidth: 14,
2073
+ height: 14,
2074
+ borderRadius: "50%",
2075
+ backgroundColor: tokens.colors.accent.primary,
2076
+ color: "#fff",
2077
+ fontSize: "0.5625rem",
2078
+ fontWeight: 700,
2079
+ display: "flex",
2080
+ alignItems: "center",
2081
+ justifyContent: "center",
2082
+ lineHeight: 1
2083
+ },
2084
+ children: filterChipCount
2085
+ }
2086
+ ) : null
2087
+ ]
2088
+ }
2089
+ ) })
2090
+ ] }),
2091
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12, flexShrink: 0 }, children: [
2092
+ zoomable && (viewMode === "chart" || viewMode === "scatter") && /* @__PURE__ */ jsxs(
2093
+ "div",
2094
+ {
2095
+ style: {
2096
+ display: "flex",
2097
+ alignItems: "center",
2098
+ gap: 4,
2099
+ paddingRight: 12,
2100
+ borderRight: `1px solid ${tokens.colors.border.muted}`
2101
+ },
2102
+ children: [
2103
+ /* @__PURE__ */ jsx(Tooltip, { content: "Zoom out", children: /* @__PURE__ */ jsx(
2104
+ "button",
2105
+ {
2106
+ type: "button",
2107
+ "aria-label": "Zoom out",
2108
+ onClick: () => setZoom(Math.max(zoom - 0.5, 1)),
2109
+ disabled: zoom <= 1,
2110
+ style: {
2111
+ width: 28,
2112
+ height: 28,
2113
+ display: "flex",
2114
+ alignItems: "center",
2115
+ justifyContent: "center",
2116
+ backgroundColor: zoom <= 1 ? "transparent" : tokens.colors.background.elevated,
2117
+ border: `1px solid ${tokens.colors.border.muted}`,
2118
+ borderRadius: tokens.borderRadius.sm,
2119
+ color: zoom <= 1 ? tokens.colors.text.tertiary : tokens.colors.text.primary,
2120
+ cursor: zoom <= 1 ? "not-allowed" : "pointer",
2121
+ fontSize: "1rem",
2122
+ fontWeight: 500,
2123
+ // AstroUXDS medium (was 600)
2124
+ opacity: zoom <= 1 ? 0.5 : 1
2125
+ },
2126
+ children: "−"
2127
+ }
2128
+ ) }),
2129
+ /* @__PURE__ */ jsxs(
2130
+ "span",
2131
+ {
2132
+ style: {
2133
+ minWidth: 40,
2134
+ textAlign: "center",
2135
+ fontSize: "0.75rem",
2136
+ fontFamily: tokens.typography.fontFamily.mono,
2137
+ color: tokens.colors.text.secondary
2138
+ },
2139
+ children: [
2140
+ Math.round(zoom * 100),
2141
+ "%"
2142
+ ]
2143
+ }
2144
+ ),
2145
+ /* @__PURE__ */ jsx(Tooltip, { content: "Zoom in", children: /* @__PURE__ */ jsx(
2146
+ "button",
2147
+ {
2148
+ type: "button",
2149
+ "aria-label": "Zoom in",
2150
+ onClick: () => setZoom(Math.min(zoom + 0.5, 4)),
2151
+ disabled: zoom >= 4,
2152
+ style: {
2153
+ width: 28,
2154
+ height: 28,
2155
+ display: "flex",
2156
+ alignItems: "center",
2157
+ justifyContent: "center",
2158
+ backgroundColor: zoom >= 4 ? "transparent" : tokens.colors.background.elevated,
2159
+ border: `1px solid ${tokens.colors.border.muted}`,
2160
+ borderRadius: tokens.borderRadius.sm,
2161
+ color: zoom >= 4 ? tokens.colors.text.tertiary : tokens.colors.text.primary,
2162
+ cursor: zoom >= 4 ? "not-allowed" : "pointer",
2163
+ fontSize: "1rem",
2164
+ fontWeight: 500,
2165
+ // AstroUXDS medium (was 600)
2166
+ opacity: zoom >= 4 ? 0.5 : 1
2167
+ },
2168
+ children: "+"
2169
+ }
2170
+ ) }),
2171
+ /* @__PURE__ */ jsx(Tooltip, { content: "Reset zoom", children: /* @__PURE__ */ jsx(
2172
+ "button",
2173
+ {
2174
+ type: "button",
2175
+ "aria-label": "Reset zoom",
2176
+ onClick: () => setZoom(1),
2177
+ style: {
2178
+ height: 28,
2179
+ padding: "0 10px",
2180
+ display: "flex",
2181
+ alignItems: "center",
2182
+ justifyContent: "center",
2183
+ backgroundColor: tokens.colors.background.elevated,
2184
+ border: `1px solid ${tokens.colors.border.muted}`,
2185
+ borderRadius: tokens.borderRadius.sm,
2186
+ color: tokens.colors.text.secondary,
2187
+ cursor: "pointer",
2188
+ fontSize: "0.6875rem",
2189
+ fontWeight: 500
2190
+ },
2191
+ children: "FIT"
2192
+ }
2193
+ ) })
2194
+ ]
2195
+ }
2196
+ ),
2197
+ showViewToggle && /* @__PURE__ */ jsx(
2198
+ "div",
2199
+ {
2200
+ role: "group",
2201
+ "aria-label": "View mode",
2202
+ style: {
2203
+ display: "flex",
2204
+ gap: "2px",
2205
+ backgroundColor: isTransparentTheme ? "transparent" : tokens.colors.background.elevated,
2206
+ ...isTransparentTheme && { backdropFilter: "blur(12px)", WebkitBackdropFilter: "blur(12px)" },
2207
+ padding: "4px",
2208
+ borderRadius: tokens.borderRadius.lg
2209
+ },
2210
+ children: ["chart", "list", "scatter"].map((mode) => {
2211
+ const isActive = viewMode === mode;
2212
+ const icons = {
2213
+ chart: "chart",
2214
+ list: "list",
2215
+ scatter: "timeline"
2216
+ };
2217
+ const labels = {
2218
+ chart: "Gantt Chart",
2219
+ list: "List View",
2220
+ scatter: "Scatter View"
2221
+ };
2222
+ return /* @__PURE__ */ jsx(Tooltip, { content: labels[mode], children: /* @__PURE__ */ jsx(
2223
+ "button",
2224
+ {
2225
+ type: "button",
2226
+ "aria-label": labels[mode],
2227
+ "aria-pressed": isActive,
2228
+ onClick: () => handleViewModeChange(mode),
2229
+ style: {
2230
+ display: "flex",
2231
+ alignItems: "center",
2232
+ justifyContent: "center",
2233
+ width: "32px",
2234
+ height: "32px",
2235
+ backgroundColor: isActive ? tokens.colors.accent.primary : "transparent",
2236
+ border: "none",
2237
+ borderRadius: tokens.borderRadius.md,
2238
+ color: isActive ? "#ffffff" : tokens.colors.text.secondary,
2239
+ cursor: "pointer",
2240
+ transition: "all 200ms cubic-bezier(0.4, 0, 0.2, 1)",
2241
+ transform: isActive ? "scale(1)" : "scale(0.95)",
2242
+ boxShadow: isActive ? `0 2px 8px ${tokens.colors.accent.primary}40` : "none"
2243
+ },
2244
+ onMouseEnter: (e) => {
2245
+ if (!isActive) {
2246
+ e.currentTarget.style.backgroundColor = `${tokens.colors.accent.primary}20`;
2247
+ e.currentTarget.style.transform = "scale(1)";
2248
+ }
2249
+ },
2250
+ onMouseLeave: (e) => {
2251
+ if (!isActive) {
2252
+ e.currentTarget.style.backgroundColor = "transparent";
2253
+ e.currentTarget.style.transform = "scale(0.95)";
2254
+ }
2255
+ },
2256
+ children: /* @__PURE__ */ jsx(Icon, { name: icons[mode], size: 16 })
2257
+ }
2258
+ ) }, mode);
2259
+ })
2260
+ }
2261
+ )
2262
+ ] }),
2263
+ " "
2264
+ ] }),
2265
+ " "
1697
2266
  ]
1698
2267
  }
1699
2268
  ),
2269
+ " ",
2270
+ _showFilters ? /* @__PURE__ */ jsx(
2271
+ TimelineFiltersPanel,
2272
+ {
2273
+ filter,
2274
+ onFilterChange: setFilter,
2275
+ trackOptions: trackOptionsForToolbar,
2276
+ events,
2277
+ expanded: filtersExpanded,
2278
+ teamLabel
2279
+ }
2280
+ ) : null,
1700
2281
  loading && /* @__PURE__ */ jsx(
1701
2282
  "div",
1702
2283
  {
@@ -1708,7 +2289,7 @@ const UnifiedTimeline = memo(function UnifiedTimeline2({
1708
2289
  justifyContent: "center",
1709
2290
  backgroundColor: `${tokens.colors.background.overlay}80`,
1710
2291
  backdropFilter: "blur(4px)",
1711
- zIndex: 100
2292
+ zIndex: Z.loadingOverlay
1712
2293
  },
1713
2294
  children: /* @__PURE__ */ jsx(
1714
2295
  "div",
@@ -1751,6 +2332,43 @@ const UnifiedTimeline = memo(function UnifiedTimeline2({
1751
2332
  /* @__PURE__ */ jsx("span", { style: { fontSize: "0.875rem" }, children: "No events to display" })
1752
2333
  ]
1753
2334
  }
2335
+ ) : filteredEvents.length === 0 ? /* @__PURE__ */ jsxs(
2336
+ "div",
2337
+ {
2338
+ style: {
2339
+ display: "flex",
2340
+ flexDirection: "column",
2341
+ alignItems: "center",
2342
+ justifyContent: "center",
2343
+ padding: "48px 24px",
2344
+ color: tokens.colors.text.tertiary,
2345
+ gap: 12,
2346
+ textAlign: "center"
2347
+ },
2348
+ children: [
2349
+ /* @__PURE__ */ jsx(Icon, { name: "search", size: 32 }),
2350
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "0.875rem", color: tokens.colors.text.secondary }, children: "No events match the current filters." }),
2351
+ _showFilters && isTimelineFilterActive(filter) ? /* @__PURE__ */ jsx(
2352
+ "button",
2353
+ {
2354
+ type: "button",
2355
+ onClick: () => setFilter({}),
2356
+ style: {
2357
+ marginTop: 4,
2358
+ padding: "8px 16px",
2359
+ borderRadius: tokens.borderRadius.md,
2360
+ border: `1px solid ${tokens.colors.accent.primary}`,
2361
+ backgroundColor: `${tokens.colors.accent.primary}18`,
2362
+ color: tokens.colors.accent.primary,
2363
+ cursor: "pointer",
2364
+ fontSize: "0.8125rem",
2365
+ fontWeight: 500
2366
+ },
2367
+ children: "Clear filters"
2368
+ }
2369
+ ) : null
2370
+ ]
2371
+ }
1754
2372
  ) : viewMode === "list" ? (
1755
2373
  // List View
1756
2374
  sortedEvents.map((event, index) => /* @__PURE__ */ jsx(
@@ -1767,8 +2385,8 @@ const UnifiedTimeline = memo(function UnifiedTimeline2({
1767
2385
  /* @__PURE__ */ jsx(
1768
2386
  ScatterView,
1769
2387
  {
1770
- events,
1771
- tracks: effectiveTracks,
2388
+ events: filteredEvents,
2389
+ tracks: effectiveTracksForViews,
1772
2390
  start,
1773
2391
  end,
1774
2392
  trackHeight,
@@ -1783,8 +2401,8 @@ const UnifiedTimeline = memo(function UnifiedTimeline2({
1783
2401
  /* @__PURE__ */ jsx(
1784
2402
  ChartView,
1785
2403
  {
1786
- events,
1787
- tracks: effectiveTracks,
2404
+ events: filteredEvents,
2405
+ tracks: effectiveTracksForViews,
1788
2406
  start,
1789
2407
  end,
1790
2408
  trackHeight,
@@ -1810,6 +2428,14 @@ const UnifiedTimeline = memo(function UnifiedTimeline2({
1810
2428
  );
1811
2429
  });
1812
2430
  export {
1813
- UnifiedTimeline
2431
+ TIMELINE_FILTER_TEAM_LEGACY,
2432
+ TIMELINE_FILTER_TEAM_NONE,
2433
+ UnifiedTimeline,
2434
+ getTimelineEventDisplayStatus,
2435
+ getTimelineTeamAccent,
2436
+ getTimelineTeamDisplayLabel,
2437
+ getTimelineTeamFilterKey,
2438
+ isTimelineFilterActive,
2439
+ matchesTimelineFilter
1814
2440
  };
1815
2441
  //# sourceMappingURL=UnifiedTimeline.js.map