@tensor_1/react 2.33.0 → 2.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -412,6 +412,54 @@ const spanLayout = layoutMonthSpans(spansInSalienceOrder, grid, { timeZone, week
412
412
  change stays where it was, so a refused move snaps back by construction. `movable: false` pins an entry.
413
413
  - Overlapping items share at most `maxLanes` lanes, fewer in a narrow column; the column "+N" is the consumer's
414
414
  `days[date].overflowCount`. `renderEntry(entry, { availableLines })` says how many text lines fit.
415
+ - **Agenda grouped by day** (`agendaGroups`, HAR-1520): the consumer builds the day groups and the calendar renders
416
+ them in order, each under a heading.
417
+
418
+ ```tsx
419
+ <SchedulingCalendar view="agenda" timeZone="Europe/Berlin" anchorDate="2026-10-07" entries={[]} showHeader={false}
420
+ agendaGroups={[
421
+ { date: '2026-10-07', annotations: [hold], rows: firstTen, hiddenCount: 4 }, // "4 not shown — open day"
422
+ { date: '2026-10-08', rows: [], hiddenCount: 12, continuesCount: 2 }, // the heading stays
423
+ ]}
424
+ onSelectEntry={openItem} onSelectOverlay={openWindow} onShowMore={openDay}
425
+ notice={truncated && <p>Showing the first 500 items.</p>} />
426
+ ```
427
+
428
+ Above `virtualizeAbove` rows (default 200) the agenda is one scroller of 44 px rows and only the rows near the
429
+ viewport are mounted, on the server too; each window note then has a row of its own under the heading. In a box
430
+ of 576 px or less those rows are 64 px with three lines: the title, the time, then markers and status. A row
431
+ there holds one line of title, so `renderEntry` content must fit one line. A row shows its status as words and
432
+ its markers with their text. Its time is two times of day when the entry starts on that day and ends within a
433
+ day by the clock; otherwise both ends go through `formatInstant` (pass a short form for narrow screens: an end
434
+ that does not fit its half of the line is cut), and an `allDay` entry reads `labels.allDay` or the days it
435
+ covers. Crossing `virtualizeAbove` swaps the two forms, which resets focus and scroll inside the agenda. Type
436
+ the groups with `SchedulingAgendaGroup`.
437
+ - **A narrow month** (`monthDensity="counts"`): a cell is the date, the consumer's `days[date].count` and its markers.
438
+ No entry is drawn and none is counted: a row's "+N" is for windows over `windowLaneCap` only. The setting does not
439
+ touch the week.
440
+ - **Keyboard (HAR-1527).** The month grid, the time grid and the agenda are each **one tab stop**; no item is a
441
+ tab stop of its own.
442
+ - Month: arrows move between days, Home/End go to the first/last day shown. **Enter** on a day with items moves
443
+ into them (ArrowUp/ArrowDown, Home, End between them; Enter calls `onSelectEntry`; **Esc** returns to the day).
444
+ **Space** or a click on the day number calls `onSelectDate`; so does Enter on a day with no items.
445
+ - Week/Day time grid: the tab stop is a day head (ArrowLeft/ArrowRight between days). Enter goes into the
446
+ day's items and windows. On an item, Enter selects it, or confirms a pending move (`onProposeMove`); Esc drops
447
+ a pending move, otherwise returns to the day head.
448
+ - Agenda (grouped or flat): ArrowUp/ArrowDown, Home and End walk rows, window notes and "open day", also
449
+ through the rows a long agenda has not mounted yet.
450
+ - The keys act on the calendar's own items only: a field or link you render inside a cell or an item keeps its
451
+ keys. A row's "+N" is reached from the day it opens. An item reached with the pointer takes the tab stop to
452
+ its day. Esc on an item returns to its day and is not passed on; a second Esc reaches a surrounding dialog.
453
+ - A day button is named with the date and `days[date].label` (the count and the highest signal), through
454
+ `labels.dayName` (`'{date}, {label}'`). If your labels already say the date, set `dayName: '{label}'`.
455
+ - To put focus back on an item after closing your own panel: `querySelector('[data-item-id="…"]')?.focus()`.
456
+ - **Emphasis.** `entry.emphasis: 'highlight' | 'dim'` sets `data-highlight` / `data-dim` on that item in every
457
+ view. Highlight is a heavier edge in the text colour and a heavier weight, dim is quieter text;
458
+ neither changes a hue, the line style of the state, or the band's leading edge. With forced colours a
459
+ highlighted title is underlined and dimmed text is grey.
460
+ - **`<List roving>`**: the items are one tab stop (ArrowUp/ArrowDown, Home, End; Enter or Space activates the
461
+ focused item's `onClick`). A control inside an item keeps its own tab stop and keys. Items may be wrapped or
462
+ rendered later by a child component.
415
463
  - **Colour.** Every calendar colour is named once at the top of `scheduling-calendar.css` (`--calendar-*`).
416
464
  - **Deprecated, still working in 2.x:** `entry.state` / `SchedulingEntryState`, `overlay.kind` /
417
465
  `SchedulingOverlayKind` and the `previous` / `next` labels. An entry with `state` is no longer tinted: the two
@@ -97,6 +97,12 @@ interface SchedulingCalendarEntry {
97
97
  allDay?: boolean;
98
98
  /** `false` keeps this entry where it is when the grid allows moves (`canMove`). */
99
99
  movable?: boolean;
100
+ /**
101
+ * Draws the item with more or less weight than its neighbours, for example the partners of
102
+ * the item the user picked (`'highlight'`) against everything else (`'dim'`). It is a line
103
+ * weight and a quieter text colour, never a hue.
104
+ */
105
+ emphasis?: 'highlight' | 'dim';
100
106
  }
101
107
  interface SchedulingCalendarOverlay {
102
108
  id: string;
@@ -123,10 +129,28 @@ interface SchedulingCalendarDay {
123
129
  count: number;
124
130
  /** The "+N" of the cell. 0 shows none. */
125
131
  overflowCount: number;
126
- /** The accessible text of the count, e.g. "7 items, 2 need review". */
132
+ /**
133
+ * The accessible text of the count, e.g. "7 items, 2 need review". The day's button is named
134
+ * with the date and this text (`labels.dayName`), so a screen reader hears the date, the count
135
+ * and the highest signal in one go.
136
+ */
127
137
  label?: string;
128
138
  markers?: SchedulingMarker[];
129
139
  }
140
+ /** One day of the agenda, built by the consumer. The calendar renders the groups and their rows in the order given. */
141
+ interface SchedulingAgendaGroup {
142
+ /** The day the group is for (`YYYY-MM-DD`). */
143
+ date: string;
144
+ /** Replaces the heading text. Default: the date through `formatDate(date, 'day')`. */
145
+ heading?: ReactNode;
146
+ /** Windows that touch the day: one note each beside the heading, never one per row. */
147
+ annotations?: SchedulingCalendarOverlay[];
148
+ rows: SchedulingCalendarEntry[];
149
+ /** Entries of the day that are not in `rows`. Shows "N not shown — open day", which calls `onShowMore`. */
150
+ hiddenCount?: number;
151
+ /** Entries that go on into this day and are listed under an earlier one. */
152
+ continuesCount?: number;
153
+ }
130
154
  interface SchedulingLegendItem {
131
155
  id: string;
132
156
  label: string;
@@ -179,6 +203,8 @@ interface SchedulingCalendarLabels {
179
203
  moreSpans?: string;
180
204
  /** Accessible name of that control. `{count}` and `{date}` (the first day with a hidden span). */
181
205
  moreSpansLabel?: string;
206
+ /** The name of a day's button when `days[date].label` is given. `{date}` and `{label}` placeholders; use `'{label}'` if your labels already say the date. */
207
+ dayName?: string;
182
208
  /** The accessible text of a day's count when `days[date].label` is not given. `{count}` placeholder. */
183
209
  dayCount?: string;
184
210
  states: Record<SchedulingEntryState, string>;
@@ -196,6 +222,12 @@ interface SchedulingCalendarLabels {
196
222
  topLane?: string;
197
223
  /** The cue on the second-day part of an entry that crosses midnight. `{time}` is its start. */
198
224
  continuesFrom?: string;
225
+ /** The time text of an agenda row whose entry has no time of day (`allDay`). */
226
+ allDay?: string;
227
+ /** Under an agenda day whose rows are not all listed. `{count}` placeholder. */
228
+ hiddenInDay?: string;
229
+ /** Under an agenda day that entries from an earlier day run into. `{count}` placeholder. */
230
+ continuesInDay?: string;
199
231
  /** Read after an item's name when it can be moved. */
200
232
  moveHint?: string;
201
233
  /** Announced for a pending move. `{start}` and `{end}` placeholders. */
@@ -301,6 +333,23 @@ interface SchedulingCalendarProps {
301
333
  emptyNote?: ReactNode;
302
334
  /** Called by a click or Enter on a day number. */
303
335
  onSelectDate?: (date: string) => void;
336
+ /**
337
+ * The agenda as day groups built by the consumer: which entry is listed under which day,
338
+ * in which order, and how many are left out. When set, `view="agenda"` renders exactly
339
+ * these, each day under a heading, and never sorts, groups or counts. Unset keeps the flat
340
+ * list sorted by start.
341
+ */
342
+ agendaGroups?: SchedulingAgendaGroup[];
343
+ /** The heading level of an agenda day. Default 3. */
344
+ agendaHeadingLevel?: 2 | 3 | 4 | 5 | 6;
345
+ /** Above this many agenda rows only the rows near the viewport are mounted. Default 200. */
346
+ virtualizeAbove?: number;
347
+ /**
348
+ * `'counts'` draws each month cell with the consumer's `days[date].count` and markers
349
+ * only: no chips and no entry bars, and narrower columns, for a narrow screen. Windows keep
350
+ * their bar and name. Default `'full'`.
351
+ */
352
+ monthDensity?: 'full' | 'counts';
304
353
  /**
305
354
  * Draws `view="week"` as a time grid: seven day columns on an hour axis. `view="day"` is
306
355
  * always a time grid. Default `false`: the week stays seven day cells.
@@ -334,6 +383,6 @@ interface SchedulingCalendarProps {
334
383
  labels?: Partial<SchedulingCalendarLabels>;
335
384
  }
336
385
  /** Month/week/agenda calendar for UTC ranges rendered in an explicit IANA time zone. */
337
- declare function SchedulingCalendar({ entries, anchorDate, timeZone, view: controlledView, onViewChange, onAnchorDateChange, onSelectEntry, overlays, onSelectOverlay, filter, renderEntry, locale, weekStartsOn, formatDate, formatInstant: formatInstantProp, maxEntriesPerDay, onShowMore, renderDayBadge, days: dayInfo, dayEntries, spanLayout, windowLaneCap, spanLaneCap, legend, legendCaption, showHeader, notice, emptyNote, onSelectDate, timeGrid, now, canMove, step, onProposeMove, topLaneCrossMidnightMinutes, topLaneCap, maxLanes, loading, error, onRetry, className, labels: labelOverrides, }: SchedulingCalendarProps): react.JSX.Element;
386
+ declare function SchedulingCalendar({ entries, anchorDate, timeZone, view: controlledView, onViewChange, onAnchorDateChange, onSelectEntry, overlays, onSelectOverlay, filter, renderEntry, locale, weekStartsOn, formatDate, formatInstant: formatInstantProp, maxEntriesPerDay, onShowMore, renderDayBadge, days: dayInfo, dayEntries, spanLayout, windowLaneCap, spanLaneCap, legend, legendCaption, showHeader, notice, emptyNote, onSelectDate, agendaGroups, agendaHeadingLevel, virtualizeAbove, monthDensity, timeGrid, now, canMove, step, onProposeMove, topLaneCrossMidnightMinutes, topLaneCap, maxLanes, loading, error, onRetry, className, labels: labelOverrides, }: SchedulingCalendarProps): react.JSX.Element;
338
387
 
339
- export { DEFAULT_SCHEDULING_CALENDAR_LABELS as D, type SchedulingBand as S, type TimeGridEntryContext as T, SchedulingCalendar as a, type SchedulingCalendarDay as b, type SchedulingCalendarEntry as c, type SchedulingCalendarLabels as d, type SchedulingCalendarOverlay as e, type SchedulingCalendarProps as f, type SchedulingCalendarView as g, type SchedulingDatePart as h, type SchedulingEntryState as i, type SchedulingLegendItem as j, type SchedulingMarker as k, type SchedulingMarkerEmphasis as l, type SchedulingOverlayKind as m, type SchedulingOverlayPattern as n, type SchedulingStatus as o, SchedulingTimeGrid as p, type SchedulingTimeGridProps as q };
388
+ export { DEFAULT_SCHEDULING_CALENDAR_LABELS as D, type SchedulingAgendaGroup as S, type TimeGridEntryContext as T, type SchedulingBand as a, SchedulingCalendar as b, type SchedulingCalendarDay as c, type SchedulingCalendarEntry as d, type SchedulingCalendarLabels as e, type SchedulingCalendarOverlay as f, type SchedulingCalendarProps as g, type SchedulingCalendarView as h, type SchedulingDatePart as i, type SchedulingEntryState as j, type SchedulingLegendItem as k, type SchedulingMarker as l, type SchedulingMarkerEmphasis as m, type SchedulingOverlayKind as n, type SchedulingOverlayPattern as o, type SchedulingStatus as p, SchedulingTimeGrid as q, type SchedulingTimeGridProps as r };
@@ -3,9 +3,16 @@ import { HTMLAttributes, ReactNode } from 'react';
3
3
 
4
4
  interface ListProps extends HTMLAttributes<HTMLDivElement> {
5
5
  children?: ReactNode;
6
+ /**
7
+ * Makes the list one tab stop: ArrowUp and ArrowDown move between its `ListItem`s, Home and
8
+ * End go to the first and last, Enter and Space activate the focused item (its `onClick`).
9
+ * Focus stays on the same item across re-renders with the same keys. A roving list is a
10
+ * client component; without `roving` the list stays server-renderable.
11
+ */
12
+ roving?: boolean;
6
13
  }
7
14
  /** Bordered, rounded list container over `.uix-list`. */
8
- declare function List({ children, className, ...props }: ListProps): react.JSX.Element;
15
+ declare function List({ children, className, roving, ...props }: ListProps): react.JSX.Element;
9
16
  interface ListItemProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
10
17
  title?: ReactNode;
11
18
  meta?: ReactNode;
@@ -1,6 +1,8 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { cx } from "../cx.js";
3
- function List({ children, className, ...props }) {
3
+ import { RovingList } from "./ListRoving.js";
4
+ function List({ children, className, roving, ...props }) {
5
+ if (roving) return /* @__PURE__ */ jsx(RovingList, { className, ...props, children });
4
6
  return /* @__PURE__ */ jsx("div", { className: cx("uix-list", className), ...props, children });
5
7
  }
6
8
  function ListItem({ title, meta, trail, leading, children, className, ...props }) {
@@ -0,0 +1,12 @@
1
+ import * as react from 'react';
2
+ import { HTMLAttributes } from 'react';
3
+
4
+ /**
5
+ * `List` with `roving`: the items of the list are one tab stop. ArrowUp and ArrowDown move
6
+ * between them, Home and End go to the first and last, Enter and Space activate the focused
7
+ * item (its `onClick`). A control inside an item keeps its own tab stop and its own keys.
8
+ * Internal: rendered by `List`, which stays a server component without it.
9
+ */
10
+ declare function RovingList({ children, className, onKeyDown, onFocus, ...props }: HTMLAttributes<HTMLDivElement>): react.JSX.Element;
11
+
12
+ export { RovingList };
@@ -0,0 +1,51 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useEffect, useRef } from "react";
4
+ import { cx } from "../cx.js";
5
+ import { syncRovingStop } from "../roving.js";
6
+ function RovingList({ children, className, onKeyDown, onFocus, ...props }) {
7
+ const ref = useRef(null);
8
+ const stop = useRef({ node: null, index: 0 });
9
+ const items = () => Array.from(ref.current?.querySelectorAll(".uix-list__item") ?? []).filter((element) => element.closest(".uix-list") === ref.current);
10
+ const sync = (next) => {
11
+ const all = items();
12
+ for (const element of all) if (!element.hasAttribute("role")) element.setAttribute("role", "listitem");
13
+ stop.current = syncRovingStop(all, stop.current, next);
14
+ };
15
+ useEffect(() => {
16
+ sync();
17
+ });
18
+ useEffect(() => {
19
+ const list = ref.current;
20
+ if (!list || typeof MutationObserver === "undefined") return;
21
+ const observer = new MutationObserver(() => sync());
22
+ observer.observe(list, { childList: true, subtree: true });
23
+ return () => observer.disconnect();
24
+ }, []);
25
+ const handleKeyDown = (event) => {
26
+ onKeyDown?.(event);
27
+ if (event.defaultPrevented) return;
28
+ const all = items();
29
+ const index = all.indexOf(event.target);
30
+ if (index === -1) return;
31
+ const target = event.key === "ArrowDown" ? index + 1 : event.key === "ArrowUp" ? index - 1 : event.key === "Home" ? 0 : event.key === "End" ? all.length - 1 : null;
32
+ if (target !== null) {
33
+ event.preventDefault();
34
+ const next = all[Math.max(0, Math.min(all.length - 1, target))];
35
+ sync(next);
36
+ next.focus();
37
+ } else if (event.key === "Enter" || event.key === " ") {
38
+ event.preventDefault();
39
+ all[index].click();
40
+ }
41
+ };
42
+ const handleFocus = (event) => {
43
+ onFocus?.(event);
44
+ const item = items().find((element) => element === event.target);
45
+ if (item) sync(item);
46
+ };
47
+ return /* @__PURE__ */ jsx("div", { ref, role: "list", ...props, className: cx("uix-list", "uix-list--roving", className), onKeyDown: handleKeyDown, onFocus: handleFocus, children });
48
+ }
49
+ export {
50
+ RovingList
51
+ };
@@ -0,0 +1,31 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { S as SchedulingAgendaGroup, e as SchedulingCalendarLabels, i as SchedulingDatePart, d as SchedulingCalendarEntry, l as SchedulingMarker, f as SchedulingCalendarOverlay } from '../SchedulingCalendar-B6TakO2h.js';
4
+ import '../calendar-model.js';
5
+ import '../scheduling-calendar-model.js';
6
+
7
+ /** The height of one row of the virtualised agenda, in px. The stylesheet uses the same number. */
8
+ declare const AGENDA_VIRTUAL_ROW = 44;
9
+ interface SchedulingAgendaProps {
10
+ groups: SchedulingAgendaGroup[];
11
+ timeZone: string;
12
+ labels: Required<SchedulingCalendarLabels>;
13
+ headingLevel: 2 | 3 | 4 | 5 | 6;
14
+ virtualizeAbove: number;
15
+ dateText: (date: string, part: SchedulingDatePart) => string;
16
+ formatInstant: (instant: string) => string;
17
+ entryName: (entry: SchedulingCalendarEntry) => string;
18
+ entryStatus: (entry: SchedulingCalendarEntry) => string;
19
+ entryStatusText: (entry: SchedulingCalendarEntry) => string;
20
+ entryMarkers: (entry: SchedulingCalendarEntry) => SchedulingMarker[];
21
+ overlayName: (overlay: SchedulingCalendarOverlay) => string;
22
+ renderEntry?: (entry: SchedulingCalendarEntry) => ReactNode;
23
+ renderMarker: (marker: SchedulingMarker, showLabel?: boolean) => ReactNode;
24
+ onSelectEntry?: (entry: SchedulingCalendarEntry) => void;
25
+ onSelectOverlay?: (overlay: SchedulingCalendarOverlay) => void;
26
+ onShowMore?: (date: string, entries: SchedulingCalendarEntry[]) => void;
27
+ }
28
+ /** Day headings with their rows, in the consumer's order. */
29
+ declare function SchedulingAgenda(props: SchedulingAgendaProps): react.JSX.Element;
30
+
31
+ export { AGENDA_VIRTUAL_ROW, SchedulingAgenda, type SchedulingAgendaProps };
@@ -0,0 +1,258 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { createElement, useEffect, useLayoutEffect, useRef, useState } from "react";
4
+ import { addCalendarDays, zonedDateKey, zonedTimeOfDay } from "../calendar-model.js";
5
+ import { itemDaySpan } from "../scheduling-calendar-model.js";
6
+ import { useVirtualRows } from "../hooks/useVirtualRows.js";
7
+ import { fillLabel } from "../fill-label.js";
8
+ import { syncRovingStop } from "../roving.js";
9
+ const AGENDA_VIRTUAL_ROW = 44;
10
+ const AGENDA_NARROW_WIDTH = 576;
11
+ const AGENDA_NARROW_ROW = 64;
12
+ const AGENDA_VIRTUAL_VIEWPORT = 640;
13
+ function uniqueKeys(ids) {
14
+ const used = /* @__PURE__ */ new Set();
15
+ return ids.map((id) => {
16
+ let key = id;
17
+ for (let times = 1; used.has(key); times++) key = `${id}#${times}`;
18
+ used.add(key);
19
+ return key;
20
+ });
21
+ }
22
+ function SchedulingAgenda(props) {
23
+ const rowCount = props.groups.reduce((sum, group) => sum + group.rows.length, 0);
24
+ const flat = rowCount > props.virtualizeAbove;
25
+ return /* @__PURE__ */ jsx(AgendaBody, { ...props, flat }, flat ? "flat" : "grouped");
26
+ }
27
+ function AgendaBody({
28
+ groups,
29
+ flat,
30
+ timeZone,
31
+ labels,
32
+ headingLevel,
33
+ dateText,
34
+ formatInstant,
35
+ entryName,
36
+ entryStatus,
37
+ entryStatusText,
38
+ entryMarkers,
39
+ overlayName,
40
+ renderEntry,
41
+ renderMarker,
42
+ onSelectEntry,
43
+ onSelectOverlay,
44
+ onShowMore
45
+ }) {
46
+ const groupKeys = uniqueKeys(groups.map((group) => group.date));
47
+ const flatRows = [];
48
+ if (flat) {
49
+ groups.forEach((group, index) => {
50
+ const groupKey = groupKeys[index];
51
+ flatRows.push({ kind: "heading", key: `h-${groupKey}`, group });
52
+ const noteKeys = uniqueKeys((group.annotations ?? []).map((overlay) => overlay.id));
53
+ (group.annotations ?? []).forEach((overlay, at) => flatRows.push({ kind: "note", key: `n-${groupKey}-${noteKeys[at]}`, group, overlay }));
54
+ const rowKeys = uniqueKeys(group.rows.map((entry) => entry.id));
55
+ group.rows.forEach((entry, at) => flatRows.push({ kind: "entry", key: `e-${groupKey}-${rowKeys[at]}`, group, entry }));
56
+ if ((group.hiddenCount ?? 0) > 0) flatRows.push({ kind: "hidden", key: `m-${groupKey}`, group });
57
+ if ((group.continuesCount ?? 0) > 0) flatRows.push({ kind: "continues", key: `c-${groupKey}`, group });
58
+ });
59
+ }
60
+ const [narrow, setNarrow] = useState(false);
61
+ const virtual = useVirtualRows(flatRows, { rowHeight: narrow ? AGENDA_NARROW_ROW : AGENDA_VIRTUAL_ROW, threshold: 0, estimatedViewportHeight: AGENDA_VIRTUAL_VIEWPORT });
62
+ const scroller = virtual.containerRef;
63
+ useLayoutEffect(() => {
64
+ const box = scroller.current;
65
+ if (!box) return;
66
+ const measure = () => setNarrow(box.clientWidth > 0 && box.clientWidth <= AGENDA_NARROW_WIDTH);
67
+ measure();
68
+ const observer = new ResizeObserver(measure);
69
+ observer.observe(box);
70
+ return () => observer.disconnect();
71
+ }, [scroller]);
72
+ const grouped = useRef(null);
73
+ useLayoutEffect(() => {
74
+ const box = grouped.current;
75
+ if (!box) return;
76
+ const observer = new ResizeObserver((entries) => {
77
+ for (const entry of entries) {
78
+ const head = entry.target;
79
+ head.parentElement?.style.setProperty("--uix-scheduling-calendar-agenda-head", `${Math.ceil(head.getBoundingClientRect().height)}px`);
80
+ }
81
+ });
82
+ box.querySelectorAll(":scope > section > .uix-scheduling-calendar__agenda-head").forEach((head) => observer.observe(head));
83
+ return () => observer.disconnect();
84
+ }, [groups]);
85
+ const stop = useRef({ node: null, index: 0 });
86
+ const container = () => flat ? scroller.current : grouped.current;
87
+ const controls = () => Array.from(container()?.querySelectorAll("button") ?? []);
88
+ const focusStop = (target) => {
89
+ if (!target) return;
90
+ stop.current = syncRovingStop(controls(), stop.current, target);
91
+ target.focus();
92
+ };
93
+ const holdsControl = (row2) => row2 !== void 0 && (row2.kind === "entry" || row2.kind === "note" || row2.kind === "hidden" && onShowMore !== void 0);
94
+ const controlAt = (index) => scroller.current?.querySelector(`[data-flat-index="${index}"] button`) ?? null;
95
+ const focusIn = (box) => {
96
+ const tree = box.getRootNode();
97
+ const active = tree.activeElement ?? null;
98
+ return active === document.body ? null : active;
99
+ };
100
+ const focusIsElsewhere = (box) => {
101
+ const active = focusIn(box);
102
+ if (active) return !box.contains(active);
103
+ const outer = document.activeElement;
104
+ return box.getRootNode() !== document && outer !== null && outer !== document.body && !outer.contains(box);
105
+ };
106
+ const rowHeight = narrow ? AGENDA_NARROW_ROW : AGENDA_VIRTUAL_ROW;
107
+ const scrollToRow = (box, index) => {
108
+ box.scrollTop = Math.max(0, index * rowHeight - (box.clientHeight - rowHeight) / 2);
109
+ };
110
+ const landing = useRef(null);
111
+ const land = () => {
112
+ const wanted = landing.current;
113
+ if (!wanted) return;
114
+ const box = container();
115
+ const index = flatRows[wanted.index]?.key === wanted.key ? wanted.index : flatRows.findIndex((row2) => row2.key === wanted.key);
116
+ if (!box || index === -1 || !holdsControl(flatRows[index]) || focusIsElsewhere(box)) {
117
+ landing.current = null;
118
+ return;
119
+ }
120
+ const target = controlAt(index);
121
+ if (target) {
122
+ landing.current = null;
123
+ focusStop(target);
124
+ return;
125
+ }
126
+ if (index !== wanted.index) {
127
+ landing.current = { key: wanted.key, index };
128
+ scrollToRow(box, index);
129
+ }
130
+ };
131
+ useEffect(() => {
132
+ const all = controls();
133
+ stop.current = syncRovingStop(all, stop.current);
134
+ const box = flat ? scroller.current : null;
135
+ if (box) {
136
+ if (all.length === 0 || focusIn(box) === box) box.tabIndex = 0;
137
+ else box.removeAttribute("tabindex");
138
+ }
139
+ land();
140
+ });
141
+ const onFocus = (event) => {
142
+ stop.current = syncRovingStop(controls(), stop.current, event.target);
143
+ };
144
+ const onBlur = (event) => {
145
+ if (event.relatedTarget && !event.currentTarget.contains(event.relatedTarget)) landing.current = null;
146
+ if (event.target === event.currentTarget && controls().length > 0) event.currentTarget.removeAttribute("tabindex");
147
+ };
148
+ const dropLanding = () => {
149
+ landing.current = null;
150
+ };
151
+ const onKeyDown = (event) => {
152
+ const from = event.target;
153
+ if (event.defaultPrevented || !["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key) || !controls().includes(from)) return;
154
+ event.preventDefault();
155
+ const box = flat ? scroller.current : null;
156
+ if (!box) {
157
+ const all = controls();
158
+ const to = event.key === "Home" ? 0 : event.key === "End" ? all.length - 1 : all.indexOf(from) + (event.key === "ArrowDown" ? 1 : -1);
159
+ focusStop(all[Math.max(0, Math.min(all.length - 1, to))]);
160
+ return;
161
+ }
162
+ const at = Number(from.closest("[data-flat-index]")?.getAttribute("data-flat-index"));
163
+ let target = -1;
164
+ if (event.key === "Home") target = flatRows.findIndex(holdsControl);
165
+ else if (event.key === "End") {
166
+ for (let index = flatRows.length - 1; index >= 0 && target === -1; index--) if (holdsControl(flatRows[index])) target = index;
167
+ } else if (Number.isFinite(at)) {
168
+ const step = event.key === "ArrowDown" ? 1 : -1;
169
+ for (let index = at + step; index >= 0 && index < flatRows.length && target === -1; index += step) if (holdsControl(flatRows[index])) target = index;
170
+ }
171
+ if (target === -1) return;
172
+ const mounted = controlAt(target);
173
+ if (mounted) {
174
+ landing.current = null;
175
+ focusStop(mounted);
176
+ return;
177
+ }
178
+ landing.current = { key: flatRows[target].key, index: target };
179
+ scrollToRow(box, target);
180
+ };
181
+ const note = (overlay) => /* @__PURE__ */ jsx("button", { type: "button", className: "uix-scheduling-calendar__window", "data-overlay-id": overlay.id, "data-pattern": overlay.pattern ?? "solid", "data-global": overlay.global || void 0, onClick: () => onSelectOverlay?.(overlay), "aria-label": overlayName(overlay), children: /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-calendar__window-text", children: [
182
+ overlay.kindLabel && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__window-kind", children: overlay.kindLabel }),
183
+ /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__window-name", children: overlay.label }),
184
+ overlay.scopeLabel && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__window-scope", children: overlay.scopeLabel })
185
+ ] }) });
186
+ const heading = (group, withNotes) => /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__agenda-head", children: [
187
+ createElement(`h${headingLevel}`, { className: "uix-scheduling-calendar__agenda-heading" }, group.heading ?? dateText(group.date, "day")),
188
+ withNotes && group.annotations && group.annotations.length > 0 && /* @__PURE__ */ jsx("ul", { className: "uix-scheduling-calendar__agenda-notes", children: uniqueKeys(group.annotations.map((overlay) => overlay.id)).map((key, at) => /* @__PURE__ */ jsx("li", { children: note(group.annotations[at]) }, key)) })
189
+ ] });
190
+ const timeText = (entry, date) => {
191
+ const part = (text) => /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__agenda-time-part", children: text });
192
+ const dash = /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: " \u2013 " });
193
+ if (entry.allDay) {
194
+ let span;
195
+ try {
196
+ span = itemDaySpan(entry.start, entry.end, timeZone);
197
+ } catch {
198
+ span = itemDaySpan(entry.start, entry.start, timeZone);
199
+ }
200
+ const last = addCalendarDays(span.end, -1);
201
+ if (span.start === date && last === date) return labels.allDay;
202
+ return span.start === last ? part(dateText(last, "day")) : /* @__PURE__ */ jsxs(Fragment, { children: [
203
+ part(dateText(span.start, "day")),
204
+ dash,
205
+ part(dateText(last, "day"))
206
+ ] });
207
+ }
208
+ const startTime = zonedTimeOfDay(entry.start, timeZone);
209
+ const endTime = zonedTimeOfDay(entry.end, timeZone);
210
+ const endDay = zonedDateKey(entry.end, timeZone);
211
+ const underItsDay = zonedDateKey(entry.start, timeZone) === date && (endDay === date || endDay === addCalendarDays(date, 1) && endTime < startTime);
212
+ return underItsDay ? /* @__PURE__ */ jsxs(Fragment, { children: [
213
+ startTime,
214
+ dash,
215
+ endTime
216
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
217
+ part(formatInstant(entry.start)),
218
+ dash,
219
+ part(formatInstant(entry.end))
220
+ ] });
221
+ };
222
+ const row = (entry, date) => {
223
+ const markers = entryMarkers(entry);
224
+ const name = entry.accessibleName ?? (entry.meta ? `${entryName(entry)}, ${entry.meta}` : entryName(entry));
225
+ return /* @__PURE__ */ jsxs("button", { type: "button", className: "uix-scheduling-calendar__agenda-row", "data-item-id": entry.id, "data-highlight": entry.emphasis === "highlight" || void 0, "data-dim": entry.emphasis === "dim" || void 0, "data-band": entry.band ?? "none", "data-status": entryStatus(entry), "data-state": entry.state, onClick: () => onSelectEntry?.(entry), "aria-label": name, children: [
226
+ /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__swatch", "aria-hidden": "true", "data-band": entry.band ?? "none", "data-status": entryStatus(entry) }),
227
+ /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__agenda-time", children: timeText(entry, date) }),
228
+ /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-calendar__agenda-main", children: [
229
+ renderEntry?.(entry) ?? /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__title", children: entry.title }),
230
+ entry.meta && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__agenda-meta", children: entry.meta })
231
+ ] }),
232
+ markers.length > 0 && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__markers", children: markers.map((marker) => /* @__PURE__ */ jsx("span", { children: renderMarker(marker, true) }, marker.id)) }),
233
+ /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__status", children: entryStatusText(entry) })
234
+ ] });
235
+ };
236
+ const hidden = (group) => {
237
+ const text = fillLabel(labels.hiddenInDay, { count: group.hiddenCount ?? 0 });
238
+ return onShowMore ? /* @__PURE__ */ jsx("button", { type: "button", className: "uix-scheduling-calendar__agenda-more", onClick: () => onShowMore(group.date, group.rows), children: text }) : /* @__PURE__ */ jsx("p", { className: "uix-scheduling-calendar__agenda-more", children: text });
239
+ };
240
+ const continues = (group) => /* @__PURE__ */ jsx("p", { className: "uix-scheduling-calendar__agenda-continues", children: fillLabel(labels.continuesInDay, { count: group.continuesCount ?? 0 }) });
241
+ if (groups.length === 0) return /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__agenda", role: "region", "aria-label": labels.agenda, children: /* @__PURE__ */ jsx("p", { children: labels.agendaEmpty }) });
242
+ if (flat) {
243
+ return /* @__PURE__ */ jsx("div", { ref: virtual.containerRef, className: "uix-scheduling-calendar__agenda uix-scheduling-calendar__agenda--virtual", "data-narrow": narrow || void 0, role: "region", "aria-label": labels.agenda, onKeyDown, onFocus, onBlur, onWheel: dropLanding, onPointerDown: dropLanding, children: /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__agenda-window", style: { paddingTop: virtual.padTop, paddingBottom: virtual.padBottom }, children: virtual.rows.map((item, at) => /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__agenda-vrow", "data-kind": item.kind, "data-flat-index": virtual.startIndex + at, children: item.kind === "heading" ? heading(item.group, false) : item.kind === "note" ? note(item.overlay) : item.kind === "entry" ? row(item.entry, item.group.date) : item.kind === "hidden" ? hidden(item.group) : continues(item.group) }, item.key)) }) });
244
+ }
245
+ return /* @__PURE__ */ jsx("div", { ref: grouped, className: "uix-scheduling-calendar__agenda", role: "region", "aria-label": labels.agenda, onKeyDown, onFocus, children: groups.map((group, index) => {
246
+ const rowKeys = uniqueKeys(group.rows.map((entry) => entry.id));
247
+ return /* @__PURE__ */ jsxs("section", { className: "uix-scheduling-calendar__agenda-group", "data-date": group.date, children: [
248
+ heading(group, true),
249
+ group.rows.length > 0 && /* @__PURE__ */ jsx("ol", { className: "uix-scheduling-calendar__agenda-rows", children: group.rows.map((entry, at) => /* @__PURE__ */ jsx("li", { children: row(entry, group.date) }, rowKeys[at])) }),
250
+ (group.hiddenCount ?? 0) > 0 && hidden(group),
251
+ (group.continuesCount ?? 0) > 0 && continues(group)
252
+ ] }, groupKeys[index]);
253
+ }) });
254
+ }
255
+ export {
256
+ AGENDA_VIRTUAL_ROW,
257
+ SchedulingAgenda
258
+ };
@@ -1,4 +1,4 @@
1
1
  import 'react';
