@tensor_1/react 2.31.0 → 2.32.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 (114) hide show
  1. package/README.md +350 -0
  2. package/dist/attachment-labels.d.ts +16 -0
  3. package/dist/attachment-labels.js +9 -0
  4. package/dist/calendar-model.d.ts +141 -1
  5. package/dist/calendar-model.js +200 -11
  6. package/dist/chart-preset.cjs +127 -7
  7. package/dist/chart-preset.d.cts +2 -2
  8. package/dist/chart-preset.d.ts +1 -0
  9. package/dist/chart-preset.js +6 -1
  10. package/dist/chart-theme-Cl0IrkKQ.d.cts +112 -0
  11. package/dist/chart-theme.d.ts +51 -0
  12. package/dist/chart-theme.js +99 -0
  13. package/dist/chart.cjs +127 -7
  14. package/dist/chart.d.cts +2 -2
  15. package/dist/chart.d.ts +1 -0
  16. package/dist/chart.js +6 -2
  17. package/dist/components/Alert.d.ts +17 -1
  18. package/dist/components/Alert.js +9 -4
  19. package/dist/components/AlertDismiss.d.ts +14 -0
  20. package/dist/components/AlertDismiss.js +11 -0
  21. package/dist/components/Attachment.d.ts +15 -12
  22. package/dist/components/Attachment.js +17 -17
  23. package/dist/components/AttachmentParts.d.ts +24 -0
  24. package/dist/components/AttachmentParts.js +20 -0
  25. package/dist/components/Chart.d.ts +1 -0
  26. package/dist/components/Chart.js +6 -1
  27. package/dist/components/ChartCore.d.ts +6 -1
  28. package/dist/components/ChartCore.js +23 -5
  29. package/dist/components/Chip.d.ts +17 -2
  30. package/dist/components/Chip.js +35 -11
  31. package/dist/components/ChipRemove.d.ts +17 -0
  32. package/dist/components/ChipRemove.js +13 -0
  33. package/dist/components/CollapsibleParts.d.ts +29 -0
  34. package/dist/components/CollapsibleParts.js +20 -0
  35. package/dist/components/CollapsibleSection.d.ts +30 -3
  36. package/dist/components/CollapsibleSection.js +7 -11
  37. package/dist/components/CollapsibleSectionState.d.ts +1 -1
  38. package/dist/components/CollapsibleSectionState.js +54 -27
  39. package/dist/components/DashboardGrid.d.ts +60 -0
  40. package/dist/components/DashboardGrid.js +40 -0
  41. package/dist/components/DateRangePicker.d.ts +7 -1
  42. package/dist/components/DateRangePicker.js +5 -1
  43. package/dist/components/DescriptionList.d.ts +12 -1
  44. package/dist/components/DescriptionList.js +5 -5
  45. package/dist/components/Dialogs.d.ts +24 -3
  46. package/dist/components/Dialogs.js +74 -16
  47. package/dist/components/FileUpload.d.ts +10 -2
  48. package/dist/components/FileUpload.js +10 -4
  49. package/dist/components/FilterEditor.d.ts +4 -0
  50. package/dist/components/FilterEditor.js +13 -2
  51. package/dist/components/Heartbeat.d.ts +31 -0
  52. package/dist/components/Heartbeat.js +28 -0
  53. package/dist/components/Menu.d.ts +53 -5
  54. package/dist/components/Menu.js +104 -4
  55. package/dist/components/Meter.d.ts +7 -3
  56. package/dist/components/Meter.js +2 -1
  57. package/dist/components/Popover.d.ts +21 -2
  58. package/dist/components/Popover.js +94 -17
  59. package/dist/components/Prose.d.ts +6 -2
  60. package/dist/components/Prose.js +1 -1
  61. package/dist/components/RuleBuilder.d.ts +52 -4
  62. package/dist/components/RuleBuilder.js +109 -24
  63. package/dist/components/SchedulingCalendar.d.ts +142 -11
  64. package/dist/components/SchedulingCalendar.js +234 -57
  65. package/dist/components/SchedulingTimeline.d.ts +1 -0
  66. package/dist/components/Segmented.d.ts +17 -3
  67. package/dist/components/Segmented.js +59 -5
  68. package/dist/components/Select.d.ts +66 -2
  69. package/dist/components/Select.js +800 -10
  70. package/dist/components/Spinner.d.ts +2 -1
  71. package/dist/components/Spinner.js +1 -1
  72. package/dist/components/StatusPill.d.ts +8 -2
  73. package/dist/components/StatusPill.js +2 -2
  74. package/dist/components/Steps.d.ts +28 -4
  75. package/dist/components/Steps.js +46 -16
  76. package/dist/components/TableControls.js +4 -3
  77. package/dist/components/Tabs.d.ts +7 -2
  78. package/dist/components/Tabs.js +14 -1
  79. package/dist/components/TextDiff.d.ts +49 -0
  80. package/dist/components/TextDiff.js +124 -0
  81. package/dist/{emoji-image-6mPZ15Zy.d.cts → emoji-image-DjCKpdwE.d.cts} +1 -1
  82. package/dist/emoji.cjs +169 -27
  83. package/dist/emoji.d.cts +3 -3
  84. package/dist/file-model.d.ts +22 -3
  85. package/dist/file-model.js +6 -4
  86. package/dist/filter-model.d.ts +26 -2
  87. package/dist/filter-model.js +13 -1
  88. package/dist/hooks/useAnchoredPosition.d.ts +24 -1
  89. package/dist/hooks/useAnchoredPosition.js +58 -8
  90. package/dist/hooks/useVirtualRows.d.ts +17 -2
  91. package/dist/hooks/useVirtualRows.js +6 -5
  92. package/dist/icons/Icon.d.ts +14 -14
  93. package/dist/index.cjs +4179 -1678
  94. package/dist/index.d.cts +1706 -660
  95. package/dist/index.d.ts +16 -9
  96. package/dist/index.js +53 -4
  97. package/dist/labels-context.d.ts +28 -0
  98. package/dist/{overlay-position-Cjv5E7mn.d.cts → overlay-position-2Afke51l.d.cts} +20 -0
  99. package/dist/overlay-position.d.ts +20 -0
  100. package/dist/overlay-position.js +17 -2
  101. package/dist/rich-text.cjs +169 -27
  102. package/dist/rich-text.d.cts +3 -3
  103. package/dist/rule-builder-model.d.ts +34 -2
  104. package/dist/rule-builder-model.js +48 -12
  105. package/dist/scheduling-calendar-model.d.ts +66 -0
  106. package/dist/scheduling-calendar-model.js +82 -0
  107. package/dist/select-model.d.ts +80 -0
  108. package/dist/select-model.js +109 -0
  109. package/dist/table-engine.d.ts +7 -0
  110. package/dist/table-engine.js +2 -1
  111. package/dist/text-diff-model.d.ts +71 -0
  112. package/dist/text-diff-model.js +261 -0
  113. package/package.json +1 -1
  114. package/dist/ChartCore-DzWe10bN.d.cts +0 -59
