@tensor_1/react 2.34.0 → 2.36.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 (48) hide show
  1. package/README.md +164 -15
  2. package/dist/{SchedulingCalendar-DSLQ6AY5.d.ts → SchedulingCalendar-B6TakO2h.d.ts} +13 -1
  3. package/dist/components/Avatar.d.ts +26 -4
  4. package/dist/components/Avatar.js +26 -3
  5. package/dist/components/CommandPalette.d.ts +6 -1
  6. package/dist/components/CommandPalette.js +9 -3
  7. package/dist/components/DateFieldParts.d.ts +61 -0
  8. package/dist/components/DateFieldParts.js +195 -0
  9. package/dist/components/DatePicker.d.ts +129 -0
  10. package/dist/components/DatePicker.js +340 -0
  11. package/dist/components/DateRangePicker.d.ts +34 -3
  12. package/dist/components/DateRangePicker.js +120 -17
  13. package/dist/components/EntityPicker.d.ts +29 -4
  14. package/dist/components/EntityPicker.js +20 -8
  15. package/dist/components/InlineEdit.d.ts +74 -0
  16. package/dist/components/InlineEdit.js +176 -0
  17. package/dist/components/List.d.ts +8 -1
  18. package/dist/components/List.js +3 -1
  19. package/dist/components/ListRoving.d.ts +12 -0
  20. package/dist/components/ListRoving.js +51 -0
  21. package/dist/components/Radio.d.ts +23 -2
  22. package/dist/components/Radio.js +18 -3
  23. package/dist/components/SchedulingAgenda.d.ts +1 -1
  24. package/dist/components/SchedulingAgenda.js +101 -4
  25. package/dist/components/SchedulingCalendar.d.ts +1 -1
  26. package/dist/components/SchedulingCalendar.js +89 -9
  27. package/dist/components/SchedulingTimeGrid.d.ts +1 -1
  28. package/dist/components/SchedulingTimeGrid.js +101 -4
  29. package/dist/components/SchedulingTimeline.d.ts +130 -15
  30. package/dist/components/SchedulingTimeline.js +765 -141
  31. package/dist/components/SearchSuggest.d.ts +12 -1
  32. package/dist/components/SearchSuggest.js +7 -2
  33. package/dist/components/Tree.d.ts +30 -2
  34. package/dist/components/Tree.js +135 -10
  35. package/dist/date-field-model.d.ts +40 -0
  36. package/dist/date-field-model.js +79 -0
  37. package/dist/index.cjs +4179 -2056
  38. package/dist/index.d.cts +829 -264
  39. package/dist/index.d.ts +10 -7
  40. package/dist/index.js +40 -4
  41. package/dist/labels-context.d.ts +15 -2
  42. package/dist/roving.d.ts +15 -0
  43. package/dist/roving.js +17 -0
  44. package/dist/scheduling-timeline-model.d.ts +61 -11
  45. package/dist/scheduling-timeline-model.js +85 -26
  46. package/dist/scheduling-timeline-rows.d.ts +40 -0
  47. package/dist/scheduling-timeline-rows.js +42 -0
  48. package/package.json +5 -3
