@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.
@@ -1,8 +1,10 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useMemo, useRef, useState } from "react";
4
+ import { rovingStep, syncRovingStop } from "../roving.js";
4
5
  import { addCalendarDays, addCalendarMonths, buildMonthGrid, cachedDateTimeFormat, startOfMonth, zonedDateKey, zonedTimeOfDay } from "../calendar-model.js";
5
6
  import { TOP_LANE_CROSS_MIDNIGHT_MINUTES, itemDaySpan, layoutMonthSpans } from "../scheduling-calendar-model.js";
7
+ import { SchedulingAgenda } from "./SchedulingAgenda.js";
6
8
  import { SchedulingTimeGrid } from "./SchedulingTimeGrid.js";
7
9
  import { cx } from "../cx.js";
8
10
  import { useUixLabels } from "../labels-context.js";
@@ -36,6 +38,7 @@ const DEFAULT_SCHEDULING_CALENDAR_LABELS = {
36
38
  fewerEntries: "Show fewer",
37
39
  moreSpans: "+{count}",
38
40
  moreSpansLabel: "{count} more spanning this week, from {date}",
41
+ dayName: "{date}, {label}",
39
42
  dayCount: "{count} entries",
40
43
  states: { scheduled: "Scheduled", conflicted: "Conflicted", "in-progress": "In progress", "blackout-violation": "Blackout violation" },
41
44
  statuses: { tentative: "Tentative", committed: "Scheduled", live: "In progress", done: "Done", dead: "Cancelled" },
@@ -45,6 +48,9 @@ const DEFAULT_SCHEDULING_CALENDAR_LABELS = {
45
48
  moreWindowsLabel: "{count} more windows, from {date}",
46
49
  topLane: "All-day and longer entries",
47
50
  continuesFrom: "from {time}",
51
+ allDay: "All day",
52
+ hiddenInDay: "{count} not shown \u2014 open day",
53
+ continuesInDay: "Continues: {count} listed under an earlier day",
48
54
  moveHint: "Hold Shift and press an arrow key to move it. Enter confirms, Escape cancels.",
49
55
  moveProposed: "Move to {start} \u2013 {end}. Enter confirms, Escape cancels.",
50
56
  moveCancelled: "Move cancelled.",
@@ -60,6 +66,7 @@ const INTL_PARTS = {
60
66
  };
61
67
  const EMPTY_OVERLAYS = [];
62
68
  const EMPTY_ENTRIES = [];
69
+ const EMPTY_IDS = [];
63
70
  const DEFAULT_LANE_CAP = 2;
64
71
  const DEFAULT_CHIP_ROWS = 3;
65
72
  function coveredDays(start, end, timeZone) {
@@ -107,6 +114,10 @@ function SchedulingCalendar({
107
114
  notice,
108
115
  emptyNote,
109
116
  onSelectDate,
117
+ agendaGroups,
118
+ agendaHeadingLevel = 3,
119
+ virtualizeAbove = 200,
120
+ monthDensity = "full",
110
121
  timeGrid = false,
111
122
  now,
112
123
  canMove = false,
@@ -140,7 +151,8 @@ function SchedulingCalendar({
140
151
  const gridRef = useRef(null);
141
152
  const picksGiven = dayEntries !== void 0;
142
153
  const controlled = picksGiven || dayInfo !== void 0;
143
- const fixed = controlled || maxEntriesPerDay !== void 0 && onShowMore !== void 0;
154
+ const countsOnly = monthDensity === "counts" && view === "month";
155
+ const fixed = controlled || maxEntriesPerDay !== void 0 && onShowMore !== void 0 || countsOnly;
144
156
  const windowCap = Math.max(0, windowLaneCap ?? (fixed ? DEFAULT_LANE_CAP : Infinity));
145
157
  const spanCap = Math.max(0, spanLaneCap ?? (fixed ? DEFAULT_LANE_CAP : Infinity));
146
158
  const visibleEntries = useMemo(() => entries.filter((entry) => filter?.(entry) ?? true), [entries, filter]);
@@ -178,13 +190,13 @@ function SchedulingCalendar({
178
190
  windowIds.add(overlay.id);
179
191
  inputs.push({ id: overlay.id, start: overlay.start, end: overlay.end, group: "window" });
180
192
  }
181
- for (const { entry } of multiDay) {
193
+ for (const { entry } of countsOnly ? [] : multiDay) {
182
194
  if (itemIds.has(entry.id)) continue;
183
195
  itemIds.add(entry.id);
184
196
  inputs.push({ id: entry.id, start: entry.start, end: entry.end, group: "item" });
185
197
  }
186
198
  return layoutMonthSpans(inputs, days, { timeZone, laneCap: { window: windowCap, item: spanCap } });
187
- }, [spanLayout, view, timeGridView, overlays, multiDay, days, timeZone, windowCap, spanCap]);
199
+ }, [spanLayout, view, timeGridView, overlays, multiDay, days, timeZone, windowCap, spanCap, countsOnly]);
188
200
  const placedByRow = useMemo(() => {
189
201
  const rows = /* @__PURE__ */ new Map();
190
202
  for (const placed of layout.placed) {
@@ -212,6 +224,31 @@ function SchedulingCalendar({
212
224
  setActiveDate(date);
213
225
  requestAnimationFrame(() => gridRef.current?.querySelector(`[data-calendar-date="${date}"]`)?.focus());
214
226
  };
227
+ const enteredFrom = useRef(null);
228
+ const cellOf = (date) => gridRef.current?.querySelector(`[data-calendar-date="${date}"]`)?.closest(".uix-scheduling-calendar__day") ?? null;
229
+ const dayRing = (date) => {
230
+ const cell = cellOf(date);
231
+ const week = cell?.parentElement;
232
+ if (!cell || !week) return [];
233
+ const column = Array.from(week.children).indexOf(cell) + 1;
234
+ const covers = (element) => {
235
+ const [from, to] = element.style.gridColumn.split("/").map((part) => Number(part.trim()));
236
+ return from !== void 0 && to !== void 0 && from <= column && column < to;
237
+ };
238
+ const direct = Array.from(week.children);
239
+ return [
240
+ ...direct.filter((element) => element.matches("button.uix-scheduling-calendar__window, button.uix-scheduling-calendar__span") && covers(element)),
241
+ ...Array.from(cell.querySelectorAll(".uix-scheduling-calendar__entries button")),
242
+ // The row's "+N" opens one day; it is reached from that day.
243
+ ...direct.filter((element) => element.matches("button.uix-scheduling-calendar__rowmore") && element.getAttribute("data-more-date") === date)
244
+ ];
245
+ };
246
+ const dayOfItem = (item) => {
247
+ if (enteredFrom.current && dayRing(enteredFrom.current).includes(item)) return enteredFrom.current;
248
+ const week = item.closest(".uix-scheduling-calendar__week");
249
+ const cell = item.closest(".uix-scheduling-calendar__day") ?? (week ? Array.from(week.children)[Number(item.style.gridColumn.split("/")[0]) - 1] ?? null : null);
250
+ return item.getAttribute("data-more-date") ?? cell?.querySelector("[data-calendar-date]")?.getAttribute("data-calendar-date") ?? null;
251
+ };
215
252
  const onDayKeyDown = (event, date) => {
216
253
  const offset = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 }[event.key];
217
254
  if (offset) {
@@ -223,8 +260,57 @@ function SchedulingCalendar({
223
260
  } else if (event.key === "End") {
224
261
  event.preventDefault();
225
262
  focusDate(days[days.length - 1]);
263
+ } else if (event.key === "Enter") {
264
+ const [first] = dayRing(date);
265
+ if (!first) return;
266
+ event.preventDefault();
267
+ enteredFrom.current = date;
268
+ first.focus();
226
269
  }
227
270
  };
271
+ const onGridKeyDown = (event) => {
272
+ const item = event.target;
273
+ if (event.defaultPrevented || item.hasAttribute("data-calendar-date") || !["ArrowDown", "ArrowUp", "Home", "End", "Escape"].includes(event.key)) return;
274
+ const date = dayOfItem(item);
275
+ if (!date) return;
276
+ const ring = dayRing(date);
277
+ const index = ring.indexOf(item);
278
+ if (index === -1) return;
279
+ event.preventDefault();
280
+ if (event.key === "Escape") {
281
+ event.stopPropagation();
282
+ enteredFrom.current = null;
283
+ setActiveDate(date);
284
+ gridRef.current?.querySelector(`[data-calendar-date="${date}"]`)?.focus();
285
+ return;
286
+ }
287
+ enteredFrom.current = date;
288
+ const next = event.key === "Home" ? 0 : event.key === "End" ? ring.length - 1 : index + (event.key === "ArrowDown" ? 1 : -1);
289
+ ring[Math.max(0, Math.min(ring.length - 1, next))]?.focus();
290
+ };
291
+ const onGridFocus = (event) => {
292
+ const item = event.target;
293
+ if (item.hasAttribute("data-calendar-date")) return;
294
+ const date = dayOfItem(item);
295
+ if (date && dayRing(date).includes(item)) setActiveDate(date);
296
+ };
297
+ const flatAgendaRef = useRef(null);
298
+ const flatAgendaStop = useRef({ node: null, index: 0 });
299
+ const flatAgendaRows = () => Array.from(flatAgendaRef.current?.querySelectorAll(":scope > ol > li > button") ?? []);
300
+ useEffect(() => {
301
+ flatAgendaStop.current = syncRovingStop(flatAgendaRows(), flatAgendaStop.current);
302
+ });
303
+ const onFlatAgendaFocus = (event) => {
304
+ flatAgendaStop.current = syncRovingStop(flatAgendaRows(), flatAgendaStop.current, event.target);
305
+ };
306
+ const onFlatAgendaKeyDown = (event) => {
307
+ if (event.defaultPrevented) return;
308
+ const next = rovingStep(flatAgendaRows(), event.target, event.key);
309
+ if (!next) return;
310
+ event.preventDefault();
311
+ flatAgendaStop.current = syncRovingStop(flatAgendaRows(), flatAgendaStop.current, next);
312
+ next.focus();
313
+ };
228
314
  const entriesOn = (date) => coverage.filter((covered) => date >= covered.start && date < covered.end).map((covered) => covered.entry);
229
315
  const toggleMore = (date, all) => {
230
316
  if (onShowMore) {
@@ -246,12 +332,16 @@ function SchedulingCalendar({
246
332
  const stateText = (entry) => entry.state ? stateLabel[entry.state] : entry.status ? labels.statuses[entry.status] : stateLabel.scheduled;
247
333
  const entryName = (entry) => entry.accessibleName ?? [fillLabel(labels.entry, { title: entry.title, state: stateText(entry), start: formatInstant(entry.start), end: formatInstant(entry.end) }), ...(entry.markers ?? []).map((marker) => marker.label)].join(", ");
248
334
  const overlayName = (overlay) => overlay.accessibleName ?? fillLabel(labels.overlay, { title: [overlay.kindLabel, overlay.label, overlay.scopeLabel].filter(Boolean).join(", "), start: formatInstant(overlay.start), end: formatInstant(overlay.end) });
335
+ const emphasisOf = (entry) => ({ "data-highlight": entry.emphasis === "highlight" || void 0, "data-dim": entry.emphasis === "dim" || void 0 });
249
336
  const entryProps = (entry) => ({
250
337
  type: "button",
338
+ // Reached from its day with Enter, never with Tab.
339
+ tabIndex: -1,
251
340
  "data-item-id": entry.id,
252
341
  "data-band": entry.band ?? "none",
253
342
  "data-status": statusOf(entry),
254
343
  "data-state": entry.state,
344
+ ...emphasisOf(entry),
255
345
  onClick: () => onSelectEntry?.(entry),
256
346
  "aria-label": entryName(entry)
257
347
  });
@@ -273,24 +363,24 @@ function SchedulingCalendar({
273
363
  ["--uix-scheduling-calendar-lane"]: placed.group === "window" ? placed.lane : windowLanes + placed.lane
274
364
  });
275
365
  const nothingToShow = visibleEntries.length === 0 && !Object.values(dayEntries ?? {}).some((list) => list.length > 0) && !Object.values(dayInfo ?? {}).some((day) => day.count > 0);
276
- const chipRows = Math.max(0, maxEntriesPerDay ?? DEFAULT_CHIP_ROWS, ...Object.values(dayEntries ?? {}).map((list) => list.length));
366
+ const chipRows = countsOnly ? -1 : Math.max(0, maxEntriesPerDay ?? DEFAULT_CHIP_ROWS, ...Object.values(dayEntries ?? {}).map((list) => list.length));
277
367
  const renderDay = (date, index) => {
278
368
  const info = dayInfo?.[date];
279
369
  const all = picksGiven ? dayEntries[date] ?? EMPTY_ENTRIES : singles.get(date) ?? EMPTY_ENTRIES;
280
370
  const overflowing = !picksGiven && maxEntriesPerDay !== void 0 && all.length > maxEntriesPerDay;
281
371
  const collapsed = overflowing && (controlled || onShowMore !== void 0 || !expandedDates.has(date));
282
- const shown = collapsed ? all.slice(0, Math.max(0, maxEntriesPerDay)) : all;
372
+ const shown = countsOnly ? EMPTY_ENTRIES : collapsed ? all.slice(0, Math.max(0, maxEntriesPerDay)) : all;
283
373
  const hiddenCount = info ? info.overflowCount : picksGiven ? 0 : all.length - shown.length;
284
374
  const onDay = picksGiven ? all : entriesOn(date);
285
375
  const badge = renderDayBadge?.(date, onDay);
286
376
  const hasBadge = badge != null && badge !== false;
287
- const dateButton = /* @__PURE__ */ jsx("button", { type: "button", className: "uix-scheduling-calendar__date", "data-calendar-date": date, tabIndex: activeDate === date ? 0 : -1, onFocus: () => setActiveDate(date), onKeyDown: (event) => onDayKeyDown(event, date), onClick: onSelectDate ? () => onSelectDate(date) : void 0, "aria-label": dateText(date, "day"), children: Number(date.slice(-2)) });
377
+ const dateButton = /* @__PURE__ */ jsx("button", { type: "button", className: "uix-scheduling-calendar__date", "data-calendar-date": date, tabIndex: activeDate === date ? 0 : -1, onFocus: () => setActiveDate(date), onKeyDown: (event) => onDayKeyDown(event, date), onClick: onSelectDate ? () => onSelectDate(date) : void 0, "aria-label": info?.label ? fillLabel(labels.dayName, { date: dateText(date, "day"), label: info.label }) : dateText(date, "day"), children: Number(date.slice(-2)) });
288
378
  const moreText = fillLabel(labels.moreEntries, { count: hiddenCount });
289
379
  const moreName = fillLabel(labels.moreEntriesLabel, { count: hiddenCount, date: dateText(date, "day") });
290
380
  return /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__day", "data-outside": view === "month" && !monthDays[index].inMonth || void 0, children: [
291
381
  hasBadge || info ? /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__dayhead", children: [
292
382
  hasBadge && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__badge", children: badge }),
293
- info && /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-calendar__count", children: [
383
+ info && /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-calendar__count", "aria-hidden": info.label ? true : void 0, children: [
294
384
  /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: info.count }),
295
385
  /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: info.label ?? fillLabel(labels.dayCount, { count: info.count }) })
296
386
  ] }),
@@ -299,18 +389,20 @@ function SchedulingCalendar({
299
389
  ] }) : dateButton,
300
390
  /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__entries", children: [
301
391
  shown.map((entry) => /* @__PURE__ */ jsx("button", { className: "uix-scheduling-calendar__entry", ...entryProps(entry), children: entryContent(entry) }, entry.id)),
302
- controlled ? hiddenCount > 0 && (onShowMore ? /* @__PURE__ */ jsx("button", { type: "button", className: "uix-scheduling-calendar__more", "aria-label": moreName, onClick: () => onShowMore(date, onDay), children: moreText }) : /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-calendar__more", children: [
392
+ countsOnly ? null : controlled ? hiddenCount > 0 && (onShowMore ? /* @__PURE__ */ jsx("button", { type: "button", tabIndex: -1, className: "uix-scheduling-calendar__more", "aria-label": moreName, onClick: () => onShowMore(date, onDay), children: moreText }) : /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-calendar__more", children: [
303
393
  /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: moreText }),
304
394
  /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: moreName })
305
- ] })) : overflowing && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-scheduling-calendar__more", "aria-expanded": onShowMore ? void 0 : !collapsed, "aria-label": collapsed ? moreName : void 0, onClick: () => toggleMore(date, onDay), children: collapsed ? moreText : labels.fewerEntries })
395
+ ] })) : overflowing && /* @__PURE__ */ jsx("button", { type: "button", tabIndex: -1, className: "uix-scheduling-calendar__more", "aria-expanded": onShowMore ? void 0 : !collapsed, "aria-label": collapsed ? moreName : void 0, onClick: () => toggleMore(date, onDay), children: collapsed ? moreText : labels.fewerEntries })
306
396
  ] })