@@ -1,7 +1,8 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useMemo, useRef, useState } from "react";
4
- import { addCalendarDays, addCalendarMonths, buildMonthGrid, startOfMonth, zonedDateSpan } from "../calendar-model.js";
4
+ import { addCalendarDays, addCalendarMonths, buildMonthGrid, cachedDateTimeFormat, startOfMonth, zonedDateKey, zonedTimeOfDay } from "../calendar-model.js";
5
+ import { itemDaySpan, layoutMonthSpans } from "../scheduling-calendar-model.js";
5
6
  import { cx } from "../cx.js";
6
7
  import { useUixLabels } from "../labels-context.js";
7
8
  import { StatusPill } from "./StatusPill.js";
@@ -10,6 +11,10 @@ const DEFAULT_SCHEDULING_CALENDAR_LABELS = {
10
11
  region: "Scheduling calendar in {timeZone}",
11
12
  previous: "Previous",
12
13
  next: "Next",
14
+ previousMonth: "Previous month",
15
+ nextMonth: "Next month",
16
+ previousWeek: "Previous week",
17
+ nextWeek: "Next week",
13
18
  viewGroup: "Calendar view",
14
19
  viewMonth: "Month",
15
20
  viewWeek: "Week",
@@ -20,11 +25,16 @@ const DEFAULT_SCHEDULING_CALENDAR_LABELS = {
20
25
  agendaEmpty: "No scheduled entries match the current filters.",
21
26
  grid: "{view} schedule",
22
27
  entry: "{title}, {state}, {start} to {end}",
28
+ overlay: "{title}, {start} to {end}",
23
29
  legend: "Schedule state legend",
24
30
  moreEntries: "+{count} more",
25
31
  moreEntriesLabel: "{count} more entries on {date}",
26
32
  fewerEntries: "Show fewer",
27
- states: { scheduled: "Scheduled", conflicted: "Conflicted", "in-progress": "In progress", "blackout-violation": "Blackout violation" }
33
+ moreSpans: "+{count}",
34
+ moreSpansLabel: "{count} more spanning this week, from {date}",
35
+ dayCount: "{count} entries",
36
+ states: { scheduled: "Scheduled", conflicted: "Conflicted", "in-progress": "In progress", "blackout-violation": "Blackout violation" },
37
+ statuses: { tentative: "Tentative", committed: "Scheduled", live: "In progress", done: "Done", dead: "Cancelled" }
28
38
  };
29
39
  const stateTone = (state) => state === "scheduled" ? "info" : state === "in-progress" ? "success" : "danger";
30
40
  const utcDate = (date) => /* @__PURE__ */ new Date(`${date}T00:00:00Z`);
@@ -33,8 +43,24 @@ const INTL_PARTS = {
33
43
  weekday: { weekday: "short", timeZone: "UTC" },
34
44
  month: { month: "long", year: "numeric", timeZone: "UTC" }
35
45
  };
36
- const intlDate = (date, part, locale) => new Intl.DateTimeFormat(locale, INTL_PARTS[part]).format(utcDate(date));
37
46
  const EMPTY_OVERLAYS = [];
47
+ const EMPTY_ENTRIES = [];
48
+ const DEFAULT_LANE_CAP = 2;
49
+ const DEFAULT_CHIP_ROWS = 3;
50
+ function coveredDays(start, end, timeZone) {
51
+ try {
52
+ return itemDaySpan(start, end, timeZone);
53
+ } catch {
54
+ return itemDaySpan(start, start, timeZone);
55
+ }
56
+ }
57
+ function Marker({ marker }) {
58
+ const emphasis = marker.emphasis ?? "neutral";
59
+ return /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-calendar__marker", "data-emphasis": emphasis, "data-glyph": marker.icon == null ? emphasis : void 0, children: [
60
+ marker.icon != null && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__marker-icon", "aria-hidden": "true", children: marker.icon }),
61
+ /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: marker.label })
62
+ ] });
63
+ }
38
64
  function SchedulingCalendar({
39
65
  entries,
40
66
  anchorDate,
@@ -44,6 +70,7 @@ function SchedulingCalendar({
44
70
  onAnchorDateChange,
45
71
  onSelectEntry,
46
72
  overlays = EMPTY_OVERLAYS,
73
+ onSelectOverlay,
47
74
  filter,
48
75
  renderEntry,
49
76
  locale,
@@ -53,6 +80,17 @@ function SchedulingCalendar({
53
80
  maxEntriesPerDay,
54
81
  onShowMore,
55
82
  renderDayBadge,
83
+ days: dayInfo,
84
+ dayEntries,
85
+ spanLayout,
86
+ windowLaneCap,
87
+ spanLaneCap,
88
+ legend,
89
+ legendCaption,
90
+ showHeader = true,
91
+ notice,
92
+ emptyNote,
93
+ onSelectDate,
56
94
  loading,
57
95
  error,
58
96
  onRetry,
@@ -60,11 +98,12 @@ function SchedulingCalendar({
60
98
  labels: labelOverrides
61
99
  }) {
62
100
  const uixLabels = useUixLabels();
101
+ const overrides = { ...uixLabels.schedulingCalendar, ...labelOverrides };
63
102
  const labels = {
64
103
  ...DEFAULT_SCHEDULING_CALENDAR_LABELS,
65
- ...uixLabels.schedulingCalendar,
66
- ...labelOverrides,
67
- states: { ...DEFAULT_SCHEDULING_CALENDAR_LABELS.states, ...uixLabels.schedulingCalendar?.states, ...labelOverrides?.states }
104
+ ...overrides,
105
+ states: { ...DEFAULT_SCHEDULING_CALENDAR_LABELS.states, ...uixLabels.schedulingCalendar?.states, ...labelOverrides?.states },
106
+ statuses: { ...DEFAULT_SCHEDULING_CALENDAR_LABELS.statuses, ...uixLabels.schedulingCalendar?.statuses, ...labelOverrides?.statuses }
68
107
  };
69
108
  const stateLabel = labels.states;
70
109
  const viewLabel = { month: labels.viewMonth, week: labels.viewWeek, agenda: labels.viewAgenda };
@@ -72,16 +111,66 @@ function SchedulingCalendar({
72
111
  const view = controlledView ?? internalView;
73
112
  const [activeDate, setActiveDate] = useState(anchorDate);
74
113
  const [expandedDates, setExpandedDates] = useState(() => /* @__PURE__ */ new Set());
75
- const dateText = (date, part) => formatDate?.(date, part) ?? intlDate(date, part, locale);
114
+ const dateText = (date, part) => formatDate?.(date, part) ?? cachedDateTimeFormat(locale, INTL_PARTS[part]).format(utcDate(date));
115
+ const formatInstant = (instant) => formatInstantProp?.(instant) ?? `${dateText(zonedDateKey(instant, timeZone), "day")} ${zonedTimeOfDay(instant, timeZone)}`;
76
116
  const gridRef = useRef(null);
117
+ const picksGiven = dayEntries !== void 0;
118
+ const controlled = picksGiven || dayInfo !== void 0;
119
+ const fixed = controlled || maxEntriesPerDay !== void 0 && onShowMore !== void 0;
120
+ const windowCap = Math.max(0, windowLaneCap ?? (fixed ? DEFAULT_LANE_CAP : Infinity));
121
+ const spanCap = Math.max(0, spanLaneCap ?? (fixed ? DEFAULT_LANE_CAP : Infinity));
77
122
  const visibleEntries = useMemo(() => entries.filter((entry) => filter?.(entry) ?? true), [entries, filter]);
78
- const entrySpans = useMemo(() => new Map(visibleEntries.map((entry) => [entry.id, zonedDateSpan(entry.start, entry.end, timeZone)])), [visibleEntries, timeZone]);
79
- const overlaySpans = useMemo(() => new Map(overlays.map((overlay) => [overlay.id, { start: overlay.start.slice(0, 10), end: overlay.end.slice(0, 10) }])), [overlays]);
80
123
  const monthDays = useMemo(() => buildMonthGrid(startOfMonth(anchorDate), weekStartsOn), [anchorDate, weekStartsOn]);
81
124
  const weekday = (utcDate(anchorDate).getUTCDay() - weekStartsOn + 7) % 7;
82
125
  const weekStart = addCalendarDays(anchorDate, -weekday);
83
126
  const weekDays = useMemo(() => Array.from({ length: 7 }, (_, index) => addCalendarDays(weekStart, index)), [weekStart]);
84
127
  const days = useMemo(() => view === "month" ? monthDays.map((day) => day.date) : weekDays, [view, monthDays, weekDays]);
128
+ const { singles, multiDay, coverage } = useMemo(() => {
129
+ const byDay = /* @__PURE__ */ new Map();
130
+ const spans = [];
131
+ const every = [];
132
+ for (const entry of visibleEntries) {
133
+ const covered = coveredDays(entry.start, entry.end, timeZone);
134
+ every.push({ entry, ...covered });
135
+ if (addCalendarDays(covered.start, 1) === covered.end) {
136
+ const list = byDay.get(covered.start);
137
+ if (list) list.push(entry);
138
+ else byDay.set(covered.start, [entry]);
139
+ } else spans.push({ entry, ...covered });
140
+ }
141
+ return { singles: byDay, multiDay: spans, coverage: every };
142
+ }, [visibleEntries, timeZone]);
143
+ const entryById = useMemo(() => new Map([...multiDay].reverse().map(({ entry }) => [entry.id, entry])), [multiDay]);
144
+ const overlayById = useMemo(() => new Map([...overlays].reverse().map((overlay) => [overlay.id, overlay])), [overlays]);
145
+ const layout = useMemo(() => {
146
+ if (spanLayout) return spanLayout;
147
+ if (view === "agenda") return { placed: [], hiddenByRow: {}, firstHiddenDayByRow: {} };
148
+ const inputs = [];
149
+ const windowIds = /* @__PURE__ */ new Set();
150
+ const itemIds = /* @__PURE__ */ new Set();
151
+ for (const overlay of overlays) {
152
+ if (windowIds.has(overlay.id) || !(new Date(overlay.end).getTime() >= new Date(overlay.start).getTime())) continue;
153
+ windowIds.add(overlay.id);
154
+ inputs.push({ id: overlay.id, start: overlay.start, end: overlay.end, group: "window" });
155
+ }
156
+ for (const { entry } of multiDay) {
157
+ if (itemIds.has(entry.id)) continue;
158
+ itemIds.add(entry.id);
159
+ inputs.push({ id: entry.id, start: entry.start, end: entry.end, group: "item" });
160
+ }
161
+ return layoutMonthSpans(inputs, days, { timeZone, laneCap: { window: windowCap, item: spanCap } });
162
+ }, [spanLayout, view, overlays, multiDay, days, timeZone, windowCap, spanCap]);
163
+ const placedByRow = useMemo(() => {
164
+ const rows = /* @__PURE__ */ new Map();
165
+ for (const placed of layout.placed) {
166
+ const row = rows.get(placed.weekRow);
167
+ if (row) row.push(placed);
168
+ else rows.set(placed.weekRow, [placed]);
169
+ }
170
+ return rows;
171
+ }, [layout]);
172
+ const lanesUsed = (row, group) => (placedByRow.get(row) ?? []).reduce((max, placed) => placed.group === group ? Math.max(max, placed.lane + 1) : max, 0);
173
+ const lanesUsedAnywhere = (group) => layout.placed.reduce((max, placed) => placed.group === group ? Math.max(max, placed.lane + 1) : max, 0);
85
174
  useEffect(() => {
86
175
  if (view !== "agenda" && !days.includes(activeDate)) setActiveDate(days[0]);
87
176
  }, [activeDate, days, view]);
@@ -90,6 +179,10 @@ function SchedulingCalendar({
90
179
  onViewChange?.(next);
91
180
  };
92
181
  const movePeriod = (direction) => onAnchorDateChange?.(view === "month" ? addCalendarMonths(anchorDate, direction) : addCalendarDays(anchorDate, direction * 7));
182
+ const periodLabel = (direction) => {
183
+ const key = `${direction}${view === "month" ? "Month" : "Week"}`;
184
+ return overrides[key] ?? overrides[direction] ?? labels[key];
185
+ };
93
186
  const focusDate = (date) => {
94
187
  setActiveDate(date);
95
188
  requestAnimationFrame(() => gridRef.current?.querySelector(`[data-calendar-date="${date}"]`)?.focus());
@@ -107,18 +200,10 @@ function SchedulingCalendar({
107
200
  focusDate(days[days.length - 1]);
108
201
  }
109
202
  };
110
- const entriesFor = (date) => visibleEntries.filter((entry) => {
111
- const span = entrySpans.get(entry.id);
112
- return date >= span.start && date <= span.end;
113
- });
114
- const overlaysFor = (date) => overlays.filter((overlay) => {
115
- const span = overlaySpans.get(overlay.id);
116
- return date >= span.start && date <= span.end;
117
- });
118
- const formatInstant = (instant) => formatInstantProp?.(instant) ?? new Intl.DateTimeFormat(locale, { timeZone, dateStyle: "medium", timeStyle: "short" }).format(new Date(instant));
119
- const toggleMore = (date, dayEntries) => {
203
+ const entriesOn = (date) => coverage.filter((covered) => date >= covered.start && date < covered.end).map((covered) => covered.entry);
204
+ const toggleMore = (date, all) => {
120
205
  if (onShowMore) {
121
- onShowMore(date, dayEntries);
206
+ onShowMore(date, all);
122
207
  return;
123
208
  }
124
209
  setExpandedDates((current) => {
@@ -128,21 +213,129 @@ function SchedulingCalendar({
128
213
  return next;
129
214
  });
130
215
  };
216
+ const statusOf = (entry) => entry.status ?? (entry.state === "in-progress" ? "live" : "committed");
217
+ const markersOf = (entry) => {
218
+ const flagged = entry.state === "conflicted" || entry.state === "blackout-violation";
219
+ return flagged ? [...entry.markers ?? [], { id: "state", label: stateLabel[entry.state], emphasis: "warning" }] : entry.markers ?? [];
220
+ };
221
+ const stateText = (entry) => entry.state ? stateLabel[entry.state] : entry.status ? labels.statuses[entry.status] : stateLabel.scheduled;
222
+ 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(", ");
223
+ 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) });
224
+ const entryProps = (entry) => ({
225
+ type: "button",
226
+ "data-item-id": entry.id,
227
+ "data-band": entry.band ?? "none",
228
+ "data-status": statusOf(entry),
229
+ "data-state": entry.state,
230
+ onClick: () => onSelectEntry?.(entry),
231
+ "aria-label": entryName(entry)
232
+ });
233
+ const entryContent = (entry, withTime = true) => {
234
+ const markers = markersOf(entry);
235
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
236
+ /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__entry-text", children: renderEntry?.(entry) ?? /* @__PURE__ */ jsxs(Fragment, { children: [
237
+ withTime && /* @__PURE__ */ jsxs(Fragment, { children: [
238
+ /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__time", children: zonedTimeOfDay(entry.start, timeZone) }),
239
+ " "
240
+ ] }),
241
+ /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__title", children: entry.title })
242
+ ] }) }),
243
+ markers.length > 0 && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__markers", children: markers.map((marker) => /* @__PURE__ */ jsx(Marker, { marker }, marker.id)) })
244
+ ] });
245
+ };
246
+ const laneStyle = (placed, windowLanes) => ({
247
+ gridColumn: `${placed.startCol + 1} / ${placed.endCol + 2}`,
248
+ ["--uix-scheduling-calendar-lane"]: placed.group === "window" ? placed.lane : windowLanes + placed.lane
249
+ });
250
+ const nothingToShow = visibleEntries.length === 0 && !Object.values(dayEntries ?? {}).some((list) => list.length > 0) && !Object.values(dayInfo ?? {}).some((day) => day.count > 0);
251
+ const chipRows = Math.max(0, maxEntriesPerDay ?? DEFAULT_CHIP_ROWS, ...Object.values(dayEntries ?? {}).map((list) => list.length));
252
+ const renderDay = (date, index) => {
253
+ const info = dayInfo?.[date];
254
+ const all = picksGiven ? dayEntries[date] ?? EMPTY_ENTRIES : singles.get(date) ?? EMPTY_ENTRIES;
255
+ const overflowing = !picksGiven && maxEntriesPerDay !== void 0 && all.length > maxEntriesPerDay;
256
+ const collapsed = overflowing && (controlled || onShowMore !== void 0 || !expandedDates.has(date));
257
+ const shown = collapsed ? all.slice(0, Math.max(0, maxEntriesPerDay)) : all;
258
+ const hiddenCount = info ? info.overflowCount : picksGiven ? 0 : all.length - shown.length;
259
+ const onDay = picksGiven ? all : entriesOn(date);
260
+ const badge = renderDayBadge?.(date, onDay);
261
+ const hasBadge = badge != null && badge !== false;
262
+ 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)) });
263
+ const moreText = fillLabel(labels.moreEntries, { count: hiddenCount });
264
+ const moreName = fillLabel(labels.moreEntriesLabel, { count: hiddenCount, date: dateText(date, "day") });
265
+ return /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__day", "data-outside": view === "month" && !monthDays[index].inMonth || void 0, children: [
266
+ hasBadge || info ? /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__dayhead", children: [
267
+ hasBadge && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__badge", children: badge }),
268
+ info && /* @__PURE__ */ jsxs("span", { className: "uix-scheduling-calendar__count", children: [
269
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: info.count }),
270
+ /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: info.label ?? fillLabel(labels.dayCount, { count: info.count }) })
271
+ ] }),
272
+ info?.markers && info.markers.length > 0 && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__markers", children: info.markers.map((marker) => /* @__PURE__ */ jsx(Marker, { marker }, marker.id)) }),
273
+ dateButton
274
+ ] }) : dateButton,
275
+ /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__entries", children: [
276
+ shown.map((entry) => /* @__PURE__ */ jsx("button", { className: "uix-scheduling-calendar__entry", ...entryProps(entry), children: entryContent(entry) }, entry.id)),
277
+ 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: [
278
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: moreText }),
279
+ /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: moreName })
280
+ ] })) : 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 })
281
+ ] })
282
+ ] }, date);
283
+ };
284
+ const renderWeek = (row) => {
285
+ const rowDays = days.slice(row * 7, row * 7 + 7);
286
+ const windowLanes = lanesUsed(row, "window");
287
+ const spanLanes = lanesUsed(row, "item");
288
+ const hidden = layout.hiddenByRow?.[row]?.length ?? 0;
289
+ const firstHiddenDay = layout.firstHiddenDayByRow?.[row] ?? rowDays[0];
290
+ const moreText = fillLabel(labels.moreSpans, { count: hidden });
291
+ const moreName = fillLabel(labels.moreSpansLabel, { count: hidden, date: dateText(firstHiddenDay, "day") });
292
+ const style = { ["--uix-scheduling-calendar-lanes"]: Math.max(windowLanes + spanLanes, hidden > 0 ? 1 : 0) };
293
+ return /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__week", style, children: [
294
+ rowDays.map((date, column) => renderDay(date, row * 7 + column)),
295
+ (placedByRow.get(row) ?? []).map((placed) => {
296
+ const continues = { "data-continues-before": placed.continuesBefore || void 0, "data-continues-after": placed.continuesAfter || void 0 };
297
+ if (placed.group === "window") {
298
+ const overlay = overlayById.get(placed.id);
299
+ if (!overlay) return null;
300
+ 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: [
301
+ overlay.kindLabel && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__window-kind", children: overlay.kindLabel }),
302
+ /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__window-name", children: overlay.label }),
303
+ overlay.scopeLabel && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__window-scope", children: overlay.scopeLabel })
304
+ ] }) }, `window-${placed.id}`);
305
+ }
306
+ const entry = entryById.get(placed.id);
307
+ if (!entry) return null;
308
+ 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}`);
309
+ }),
310
+ 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: [
311
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: moreText }),
312
+ /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: moreName })
313
+ ] }))
314
+ ] }, rowDays[0]);
315
+ };
316
+ const gridStyle = fixed ? {
317
+ ["--uix-scheduling-calendar-chips"]: chipRows,
318
+ ["--uix-scheduling-calendar-lane-cap"]: Math.max(
319
+ Math.max(Number.isFinite(windowCap) ? windowCap : 0, lanesUsedAnywhere("window")) + Math.max(Number.isFinite(spanCap) ? spanCap : 0, lanesUsedAnywhere("item")),
320
+ Object.keys(layout.hiddenByRow ?? {}).length > 0 ? 1 : 0
321
+ )
322
+ } : void 0;
131
323
  return /* @__PURE__ */ jsxs("section", { className: cx("uix-scheduling-calendar", className), "aria-label": fillLabel(labels.region, { timeZone }), children: [
132
- /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__header", children: [
324
+ showHeader && /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__header", children: [
133
325
  /* @__PURE__ */ jsxs("div", { children: [
134
- /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: () => movePeriod(-1), disabled: !onAnchorDateChange, children: labels.previous }),
135
- /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: () => movePeriod(1), disabled: !onAnchorDateChange, children: labels.next })
326
+ /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: () => movePeriod(-1), disabled: !onAnchorDateChange, children: periodLabel("previous") }),
327
+ /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: () => movePeriod(1), disabled: !onAnchorDateChange, children: periodLabel("next") })
136
328
  ] }),
137
329
  /* @__PURE__ */ jsx("strong", { children: view === "month" ? dateText(startOfMonth(anchorDate), "month") : `${dateText(weekStart, "day")} \u2013 ${dateText(weekDays[weekDays.length - 1], "day")}` }),
138
330
  /* @__PURE__ */ jsx("div", { className: "uix-segmented", role: "group", "aria-label": labels.viewGroup, children: ["month", "week", "agenda"].map((option) => /* @__PURE__ */ jsx("button", { type: "button", className: "uix-segmented__option", "aria-pressed": view === option, "data-selected": view === option || void 0, onClick: () => setView(option), children: viewLabel[option] }, option)) })
139
331
  ] }),
332
+ notice != null && notice !== false && /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__notice", children: notice }),
140
333
  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: [
141
334
  /* @__PURE__ */ jsx("p", { children: error }),
142
335
  onRetry && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: labels.retry })
143
336
  ] }) : 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) => {
144
- const state = entry.state ?? "scheduled";
145
- return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => onSelectEntry?.(entry), children: [
337
+ const markers = markersOf(entry);
338
+ 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: [
146
339
  /* @__PURE__ */ jsxs("span", { children: [
147
340
  /* @__PURE__ */ jsx("strong", { children: renderEntry?.(entry) ?? entry.title }),
148
341
  /* @__PURE__ */ jsxs("span", { children: [
@@ -152,40 +345,24 @@ function SchedulingCalendar({
152
345
  ] }),
153
346
  entry.meta && /* @__PURE__ */ jsx("span", { children: entry.meta })
154
347
  ] }),
155
- /* @__PURE__ */ jsx(StatusPill, { tone: stateTone(state), children: stateLabel[state] })
348
+ markers.length > 0 && /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__markers", children: markers.map((marker) => /* @__PURE__ */ jsx(Marker, { marker }, marker.id)) }),
349
+ entry.state ? /* @__PURE__ */ jsx(StatusPill, { tone: stateTone(entry.state), children: stateLabel[entry.state] }) : /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__status", children: stateText(entry) })
156
350
  ] }) }, entry.id);
157
- }) }) }) : /* @__PURE__ */ jsxs("div", { ref: gridRef, className: cx("uix-scheduling-calendar__grid", view === "week" && "uix-scheduling-calendar__grid--week"), role: "group", "aria-label": fillLabel(labels.grid, { view: viewLabel[view] }), children: [
351
+ }) }) }) : /* @__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: [
158
352
  days.slice(0, 7).map((date) => /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__weekday", "aria-hidden": "true", children: dateText(date, "weekday") }, `weekday-${date}`)),
159
- days.map((date, index) => {
160
- const dayEntries = entriesFor(date);
161
- const dayOverlays = overlaysFor(date);
162
- const overflowing = maxEntriesPerDay !== void 0 && dayEntries.length > maxEntriesPerDay;
163
- const collapsed = overflowing && (onShowMore !== void 0 || !expandedDates.has(date));
164
- const shownEntries = collapsed ? dayEntries.slice(0, Math.max(0, maxEntriesPerDay)) : dayEntries;
165
- const hiddenCount = dayEntries.length - shownEntries.length;
166
- const badge = renderDayBadge?.(date, dayEntries);
167
- 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), "aria-label": dateText(date, "day"), children: Number(date.slice(-2)) });
168
- return /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__day", "data-outside": view === "month" && !monthDays[index].inMonth || void 0, children: [
169
- badge == null || badge === false ? dateButton : /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__dayhead", children: [
170
- /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__badge", children: badge }),
171
- dateButton
172
- ] }),
173
- dayOverlays.map((overlay) => /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__overlay", "data-kind": overlay.kind, children: overlay.label }, overlay.id)),
174
- /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__entries", children: [
175
- shownEntries.map((entry) => {
176
- const span = entrySpans.get(entry.id);
177
- const state = entry.state ?? "scheduled";
178
- return /* @__PURE__ */ jsxs("button", { type: "button", className: "uix-scheduling-calendar__entry", "data-state": state, "data-range-start": date === span.start || void 0, "data-range-end": date === span.end || void 0, onClick: () => onSelectEntry?.(entry), "aria-label": fillLabel(labels.entry, { title: entry.title, state: stateLabel[state], start: formatInstant(entry.start), end: formatInstant(entry.end) }), children: [
179
- /* @__PURE__ */ jsx("span", { children: renderEntry?.(entry) ?? entry.title }),
180
- /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: stateLabel[state] })
181
- ] }, entry.id);
182
- }),
183
- overflowing && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-scheduling-calendar__more", "aria-expanded": onShowMore ? void 0 : !collapsed, "aria-label": collapsed ? fillLabel(labels.moreEntriesLabel, { count: hiddenCount, date: dateText(date, "day") }) : void 0, onClick: () => toggleMore(date, dayEntries), children: collapsed ? fillLabel(labels.moreEntries, { count: hiddenCount }) : labels.fewerEntries })
184
- ] })
185
- ] }, date);
186
- })
353
+ Array.from({ length: days.length / 7 }, (_, row) => renderWeek(row)),
354
+ emptyNote != null && emptyNote !== false && nothingToShow && /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__empty", children: emptyNote })
187
355
  ] }),
188
- /* @__PURE__ */ jsx("div", { className: "uix-scheduling-calendar__legend", "aria-label": labels.legend, children: ["scheduled", "conflicted", "in-progress", "blackout-violation"].map((state) => /* @__PURE__ */ jsx("span", { "data-state": state, children: stateLabel[state] }, state)) })
356
+ legend ? (legend.length > 0 || legendCaption != null && legendCaption !== false) && /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__legend", role: "group", "aria-label": labels.legend, children: [
357
+ legend.length > 0 && /* @__PURE__ */ jsx("ul", { className: "uix-scheduling-calendar__legend-list", children: legend.map((item) => /* @__PURE__ */ jsxs("li", { "data-legend-id": item.id, children: [
358
+ /* @__PURE__ */ jsx("span", { className: "uix-scheduling-calendar__swatch", "aria-hidden": "true", "data-band": item.swatch.band, "data-status": item.swatch.status, "data-pattern": item.swatch.pattern, children: item.swatch.icon }),
359
+ item.label
360
+ ] }, item.id)) }),
361
+ legendCaption != null && legendCaption !== false && /* @__PURE__ */ jsx("p", { className: "uix-scheduling-calendar__legend-caption", children: legendCaption })
362
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "uix-scheduling-calendar__legend", role: "group", "aria-label": labels.legend, children: [
363
+ ["scheduled", "conflicted", "in-progress", "blackout-violation"].map((state) => /* @__PURE__ */ jsx("span", { "data-state": state, children: stateLabel[state] }, state)),
364
+ legendCaption != null && legendCaption !== false && /* @__PURE__ */ jsx("p", { className: "uix-scheduling-calendar__legend-caption", children: legendCaption })
365
+ ] })
189
366
  ] });
190
367
  }
191
368
  export {
@@ -3,6 +3,7 @@ import { ReactNode } from 'react';
3
3
  import { TimelineRange, TimelineScale } from '../scheduling-timeline-model.js';
4
4
  import { SchedulingEntryState } from './SchedulingCalendar.js';
5
5
  import '../calendar-model.js';
6
+ import '../scheduling-calendar-model.js';
6
7
 
7
8
  interface SchedulingTimelineLane {
8
9
  id: string;
@@ -6,6 +6,14 @@ interface SegmentedProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'
6
6
  value?: string;
7
7
  /** Fires with the option's value when a different option is chosen. */
8
8
  onChange?: (value: string) => void;
9
+ /**
10
+ * How the choice is exposed to assistive technology (HAR-1604).
11
+ * `'toggle'` (default): `role="group"` of buttons with `aria-pressed`, for a view or filter
12
+ * toggle bar. `'radio'`: `role="radiogroup"` of `role="radio"` with `aria-checked`, for a
13
+ * setting with exactly one value (theme, density, language). Name the group with
14
+ * `aria-label` or `aria-labelledby` in either mode. The keyboard model is the same in both.
15
+ */
16
+ selection?: 'toggle' | 'radio';
9
17
  children?: ReactNode;
10
18
  }
11
19
  /**
@@ -13,16 +21,22 @@ interface SegmentedProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'
13
21
  * (density, audience, view mode …) that reads as one pill with a lifted active
14
22
  * segment. Selection state is shared with `<SegmentedOption>` via context, so
15
23
  * the caller only wires `value` + `onChange` once.
24
+ *
25
+ * Keyboard (HAR-1604): the group is ONE tab stop, on the selected option (the first enabled
26
+ * one when nothing is selected). Arrow Right/Down and Left/Up move to the next / previous
27
+ * enabled option and select it, wrapping at the ends; Home and End jump; Left/Right swap in
28
+ * a right-to-left group. Before 2.32.0 every option was its own tab stop.
16
29
  */
17
- declare function Segmented({ value, onChange, children, className, ...props }: SegmentedProps): react.JSX.Element;
30
+ declare function Segmented({ value, onChange, selection, children, className, onKeyDown, ...props }: SegmentedProps): react.JSX.Element;
18
31
  interface SegmentedOptionProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'value' | 'onChange'> {
19
32
  value: string;
20
33
  children?: ReactNode;
21
34
  }
22
35
  /**
23
36
  * One option in a `<Segmented>`. Active state is derived from the parent's
24
- * `value`; the pressed segment lifts to the surface via
25
- * `.uix-segmented__option[aria-pressed="true"]`.
37
+ * `value`; the selected segment lifts to the surface via
38
+ * `.uix-segmented__option[aria-pressed="true"]` (or `[aria-checked="true"]` in a
39
+ * `selection="radio"` group).
26
40
  */
27
41
  declare function SegmentedOption({ value, children, className, onClick, ...props }: SegmentedOptionProps): react.JSX.Element;
28
42
 
@@ -1,11 +1,61 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
- import { createContext, useContext, useMemo } from "react";
3
+ import { createContext, useContext, useEffect, useMemo, useRef } from "react";
4
4
  import { cx } from "../cx.js";
5
5
  const SegmentedCtx = createContext({});
6
- function Segmented({ value, onChange, children, className, ...props }) {
7
- const contextValue = useMemo(() => ({ value, onChange }), [value, onChange]);
8
- return /* @__PURE__ */ jsx(SegmentedCtx.Provider, { value: contextValue, children: /* @__PURE__ */ jsx("div", { role: "group", className: cx("uix-segmented", className), ...props, children }) });
6
+ const OPTION = ".uix-segmented__option";
7
+ function Segmented({ value, onChange, selection = "toggle", children, className, onKeyDown, ...props }) {
8
+ const ref = useRef(null);
9
+ const contextValue = useMemo(() => ({ value, onChange, selection }), [value, onChange, selection]);
10
+ useEffect(() => {
11
+ const group = ref.current;
12
+ if (!group) return;
13
+ const all = Array.from(group.querySelectorAll(OPTION));
14
+ const enabled = all.filter((o) => !o.disabled);
15
+ const current = enabled.find((o) => o.getAttribute("aria-pressed") === "true" || o.getAttribute("aria-checked") === "true") ?? enabled[0];
16
+ for (const o of all) o.tabIndex = o === current ? 0 : -1;
17
+ });
18
+ const handleKeyDown = (event) => {
19
+ onKeyDown?.(event);
20
+ if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey) return;
21
+ const group = ref.current;
22
+ const from = event.target.closest(OPTION);
23
+ if (!group || !from || !group.contains(from)) return;
24
+ const options = Array.from(group.querySelectorAll(`${OPTION}:not(:disabled)`));
25
+ const index = options.indexOf(from);
26
+ if (index < 0) return;
27
+ const rtl = getComputedStyle(group).direction === "rtl";
28
+ let next;
29
+ switch (event.key) {
30
+ case "ArrowDown":
31
+ next = index + 1;
32
+ break;
33
+ case "ArrowUp":
34
+ next = index - 1;
35
+ break;
36
+ case "ArrowRight":
37
+ next = index + (rtl ? -1 : 1);
38
+ break;
39
+ case "ArrowLeft":
40
+ next = index + (rtl ? 1 : -1);
41
+ break;
42
+ case "Home":
43
+ next = 0;
44
+ break;
45
+ case "End":
46
+ next = options.length - 1;
47
+ break;
48
+ default:
49
+ return;
50
+ }
51
+ event.preventDefault();
52
+ event.stopPropagation();
53
+ const target = options[(next + options.length) % options.length];
54
+ if (!target || target === from) return;
55
+ target.focus();
56
+ target.click();
57
+ };
58
+ return /* @__PURE__ */ jsx(SegmentedCtx.Provider, { value: contextValue, children: /* @__PURE__ */ jsx("div", { ref, role: selection === "radio" ? "radiogroup" : "group", className: cx("uix-segmented", className), onKeyDown: handleKeyDown, ...props, children }) });
9
59
  }
10
60
  function SegmentedOption({
11
61
  value,
@@ -16,12 +66,16 @@ function SegmentedOption({
16
66
  }) {
17
67
  const ctx = useContext(SegmentedCtx);
18
68
  const selected = ctx.value === value;
69
+ const radio = ctx.selection === "radio";
19
70
  return /* @__PURE__ */ jsx(
20
71
  "button",
21
72
  {
22
73
  type: "button",
74
+ role: radio ? "radio" : void 0,
23
75
  className: cx("uix-segmented__option", className),
24
- "aria-pressed": selected,
76
+ "aria-pressed": radio ? void 0 : selected,
77
+ "aria-checked": radio ? selected : void 0,
78
+ tabIndex: ctx.selection ? selected ? 0 : -1 : void 0,
25
79
  onClick: (e) => {
26
80
  ctx.onChange?.(value);
27
81
  onClick?.(e);
@@ -1,9 +1,73 @@
1
1
  import * as react from 'react';
2
- import { SelectHTMLAttributes } from 'react';
2
+ import { SelectHTMLAttributes, ReactNode } from 'react';
3
+ import { Placement } from '../overlay-position.js';
4
+ import { SelectOption, SelectGroup } from '../select-model.js';
3
5
 
6
+ /** Chrome strings of `Select`; `{count}` is replaced where noted. */
7
+ interface SelectLabels {
8
+ /** Accessible name of the filter field when the Select has no label of its own. */
9
+ search: string;
10
+ /** Placeholder of the filter field. */
11
+ searchPlaceholder: string;
12
+ loading: string;
13
+ /** No options at all. */
14
+ empty: string;
15
+ /** A filter matched nothing. */
16
+ noMatches: string;
17
+ /** Async `loadOptions` failed. */
18
+ error: string;
19
+ retry: string;
20
+ /** Multi-select: clears every value. */
21
+ clear: string;
22
+ /** Multi-select on a phone: closes the sheet. */
23
+ done: string;
24
+ /** Multi-select trigger, spoken after the "+N". `{count}` = how many are selected. */
25
+ selectedCount: string;
26
+ /** Announced after filtering. `{count}`. */
27
+ results: string;
28
+ }
4
29
  interface SelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement>, 'size'> {
5
30
  size?: 'sm' | 'md';
31
+ /** The options as data, instead of `<option>` / `<optgroup>` children. */
32
+ options?: readonly (SelectOption | SelectGroup)[];
33
+ /**
34
+ * Called with the new value: a string, or every selected value with `multiple`. `option` is the
35
+ * option that was chosen or toggled. `onChange(e)` still fires as on a native select.
36
+ */
37
+ onValueChange?: (value: string | string[], option?: SelectOption) => void;
38
+ /** Shown while nothing is selected. Also makes "nothing" the starting value (and `required` checks it). */
39
+ placeholder?: string;
40
+ /** Marks the control invalid (`aria-invalid`), e.g. from your own validation. */
41
+ invalid?: boolean;
42
+ /** Shows and submits the value, but does not open. */
43
+ readOnly?: boolean;
44
+ /** A filter field at the top of the list. `'auto'` adds it for more than 12 options. Default `false`. */
45
+ searchable?: boolean | 'auto';
46
+ /**
47
+ * Load the options when the list opens (and, when `searchable`, as the filter changes). The
48
+ * previous request's `signal` is aborted when a newer one starts or the list closes. Pass the
49
+ * selected option in `options` so its label shows before the first load.
50
+ */
51
+ loadOptions?: (query: string, signal: AbortSignal) => Promise<readonly (SelectOption | SelectGroup)[]>;
52
+ /** Draw an option's content (the check mark for the selected state is drawn for you). */
53
+ renderOption?: (option: SelectOption, state: {
54
+ selected: boolean;
55
+ active: boolean;
56
+ }) => ReactNode;
57
+ /** Draw the trigger's content for the selected option(s). */
58
+ renderValue?: (selected: readonly SelectOption[]) => ReactNode;
59
+ /** Preferred side of the list. Default `'bottom-start'`; it flips when there is no room. */
60
+ placement?: Placement;
61
+ labels?: Partial<SelectLabels>;
6
62
  }
63
+ /**
64
+ * A select that draws its own list (HAR-1572): the WAI-ARIA APG select-only combobox — focus
65
+ * stays on the trigger, `aria-activedescendant` marks the active option — in a top-layer popover
66
+ * placed with `useAnchoredPosition`, or a bottom sheet on a phone. A visually hidden native
67
+ * `<select>` carries `name`, `required`, `form`, `multiple` and the value, so `<option>` children,
68
+ * `onChange(e)` with `e.target.value`, FormData, `form.reset()`, `ref` and react-hook-form keep
69
+ * working; `ref` points at that select.
70
+ */
7
71
  declare const Select: react.ForwardRefExoticComponent<SelectProps & react.RefAttributes<HTMLSelectElement>>;
8
72
 
9
- export { Select, type SelectProps };
73
+ export { Select, SelectGroup, type SelectLabels, SelectOption, type SelectProps };