@@ -1,10 +1,13 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- import { useId, useMemo, useRef, useState } from "react";
3
+ import { useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
4
+ import { cachedDateTimeFormat, zonedDayBounds, zonedTimeOfDay } from "../calendar-model.js";
5
+ import { proposeMove } from "../scheduling-calendar-model.js";
4
6
  import { cx } from "../cx.js";
5
7
  import { fillLabel } from "../fill-label.js";
6
8
  import { useUixLabels } from "../labels-context.js";
7
- import { defaultTimelineStep, layoutLane, pixelsToMs, placeSpan, shiftSpan, timelineTicks } from "../scheduling-timeline-model.js";
9
+ import { defaultTimelineStep, layoutLane, pixelsToMs, placeSpan, shiftSpan, timelineRepeatedHourOffset, timelineStepDelta, timelineSubTicks, timelineTicks } from "../scheduling-timeline-model.js";
10
+ import { timelineRowExtents, timelineWindow } from "../scheduling-timeline-rows.js";
8
11
  const DEFAULT_SCHEDULING_TIMELINE_LABELS = {
9
12
  region: "Schedule timeline in {timeZone}",
10
13
  lanes: "Lanes",
@@ -19,7 +22,17 @@ const DEFAULT_SCHEDULING_TIMELINE_LABELS = {
19
22
  windows: "Windows and markers in this range",
20
23
  now: "Now",
21
24
  states: { scheduled: "Scheduled", conflicted: "Conflicted", "in-progress": "In progress", "blackout-violation": "Blackout violation" },
22
- overlays: { freeze: "Change freeze", maintenance: "Maintenance window", blackout: "Blackout" }
25
+ overlays: { "freeze": "Change freeze", maintenance: "Maintenance window", "blackout": "Blackout" },
26
+ statuses: { tentative: "Tentative", committed: "Scheduled", live: "In progress", done: "Done", dead: "Cancelled" },
27
+ overlay: "{title}, {start} to {end}",
28
+ moveKeysHint: "Shift and an arrow key moves it.",
29
+ resizeHint: "Alt, Shift and an arrow key moves its end.",
30
+ proposeHint: "Hold Shift and press an arrow key to move it. Enter confirms, Escape cancels.",
31
+ moveProposed: "Move to {start} \u2013 {end}. Enter confirms, Escape cancels.",
32
+ moveCancelled: "Move cancelled.",
33
+ gapForward: "That time does not exist on this day. Moved forward to {time}.",
34
+ groupSummary: "Items: {count}",
35
+ moveSent: "Requested a move to {start} \u2013 {end}."
23
36
  };
24
37
  const TICK_WIDTH = { hour: "3.5rem", day: "7rem", week: "6rem", month: "8rem" };
25
38
  const TICK_FORMAT = {
@@ -28,6 +41,36 @@ const TICK_FORMAT = {
28
41
  week: { day: "numeric", month: "short" },
29
42
  month: { month: "short", year: "numeric" }
30
43
  };
44
+ const EMPTY_ITEMS = [];
45
+ const EMPTY_OVERLAYS = [];
46
+ const EMPTY_MARKERS = [];
47
+ const DRAG_THRESHOLD = 4;
48
+ const DEFAULT_UNITS = { row: 36, pad: 16, head: 40 };
49
+ const ESTIMATED_VIEWPORT = 720;
50
+ const OVERSCAN_ROWS = 4;
51
+ const BAR_MIN_FALLBACK = 1.5 * 16;
52
+ const useIsomorphicLayoutEffect = typeof document !== "undefined" ? useLayoutEffect : useEffect;
53
+ const sameExtent = (a, b) => a.subRows === b.subRows && a.lanes === b.lanes && a.heads === b.heads;
54
+ const IN_LANE = { scope: "lane", labelTops: ["0%"] };
55
+ const vars = (values) => values;
56
+ const extentCss = (extent) => `calc(${extent.subRows} * var(--timeline-bar-row) + ${extent.lanes} * var(--timeline-lane-pad) + ${extent.heads} * var(--timeline-head))`;
57
+ const between = (from, to) => ({ subRows: to.subRows - from.subRows, lanes: to.lanes - from.lanes, heads: to.heads - from.heads });
58
+ function uniqueKeys(ids) {
59
+ const seen = /* @__PURE__ */ new Map();
60
+ return ids.map((id) => {
61
+ const count = seen.get(id) ?? 0;
62
+ seen.set(id, count + 1);
63
+ return count === 0 ? id : `${id}#${count}`;
64
+ });
65
+ }
66
+ const lanesOf = (target) => target.laneIds !== void 0 ? new Set(target.laneIds) : target.laneId ? /* @__PURE__ */ new Set([target.laneId]) : null;
67
+ function ItemMarker({ marker, showLabel }) {
68
+ const emphasis = marker.emphasis ?? "neutral";
69
+ return /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-timeline__item-marker", "data-emphasis": emphasis, "data-glyph": marker.icon == null ? emphasis : void 0, children: [
70
+ marker.icon != null && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__item-marker-icon", "aria-hidden": "true", children: marker.icon }),
71
+ /* @__PURE__ */ jsx("span", { className: showLabel ? "uix-scheduling-timeline__item-marker-label" : "uix-visually-hidden", children: marker.label })
72
+ ] });
73
+ }
31
74
  function SchedulingTimeline({
32
75
  lanes,
33
76
  items,
@@ -35,17 +78,28 @@ function SchedulingTimeline({
35
78
  scale = "day",
36
79
  timeZone,
37
80
  locale,
38
- overlays = [],
39
- markers = [],
81
+ groups,
82
+ onToggleGroup,
83
+ overlays = EMPTY_OVERLAYS,
84
+ onSelectOverlay,
85
+ markers = EMPTY_MARKERS,
40
86
  now,
41
87
  weekStartsOn = 1,
42
88
  tickWidth,
43
89
  step: stepProp,
90
+ subTicks,
44
91
  formatTick,
45
92
  formatInstant: formatInstantProp,
46
93
  renderItem,
47
94
  onSelectItem,
48
95
  onMoveItem,
96
+ onProposeMove,
97
+ onResizeItem,
98
+ flagOverlaps = true,
99
+ virtualizeAbove = 150,
100
+ maxHeight,
101
+ notice,
102
+ columnNotes,
49
103
  loading,
50
104
  error,
51
105
  onRetry,
@@ -53,47 +107,358 @@ function SchedulingTimeline({
53
107
  className
54
108
  }) {
55
109
  const uixLabels = useUixLabels();
110
+ const provided = { ...uixLabels.schedulingTimeline, ...labelOverrides };
56
111
  const labels = {
57
112
  ...DEFAULT_SCHEDULING_TIMELINE_LABELS,
58
- ...uixLabels.schedulingTimeline,
59
- ...labelOverrides,
113
+ ...provided,
60
114
  states: { ...DEFAULT_SCHEDULING_TIMELINE_LABELS.states, ...uixLabels.schedulingTimeline?.states, ...labelOverrides?.states },
61
- overlays: { ...DEFAULT_SCHEDULING_TIMELINE_LABELS.overlays, ...uixLabels.schedulingTimeline?.overlays, ...labelOverrides?.overlays }
115
+ overlays: { ...DEFAULT_SCHEDULING_TIMELINE_LABELS.overlays, ...uixLabels.schedulingTimeline?.overlays, ...labelOverrides?.overlays },
116
+ statuses: { ...DEFAULT_SCHEDULING_TIMELINE_LABELS.statuses, ...uixLabels.schedulingTimeline?.statuses, ...labelOverrides?.statuses }
62
117
  };
63
118
  const id = useId();
64
- const step = stepProp ?? defaultTimelineStep(scale);
119
+ const hintId = `${id}-hint`;
120
+ const step = stepProp !== void 0 && Number.isFinite(stepProp) && stepProp > 0 ? stepProp : defaultTimelineStep(scale);
121
+ const rootRef = useRef(null);
122
+ const [scroller, setScroller] = useState(null);
123
+ const [layerTrack, setLayerTrack] = useState(null);
124
+ const probeRef = useRef(null);
125
+ const bodyRef = useRef(null);
126
+ const barMinRef = useRef(null);
127
+ const [barShare, setBarShare] = useState(0);
128
+ const barShareRef = useRef(0);
129
+ const anchorRef = useRef(null);
130
+ const [anchorKey, setAnchorKey] = useState(null);
131
+ const [focusRowKey, setFocusRowKey] = useState(null);
132
+ const [activeId, setActiveId] = useState();
133
+ const [focusRequest, setFocusRequest] = useState(null);
134
+ const [stepDrag, setStepDrag] = useState(null);
135
+ const [ghost, setGhost] = useState(null);
136
+ const [announcement, setAnnouncement] = useState("");
137
+ const [selfCollapsed, setSelfCollapsed] = useState({});
138
+ const [units, setUnits] = useState(DEFAULT_UNITS);
139
+ const [view, setView] = useState({ top: 0, height: ESTIMATED_VIEWPORT });
140
+ const measureViewRef = useRef(null);
141
+ const proposing = onProposeMove !== void 0;
142
+ const stepping = !proposing && onMoveItem !== void 0;
143
+ const resizing = onResizeItem !== void 0;
144
+ const canMove = (item) => (proposing || stepping) && item.movable !== false;
145
+ const canResize = (item) => resizing && item.movable !== false;
146
+ const hint = proposing ? [labels.proposeHint, resizing ? labels.resizeHint : ""].filter(Boolean).join(" ") : stepping ? resizing ? labels.moveHint : labels.moveKeysHint : resizing ? labels.resizeHint : "";
147
+ const fmtInstant = (instant) => {
148
+ const custom = formatInstantProp?.(instant);
149
+ if (custom != null) return custom;
150
+ const text = cachedDateTimeFormat(locale, { timeZone, dateStyle: "medium", timeStyle: "short" }).format(new Date(instant));
151
+ const offset = timelineRepeatedHourOffset(instant, timeZone);
152
+ return offset ? `${text} ${offset}` : text;
153
+ };
154
+ const fmtTick = (at) => formatTick?.(new Date(at), scale) ?? cachedDateTimeFormat(locale, { timeZone, ...TICK_FORMAT[scale] }).format(new Date(at));
65
155
  const ticks = useMemo(() => timelineTicks(range, scale, timeZone, weekStartsOn), [range.start, range.end, scale, timeZone, weekStartsOn]);
66
- const laneLayouts = useMemo(
67
- () => lanes.map((lane) => ({ lane, placed: layoutLane(items.filter((item) => item.laneId === lane.id), range) })),
68
- [lanes, items, range.start, range.end]
156
+ const minorTicks = useMemo(
157
+ () => scale === "day" && subTicks && subTicks.length > 0 ? timelineSubTicks(range, subTicks, timeZone) : [],
158
+ [range.start, range.end, scale, timeZone, subTicks]
69
159
  // eslint-disable-line react-hooks/exhaustive-deps
70
160
  );
71
- const ordered = useMemo(() => laneLayouts.flatMap((l) => l.placed.map((p) => p.item.id)), [laneLayouts]);
72
- const [activeId, setActiveId] = useState();
73
- const focusId = activeId && ordered.includes(activeId) ? activeId : ordered[0];
74
- const [drag, setDrag] = useState(null);
75
- const [announcement, setAnnouncement] = useState("");
76
- const rootRef = useRef(null);
77
- const fmtInstant = (instant) => formatInstantProp?.(instant) ?? new Intl.DateTimeFormat(locale, { timeZone, dateStyle: "medium", timeStyle: "short" }).format(new Date(instant));
78
- const fmtTick = (at) => formatTick?.(new Date(at), scale) ?? new Intl.DateTimeFormat(locale, { timeZone, ...TICK_FORMAT[scale] }).format(new Date(at));
79
- const movable = (item) => !!onMoveItem && item.movable !== false;
161
+ const tickCount = ticks.length;
162
+ useIsomorphicLayoutEffect(() => {
163
+ if (!layerTrack) return void 0;
164
+ const measure = () => {
165
+ const width = layerTrack.getBoundingClientRect().width;
166
+ const bar = barMinRef.current?.getBoundingClientRect().width || BAR_MIN_FALLBACK;
167
+ const share = width > 0 ? Math.round(bar / width * 1e5) / 1e5 : 0;
168
+ if (share === barShareRef.current) return;
169
+ barShareRef.current = share;
170
+ const body2 = bodyRef.current;
171
+ const edge = Math.max(0, scroller?.getBoundingClientRect().top ?? 0);
172
+ const first = body2 ? Array.from(body2.children).find((row) => row.hasAttribute("data-row-key") && row.getBoundingClientRect().bottom > edge + 1) : void 0;
173
+ anchorRef.current = first ? { key: first.getAttribute("data-row-key"), top: first.getBoundingClientRect().top } : null;
174
+ setAnchorKey(anchorRef.current?.key ?? null);
175
+ setBarShare(share);
176
+ };
177
+ measure();
178
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(measure);
179
+ observer?.observe(layerTrack);
180
+ return () => observer?.disconnect();
181
+ }, [layerTrack, scroller, tickWidth, scale, tickCount]);
182
+ const minSpan = Math.max(0, (Date.parse(range.end) - Date.parse(range.start)) * barShare) || 0;
183
+ const isCollapsed = (group) => onToggleGroup ? !!group.collapsed : selfCollapsed[group.id] ?? !!group.collapsed;
184
+ const collapsedSignature = groups?.map((group) => isCollapsed(group) ? "1" : "0").join("") ?? "";
185
+ const itemsByLane = useMemo(() => {
186
+ const byLane = /* @__PURE__ */ new Map();
187
+ for (const item of items) {
188
+ const list = byLane.get(item.laneId);
189
+ if (list) list.push(item);
190
+ else byLane.set(item.laneId, [item]);
191
+ }
192
+ return byLane;
193
+ }, [items]);
194
+ const rows = useMemo(() => {
195
+ const laneRow = (lane, laneIndex, groupIndex) => {
196
+ const placed = layoutLane(itemsByLane.get(lane.id) ?? EMPTY_ITEMS, range, { flagOverlaps, minSpan });
197
+ return { kind: "lane", key: `lane-${laneIndex}`, lane, laneIndex, groupIndex, placed, keys: uniqueKeys(placed.map((p) => p.item.id)), subRows: placed.reduce((max, p) => Math.max(max, p.row + 1), 1) };
198
+ };
199
+ if (!groups) return lanes.map((lane, laneIndex) => laneRow(lane, laneIndex));
200
+ const indexes = /* @__PURE__ */ new Map();
201
+ lanes.forEach((lane, laneIndex) => {
202
+ const list2 = indexes.get(lane.id);
203
+ if (list2) list2.push(laneIndex);
204
+ else indexes.set(lane.id, [laneIndex]);
205
+ });
206
+ const used = /* @__PURE__ */ new Set();
207
+ const list = [];
208
+ groups.forEach((group, groupIndex) => {
209
+ const collapsed = collapsedSignature[groupIndex] === "1";
210
+ const stands = /* @__PURE__ */ new Set();
211
+ list.push({ kind: "head", key: `group-${groupIndex}`, group, groupIndex, collapsed, stands });
212
+ for (const laneId of group.laneIds) {
213
+ for (const laneIndex of indexes.get(laneId) ?? []) {
214
+ if (used.has(laneIndex)) continue;
215
+ used.add(laneIndex);
216
+ if (collapsed) stands.add(laneId);
217
+ else list.push(laneRow(lanes[laneIndex], laneIndex, groupIndex));
218
+ }
219
+ }
220
+ });
221
+ lanes.forEach((lane, laneIndex) => {
222
+ if (!used.has(laneIndex)) list.push(laneRow(lane, laneIndex));
223
+ });
224
+ return list;
225
+ }, [lanes, groups, collapsedSignature, itemsByLane, range.start, range.end, flagOverlaps, minSpan]);
226
+ const laneRows = useMemo(() => rows.filter((row) => row.kind === "lane"), [rows]);
227
+ const { ordered, rowOfItem } = useMemo(() => {
228
+ const ids = [];
229
+ const rowOf = /* @__PURE__ */ new Map();
230
+ rows.forEach((row, rowIndex) => {
231
+ if (row.kind !== "lane") return;
232
+ for (const p of row.placed) {
233
+ ids.push(p.item.id);
234
+ if (!rowOf.has(p.item.id)) rowOf.set(p.item.id, rowIndex);
235
+ }
236
+ });
237
+ return { ordered: ids, rowOfItem: rowOf };
238
+ }, [rows]);
239
+ const nothingToShow = useMemo(() => {
240
+ if (ordered.length > 0) return false;
241
+ const laneIds = new Set(lanes.map((lane) => lane.id));
242
+ return !items.some((item) => laneIds.has(item.laneId) && placeSpan(item, range) !== null);
243
+ }, [ordered, lanes, items, range.start, range.end]);
244
+ const focusId = activeId !== void 0 && rowOfItem.has(activeId) ? activeId : ordered[0];
245
+ const strips = useMemo(() => {
246
+ const set = /* @__PURE__ */ new Set();
247
+ const shown = overlays.filter((overlay) => placeSpan(overlay, range) !== null).map(lanesOf);
248
+ if (shown.length === 0) return set;
249
+ rows.forEach((row, index) => {
250
+ if (row.kind !== "head" || !row.collapsed || row.stands.size === 0) return;
251
+ if (shown.some((targets) => targets === null || [...row.stands].some((laneId) => targets.has(laneId)))) set.add(index);
252
+ });
253
+ return set;
254
+ }, [rows, overlays, range.start, range.end]);
255
+ const extents = useMemo(() => timelineRowExtents(rows.map((row, index) => row.kind === "head" ? { kind: "head", subRows: strips.has(index) ? 1 : 0 } : { kind: "lane", subRows: row.subRows })), [rows, strips]);
256
+ const virtual = lanes.length + (groups?.length ?? 0) > virtualizeAbove;
257
+ const fixed = virtual || groups !== void 0 || overlays.some((overlay) => overlay.laneIds !== void 0) || markers.some((marker) => marker.laneIds !== void 0);
258
+ useEffect(() => {
259
+ if (!virtual || !scroller) return void 0;
260
+ let frame = 0;
261
+ const measure = () => {
262
+ frame = 0;
263
+ const probe = probeRef.current;
264
+ if (probe) {
265
+ const [row, pad, head] = Array.from(probe.children, (child) => child.getBoundingClientRect().height);
266
+ if (row && pad && head) setUnits((current) => current.row === row && current.pad === pad && current.head === head ? current : { row, pad, head });
267
+ }
268
+ const box = scroller.getBoundingClientRect();
269
+ const viewport = window.innerHeight || document.documentElement.clientHeight || 0;
270
+ const shown = Math.min(box.bottom, viewport) - Math.max(box.top, 0);
271
+ const top = scroller.scrollTop + Math.max(0, -box.top);
272
+ const next = { top: Math.round(top), height: shown > 0 ? Math.round(shown) : ESTIMATED_VIEWPORT };
273
+ setView((current) => Math.abs(current.top - next.top) < 8 && current.height === next.height ? current : next);
274
+ };
275
+ const schedule = () => {
276
+ if (frame) return;
277
+ frame = -1;
278
+ const handle = requestAnimationFrame(measure);
279
+ if (frame === -1) frame = handle || 1;
280
+ };
281
+ measure();
282
+ measureViewRef.current = measure;
283
+ scroller.addEventListener("scroll", schedule, { passive: true });
284
+ window.addEventListener("scroll", schedule, { passive: true, capture: true });
285
+ window.addEventListener("resize", schedule);
286
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(schedule);
287
+ observer?.observe(scroller);
288
+ return () => {
289
+ measureViewRef.current = null;
290
+ scroller.removeEventListener("scroll", schedule);
291
+ window.removeEventListener("scroll", schedule, { capture: true });
292
+ window.removeEventListener("resize", schedule);
293
+ observer?.disconnect();
294
+ if (frame) cancelAnimationFrame(frame);
295
+ };
296
+ }, [virtual, scroller]);
297
+ const px = (extent) => extent.subRows * units.row + extent.lanes * units.pad + extent.heads * units.head;
298
+ const offsets = useMemo(() => virtual ? extents.map(px) : [], [virtual, extents, units]);
299
+ const overscan = OVERSCAN_ROWS * units.row;
300
+ const pinned = focusId === void 0 ? -1 : rowOfItem.get(focusId) ?? -1;
301
+ const focusedRow = useMemo(() => focusRowKey === null ? -1 : rows.findIndex((row) => row.key === focusRowKey), [rows, focusRowKey]);
302
+ const anchorRow = useMemo(() => anchorKey === null ? -1 : rows.findIndex((row) => row.key === anchorKey), [rows, anchorKey]);
303
+ const { mounted, windowStart } = useMemo(() => {
304
+ if (!virtual) return { mounted: rows.map((_, index) => index), windowStart: 0 };
305
+ const window_ = timelineWindow(offsets, view.top, view.top + view.height, { overscan });
306
+ const list = [];
307
+ for (let index = window_.start; index < window_.end; index++) list.push(index);
308
+ for (const index of /* @__PURE__ */ new Set([pinned, focusedRow, anchorRow])) {
309
+ if (index !== -1 && (index < window_.start || index >= window_.end)) list.push(index);
310
+ }
311
+ list.sort((a, b) => a - b);
312
+ return { mounted: list, windowStart: window_.start };
313
+ }, [virtual, rows, offsets, view, overscan, pinned, focusedRow, anchorRow]);
314
+ useIsomorphicLayoutEffect(() => {
315
+ const anchor = anchorRef.current;
316
+ if (anchorKey === null || !anchor || anchor.key !== anchorKey) return;
317
+ anchorRef.current = null;
318
+ setAnchorKey(null);
319
+ const body2 = bodyRef.current;
320
+ const row = body2 ? Array.from(body2.children).find((child) => child.getAttribute("data-row-key") === anchor.key) : void 0;
321
+ if (!row || !scroller) return;
322
+ const delta = row.getBoundingClientRect().top - anchor.top;
323
+ if (Math.abs(delta) < 1) return;
324
+ const view_ = scroller.ownerDocument.defaultView;
325
+ const scrolls = (element) => {
326
+ const overflow = view_?.getComputedStyle(element).overflowY;
327
+ return (overflow === "auto" || overflow === "scroll") && element.scrollHeight > element.clientHeight + 1;
328
+ };
329
+ let holder = scroller;
330
+ while (holder && !scrolls(holder)) holder = holder.parentElement;
331
+ if (holder) holder.scrollTop += delta;
332
+ else view_?.scrollBy(0, delta);
333
+ measureViewRef.current?.();
334
+ }, [anchorKey, barShare]);
335
+ const barMounted = (rowIndex, placed) => {
336
+ if (!virtual || placed.item.id === focusId) return true;
337
+ const top = offsets[rowIndex] + placed.row * units.row;
338
+ return top + units.row + units.pad > view.top - overscan && top < view.top + view.height + overscan;
339
+ };
340
+ const findBar = (itemId) => {
341
+ for (const candidate of Array.from(rootRef.current?.querySelectorAll("[data-timeline-item]") ?? [])) {
342
+ if (candidate.getAttribute("data-timeline-item") === itemId) return candidate;
343
+ }
344
+ return null;
345
+ };
80
346
  const focusItem = (itemId) => {
81
- if (!itemId) return;
347
+ if (itemId === void 0) return;
82
348
  setActiveId(itemId);
83
- requestAnimationFrame(() => rootRef.current?.querySelector(`[data-timeline-item="${CSS.escape(itemId)}"]`)?.focus());
349
+ setFocusRequest({ id: itemId });
350
+ };
351
+ useEffect(() => {
352
+ if (focusRequest) findBar(focusRequest.id)?.focus();
353
+ }, [focusRequest]);
354
+ const itemById = useMemo(() => {
355
+ const map = /* @__PURE__ */ new Map();
356
+ for (const row of laneRows) for (const p of row.placed) if (!map.has(p.item.id)) map.set(p.item.id, p.item);
357
+ return map;
358
+ }, [laneRows]);
359
+ const stepDelta = timelineStepDelta(step);
360
+ const deltaFor = (ms) => {
361
+ const steps = Math.round(ms / step);
362
+ return { days: steps * stepDelta.days, minutes: steps * stepDelta.minutes };
363
+ };
364
+ const live = useRef({ itemById, timeZone, range, step, onProposeMove, canMove, deltaFor, labels, fmtInstant });
365
+ live.current = { itemById, timeZone, range, step, onProposeMove, canMove, deltaFor, labels, fmtInstant };
366
+ const dragRef = useRef(null);
367
+ const suppressClick = useRef(false);
368
+ const refocus = useRef(null);
369
+ useEffect(() => {
370
+ if (ghost && !itemById.has(ghost.id)) setGhost(null);
371
+ }, [ghost, itemById]);
372
+ useEffect(() => {
373
+ const wanted = refocus.current;
374
+ if (!wanted || !rootRef.current) return;
375
+ const active = document.activeElement;
376
+ const lost = !active || active === document.body;
377
+ if (!lost) {
378
+ if (active.getAttribute("data-item-id") !== wanted) refocus.current = null;
379
+ return;
380
+ }
381
+ findBar(wanted)?.focus();
382
+ });
383
+ useEffect(() => () => {
384
+ dragRef.current?.stop();
385
+ dragRef.current = null;
386
+ }, []);
387
+ useEffect(() => {
388
+ if (proposing) return;
389
+ dragRef.current?.stop();
390
+ dragRef.current = null;
391
+ if (ghost) {
392
+ setGhost(null);
393
+ setAnnouncement("");
394
+ }
395
+ }, [proposing]);
396
+ const send = (itemId, delta) => {
397
+ const state = live.current;
398
+ const item = state.itemById.get(itemId);
399
+ if (!item || !state.onProposeMove || !state.canMove(item) || delta.days === 0 && delta.minutes === 0) {
400
+ setGhost(null);
401
+ return;
402
+ }
403
+ const proposal = proposeMove(item, delta, state.timeZone);
404
+ const result = state.onProposeMove(itemId, proposal);
405
+ setAnnouncement(fillLabel(state.labels.moveSent, { start: state.fmtInstant(proposal.start), end: state.fmtInstant(proposal.end) }));
406
+ if (result && typeof result.then === "function") {
407
+ setGhost({ id: itemId, delta, source: "sent", proposal });
408
+ const clear = () => setGhost((current) => current && current.source === "sent" && current.proposal === proposal ? null : current);
409
+ result.then(clear, clear);
410
+ } else setGhost(null);
84
411
  };
85
412
  const commit = (item, delta, mode) => {
86
- if (!onMoveItem || delta === 0) return;
413
+ const callback = mode === "move" ? onMoveItem : onResizeItem;
414
+ if (!callback || delta === 0) return;
87
415
  const next = shiftSpan(item, delta, mode, step);
88
- onMoveItem(item.id, next);
416
+ callback(item.id, next);
89
417
  setAnnouncement(mode === "move" ? fillLabel(labels.moved, { title: item.title, start: fmtInstant(next.start), end: fmtInstant(next.end) }) : fillLabel(labels.resized, { title: item.title, end: fmtInstant(next.end) }));
90
418
  };
91
- const onItemKeyDown = (event, laneIndex, placed, index) => {
419
+ const onItemKeyDown = (event, rowIndex, placed, index) => {
92
420
  const item = placed[index].item;
421
+ const pending = ghost && ghost.id === item.id && ghost.source === "keys" ? ghost.delta : null;
422
+ if (event.key === "Enter" && pending) {
423
+ event.preventDefault();
424
+ refocus.current = item.id;
425
+ setAnnouncement("");
426
+ send(item.id, pending);
427
+ return;
428
+ }
429
+ if (event.key === "Escape" && pending) {
430
+ event.preventDefault();
431
+ event.stopPropagation();
432
+ setGhost(null);
433
+ setAnnouncement(labels.moveCancelled);
434
+ return;
435
+ }
93
436
  const horizontal = event.key === "ArrowLeft" ? -1 : event.key === "ArrowRight" ? 1 : 0;
94
- if (horizontal && event.shiftKey && movable(item)) {
437
+ if (horizontal && event.shiftKey && event.altKey) {
438
+ if (canResize(item)) {
439
+ event.preventDefault();
440
+ commit(item, horizontal * step, "resize");
441
+ }
442
+ return;
443
+ }
444
+ if (horizontal && event.shiftKey && proposing && canMove(item)) {
95
445
  event.preventDefault();
96
- commit(item, horizontal * step, event.altKey ? "resize" : "move");
446
+ const base = pending ?? { days: 0, minutes: 0 };
447
+ const next = { days: base.days + horizontal * stepDelta.days, minutes: base.minutes + horizontal * stepDelta.minutes };
448
+ if (next.days === 0 && next.minutes === 0) {
449
+ setGhost(null);
450
+ setAnnouncement(labels.moveCancelled);
451
+ return;
452
+ }
453
+ const proposal = proposeMove(item, next, timeZone);
454
+ setGhost({ id: item.id, delta: next, source: "keys", proposal });
455
+ const proposed = fillLabel(labels.moveProposed, { start: fmtInstant(proposal.start), end: fmtInstant(proposal.end) });
456
+ setAnnouncement(proposal.adjusted ? `${fillLabel(labels.gapForward, { time: zonedTimeOfDay(proposal.start, timeZone) })} ${proposed}` : proposed);
457
+ return;
458
+ }
459
+ if (horizontal && event.shiftKey && stepping && canMove(item)) {
460
+ event.preventDefault();
461
+ commit(item, horizontal * step, "move");
97
462
  return;
98
463
  }
99
464
  if (horizontal) {
@@ -110,134 +475,393 @@ function SchedulingTimeline({
110
475
  if (vertical) {
111
476
  event.preventDefault();
112
477
  const start = Date.parse(item.start);
113
- for (let l = laneIndex + vertical; l >= 0 && l < laneLayouts.length; l += vertical) {
114
- const candidates = laneLayouts[l].placed;
115
- if (candidates.length === 0) continue;
116
- const nearest = candidates.reduce((best, p) => Math.abs(Date.parse(p.item.start) - start) < Math.abs(Date.parse(best.item.start) - start) ? p : best);
478
+ for (let r = rowIndex + vertical; r >= 0 && r < rows.length; r += vertical) {
479
+ const row = rows[r];
480
+ if (row.kind !== "lane" || row.placed.length === 0) continue;
481
+ const nearest = row.placed.reduce((best, p) => Math.abs(Date.parse(p.item.start) - start) < Math.abs(Date.parse(best.item.start) - start) ? p : best);
117
482
  focusItem(nearest.item.id);
118
483
  return;
119
484
  }
120
485
  }
121
486
  };
122
- const onPointerDown = (event, item) => {
123
- if (!movable(item) || event.button !== 0) return;
487
+ const onStepPointerDown = (event, item) => {
488
+ if (event.button !== 0) return;
124
489
  const track = event.currentTarget.closest(".uix-scheduling-timeline__track");
125
490
  if (!track) return;
126
491
  event.currentTarget.setPointerCapture?.(event.pointerId);
127
- setDrag({ id: item.id, x: event.clientX, width: track.getBoundingClientRect().width, delta: 0, moved: false });
492
+ setStepDrag({ id: item.id, x: event.clientX, width: track.getBoundingClientRect().width, delta: 0, moved: false });
128
493
  };
129
- const onPointerMove = (event) => {
130
- if (!drag) return;
131
- const dx = event.clientX - drag.x;
132
- const delta = pixelsToMs(dx, drag.width, range, step);
133
- if (delta !== drag.delta || !drag.moved && Math.abs(dx) > 3) setDrag({ ...drag, delta, moved: drag.moved || Math.abs(dx) > 3 });
494
+ const onStepPointerMove = (event) => {
495
+ if (!stepDrag) return;
496
+ const dx = event.clientX - stepDrag.x;
497
+ const delta = pixelsToMs(dx, stepDrag.width, range, step);
498
+ if (delta !== stepDrag.delta || !stepDrag.moved && Math.abs(dx) > 3) setStepDrag({ ...stepDrag, delta, moved: stepDrag.moved || Math.abs(dx) > 3 });
134
499
  };
135
- const onPointerUp = (item) => {
136
- if (!drag || drag.id !== item.id) return;
137
- if (drag.moved) commit(item, drag.delta, "move");
138
- setDrag(drag.moved ? { ...drag, delta: 0 } : null);
139
- if (drag.moved) window.setTimeout(() => setDrag(null), 0);
500
+ const onStepPointerUp = (item) => {
501
+ if (!stepDrag || stepDrag.id !== item.id) return;
502
+ if (stepDrag.moved) commit(item, stepDrag.delta, "move");
503
+ setStepDrag(stepDrag.moved ? { ...stepDrag, delta: 0 } : null);
504
+ if (stepDrag.moved) window.setTimeout(() => setStepDrag(null), 0);
140
505
  };
141
- const nowPos = now ? placeSpan({ start: now, end: now }, range) : null;
142
- const overlaysFor = (laneId) => overlays.filter((o) => !o.laneId || o.laneId === laneId);
143
- const markersFor = (laneId) => markers.filter((m) => !m.laneId || m.laneId === laneId);
506
+ const onProposalPointerDown = (event, item) => {
507
+ if (event.button !== 0 || dragRef.current) return;
508
+ const track = event.currentTarget.closest(".uix-scheduling-timeline__track");
509
+ if (!track) return;
510
+ const finish = (drop) => {
511
+ const drag = dragRef.current;
512
+ if (!drag) return;
513
+ drag.stop();
514
+ dragRef.current = null;
515
+ if (!drag.moved) return;
516
+ suppressClick.current = true;
517
+ setTimeout(() => {
518
+ suppressClick.current = false;
519
+ }, 0);
520
+ if (drop && !drag.dropped) send(drag.id, live.current.deltaFor(drag.ms));
521
+ else setGhost(null);
522
+ };
523
+ const onMove = (move) => {
524
+ const drag = dragRef.current;
525
+ if (!drag || move.pointerId !== drag.pointerId) return;
526
+ if (move.buttons === 0 && move.pointerType === "mouse") {
527
+ finish(false);
528
+ return;
529
+ }
530
+ if (drag.dropped) return;
531
+ const dx = move.clientX - drag.x;
532
+ if (!drag.moved && Math.hypot(dx, move.clientY - drag.y) < DRAG_THRESHOLD) return;
533
+ drag.moved = true;
534
+ const state = live.current;
535
+ const ms = pixelsToMs(dx, drag.width, state.range, state.step);
536
+ if (ms === drag.ms) return;
537
+ drag.ms = ms;
538
+ const dragged = state.itemById.get(drag.id);
539
+ const delta = state.deltaFor(ms);
540
+ setGhost(dragged && ms !== 0 ? { id: drag.id, delta, source: "pointer", proposal: proposeMove(dragged, delta, state.timeZone) } : null);
541
+ };
542
+ const onUp = (up) => {
543
+ if (up.pointerId === dragRef.current?.pointerId) finish(true);
544
+ };
545
+ const onCancel = (cancel) => {
546
+ if (cancel.pointerId === dragRef.current?.pointerId) finish(false);
547
+ };
548
+ const onKey = (key) => {
549
+ const drag = dragRef.current;
550
+ if (key.key !== "Escape" || !drag?.moved || drag.dropped) return;
551
+ key.preventDefault();
552
+ key.stopPropagation();
553
+ drag.dropped = true;
554
+ setGhost(null);
555
+ };
556
+ window.addEventListener("pointermove", onMove);
557
+ window.addEventListener("pointerup", onUp);
558
+ window.addEventListener("pointercancel", onCancel);
559
+ window.addEventListener("keydown", onKey, true);
560
+ dragRef.current = {
561
+ id: item.id,
562
+ pointerId: event.pointerId,
563
+ x: event.clientX,
564
+ y: event.clientY,
565
+ width: track.getBoundingClientRect().width,
566
+ moved: false,
567
+ dropped: false,
568
+ ms: 0,
569
+ stop: () => {
570
+ window.removeEventListener("pointermove", onMove);
571
+ window.removeEventListener("pointerup", onUp);
572
+ window.removeEventListener("pointercancel", onCancel);
573
+ window.removeEventListener("keydown", onKey, true);
574
+ }
575
+ };
576
+ };
577
+ const toggleGroup = (group) => {
578
+ if (onToggleGroup) {
579
+ onToggleGroup(group.id);
580
+ return;
581
+ }
582
+ setSelfCollapsed((current) => ({ ...current, [group.id]: !(current[group.id] ?? !!group.collapsed) }));
583
+ };
584
+ const statusOf = (item) => item.status ?? (item.state === "in-progress" ? "live" : "committed");
585
+ const markersOf = (item) => {
586
+ const flagged = item.state === "conflicted" || item.state === "blackout-violation";
587
+ return flagged ? [...item.markers ?? [], { id: "state", label: labels.states[item.state], emphasis: "warning" }] : item.markers ?? [];
588
+ };
589
+ const stateText = (item) => item.state ? labels.states[item.state] : item.status ? labels.statuses[item.status] : labels.states.scheduled;
590
+ const itemName = (item, conflict) => item.accessibleName ?? [fillLabel(labels.item, { title: item.title, state: stateText(item), start: fmtInstant(item.start), end: fmtInstant(item.end), conflict: conflict ? labels.conflict : "" }), ...(item.markers ?? []).map((marker) => marker.label)].join(", ");
591
+ const dataState = (item) => item.state ?? (item.band === void 0 && item.status === void 0 && item.markers === void 0 && item.accessibleName === void 0 ? "scheduled" : void 0);
592
+ const overlayWords = (overlay) => [overlay.kindLabel ?? (overlay.kind ? labels.overlays[overlay.kind] : void 0), overlay.label, overlay.scopeLabel].filter(Boolean).join(", ");
593
+ const overlayName = (overlay) => overlay.accessibleName ?? fillLabel(labels.overlay, { title: overlayWords(overlay), start: fmtInstant(overlay.start), end: fmtInstant(overlay.end) });
594
+ const patternOf = (overlay) => overlay.pattern ?? (overlay.kind === void 0 || overlay.kind === "maintenance" ? "solid" : "diagonal");
144
595
  const trackStyle = { minWidth: `calc(${Math.max(ticks.length - 1, 1)} * ${tickWidth ?? TICK_WIDTH[scale]})` };
145
- const band = (o, showLabel) => {
146
- const pos = placeSpan(o, range);
147
- return pos && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__overlay", "data-kind": o.kind, style: { left: `${pos.left}%`, width: `${pos.width}%` }, children: showLabel && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__overlay-label", children: o.label }) }, o.id);
148
- };
149
- return /* @__PURE__ */ jsxs("section", { ref: rootRef, className: cx("uix-scheduling-timeline", className), "aria-label": fillLabel(labels.region, { timeZone }), children: [
150
- loading ? /* @__PURE__ */ jsx("div", { className: "uix-scheduling-timeline__state", role: "status", children: labels.loading }) : error ? /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-timeline__state", role: "alert", children: [
151
- /* @__PURE__ */ jsx("p", { children: error }),
152
- onRetry && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: labels.retry })
153
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
154
- (overlays.length > 0 || markers.length > 0) && /* @__PURE__ */ jsxs("div", { className: "uix-visually-hidden", children: [
155
- /* @__PURE__ */ jsx("p", { id: `${id}-windows`, children: labels.windows }),
156
- /* @__PURE__ */ jsxs("ul", { "aria-labelledby": `${id}-windows`, children: [
157
- overlays.map((o) => /* @__PURE__ */ jsxs("li", { children: [
158
- labels.overlays[o.kind],
159
- ": ",
160
- o.label,
161
- ", ",
162
- fmtInstant(o.start),
163
- " \u2013 ",
164
- fmtInstant(o.end)
165
- ] }, o.id)),
166
- markers.map((m) => /* @__PURE__ */ jsxs("li", { children: [
167
- m.label,
168
- ", ",
169
- fmtInstant(m.at)
170
- ] }, m.id))
171
- ] })
596
+ const nowPos = now ? placeSpan({ start: now, end: now }, range) : null;
597
+ const stretchOf = (index) => {
598
+ const row = rows[index];
599
+ if (row.kind === "lane") return { row: index, from: extents[index], to: extents[index + 1] };
600
+ if (!strips.has(index)) return null;
601
+ const top = extents[index];
602
+ return { row: index, from: { subRows: top.subRows, lanes: top.lanes, heads: top.heads + 1 }, to: extents[index + 1] };
603
+ };
604
+ const labelTopIn = (run) => {
605
+ for (const stretch of run) {
606
+ if (!virtual) return [extentCss(stretch.from)];
607
+ if (stretch.row < windowStart) continue;
608
+ const top = px(stretch.from);
609
+ if (top > view.top + view.height + overscan) return [];
610
+ if (top >= view.top) return [extentCss(stretch.from)];
611
+ }
612
+ return [];
613
+ };
614
+ const scopeOf = (targets) => {
615
+ if (targets === null) {
616
+ const all = rows.flatMap((_, index) => stretchOf(index) ?? []);
617
+ const onLane = labelTopIn(all.filter((stretch) => rows[stretch.row].kind === "lane"));
618
+ return { scope: "all", labelTops: onLane.length > 0 ? onLane : labelTopIn(all) };
619
+ }
620
+ const covers = (row) => row.kind === "lane" ? targets.has(row.lane.id) : row.collapsed && [...row.stands].some((laneId) => targets.has(laneId));
621
+ const runs = [];
622
+ rows.forEach((row, index) => {
623
+ const stretch = covers(row) ? stretchOf(index) : null;
624
+ if (!stretch) return;
625
+ const last = runs[runs.length - 1];
626
+ if (last && sameExtent(last[last.length - 1].to, stretch.from)) last.push(stretch);
627
+ else runs.push([stretch]);
628
+ });
629
+ if (runs.length === 0) return null;
630
+ const clip = `polygon(${runs.map((run) => {
631
+ const from = extentCss(run[0].from);
632
+ const to = extentCss(run[run.length - 1].to);
633
+ return `0 ${from}, 100% ${from}, 100% ${to}, 0 ${to}`;
634
+ }).join(", ")})`;
635
+ return { scope: "lanes", clip, labelTops: runs.flatMap(labelTopIn) };
636
+ };
637
+ const renderOverlay = (overlay, key, scope) => {
638
+ const pos = placeSpan(overlay, range);
639
+ if (!pos || !scope) return null;
640
+ const shared = {
641
+ className: "uix-scheduling-timeline__overlay",
642
+ "data-overlay-id": overlay.id,
643
+ "data-pattern": patternOf(overlay),
644
+ "data-kind": overlay.kind,
645
+ "data-scope": scope.scope,
646
+ style: { left: `${pos.left}%`, width: `${pos.width}%`, ...vars({ "--uix-timeline-clip": scope.clip }) }
647
+ };
648
+ const text = scope.labelTops.map((top) => /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-timeline__overlay-label", style: vars({ "--uix-timeline-label-top": top }), children: [
649
+ overlay.kindLabel && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__overlay-kind", children: overlay.kindLabel }),
650
+ /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__overlay-name", children: overlay.label }),
651
+ overlay.scopeLabel && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__overlay-scope", children: overlay.scopeLabel })
652
+ ] }, top));
653
+ return onSelectOverlay ? /* @__PURE__ */ jsx("button", { type: "button", ...shared, "aria-label": overlayName(overlay), onClick: () => onSelectOverlay(overlay), children: text }, key) : /* @__PURE__ */ jsx("span", { ...shared, "aria-hidden": "true", children: text }, key);
654
+ };
655
+ const renderMarker = (marker, key, scope) => {
656
+ const pos = placeSpan({ start: marker.at, end: marker.at }, range);
657
+ if (!pos || !scope) return null;
658
+ return /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__marker", "data-marker-id": marker.id, "data-scope": scope.scope, style: { left: `${pos.left}%`, ...vars({ "--uix-timeline-clip": scope.clip }) }, title: marker.label, "aria-hidden": "true" }, key);
659
+ };
660
+ const overlayKeys = uniqueKeys(overlays.map((overlay) => overlay.id));
661
+ const markerKeys = uniqueKeys(markers.map((marker) => marker.id));
662
+ const inLayer = (target) => fixed || lanesOf(target) === null;
663
+ const columnNoteList = columnNotes ? Object.keys(columnNotes).flatMap((date) => {
664
+ const node = columnNotes[date];
665
+ if (node == null || node === false) return [];
666
+ let pos = null;
667
+ try {
668
+ const bounds = zonedDayBounds(date, timeZone);
669
+ pos = placeSpan({ start: bounds.start.toISOString(), end: bounds.end.toISOString() }, range);
670
+ } catch {
671
+ pos = null;
672
+ }
673
+ return pos && pos.width > 0 ? [{ date, node, pos }] : [];
674
+ }) : [];
675
+ const renderItems = (row, rowIndex) => row.placed.map((p, index) => {
676
+ if (!barMounted(rowIndex, p)) return null;
677
+ const item = p.item;
678
+ const dragging = stepDrag?.id === item.id && stepDrag.moved;
679
+ const shown = dragging && stepDrag ? placeSpan(shiftSpan(item, stepDrag.delta, "move", step), range) ?? p : p;
680
+ const movable = canMove(item);
681
+ const described = movable || canResize(item);
682
+ const itemMarkers = markersOf(item);
683
+ return /* @__PURE__ */ jsx("li", { className: "uix-scheduling-timeline__slot", style: { left: `${shown.left}%`, width: `${shown.width}%`, ...vars({ "--uix-timeline-row": p.row }) }, children: /* @__PURE__ */ jsxs(
684
+ "button",
685
+ {
686
+ type: "button",
687
+ className: "uix-scheduling-timeline__item",
688
+ "data-timeline-item": item.id,
689
+ "data-item-id": item.id,
690
+ title: item.title,
691
+ "data-band": item.band ?? "none",
692
+ "data-status": statusOf(item),
693
+ "data-state": dataState(item),
694
+ "data-conflict": p.conflict || void 0,
695
+ "data-clipped-start": p.clippedStart || void 0,
696
+ "data-clipped-end": p.clippedEnd || void 0,
697
+ "data-movable": movable || void 0,
698
+ "data-dragging": dragging || void 0,
699
+ tabIndex: focusId === item.id ? 0 : -1,
700
+ "aria-label": itemName(item, p.conflict),
701
+ "aria-describedby": described ? hintId : void 0,
702
+ onFocus: () => setActiveId(item.id),
703
+ onBlur: proposing ? () => {
704
+ refocus.current = null;
705
+ if (ghost && ghost.id === item.id && ghost.source === "keys") {
706
+ setGhost(null);
707
+ setAnnouncement(labels.moveCancelled);
708
+ }
709
+ } : void 0,
710
+ onKeyDown: (event) => onItemKeyDown(event, rowIndex, row.placed, index),
711
+ onPointerDown: !movable ? void 0 : proposing ? (event) => onProposalPointerDown(event, item) : (event) => onStepPointerDown(event, item),
712
+ onPointerMove: stepping ? onStepPointerMove : void 0,
713
+ onPointerUp: stepping ? () => onStepPointerUp(item) : void 0,
714
+ onPointerCancel: stepping ? () => setStepDrag(null) : void 0,
715
+ onClick: () => {
716
+ if (stepDrag?.moved) return;
717
+ if (ghost && ghost.id === item.id && ghost.source === "keys") return;
718
+ onSelectItem?.(item);
719
+ },
720
+ children: [
721
+ /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__item-title", children: renderItem?.(item) ?? item.title }),
722
+ item.meta && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__item-meta", children: item.meta }),
723
+ itemMarkers.length > 0 && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__item-markers", children: itemMarkers.map((marker) => /* @__PURE__ */ jsx(ItemMarker, { marker }, marker.id)) })
724
+ ]
725
+ }
726
+ ) }, row.keys[index]);
727
+ });
728
+ const renderRow = (row, rowIndex) => {
729
+ if (row.kind === "head") {
730
+ const { group, collapsed } = row;
731
+ return /* @__PURE__ */ jsxs("div", { className: cx("uix-scheduling-timeline__row", "uix-scheduling-timeline__row--group", collapsed && "uix-scheduling-timeline__row--summary"), "data-row-key": row.key, "data-group-id": group.id, "data-collapsed": collapsed || void 0, "data-strip": strips.has(rowIndex) || void 0, children: [
732
+ /* @__PURE__ */ jsx("div", { className: "uix-scheduling-timeline__group-head", children: /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-timeline__group-label", children: [
733
+ /* @__PURE__ */ jsxs("button", { type: "button", className: "uix-scheduling-timeline__group-toggle", "aria-expanded": !collapsed, onClick: () => toggleGroup(group), children: [
734
+ /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__group-chevron", "aria-hidden": "true" }),
735
+ /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__group-name", id: `${id}-group-${row.groupIndex}`, children: group.label })
736
+ ] }),
737
+ group.meta != null && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__group-meta", children: group.meta }),
738
+ collapsed && group.summary && /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-timeline__group-summary", children: [
739
+ /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__group-count", children: fillLabel(labels.groupSummary, { count: group.summary.count }) }),
740
+ group.summary.markers && group.summary.markers.length > 0 && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__item-markers", children: group.summary.markers.map((marker) => /* @__PURE__ */ jsx(ItemMarker, { marker, showLabel: true }, marker.id)) })
741
+ ] })
742
+ ] }) }),
743
+ strips.has(rowIndex) && /* @__PURE__ */ jsx("div", { className: "uix-scheduling-timeline__group-strip", "aria-hidden": "true" })
744
+ ] }, row.key);
745
+ }
746
+ const { lane } = row;
747
+ const laneLabelId = `${id}-lane-${row.laneIndex}`;
748
+ const ghostPlaced = ghost ? row.placed.find((p) => p.item.id === ghost.id) : void 0;
749
+ const ghostPos = ghost && ghostPlaced && rowOfItem.get(ghost.id) === rowIndex ? placeSpan(ghost.proposal, range) : null;
750
+ return /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-timeline__row", "data-row-key": row.key, "data-lane-id": lane.id, style: vars({ "--uix-timeline-rows": row.subRows }), children: [
751
+ /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-timeline__lane-label", id: laneLabelId, children: [
752
+ /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__lane-name", children: lane.label }),
753
+ lane.meta != null && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__lane-meta", children: lane.meta })
172
754
  ] }),
173
- /* @__PURE__ */ jsx("div", { className: "uix-scheduling-timeline__scroller", children: /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-timeline__grid", children: [
174
- /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-timeline__row uix-scheduling-timeline__row--axis", "aria-hidden": "true", children: [
175
- /* @__PURE__ */ jsx("div", { className: "uix-scheduling-timeline__lane-label", children: labels.lanes }),
176
- /* @__PURE__ */ jsx("div", { className: "uix-scheduling-timeline__track uix-scheduling-timeline__axis", style: trackStyle, children: ticks.map((tick) => /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__tick", "data-major": tick.major || void 0, style: { left: `${tick.offset}%` }, children: tick.offset < 100 && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__tick-label", children: fmtTick(tick.at) }) }, tick.at)) })
177
- ] }),
178
- laneLayouts.map(({ lane, placed }, laneIndex) => {
179
- const rows = placed.reduce((max, p) => Math.max(max, p.row + 1), 1);
180
- const laneLabelId = `${id}-lane-${laneIndex}`;
181
- return /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-timeline__row", style: { "--uix-timeline-rows": rows }, children: [
182
- /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-timeline__lane-label", id: laneLabelId, children: [
183
- /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__lane-name", children: lane.label }),
184
- lane.meta != null && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__lane-meta", children: lane.meta })
755
+ /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-timeline__track", style: trackStyle, children: [
756
+ overlays.map((overlay, index) => !inLayer(overlay) && lanesOf(overlay).has(lane.id) ? renderOverlay(overlay, overlayKeys[index], IN_LANE) : null),
757
+ markers.map((marker, index) => !inLayer(marker) && lanesOf(marker).has(lane.id) ? renderMarker(marker, markerKeys[index], IN_LANE) : null),
758
+ /* @__PURE__ */ jsx("ul", { className: "uix-scheduling-timeline__items", "aria-labelledby": row.groupIndex === void 0 ? laneLabelId : `${id}-group-${row.groupIndex} ${laneLabelId}`, children: renderItems(row, rowIndex) }),
759
+ ghostPos && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__ghost", "aria-hidden": "true", "data-sent": ghost?.source === "sent" || void 0, style: { left: `${ghostPos.left}%`, width: `${ghostPos.width}%`, ...vars({ "--uix-timeline-row": ghostPlaced.row }) } })
760
+ ] })
761
+ ] }, row.key);
762
+ };
763
+ const body = [];
764
+ let cursor = 0;
765
+ const spacer = (from, to) => /* @__PURE__ */ jsx("div", { className: "uix-scheduling-timeline__spacer", "aria-hidden": "true", style: { height: extentCss(between(extents[from], extents[to])) } }, `spacer-${from}`);
766
+ for (const index of mounted) {
767
+ if (index > cursor) body.push(spacer(cursor, index));
768
+ body.push(renderRow(rows[index], index));
769
+ cursor = index + 1;
770
+ }
771
+ if (cursor < rows.length) body.push(spacer(cursor, rows.length));
772
+ const listed = onSelectOverlay ? EMPTY_OVERLAYS : overlays;
773
+ return /* @__PURE__ */ jsxs(
774
+ "section",
775
+ {
776
+ ref: rootRef,
777
+ className: cx("uix-scheduling-timeline", className),
778
+ "aria-label": fillLabel(labels.region, { timeZone }),
779
+ "data-fixed": fixed || void 0,
780
+ onClickCapture: (event) => {
781
+ if (suppressClick.current) {
782
+ suppressClick.current = false;
783
+ event.preventDefault();
784
+ event.stopPropagation();
785
+ }
786
+ },
787
+ children: [
788
+ notice != null && notice !== false && /* @__PURE__ */ jsx("div", { className: "uix-scheduling-timeline__notice", children: notice }),
789
+ loading ? /* @__PURE__ */ jsx("div", { className: "uix-scheduling-timeline__state", role: "status", children: labels.loading }) : error ? /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-timeline__state", role: "alert", children: [
790
+ /* @__PURE__ */ jsx("p", { children: error }),
791
+ onRetry && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: labels.retry })
792
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
793
+ (listed.length > 0 || markers.length > 0) && /* @__PURE__ */ jsxs("div", { className: "uix-visually-hidden", children: [
794
+ /* @__PURE__ */ jsx("p", { id: `${id}-windows`, children: labels.windows }),
795
+ /* @__PURE__ */ jsxs("ul", { "aria-labelledby": `${id}-windows`, children: [
796
+ listed.map((o, index) => /* @__PURE__ */ jsx("li", { children: o.accessibleName ?? /* @__PURE__ */ jsxs(Fragment, { children: [
797
+ o.kindLabel === void 0 && o.kind ? `${labels.overlays[o.kind]}: ${o.label}${o.scopeLabel ? `, ${o.scopeLabel}` : ""}` : overlayWords(o),
798
+ ", ",
799
+ fmtInstant(o.start),
800
+ " \u2013 ",
801
+ fmtInstant(o.end)
802
+ ] }) }, overlayKeys[index])),
803
+ markers.map((m, index) => /* @__PURE__ */ jsxs("li", { children: [
804
+ m.label,
805
+ ", ",
806
+ fmtInstant(m.at)
807
+ ] }, markerKeys[index]))
808
+ ] })
809
+ ] }),
810
+ /* @__PURE__ */ jsxs("div", { ref: setScroller, className: "uix-scheduling-timeline__scroller", "data-virtual": virtual || void 0, "data-scroll-y": maxHeight !== void 0 || void 0, style: maxHeight !== void 0 ? { maxHeight } : void 0, children: [
811
+ /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-timeline__grid", "data-sub-ticks": minorTicks.length > 0 || void 0, children: [
812
+ /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-timeline__row uix-scheduling-timeline__row--axis", "aria-hidden": "true", children: [
813
+ /* @__PURE__ */ jsx("div", { className: "uix-scheduling-timeline__lane-label", children: labels.lanes }),
814
+ /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-timeline__track uix-scheduling-timeline__axis", style: trackStyle, children: [
815
+ ticks.map((tick) => /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__tick", "data-major": tick.major || void 0, style: { left: `${tick.offset}%` }, children: tick.offset < 100 && /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-timeline__tick-label", children: [
816
+ fmtTick(tick.at),
817
+ tick.offsetLabel && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__tick-offset", children: tick.offsetLabel })
818
+ ] }) }, tick.at)),
819
+ minorTicks.map((tick) => /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__tick", "data-minor": "true", style: { left: `${tick.offset}%` }, children: /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__tick-label", children: formatTick?.(new Date(tick.at), "hour") ?? tick.label }) }, `minor-${tick.at}`))
820
+ ] })
821
+ ] }),
822
+ columnNoteList.length > 0 && /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-timeline__row uix-scheduling-timeline__row--notes", children: [
823
+ /* @__PURE__ */ jsx("div", { className: "uix-scheduling-timeline__lane-label" }),
824
+ /* @__PURE__ */ jsx("div", { className: "uix-scheduling-timeline__track", style: trackStyle, children: columnNoteList.map(({ date, node, pos }) => /* @__PURE__ */ jsx("div", { className: "uix-scheduling-timeline__column-note", "data-date": date, style: { left: `${pos.left}%`, width: `${pos.width}%` }, children: node }, date)) })
825
+ ] }),
826
+ /* @__PURE__ */ jsxs(
827
+ "div",
828
+ {
829
+ ref: bodyRef,
830
+ className: "uix-scheduling-timeline__body",
831
+ onFocus: (event) => setFocusRowKey(event.target.closest?.("[data-row-key]")?.getAttribute("data-row-key") ?? null),
832
+ onBlur: (event) => {
833
+ if (!event.currentTarget.contains(event.relatedTarget)) setFocusRowKey(null);
834
+ },
835
+ children: [
836
+ /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-timeline__layer", children: [
837
+ /* @__PURE__ */ jsx("div", { className: "uix-scheduling-timeline__lane-label", "aria-hidden": "true" }),
838
+ /* @__PURE__ */ jsxs("div", { ref: setLayerTrack, className: "uix-scheduling-timeline__track", style: trackStyle, children: [
839
+ /* @__PURE__ */ jsx("span", { ref: barMinRef, className: "uix-scheduling-timeline__bar-min", "aria-hidden": "true" }),
840
+ ticks.map((tick) => /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__gridline", "data-major": tick.major || void 0, style: { left: `${tick.offset}%` }, "aria-hidden": "true" }, tick.at)),
841
+ minorTicks.map((tick) => /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__gridline", "data-minor": "true", style: { left: `${tick.offset}%` }, "aria-hidden": "true" }, `minor-${tick.at}`)),
842
+ overlays.map((overlay, index) => inLayer(overlay) ? renderOverlay(overlay, overlayKeys[index], scopeOf(lanesOf(overlay))) : null),
843
+ markers.map((marker, index) => inLayer(marker) ? renderMarker(marker, markerKeys[index], scopeOf(lanesOf(marker))) : null),
844
+ nowPos && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__now", style: { left: `${nowPos.left}%` }, "aria-hidden": "true", children: /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__now-label", children: labels.now }) })
845
+ ] })
846
+ ] }),
847
+ body
848
+ ]
849
+ }
850
+ )
185
851
  ] }),