307
397
  ] }, date);
308
398
  };
309
399
  const renderWeek = (row) => {
310
400
  const rowDays = days.slice(row * 7, row * 7 + 7);
311
401
  const windowLanes = lanesUsed(row, "window");
312
- const spanLanes = lanesUsed(row, "item");
313
- const hidden = layout.hiddenByRow?.[row]?.length ?? 0;
402
+ const spanLanes = countsOnly ? 0 : lanesUsed(row, "item");
403
+ const hiddenIds = layout.hiddenByRow?.[row] ?? EMPTY_IDS;
404
+ const drawnWindows = countsOnly ? new Set((placedByRow.get(row) ?? []).filter((placed) => placed.group === "window").map((placed) => placed.id)) : void 0;
405
+ const hidden = countsOnly ? hiddenIds.filter((id) => overlayById.has(id) && !drawnWindows.has(id)).length : hiddenIds.length;
314
406
  const firstHiddenDay = layout.firstHiddenDayByRow?.[row] ?? rowDays[0];
315
407
  const moreText = fillLabel(labels.moreSpans, { count: hidden });
316
408
  const moreName = fillLabel(labels.moreSpansLabel, { count: hidden, date: dateText(firstHiddenDay, "day") });
@@ -322,17 +414,17 @@ function SchedulingCalendar({
322
414
  if (placed.group === "window") {
323
415
  const overlay = overlayById.get(placed.id);
324
416
  if (!overlay) return null;
325
- return /* @__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, "data-kind": overlay.kind, ...continues, style: laneStyle(placed, windowLanes), onClick: () => onSelectOverlay?.(overlay), "aria-label": overlayName(overlay), children: /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-calendar__window-text", children: [
417
+ return /* @__PURE__ */ jsx("button", { type: "button", tabIndex: -1, className: "uix-scheduling-calendar__window", "data-overlay-id": overlay.id, "data-pattern": overlay.pattern ?? "solid", "data-global": overlay.global || void 0, "data-kind": overlay.kind, ...continues, style: laneStyle(placed, windowLanes), onClick: () => onSelectOverlay?.(overlay), "aria-label": overlayName(overlay), children: /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-calendar__window-text", children: [
326
418
  overlay.kindLabel && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__window-kind", children: overlay.kindLabel }),
327
419
  /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__window-name", children: overlay.label }),
328
420
  overlay.scopeLabel && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__window-scope", children: overlay.scopeLabel })
329
421
  ] }) }, `window-${placed.id}`);
330
422
  }
