@arbidocs/blocks 0.3.177 → 0.3.180
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/{chunk-6HGMAQLB.cjs → chunk-DBAUZD7A.cjs} +448 -113
- package/dist/chunk-DBAUZD7A.cjs.map +1 -0
- package/dist/{chunk-27Q4AST5.js → chunk-FZ2S6VAZ.js} +449 -116
- package/dist/chunk-FZ2S6VAZ.js.map +1 -0
- package/dist/{index-er1L8kkn.d.cts → index-DduoEosi.d.cts} +65 -2
- package/dist/{index-er1L8kkn.d.ts → index-DduoEosi.d.ts} +65 -2
- package/dist/index.cjs +268 -183
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +78 -9
- package/dist/index.d.ts +78 -9
- package/dist/index.js +120 -45
- package/dist/index.js.map +1 -1
- package/dist/site/index.cjs +61 -61
- package/dist/site/index.d.cts +1 -1
- package/dist/site/index.d.ts +1 -1
- package/dist/site/index.js +1 -1
- package/package.json +3 -3
- package/src/components/grid.css +10 -0
- package/dist/chunk-27Q4AST5.js.map +0 -1
- package/dist/chunk-6HGMAQLB.cjs.map +0 -1
|
@@ -6,6 +6,7 @@ var react = require('react');
|
|
|
6
6
|
var lucideReact = require('lucide-react');
|
|
7
7
|
var ui = require('@arbidocs/react/ui');
|
|
8
8
|
var jsxRuntime = require('react/jsx-runtime');
|
|
9
|
+
var reactDom = require('react-dom');
|
|
9
10
|
|
|
10
11
|
// src/theme/tokens.ts
|
|
11
12
|
var THEME_STYLE_ID = "arbi-theme-vars";
|
|
@@ -678,20 +679,62 @@ function formatSpan(startIso, endIso, timezone) {
|
|
|
678
679
|
return `${day(start)} \u2013 ${day(end)}`;
|
|
679
680
|
}
|
|
680
681
|
var DEFAULT_MAX_LANES = 3;
|
|
681
|
-
var MAX_WEEKS =
|
|
682
|
-
function isPrecise(item) {
|
|
683
|
-
if (!item.start || !item.end) return false;
|
|
684
|
-
return item.start.slice(0, 10) === item.end.slice(0, 10);
|
|
685
|
-
}
|
|
682
|
+
var MAX_WEEKS = 2600;
|
|
686
683
|
function TimelineCalendar({
|
|
687
684
|
items,
|
|
688
685
|
onSelect,
|
|
686
|
+
selectedIds,
|
|
689
687
|
maxLanes = DEFAULT_MAX_LANES,
|
|
688
|
+
navigatorContainer,
|
|
689
|
+
navigatorTotal,
|
|
690
690
|
emptyState,
|
|
691
691
|
className,
|
|
692
692
|
testId = "timeline-calendar"
|
|
693
693
|
}) {
|
|
694
694
|
const dated = react.useMemo(() => items.filter((i) => i.start), [items]);
|
|
695
|
+
const sortedItems = react.useMemo(
|
|
696
|
+
() => [...dated].sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime()),
|
|
697
|
+
[dated]
|
|
698
|
+
);
|
|
699
|
+
const indexById = react.useMemo(() => new Map(sortedItems.map((it, i) => [it.id, i])), [sortedItems]);
|
|
700
|
+
const [activeIndex, setActiveIndex] = react.useState(null);
|
|
701
|
+
const scrollRef = react.useRef(null);
|
|
702
|
+
const select = react.useCallback(
|
|
703
|
+
(index) => {
|
|
704
|
+
const clamped = Math.max(0, Math.min(sortedItems.length - 1, index));
|
|
705
|
+
setActiveIndex(clamped);
|
|
706
|
+
onSelect?.(sortedItems[clamped]);
|
|
707
|
+
return clamped;
|
|
708
|
+
},
|
|
709
|
+
[onSelect, sortedItems]
|
|
710
|
+
);
|
|
711
|
+
const scrollToItem = react.useCallback(
|
|
712
|
+
(id, behavior) => {
|
|
713
|
+
scrollRef.current?.querySelector(`[data-testid="${testId}-entry-${id}"]`)?.scrollIntoView({ block: "center", behavior });
|
|
714
|
+
},
|
|
715
|
+
[testId]
|
|
716
|
+
);
|
|
717
|
+
const lastNavScrollRef = react.useRef(null);
|
|
718
|
+
const goTo = react.useCallback(
|
|
719
|
+
(index) => {
|
|
720
|
+
const target = sortedItems[select(index)];
|
|
721
|
+
if (target) {
|
|
722
|
+
lastNavScrollRef.current = target.id;
|
|
723
|
+
scrollToItem(target.id, "smooth");
|
|
724
|
+
}
|
|
725
|
+
},
|
|
726
|
+
[select, sortedItems, scrollToItem]
|
|
727
|
+
);
|
|
728
|
+
const selectedKey = (selectedIds ?? []).join("\0");
|
|
729
|
+
react.useEffect(() => {
|
|
730
|
+
const id = selectedKey ? selectedKey.split("\0")[0] : null;
|
|
731
|
+
if (!id) return;
|
|
732
|
+
if (lastNavScrollRef.current === id) {
|
|
733
|
+
lastNavScrollRef.current = null;
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
scrollToItem(id, "auto");
|
|
737
|
+
}, [selectedKey, scrollToItem]);
|
|
695
738
|
const weeks = react.useMemo(() => {
|
|
696
739
|
if (dated.length === 0) return [];
|
|
697
740
|
const times = dated.flatMap((i) => [
|
|
@@ -717,22 +760,32 @@ function TimelineCalendar({
|
|
|
717
760
|
return out;
|
|
718
761
|
}, [dated]);
|
|
719
762
|
const segmentsByWeek = react.useMemo(() => {
|
|
720
|
-
|
|
763
|
+
if (weeks.length === 0) return [];
|
|
764
|
+
const gridStartMs = weeks[0][0].getTime();
|
|
765
|
+
const WEEK_MS2 = 7 * DAY_MS;
|
|
766
|
+
const ranged = dated.map((item) => ({
|
|
767
|
+
item,
|
|
768
|
+
start: utcDay(new Date(item.start)),
|
|
769
|
+
end: utcDay(new Date(item.end ?? item.start))
|
|
770
|
+
}));
|
|
771
|
+
ranged.sort((a, b) => {
|
|
772
|
+
const lenA = b.end.getTime() - b.start.getTime();
|
|
773
|
+
const lenB = a.end.getTime() - a.start.getTime();
|
|
774
|
+
return lenA - lenB || a.start.getTime() - b.start.getTime();
|
|
775
|
+
});
|
|
776
|
+
const buckets = weeks.map(() => []);
|
|
777
|
+
for (const r of ranged) {
|
|
778
|
+
const from = Math.max(0, Math.floor((r.start.getTime() - gridStartMs) / WEEK_MS2));
|
|
779
|
+
const to = Math.min(weeks.length - 1, Math.floor((r.end.getTime() - gridStartMs) / WEEK_MS2));
|
|
780
|
+
for (let w = from; w <= to; w++) buckets[w].push(r);
|
|
781
|
+
}
|
|
782
|
+
return weeks.map((week, wi) => {
|
|
721
783
|
const weekStart = week[0];
|
|
722
784
|
const weekEnd = week[6];
|
|
723
|
-
const overlapping = dated.map((item) => ({
|
|
724
|
-
item,
|
|
725
|
-
start: utcDay(new Date(item.start)),
|
|
726
|
-
end: utcDay(new Date(item.end ?? item.start))
|
|
727
|
-
})).filter(({ start, end }) => start <= weekEnd && end >= weekStart).sort((a, b) => {
|
|
728
|
-
const lenA = b.end.getTime() - b.start.getTime();
|
|
729
|
-
const lenB = a.end.getTime() - a.start.getTime();
|
|
730
|
-
return lenA - lenB || a.start.getTime() - b.start.getTime();
|
|
731
|
-
});
|
|
732
785
|
const laneEnds = [];
|
|
733
786
|
const segments = [];
|
|
734
787
|
let overflow = 0;
|
|
735
|
-
for (const { item, start, end } of
|
|
788
|
+
for (const { item, start, end } of buckets[wi]) {
|
|
736
789
|
const from = start < weekStart ? weekStart : start;
|
|
737
790
|
const to = end > weekEnd ? weekEnd : end;
|
|
738
791
|
const startCol = Math.round((from.getTime() - weekStart.getTime()) / DAY_MS);
|
|
@@ -759,9 +812,52 @@ function TimelineCalendar({
|
|
|
759
812
|
if (weeks.length === 0 && emptyState) {
|
|
760
813
|
return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: emptyState });
|
|
761
814
|
}
|
|
815
|
+
const selectedSet = new Set(selectedIds ?? []);
|
|
816
|
+
const selectedIndex = sortedItems.findIndex((i) => selectedSet.has(i.id));
|
|
817
|
+
const currentIndex = selectedIndex >= 0 ? selectedIndex : activeIndex;
|
|
818
|
+
const navigator = /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-0.5", "data-testid": `${testId}-nav`, children: [
|
|
819
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
820
|
+
"button",
|
|
821
|
+
{
|
|
822
|
+
type: "button",
|
|
823
|
+
"aria-label": "Previous event",
|
|
824
|
+
"data-testid": `${testId}-nav-prev`,
|
|
825
|
+
disabled: sortedItems.length === 0 || currentIndex === null || currentIndex <= 0,
|
|
826
|
+
onClick: () => goTo((currentIndex ?? 0) - 1),
|
|
827
|
+
className: "rounded p-0.5 text-muted-foreground enabled:hover:bg-accent enabled:hover:text-foreground disabled:opacity-30",
|
|
828
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-4 w-4" })
|
|
829
|
+
}
|
|
830
|
+
),
|
|
831
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
832
|
+
"span",
|
|
833
|
+
{
|
|
834
|
+
className: "tabular-nums text-sm font-light text-muted-foreground",
|
|
835
|
+
"data-testid": `${testId}-nav-position`,
|
|
836
|
+
children: [
|
|
837
|
+
currentIndex !== null ? currentIndex + 1 : "\u2013",
|
|
838
|
+
" /",
|
|
839
|
+
" ",
|
|
840
|
+
navigatorTotal ?? (sortedItems.length > 0 ? sortedItems.length : "\u2013")
|
|
841
|
+
]
|
|
842
|
+
}
|
|
843
|
+
),
|
|
844
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
845
|
+
"button",
|
|
846
|
+
{
|
|
847
|
+
type: "button",
|
|
848
|
+
"aria-label": "Next event",
|
|
849
|
+
"data-testid": `${testId}-nav-next`,
|
|
850
|
+
disabled: sortedItems.length === 0 || currentIndex !== null && currentIndex >= sortedItems.length - 1,
|
|
851
|
+
onClick: () => goTo(currentIndex === null ? 0 : currentIndex + 1),
|
|
852
|
+
className: "rounded p-0.5 text-muted-foreground enabled:hover:bg-accent enabled:hover:text-foreground disabled:opacity-30",
|
|
853
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-4 w-4" })
|
|
854
|
+
}
|
|
855
|
+
)
|
|
856
|
+
] });
|
|
762
857
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ui.cn("flex flex-col h-full", className), "data-testid": testId, children: [
|
|
858
|
+
navigatorContainer === void 0 ? navigator && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex shrink-0 items-center justify-end border-b border-border px-2 py-1", children: navigator }) : navigatorContainer && navigator && reactDom.createPortal(navigator, navigatorContainer),
|
|
763
859
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-7 border-b border-border bg-background", children: WEEKDAY_NAMES.map((d) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2 py-1 text-[11px] text-muted-foreground text-center", children: d }, d)) }),
|
|
764
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-auto", "data-testid": `${testId}-scroll`, children: weeks.map((week, wi) => {
|
|
860
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { ref: scrollRef, className: "flex-1 overflow-auto", "data-testid": `${testId}-scroll`, children: weeks.map((week, wi) => {
|
|
765
861
|
const { segments, overflow } = segmentsByWeek[wi];
|
|
766
862
|
const monthStartDay = week.find((d) => d.getUTCDate() === 1);
|
|
767
863
|
const marker = monthStartDay ?? (wi === 0 ? week[0] : void 0);
|
|
@@ -796,13 +892,13 @@ function TimelineCalendar({
|
|
|
796
892
|
"div",
|
|
797
893
|
{
|
|
798
894
|
className: "grid grid-cols-7 gap-y-0.5 px-0.5 pb-1 bg-muted/40",
|
|
799
|
-
style: { gridAutoRows: "
|
|
895
|
+
style: { gridAutoRows: "18px", minHeight: 62 },
|
|
800
896
|
children: [
|
|
801
897
|
segments.map((seg) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
802
898
|
"button",
|
|
803
899
|
{
|
|
804
900
|
type: "button",
|
|
805
|
-
onClick: () =>
|
|
901
|
+
onClick: () => select(indexById.get(seg.item.id) ?? 0),
|
|
806
902
|
title: `${formatSpan(seg.item.start, seg.item.end)} \u2014 ${seg.item.label}`,
|
|
807
903
|
"data-testid": `${testId}-entry-${seg.item.id}`,
|
|
808
904
|
style: {
|
|
@@ -810,8 +906,13 @@ function TimelineCalendar({
|
|
|
810
906
|
gridRow: seg.lane + 1
|
|
811
907
|
},
|
|
812
908
|
className: ui.cn(
|
|
813
|
-
|
|
814
|
-
|
|
909
|
+
// One line per event (truncated), so several events stack
|
|
910
|
+
// compactly on the same day instead of a few tall bars
|
|
911
|
+
// pushing the rest into a "+n more". Every event is the
|
|
912
|
+
// SAME neutral colour; the one open in the composer is
|
|
913
|
+
// marked by the caller (a ring), not by a different fill.
|
|
914
|
+
"block h-full w-full truncate text-left text-[10px] leading-[14px] py-[1px] px-1",
|
|
915
|
+
"bg-muted text-muted-foreground",
|
|
815
916
|
seg.isStart && "rounded-l",
|
|
816
917
|
seg.isEnd && "rounded-r"
|
|
817
918
|
),
|
|
@@ -839,38 +940,152 @@ function TimelineCalendar({
|
|
|
839
940
|
}) })
|
|
840
941
|
] });
|
|
841
942
|
}
|
|
842
|
-
var
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
943
|
+
var TIMELINE_ZOOM = { MIN: 1, MAX: 10, STEP: 0.25 };
|
|
944
|
+
function TimelineZoomControl({
|
|
945
|
+
zoom,
|
|
946
|
+
onZoomChange,
|
|
947
|
+
className,
|
|
948
|
+
testId = "timeline-gantt"
|
|
949
|
+
}) {
|
|
950
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
951
|
+
"div",
|
|
952
|
+
{
|
|
953
|
+
className: ui.cn(
|
|
954
|
+
"flex items-center gap-1.5 rounded-md border border-border bg-background px-2 py-1",
|
|
955
|
+
className
|
|
956
|
+
),
|
|
957
|
+
"data-testid": `${testId}-zoom`,
|
|
958
|
+
children: [
|
|
959
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ZoomOut, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true }),
|
|
960
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
961
|
+
"input",
|
|
962
|
+
{
|
|
963
|
+
type: "range",
|
|
964
|
+
"aria-label": "Timeline zoom",
|
|
965
|
+
"data-testid": `${testId}-zoom-slider`,
|
|
966
|
+
min: TIMELINE_ZOOM.MIN,
|
|
967
|
+
max: TIMELINE_ZOOM.MAX,
|
|
968
|
+
step: TIMELINE_ZOOM.STEP,
|
|
969
|
+
value: zoom,
|
|
970
|
+
onChange: (e) => onZoomChange(Number(e.target.value)),
|
|
971
|
+
className: "h-1 w-24 cursor-pointer accent-primary"
|
|
972
|
+
}
|
|
973
|
+
),
|
|
974
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ZoomIn, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground", "aria-hidden": true }),
|
|
975
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "w-11 shrink-0 text-right text-[11px] tabular-nums text-muted-foreground", children: [
|
|
976
|
+
Math.round(zoom * 100),
|
|
977
|
+
"%"
|
|
978
|
+
] })
|
|
979
|
+
]
|
|
980
|
+
}
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
var NEUTRAL_BAR = "#94a3b8";
|
|
984
|
+
function barBackground(colors) {
|
|
985
|
+
const c = (colors ?? []).filter(Boolean).slice(0, 3);
|
|
986
|
+
if (c.length === 0) return NEUTRAL_BAR;
|
|
987
|
+
if (c.length === 1) return c[0];
|
|
988
|
+
const step = 100 / c.length;
|
|
989
|
+
const stops = c.map((col, i) => `${col} ${i * step}% ${(i + 1) * step}%`).join(", ");
|
|
990
|
+
return `linear-gradient(180deg, ${stops})`;
|
|
991
|
+
}
|
|
859
992
|
var WEEK_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
860
|
-
function formatAxisDate(d) {
|
|
861
|
-
|
|
993
|
+
function formatAxisDate(d, intervalDays) {
|
|
994
|
+
if (intervalDays < 45) {
|
|
995
|
+
return d.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
|
|
996
|
+
}
|
|
997
|
+
if (intervalDays < 365 * 2) {
|
|
998
|
+
return d.toLocaleDateString("en-US", { month: "short", year: "numeric" });
|
|
999
|
+
}
|
|
1000
|
+
return d.toLocaleDateString("en-US", { year: "numeric" });
|
|
862
1001
|
}
|
|
863
1002
|
function TimelineGantt({
|
|
864
1003
|
items,
|
|
865
1004
|
onSelect,
|
|
1005
|
+
selectedIds,
|
|
866
1006
|
renderDetail,
|
|
867
1007
|
labelWidth = 240,
|
|
1008
|
+
onLabelWidthChange,
|
|
1009
|
+
minLabelWidth = 120,
|
|
1010
|
+
maxLabelWidth = 600,
|
|
1011
|
+
zoom: zoomProp,
|
|
1012
|
+
onZoomChange,
|
|
1013
|
+
navigatorContainer,
|
|
1014
|
+
navigatorTotal,
|
|
868
1015
|
emptyMessage = "Nothing dated yet.",
|
|
869
1016
|
className,
|
|
870
1017
|
testId = "timeline-gantt"
|
|
871
1018
|
}) {
|
|
872
1019
|
const [activeIndex, setActiveIndex] = react.useState(null);
|
|
1020
|
+
const [internalZoom, setInternalZoom] = react.useState(1);
|
|
1021
|
+
const zoomControlled = zoomProp != null && onZoomChange != null;
|
|
1022
|
+
const zoomValue = zoomControlled ? zoomProp : internalZoom;
|
|
1023
|
+
const setZoomValue = zoomControlled ? onZoomChange : setInternalZoom;
|
|
1024
|
+
const dragRef = react.useRef(null);
|
|
1025
|
+
const chartRef = react.useRef(null);
|
|
1026
|
+
const gutterBodyRef = react.useRef(null);
|
|
1027
|
+
const startResize = (e) => {
|
|
1028
|
+
if (!onLabelWidthChange) return;
|
|
1029
|
+
e.preventDefault();
|
|
1030
|
+
dragRef.current = { startX: e.clientX, startWidth: labelWidth };
|
|
1031
|
+
const onMove = (ev) => {
|
|
1032
|
+
const drag = dragRef.current;
|
|
1033
|
+
if (!drag) return;
|
|
1034
|
+
const next = Math.min(
|
|
1035
|
+
maxLabelWidth,
|
|
1036
|
+
Math.max(minLabelWidth, drag.startWidth + (ev.clientX - drag.startX))
|
|
1037
|
+
);
|
|
1038
|
+
onLabelWidthChange(next);
|
|
1039
|
+
};
|
|
1040
|
+
const onUp = () => {
|
|
1041
|
+
dragRef.current = null;
|
|
1042
|
+
window.removeEventListener("mousemove", onMove);
|
|
1043
|
+
window.removeEventListener("mouseup", onUp);
|
|
1044
|
+
};
|
|
1045
|
+
window.addEventListener("mousemove", onMove);
|
|
1046
|
+
window.addEventListener("mouseup", onUp);
|
|
1047
|
+
};
|
|
873
1048
|
const rows = items.filter((i) => parseIso2(i.start) !== null).slice().sort((a, b) => parseIso2(a.start).getTime() - parseIso2(b.start).getTime());
|
|
1049
|
+
const select = (index) => {
|
|
1050
|
+
setActiveIndex(index);
|
|
1051
|
+
onSelect?.(index === null ? null : rows[index]);
|
|
1052
|
+
};
|
|
1053
|
+
const selectedSet = new Set(selectedIds ?? []);
|
|
1054
|
+
const selectedRowIndex = rows.findIndex((r) => selectedSet.has(r.id));
|
|
1055
|
+
const currentIndex = selectedRowIndex >= 0 ? selectedRowIndex : activeIndex;
|
|
1056
|
+
const goTo = (index) => select(Math.max(0, Math.min(rows.length - 1, index)));
|
|
1057
|
+
const navigator = /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-0.5 text-sm font-light", "data-testid": `${testId}-nav`, children: [
|
|
1058
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1059
|
+
"button",
|
|
1060
|
+
{
|
|
1061
|
+
type: "button",
|
|
1062
|
+
"aria-label": "Previous event",
|
|
1063
|
+
"data-testid": `${testId}-nav-prev`,
|
|
1064
|
+
disabled: rows.length === 0 || currentIndex === null || currentIndex <= 0,
|
|
1065
|
+
onClick: () => goTo((currentIndex ?? 0) - 1),
|
|
1066
|
+
className: "rounded p-0.5 text-muted-foreground enabled:hover:bg-accent enabled:hover:text-foreground disabled:opacity-30",
|
|
1067
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "h-4 w-4" })
|
|
1068
|
+
}
|
|
1069
|
+
),
|
|
1070
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "tabular-nums text-muted-foreground", "data-testid": `${testId}-nav-position`, children: [
|
|
1071
|
+
currentIndex !== null ? currentIndex + 1 : "\u2013",
|
|
1072
|
+
" /",
|
|
1073
|
+
" ",
|
|
1074
|
+
navigatorTotal ?? (rows.length > 0 ? rows.length : "\u2013")
|
|
1075
|
+
] }),
|
|
1076
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1077
|
+
"button",
|
|
1078
|
+
{
|
|
1079
|
+
type: "button",
|
|
1080
|
+
"aria-label": "Next event",
|
|
1081
|
+
"data-testid": `${testId}-nav-next`,
|
|
1082
|
+
disabled: rows.length === 0 || currentIndex !== null && currentIndex >= rows.length - 1,
|
|
1083
|
+
onClick: () => goTo(currentIndex === null ? 0 : currentIndex + 1),
|
|
1084
|
+
className: "rounded p-0.5 text-muted-foreground enabled:hover:bg-accent enabled:hover:text-foreground disabled:opacity-30",
|
|
1085
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-4 w-4" })
|
|
1086
|
+
}
|
|
1087
|
+
)
|
|
1088
|
+
] });
|
|
874
1089
|
if (rows.length === 0) {
|
|
875
1090
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
876
1091
|
"div",
|
|
@@ -882,7 +1097,8 @@ function TimelineGantt({
|
|
|
882
1097
|
),
|
|
883
1098
|
children: [
|
|
884
1099
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Calendar, { className: "w-12 h-12 mb-3 opacity-30" }),
|
|
885
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: emptyMessage })
|
|
1100
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: emptyMessage }),
|
|
1101
|
+
navigatorContainer && reactDom.createPortal(navigator, navigatorContainer)
|
|
886
1102
|
]
|
|
887
1103
|
}
|
|
888
1104
|
);
|
|
@@ -898,89 +1114,200 @@ function TimelineGantt({
|
|
|
898
1114
|
const axisMax = new Date(maxTime + pad);
|
|
899
1115
|
const axisRange = axisMax.getTime() - axisMin.getTime();
|
|
900
1116
|
const toPercent = (d) => Math.min(100, Math.max(0, (d.getTime() - axisMin.getTime()) / axisRange * 100));
|
|
1117
|
+
const tickCount = Math.max(2, Math.min(50, Math.round(8 * zoomValue) + 1));
|
|
1118
|
+
const tickStep = axisRange / (tickCount - 1);
|
|
1119
|
+
const tickIntervalDays = tickStep / DAY_MS;
|
|
901
1120
|
const ticks = Array.from(
|
|
902
|
-
{ length:
|
|
903
|
-
(_, i) => new Date(axisMin.getTime() +
|
|
1121
|
+
{ length: tickCount },
|
|
1122
|
+
(_, i) => new Date(axisMin.getTime() + tickStep * i)
|
|
904
1123
|
);
|
|
905
1124
|
const today = /* @__PURE__ */ new Date();
|
|
906
1125
|
const todayPct = toPercent(today);
|
|
907
1126
|
const showToday = today > axisMin && today < axisMax;
|
|
908
1127
|
const activeItem = activeIndex !== null ? rows[activeIndex] : null;
|
|
909
1128
|
const detail = activeItem ? renderDetail?.(activeItem) : null;
|
|
910
|
-
const select = (index) => {
|
|
911
|
-
setActiveIndex(index);
|
|
912
|
-
onSelect?.(index === null ? null : rows[index]);
|
|
913
|
-
};
|
|
914
1129
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ui.cn("flex h-full min-h-0", className), children: [
|
|
915
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex
|
|
916
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1130
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex min-h-0 flex-1 flex-col p-5", children: [
|
|
1131
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1132
|
+
"div",
|
|
1133
|
+
{
|
|
1134
|
+
"aria-hidden": true,
|
|
1135
|
+
"data-testid": `${testId}-gutter-divider`,
|
|
1136
|
+
style: { left: `calc(20px + ${labelWidth}px)` },
|
|
1137
|
+
className: "pointer-events-none absolute top-0 bottom-0 z-30 w-[3px] -translate-x-1/2 bg-border"
|
|
1138
|
+
}
|
|
1139
|
+
),
|
|
1140
|
+
onLabelWidthChange && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1141
|
+
"div",
|
|
1142
|
+
{
|
|
1143
|
+
role: "separator",
|
|
1144
|
+
"aria-orientation": "vertical",
|
|
1145
|
+
"aria-label": "Resize event column",
|
|
1146
|
+
"data-testid": `${testId}-label-resizer`,
|
|
1147
|
+
onMouseDown: startResize,
|
|
1148
|
+
style: { left: `calc(20px + ${labelWidth}px)` },
|
|
1149
|
+
className: "absolute top-0 bottom-0 z-30 w-2 -translate-x-1/2 cursor-col-resize group",
|
|
1150
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto h-full w-[3px] transition-colors group-hover:bg-primary" })
|
|
1151
|
+
}
|
|
1152
|
+
),
|
|
1153
|
+
!zoomControlled && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-2 top-2 z-30 shadow-sm", children: /* @__PURE__ */ jsxRuntime.jsx(TimelineZoomControl, { zoom: zoomValue, onZoomChange: setZoomValue, testId }) }),
|
|
1154
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 flex min-h-0 flex-1", children: [
|
|
1155
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { width: labelWidth, flexShrink: 0 }, className: "flex min-h-0 flex-col", children: [
|
|
1156
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-1 flex h-6 shrink-0 items-center gap-1 border-b border-border pr-3 text-sm font-medium text-muted-foreground", children: [
|
|
1157
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Event" }),
|
|
1158
|
+
navigatorContainer === void 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-auto", children: navigator }) : navigatorContainer && reactDom.createPortal(navigator, navigatorContainer)
|
|
1159
|
+
] }),
|
|
1160
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1161
|
+
"div",
|
|
1162
|
+
{
|
|
1163
|
+
ref: gutterBodyRef,
|
|
1164
|
+
"data-testid": `${testId}-gutter`,
|
|
1165
|
+
className: "min-h-0 flex-1 overflow-hidden",
|
|
1166
|
+
onWheel: (e) => {
|
|
1167
|
+
if (chartRef.current) chartRef.current.scrollTop += e.deltaY;
|
|
1168
|
+
},
|
|
1169
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-4 pb-6", children: rows.map((item, i) => {
|
|
1170
|
+
const isActive = activeIndex === i;
|
|
1171
|
+
const isSelected = selectedIds?.includes(item.id) ?? false;
|
|
1172
|
+
const highlighted = isActive || isSelected;
|
|
1173
|
+
const zebra = i % 2 === 1;
|
|
1174
|
+
return (
|
|
1175
|
+
// Fixed h-9 = the track row's h-7 bar + py-1, so the two
|
|
1176
|
+
// columns stay row-for-row aligned. Clicking the label
|
|
1177
|
+
// selects the row exactly like its bar does — the label IS
|
|
1178
|
+
// the event's title, so it should be a target too. Keep the
|
|
1179
|
+
// `break-words leading-snug` tail on THIS element — a scoped
|
|
1180
|
+
// CSS rule (.facts-gantt .break-words.leading-snug) clamps it
|
|
1181
|
+
// to one line. A selected row gets the tint plus a left
|
|
1182
|
+
// accent bar, mirroring the facts table's edited-row mark.
|
|
1183
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1184
|
+
"div",
|
|
1185
|
+
{
|
|
1186
|
+
onClick: () => select(isActive ? null : i),
|
|
1187
|
+
className: ui.cn(
|
|
1188
|
+
"flex h-9 cursor-pointer items-center rounded border-l-[3px] pr-3 text-xs font-medium",
|
|
1189
|
+
isSelected ? "border-l-primary bg-primary/10" : zebra ? "border-l-transparent bg-muted" : "border-l-transparent bg-background",
|
|
1190
|
+
highlighted ? "text-foreground" : "text-muted-foreground",
|
|
1191
|
+
"break-words leading-snug"
|
|
1192
|
+
),
|
|
1193
|
+
title: item.label,
|
|
1194
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 flex-1 truncate", children: [
|
|
1195
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mr-1 tabular-nums text-muted-foreground/60", children: [
|
|
1196
|
+
i + 1,
|
|
1197
|
+
"."
|
|
1198
|
+
] }),
|
|
1199
|
+
item.label
|
|
1200
|
+
] })
|
|
1201
|
+
},
|
|
1202
|
+
item.id
|
|
1203
|
+
)
|
|
1204
|
+
);
|
|
1205
|
+
}) })
|
|
1206
|
+
}
|
|
1207
|
+
)
|
|
1208
|
+
] }),
|
|
917
1209
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
918
1210
|
"div",
|
|
919
1211
|
{
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
),
|
|
925
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 relative h-6 border-b border-border", children: ticks.map((tick, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
926
|
-
"span",
|
|
927
|
-
{
|
|
928
|
-
style: { left: `${toPercent(tick)}%` },
|
|
929
|
-
className: "absolute bottom-1 text-[10px] text-muted-foreground -translate-x-1/2 whitespace-nowrap select-none",
|
|
930
|
-
children: formatAxisDate(tick)
|
|
931
|
-
},
|
|
932
|
-
i
|
|
933
|
-
)) })
|
|
934
|
-
] }),
|
|
935
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative mt-2", children: [
|
|
936
|
-
showToday && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
937
|
-
"div",
|
|
938
|
-
{
|
|
939
|
-
className: "absolute top-0 bottom-0 pointer-events-none z-10",
|
|
940
|
-
style: {
|
|
941
|
-
left: `calc(${labelWidth}px + (100% - ${labelWidth}px) * ${todayPct / 100})`
|
|
1212
|
+
ref: chartRef,
|
|
1213
|
+
"data-testid": `${testId}-scroller`,
|
|
1214
|
+
onScroll: (e) => {
|
|
1215
|
+
if (gutterBodyRef.current) gutterBodyRef.current.scrollTop = e.currentTarget.scrollTop;
|
|
942
1216
|
},
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
]
|
|
947
|
-
}
|
|
948
|
-
),
|
|
949
|
-
rows.map((item, i) => {
|
|
950
|
-
const startDate = parseIso2(item.start);
|
|
951
|
-
const endDate = parseIso2(item.end ?? null) ?? startDate;
|
|
952
|
-
const leftPct = toPercent(startDate);
|
|
953
|
-
const widthPct = Math.max(1, toPercent(endDate) - toPercent(startDate));
|
|
954
|
-
const color = BAR_PALETTE[i % BAR_PALETTE.length];
|
|
955
|
-
const isActive = activeIndex === i;
|
|
956
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center mb-2", children: [
|
|
957
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
958
|
-
"div",
|
|
959
|
-
{
|
|
960
|
-
style: { width: labelWidth, flexShrink: 0 },
|
|
961
|
-
className: "text-xs font-medium text-muted-foreground pr-3 break-words leading-snug",
|
|
962
|
-
children: item.label
|
|
963
|
-
}
|
|
1217
|
+
className: ui.cn(
|
|
1218
|
+
"scrollbar-subtle relative min-h-0 min-w-0 flex-1 overflow-y-auto",
|
|
1219
|
+
zoomValue > 1 ? "overflow-x-auto" : "overflow-x-hidden"
|
|
964
1220
|
),
|
|
965
|
-
|
|
966
|
-
"
|
|
1221
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1222
|
+
"div",
|
|
967
1223
|
{
|
|
968
|
-
"
|
|
969
|
-
"
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1224
|
+
className: "relative min-h-full",
|
|
1225
|
+
style: { width: `${zoomValue * 100}%`, minWidth: "100%" },
|
|
1226
|
+
children: [
|
|
1227
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky top-0 z-20 mb-1 h-6 border-b border-border bg-background", children: ticks.map((tick, i) => {
|
|
1228
|
+
const isFirst = i === 0;
|
|
1229
|
+
const isLast = i === ticks.length - 1;
|
|
1230
|
+
const edgeShift = isFirst ? "translate-x-1" : isLast ? "-translate-x-full" : "-translate-x-1/2";
|
|
1231
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1232
|
+
"span",
|
|
1233
|
+
{
|
|
1234
|
+
style: { left: `${toPercent(tick)}%` },
|
|
1235
|
+
className: `absolute bottom-1 text-[10px] text-muted-foreground whitespace-nowrap select-none ${edgeShift}`,
|
|
1236
|
+
children: formatAxisDate(tick, tickIntervalDays)
|
|
1237
|
+
},
|
|
1238
|
+
i
|
|
1239
|
+
);
|
|
1240
|
+
}) }),
|
|
1241
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative pt-4", children: [
|
|
1242
|
+
ticks.map((tick, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1243
|
+
"div",
|
|
1244
|
+
{
|
|
1245
|
+
"aria-hidden": true,
|
|
1246
|
+
className: "pointer-events-none absolute top-0 bottom-0 z-10 border-l-2 border-border/30",
|
|
1247
|
+
style: { left: `${toPercent(tick)}%` }
|
|
1248
|
+
},
|
|
1249
|
+
`grid-${i}`
|
|
1250
|
+
)),
|
|
1251
|
+
showToday && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1252
|
+
"div",
|
|
1253
|
+
{
|
|
1254
|
+
className: "pointer-events-none absolute top-0 bottom-0 z-10",
|
|
1255
|
+
style: { left: `${todayPct}%` },
|
|
1256
|
+
children: [
|
|
1257
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full border-l-2 border-dashed border-muted-foreground/50" }),
|
|
1258
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute top-0 -translate-x-1/2 rounded border bg-background px-1 text-[9px] text-muted-foreground", children: "today" })
|
|
1259
|
+
]
|
|
1260
|
+
}
|
|
1261
|
+
),
|
|
1262
|
+
rows.map((item, i) => {
|
|
1263
|
+
const startDate = parseIso2(item.start);
|
|
1264
|
+
const endDate = parseIso2(item.end ?? null) ?? startDate;
|
|
1265
|
+
const leftPct = toPercent(startDate);
|
|
1266
|
+
const dayPct = DAY_MS / axisRange * 100;
|
|
1267
|
+
const widthPct = Math.max(dayPct, toPercent(endDate) - toPercent(startDate));
|
|
1268
|
+
const isActive = activeIndex === i;
|
|
1269
|
+
const isSelected = selectedIds?.includes(item.id) ?? false;
|
|
1270
|
+
const highlighted = isActive || isSelected;
|
|
1271
|
+
const zebra = i % 2 === 1;
|
|
1272
|
+
return (
|
|
1273
|
+
// Fixed h-9 (h-7 bar + py-1) — the gutter's label rows use
|
|
1274
|
+
// the same height, which is what keeps the columns aligned.
|
|
1275
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-9 py-1", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1276
|
+
"div",
|
|
1277
|
+
{
|
|
1278
|
+
className: ui.cn(
|
|
1279
|
+
"relative h-7 overflow-visible rounded-md border border-border",
|
|
1280
|
+
isSelected ? "bg-primary/10" : zebra ? "bg-muted" : "bg-background"
|
|
1281
|
+
),
|
|
1282
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1283
|
+
"button",
|
|
1284
|
+
{
|
|
1285
|
+
"data-testid": `${testId}-bar-${i}`,
|
|
1286
|
+
"aria-label": item.label,
|
|
1287
|
+
"aria-pressed": highlighted,
|
|
1288
|
+
style: {
|
|
1289
|
+
left: `${leftPct}%`,
|
|
1290
|
+
width: `${widthPct}%`,
|
|
1291
|
+
// Colour-coded by the item's labels (up to three), not by
|
|
1292
|
+
// row order — see barBackground / TimelineItem.colors.
|
|
1293
|
+
background: barBackground(item.colors),
|
|
1294
|
+
boxShadow: highlighted ? "0 0 0 2px white, 0 0 0 4px #6366f1" : "0 1px 3px rgba(0,0,0,.2)"
|
|
1295
|
+
},
|
|
1296
|
+
title: formatSpan(item.start, item.end) || void 0,
|
|
1297
|
+
className: "absolute top-0.5 h-6 min-w-[6px] rounded cursor-pointer transition-[filter] hover:brightness-110 focus:outline-none",
|
|
1298
|
+
onClick: () => select(isActive ? null : i)
|
|
1299
|
+
}
|
|
1300
|
+
)
|
|
1301
|
+
}
|
|
1302
|
+
) }, item.id)
|
|
1303
|
+
);
|
|
1304
|
+
})
|
|
1305
|
+
] })
|
|
1306
|
+
]
|
|
980
1307
|
}
|
|
981
|
-
)
|
|
982
|
-
|
|
983
|
-
|
|
1308
|
+
)
|
|
1309
|
+
}
|
|
1310
|
+
)
|
|
984
1311
|
] })
|
|
985
1312
|
] }),
|
|
986
1313
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1010,12 +1337,18 @@ function TimelineGantt({
|
|
|
1010
1337
|
}
|
|
1011
1338
|
) }),
|
|
1012
1339
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1013
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1340
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1014
1341
|
"div",
|
|
1015
1342
|
{
|
|
1016
1343
|
"data-testid": `${testId}-panel-title`,
|
|
1017
1344
|
className: "text-sm font-semibold text-foreground leading-snug",
|
|
1018
|
-
children:
|
|
1345
|
+
children: [
|
|
1346
|
+
activeIndex !== null && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mr-1 tabular-nums text-muted-foreground", children: [
|
|
1347
|
+
activeIndex + 1,
|
|
1348
|
+
"."
|
|
1349
|
+
] }),
|
|
1350
|
+
activeItem.label
|
|
1351
|
+
]
|
|
1019
1352
|
}
|
|
1020
1353
|
),
|
|
1021
1354
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 mt-1 text-xs text-muted-foreground", children: [
|
|
@@ -2038,6 +2371,7 @@ exports.SectionHeading = SectionHeading;
|
|
|
2038
2371
|
exports.Spacer = Spacer;
|
|
2039
2372
|
exports.Steps = Steps;
|
|
2040
2373
|
exports.THEME_STYLE_ID = THEME_STYLE_ID;
|
|
2374
|
+
exports.TIMELINE_ZOOM = TIMELINE_ZOOM;
|
|
2041
2375
|
exports.Tabs = Tabs;
|
|
2042
2376
|
exports.TeamGrid = TeamGrid;
|
|
2043
2377
|
exports.Testimonial = Testimonial;
|
|
@@ -2046,6 +2380,7 @@ exports.ThemeEditor = ThemeEditor;
|
|
|
2046
2380
|
exports.Timeline = Timeline;
|
|
2047
2381
|
exports.TimelineCalendar = TimelineCalendar;
|
|
2048
2382
|
exports.TimelineGantt = TimelineGantt;
|
|
2383
|
+
exports.TimelineZoomControl = TimelineZoomControl;
|
|
2049
2384
|
exports.VideoEmbed = VideoEmbed;
|
|
2050
2385
|
exports.applyFontVars = applyFontVars;
|
|
2051
2386
|
exports.applyStoredTheme = applyStoredTheme;
|
|
@@ -2078,5 +2413,5 @@ exports.toHslTriplet = toHslTriplet;
|
|
|
2078
2413
|
exports.useAssetRenderer = useAssetRenderer;
|
|
2079
2414
|
exports.useAssetResolverActive = useAssetResolverActive;
|
|
2080
2415
|
exports.utcDay = utcDay;
|
|
2081
|
-
//# sourceMappingURL=chunk-
|
|
2082
|
-
//# sourceMappingURL=chunk-
|
|
2416
|
+
//# sourceMappingURL=chunk-DBAUZD7A.cjs.map
|
|
2417
|
+
//# sourceMappingURL=chunk-DBAUZD7A.cjs.map
|