2
2
  import '../calendar-model.js';
3
3
  import '../scheduling-calendar-model.js';
4
- export { D as DEFAULT_SCHEDULING_CALENDAR_LABELS, S as SchedulingBand, a as SchedulingCalendar, b as SchedulingCalendarDay, c as SchedulingCalendarEntry, d as SchedulingCalendarLabels, e as SchedulingCalendarOverlay, f as SchedulingCalendarProps, g as SchedulingCalendarView, h as SchedulingDatePart, i as SchedulingEntryState, j as SchedulingLegendItem, k as SchedulingMarker, l as SchedulingMarkerEmphasis, m as SchedulingOverlayKind, n as SchedulingOverlayPattern, o as SchedulingStatus } from '../SchedulingCalendar-C4XVbqZr.js';
4
+ export { D as DEFAULT_SCHEDULING_CALENDAR_LABELS, S as SchedulingAgendaGroup, a as SchedulingBand, b as SchedulingCalendar, c as SchedulingCalendarDay, d as SchedulingCalendarEntry, e as SchedulingCalendarLabels, f as SchedulingCalendarOverlay, g as SchedulingCalendarProps, h as SchedulingCalendarView, i as SchedulingDatePart, j as SchedulingEntryState, k as SchedulingLegendItem, l as SchedulingMarker, m as SchedulingMarkerEmphasis, n as SchedulingOverlayKind, o as SchedulingOverlayPattern, p as SchedulingStatus } from '../SchedulingCalendar-B6TakO2h.js';