331
423
  const entry = entryById.get(placed.id);
332
- if (!entry) return null;
424
+ if (!entry || countsOnly) return null;
333
425
  return /* @__PURE__ */ jsx("button", { className: "uix-scheduling-calendar__entry uix-scheduling-calendar__span", ...entryProps(entry), ...continues, style: laneStyle(placed, windowLanes), children: entryContent(entry, !placed.continuesBefore) }, `span-${placed.id}`);
334
426
  }),
335
- hidden > 0 && (onShowMore ? /* @__PURE__ */ jsx("button", { type: "button", className: "uix-scheduling-calendar__rowmore", "aria-label": moreName, onClick: () => onShowMore(firstHiddenDay, picksGiven ? dayEntries[firstHiddenDay] ?? EMPTY_ENTRIES : entriesOn(firstHiddenDay)), children: moreText }) : /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-calendar__rowmore", children: [
427
+ hidden > 0 && (onShowMore ? /* @__PURE__ */ jsx("button", { type: "button", tabIndex: -1, className: "uix-scheduling-calendar__rowmore", "data-more-date": rowDays.includes(firstHiddenDay) ? firstHiddenDay : rowDays[0], "aria-label": moreName, onClick: () => onShowMore(firstHiddenDay, picksGiven ? dayEntries[firstHiddenDay] ?? EMPTY_ENTRIES : entriesOn(firstHiddenDay)), children: moreText }) : /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-calendar__rowmore", children: [
336
428
  /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: moreText }),