186
- /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-timeline__track", style: trackStyle, children: [
187
- ticks.map((tick) => /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__gridline", "data-major": tick.major || void 0, style: { left: `${tick.offset}%` }, "aria-hidden": "true" }, tick.at)),
188
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: overlaysFor(lane.id).map((o) => band(o, laneIndex === 0 || !!o.laneId)) }),
189
- nowPos && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__now", style: { left: `${nowPos.left}%` }, "aria-hidden": "true", children: laneIndex === 0 && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__now-label", children: labels.now }) }),
190
- markersFor(lane.id).map((m) => {
191
- const pos = placeSpan({ start: m.at, end: m.at }, range);
192
- return pos && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__marker", style: { left: `${pos.left}%` }, title: m.label, "aria-hidden": "true" }, m.id);
193
- }),
194
- /* @__PURE__ */ jsx("ul", { className: "uix-scheduling-timeline__items", "aria-labelledby": laneLabelId, children: placed.map((p, index) => {
195
- const item = p.item;
196
- const state = item.state ?? "scheduled";
197
- const dragging = drag?.id === item.id && drag.moved;
198
- const shown = dragging && drag ? placeSpan(shiftSpan(item, drag.delta, "move", step), range) ?? p : p;
199
- return /* @__PURE__ */ jsx("li", { className: "uix-scheduling-timeline__slot", style: { left: `${shown.left}%`, width: `${shown.width}%`, "--uix-timeline-row": p.row }, children: /* @__PURE__ */ jsxs(
200
- "button",
201
- {
202
- type: "button",
203
- className: "uix-scheduling-timeline__item",
204
- "data-timeline-item": item.id,
205
- title: item.title,
206
- "data-state": state,
207
- "data-conflict": p.conflict || void 0,
208
- "data-clipped-start": p.clippedStart || void 0,
209
- "data-clipped-end": p.clippedEnd || void 0,
210
- "data-movable": movable(item) || void 0,
211
- "data-dragging": dragging || void 0,
212
- tabIndex: focusId === item.id ? 0 : -1,
213
- "aria-label": fillLabel(labels.item, { title: item.title, state: labels.states[state], start: fmtInstant(item.start), end: fmtInstant(item.end), conflict: p.conflict ? labels.conflict : "" }),
214
- "aria-describedby": movable(item) ? `${id}-hint` : void 0,
215
- onFocus: () => setActiveId(item.id),
216
- onKeyDown: (event) => onItemKeyDown(event, laneIndex, placed, index),
217
- onPointerDown: (event) => onPointerDown(event, item),
218
- onPointerMove,
219
- onPointerUp: () => onPointerUp(item),
220
- onPointerCancel: () => setDrag(null),
221
- onClick: () => {
222
- if (drag?.moved) return;
223
- onSelectItem?.(item);
224
- },
225
- children: [
226
- /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__item-title", children: renderItem?.(item) ?? item.title }),
227
- item.meta && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-timeline__item-meta", children: item.meta })
228
- ]
229
- }
230
- ) }, item.id);
231
- }) })
852
+ virtual && /* @__PURE__ */ jsxs("span", { ref: probeRef, className: "uix-scheduling-timeline__probe", "aria-hidden": "true", children: [
853
+ /* @__PURE__ */ jsx("span", {}),
854
+ /* @__PURE__ */ jsx("span", {}),
855
+ /* @__PURE__ */ jsx("span", {})
232
856
  ] })
233
- ] }, lane.id);
234
- })
235
- ] }) }),
236
- ordered.length === 0 && /* @__PURE__ */ jsx("p", { className: "uix-scheduling-timeline__empty", children: labels.empty })
237
- ] }),
238
- onMoveItem && /* @__PURE__ */ jsx("span", { id: `${id}-hint`, className: "uix-visually-hidden", children: labels.moveHint }),
239
- /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", role: "status", "aria-live": "polite", children: announcement })
240
- ] });
857
+ ] }),
858
+ nothingToShow && /* @__PURE__ */ jsx("p", { className: "uix-scheduling-timeline__empty", children: labels.empty })
859
+ ] }),
860
+ hint && /* @__PURE__ */ jsx("span", { id: hintId, className: "uix-visually-hidden", children: hint }),
861
+ /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", role: "status", "aria-live": "polite", children: announcement })
862
+ ]
863
+ }
864
+ );
241
865
  }
242
866
  export {
243
867
  DEFAULT_SCHEDULING_TIMELINE_LABELS,