337
429
  /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: moreName })
338
430
  ] }))
@@ -341,7 +433,7 @@ function SchedulingCalendar({
341
433
  const gridStyle = fixed ? {
342
434
  ["--uix-scheduling-calendar-chips"]: chipRows,
343
435
  ["--uix-scheduling-calendar-lane-cap"]: Math.max(
344
- Math.max(Number.isFinite(windowCap) ? windowCap : 0, lanesUsedAnywhere("window")) + Math.max(Number.isFinite(spanCap) ? spanCap : 0, lanesUsedAnywhere("item")),
436
+ Math.max(Number.isFinite(windowCap) ? windowCap : 0, lanesUsedAnywhere("window")) + (countsOnly ? 0 : Math.max(Number.isFinite(spanCap) ? spanCap : 0, lanesUsedAnywhere("item"))),
345
437
  Object.keys(layout.hiddenByRow ?? {}).length > 0 ? 1 : 0
346
438
  )
347
439
  } : void 0;
@@ -358,9 +450,30 @@ function SchedulingCalendar({
358
450
  loading ? /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__state", role: "status", children: labels.loading }) : error ? /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__state", role: "alert", children: [
359
451
  /* @__PURE__ */ jsx("p", { children: error }),
360
452
  onRetry && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: labels.retry })
361
- ] }) : view === "agenda" ? /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__agenda", role: "region", "aria-label": labels.agenda, children: visibleEntries.length === 0 ? /* @__PURE__ */ jsx("p", { children: labels.agendaEmpty }) : /* @__PURE__ */ jsx("ol", { children: [...visibleEntries].sort((a, b) => a.start.localeCompare(b.start)).map((entry) => {
453
+ ] }) : view === "agenda" && agendaGroups ? /* @__PURE__ */ jsx(
454
+ SchedulingAgenda,
455
+ {
456
+ groups: agendaGroups,
457
+ timeZone,
458
+ labels,
459
+ headingLevel: agendaHeadingLevel,
460
+ virtualizeAbove,
461
+ dateText,
462
+ formatInstant,
463
+ entryName,
464
+ entryStatus: statusOf,
465
+ entryStatusText: stateText,
466
+ entryMarkers: markersOf,
467
+ overlayName,
468
+ renderEntry,
469
+ renderMarker,
470
+ onSelectEntry,
471
+ onSelectOverlay,
472
+ onShowMore
473
+ }
474
+ ) : view === "agenda" ? /* @__PURE__ */ jsx("div", { ref: flatAgendaRef, className: "uix-scheduling-calendar__agenda", role: "region", "aria-label": labels.agenda, onKeyDown: onFlatAgendaKeyDown, onFocus: onFlatAgendaFocus, children: visibleEntries.length === 0 ? /* @__PURE__ */ jsx("p", { children: labels.agendaEmpty }) : /* @__PURE__ */ jsx("ol", { children: [...visibleEntries].sort((a, b) => a.start.localeCompare(b.start)).map((entry) => {
362
475
  const markers = markersOf(entry);
363
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("button", { type: "button", "data-item-id": entry.id, "data-band": entry.band ?? "none", "data-status": statusOf(entry), "data-state": entry.state, onClick: () => onSelectEntry?.(entry), children: [
476
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("button", { type: "button", "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": statusOf(entry), "data-state": entry.state, onClick: () => onSelectEntry?.(entry), children: [
364
477
  /* @__PURE__ */ jsxs("span", { children: [
365
478
  /* @__PURE__ */ jsx("strong", { children: renderEntry?.(entry) ?? entry.title }),
366
479
  /* @__PURE__ */ jsxs("span", { children: [
@@ -407,7 +520,7 @@ function SchedulingCalendar({
407
520
  emptyNote,
408
521
  nothingToShow
409
522
  }
410
- ) : /* @__PURE__ */ jsxs("div", { ref: gridRef, className: cx("uix-scheduling-calendar__grid", view === "week" && "uix-scheduling-calendar__grid--week"), "data-fixed": fixed || void 0, style: gridStyle, role: "group", "aria-label": fillLabel(labels.grid, { view: viewLabel[view] }), children: [
523
+ ) : /* @__PURE__ */ jsxs("div", { ref: gridRef, className: cx("uix-scheduling-calendar__grid", view === "week" && "uix-scheduling-calendar__grid--week"), "data-fixed": fixed || void 0, "data-density": countsOnly ? "counts" : void 0, style: gridStyle, onKeyDown: onGridKeyDown, onFocus: onGridFocus, role: "group", "aria-label": fillLabel(labels.grid, { view: viewLabel[view] }), children: [
411
524
  days.slice(0, 7).map((date) => /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__weekday", "aria-hidden": "true", children: dateText(date, "weekday") }, `weekday-${date}`)),
412
525
  Array.from({ length: days.length / 7 }, (_, row) => renderWeek(row)),
413
526
  emptyNote != null && emptyNote !== false && nothingToShow && /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__empty", children: emptyNote })
@@ -1,4 +1,4 @@
1
1
  import 'react';
2
2
  import '../scheduling-calendar-model.js';
3
- export { p as SchedulingTimeGrid, q as SchedulingTimeGridProps, T as TimeGridEntryContext } from '../SchedulingCalendar-C4XVbqZr.js';
3
+ export { q as SchedulingTimeGrid, r as SchedulingTimeGridProps, T as TimeGridEntryContext } from '../SchedulingCalendar-B6TakO2h.js';
4
4
  import '../calendar-model.js';
@@ -39,6 +39,8 @@ const GridItem = memo(function GridItem2({ entry, segment, column, alone, name,
39
39
  ] });
40
40
  const shared = {
41
41
  className: "uix-scheduling-calendar__entry uix-scheduling-calendar__tg-item",
42
+ "data-highlight": entry.emphasis === "highlight" || void 0,
43
+ "data-dim": entry.emphasis === "dim" || void 0,
42
44
  "data-band": entry.band ?? "none",
43
45
  "data-status": status,
44
46
  "data-state": entry.state,
@@ -54,6 +56,7 @@ const GridItem = memo(function GridItem2({ entry, segment, column, alone, name,
54
56
  "button",
55
57
  {
56
58
  type: "button",
59
+ tabIndex: -1,
57
60
  ...shared,
58
61
  "data-item-id": entry.id,
59
62
  "aria-label": name,
@@ -101,6 +104,9 @@ function SchedulingTimeGrid({
101
104
  const [widthCap, setWidthCap] = useState(Infinity);
102
105
  const [ghost, setGhost] = useState(null);
103
106
  const [announcement, setAnnouncement] = useState("");
107
+ const [activeDay, setActiveDay] = useState(days[0]);
108
+ const enteredFrom = useRef(null);
109
+ const tabStop = days.includes(activeDay) ? activeDay : days[0];
104
110
  const moving = canMove && onProposeMove !== void 0;
105
111
  const step = Number.isFinite(givenStep) && givenStep > 0 ? givenStep : DEFAULT_STEP;
106
112
  const laneOptions = useMemo(() => ({ crossMidnightMinutes: topLaneCrossMidnightMinutes }), [topLaneCrossMidnightMinutes]);
@@ -334,6 +340,78 @@ function SchedulingTimeGrid({
334
340
  dragRef.current = null;
335
341
  }, []);
336
342
  const gridColumns = (span) => `${span.startCol + 2} / ${span.endCol + 3}`;
343
+ const headButton = (date) => rootRef.current?.querySelector(`.uix-scheduling-calendar__tg-dayhead[data-date="${date}"] [data-calendar-date]`) ?? null;
344
+ const dayRing = (date) => {
345
+ const root = rootRef.current;
346
+ const column = days.indexOf(date);
347
+ if (!root || column === -1) return [];
348
+ const covers = (element) => {
349
+ const [from, to] = element.style.gridColumn.split("/").map((part) => Number(part.trim()));
350
+ return from !== void 0 && to !== void 0 && from <= column + 2 && column + 2 < to;
351
+ };
352
+ const all = (selector) => Array.from(root.querySelectorAll(selector));
353
+ return [
354
+ ...all(".uix-scheduling-calendar__tg-strip > button.uix-scheduling-calendar__window").filter(covers),
355
+ ...all(".uix-scheduling-calendar__tg-top > button.uix-scheduling-calendar__tg-span").filter(covers),
356
+ ...all(`[data-tg-column="${date}"] > button[data-item-id]`),
357
+ ...all(`.uix-scheduling-calendar__tg-dayhead[data-date="${date}"] > button.uix-scheduling-calendar__more`),
358
+ // "+N windows" opens one day; it is reached from that day.
359
+ ...all(`.uix-scheduling-calendar__tg-strip > button.uix-scheduling-calendar__rowmore[data-more-date="${date}"]`)
360
+ ];
361
+ };
362
+ const dayOfItem = (item) => {
363
+ if (enteredFrom.current && dayRing(enteredFrom.current).includes(item)) return enteredFrom.current;
364
+ const inColumn = item.getAttribute("data-more-date") ?? item.closest("[data-tg-column]")?.getAttribute("data-tg-column") ?? item.closest("[data-date]")?.getAttribute("data-date");
365
+ if (inColumn) return inColumn;
366
+ const from = Number(item.style.gridColumn.split("/")[0]);
367
+ return Number.isFinite(from) ? days[Math.max(0, Math.min(days.length - 1, from - 2))] ?? null : days[days.length - 1] ?? null;
368
+ };
369
+ const onHeadKeyDown = (event, date) => {
370
+ const index = days.indexOf(date);
371
+ const target = event.key === "ArrowLeft" ? index - 1 : event.key === "ArrowRight" ? index + 1 : event.key === "Home" ? 0 : event.key === "End" ? days.length - 1 : null;
372
+ if (target !== null) {
373
+ event.preventDefault();
374
+ const next = days[Math.max(0, Math.min(days.length - 1, target))];
375
+ setActiveDay(next);
376
+ headButton(next)?.focus();
377
+ } else if (event.key === "Enter") {
378
+ const [first] = dayRing(date);
379
+ if (!first) return;
380
+ event.preventDefault();
381
+ enteredFrom.current = date;
382
+ first.focus();
383
+ }
384
+ };
385
+ const onGridKeyDown = (event) => {
386
+ const item = event.target;
387
+ if (event.defaultPrevented || event.shiftKey || item.hasAttribute("data-calendar-date") || !["ArrowDown", "ArrowUp", "Home", "End", "Escape"].includes(event.key)) return;
388
+ const date = dayOfItem(item);
389
+ if (!date) return;
390
+ const ring = dayRing(date);
391
+ const index = ring.indexOf(item);
392
+ if (index === -1) return;
393
+ event.preventDefault();
394
+ const to = event.key === "Escape" ? null : ring[Math.max(0, Math.min(ring.length - 1, event.key === "Home" ? 0 : event.key === "End" ? ring.length - 1 : index + (event.key === "ArrowDown" ? 1 : -1)))];
395
+ if (to !== item && ghost?.source === "keys") {
396
+ setGhost(null);
397
+ setAnnouncement(labels.moveCancelled);
398
+ }
399
+ if (event.key === "Escape") {
400
+ event.stopPropagation();
401
+ enteredFrom.current = null;
402
+ setActiveDay(date);
403
+ headButton(date)?.focus();
404
+ return;
405
+ }
406
+ enteredFrom.current = date;
407
+ to?.focus();
408
+ };
409
+ const onGridFocus = (event) => {
410
+ const item = event.target;
411
+ if (item.hasAttribute("data-calendar-date")) return;
412
+ const date = dayOfItem(item);
413
+ if (date && dayRing(date).includes(item)) setActiveDay(date);
414
+ };
337
415
  const nowAt = now === void 0 ? null : new Date(now).getTime();
338
416
  const nowColumn = nowAt === null || Number.isNaN(nowAt) ? -1 : days.indexOf(zonedDateKey(new Date(nowAt), timeZone));
339
417
  const topHiddenOn = (date) => top.hidden.filter((id) => {
@@ -358,6 +436,8 @@ function SchedulingTimeGrid({
358
436
  role: "group",
359
437
  "aria-label": gridLabel,
360
438
  style: vars({ "--uix-scheduling-calendar-days": days.length, "--uix-scheduling-calendar-hours": maxHours }),
439
+ onKeyDown: onGridKeyDown,
440
+ onFocus: onGridFocus,
361
441
  onClickCapture: (event) => {
362
442
  if (suppressClick.current) {
363
443
  suppressClick.current = false;
@@ -374,12 +454,25 @@ function SchedulingTimeGrid({
374
454
  const more = info ? info.overflowCount : hiddenIds.length;
375
455
  const moreText = fillLabel(labels.moreSpans, { count: more });
376
456
  const moreName = fillLabel(labels.moreEntriesLabel, { count: more, date: dateText(date, "day") });
377
- const headName = info ? info.label ?? `${dateText(date, "day")}, ${fillLabel(labels.dayCount, { count: info.count })}` : dateText(date, "day");
457
+ const headName = !info ? dateText(date, "day") : info.label ? fillLabel(labels.dayName, { date: dateText(date, "day"), label: info.label }) : `${dateText(date, "day")}, ${fillLabel(labels.dayCount, { count: info.count })}`;
378
458
  return /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__tg-dayhead", role: "group", "aria-label": headName, "data-date": date, children: [
379
- /* @__PURE__ */ jsx("button", { type: "button", className: "uix-scheduling-calendar__date", "data-calendar-date": date, "aria-label": dateText(date, "day"), onClick: onSelectDate ? () => onSelectDate(date) : void 0, children: dateText(date, "column") }),
459
+ /* @__PURE__ */ jsx(
460
+ "button",
461
+ {
462
+ type: "button",
463
+ className: "uix-scheduling-calendar__date",
464
+ "data-calendar-date": date,
465
+ tabIndex: tabStop === date ? 0 : -1,
466
+ "aria-label": info?.label ? fillLabel(labels.dayName, { date: dateText(date, "day"), label: info.label }) : dateText(date, "day"),
467
+ onFocus: () => setActiveDay(date),
468
+ onKeyDown: (event) => onHeadKeyDown(event, date),
469
+ onClick: onSelectDate ? () => onSelectDate(date) : void 0,
470
+ children: dateText(date, "column")
471
+ }
472
+ ),
380
473
  info && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__count", "aria-hidden": "true", children: info.count }),
381
474
  info?.markers && info.markers.length > 0 && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__markers", children: info.markers.map((marker) => /* @__PURE__ */ jsx("span", { children: renderMarker(marker, true) }, marker.id)) }),
382
- more > 0 && (onShowMore ? /* @__PURE__ */ jsx("button", { type: "button", className: "uix-scheduling-calendar__more", "aria-label": moreName, onClick: () => onShowMore(date, hiddenIds.map((id) => entryById.get(id)).filter(Boolean)), children: moreText }) : /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-calendar__more", children: [
475
+ more > 0 && (onShowMore ? /* @__PURE__ */ jsx("button", { type: "button", tabIndex: -1, className: "uix-scheduling-calendar__more", "aria-label": moreName, onClick: () => onShowMore(date, hiddenIds.map((id) => entryById.get(id)).filter(Boolean)), children: moreText }) : /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-calendar__more", children: [
383
476
  /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: moreText }),
384
477
  /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: moreName })
385
478
  ] }))
@@ -394,6 +487,7 @@ function SchedulingTimeGrid({
394
487
  "button",
395
488
  {
396
489
  type: "button",
490
+ tabIndex: -1,
397
491
  className: "uix-scheduling-calendar__window",
398
492
  "data-overlay-id": overlay.id,
399
493
  "data-pattern": overlay.pattern ?? "solid",
@@ -413,7 +507,7 @@ function SchedulingTimeGrid({
413
507
  placed.id
414
508
  );
415
509
  }),
416
- moreWindows > 0 && (onShowMore ? /* @__PURE__ */ jsx("button", { type: "button", className: "uix-scheduling-calendar__rowmore", "aria-label": fillLabel(labels.moreWindowsLabel, { count: moreWindows, date: dateText(firstHiddenWindowDay, "day") }), onClick: () => onShowMore(firstHiddenWindowDay, []), children: fillLabel(labels.moreWindows, { count: moreWindows }) }) : /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__rowmore", children: fillLabel(labels.moreWindows, { count: moreWindows }) }))
510
+ moreWindows > 0 && (onShowMore ? /* @__PURE__ */ jsx("button", { type: "button", tabIndex: -1, className: "uix-scheduling-calendar__rowmore", "data-more-date": firstHiddenWindowDay, "aria-label": fillLabel(labels.moreWindowsLabel, { count: moreWindows, date: dateText(firstHiddenWindowDay, "day") }), onClick: () => onShowMore(firstHiddenWindowDay, []), children: fillLabel(labels.moreWindows, { count: moreWindows }) }) : /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__rowmore", children: fillLabel(labels.moreWindows, { count: moreWindows }) }))
417
511
  ] }),
418
512
  (topLanes > 0 || ghostSpan) && /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__tg-strip uix-scheduling-calendar__tg-top", role: "group", "aria-label": labels.topLane, style: vars({ "--uix-scheduling-calendar-lanes": Math.max(topLanes, 1) }), children: [
419
513
  top.placed.map((placed) => {
@@ -425,8 +519,11 @@ function SchedulingTimeGrid({
425
519
  "button",
426
520
  {
427
521
  type: "button",
522
+ tabIndex: -1,
428
523
  className: "uix-scheduling-calendar__entry uix-scheduling-calendar__tg-span",
429
524
  "data-item-id": entry.id,
525
+ "data-highlight": entry.emphasis === "highlight" || void 0,
526
+ "data-dim": entry.emphasis === "dim" || void 0,
430
527
  "data-band": entry.band ?? "none",
431
528
  "data-status": entryStatus(entry),
432
529
  "data-state": entry.state,
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
3
  import { TimelineRange, TimelineScale } from '../scheduling-timeline-model.js';
4
- import { i as SchedulingEntryState } from '../SchedulingCalendar-C4XVbqZr.js';
4
+ import { j as SchedulingEntryState } from '../SchedulingCalendar-B6TakO2h.js';
5
5
  import '../calendar-model.js';
6
6
  import '../scheduling-calendar-model.js';
7
7