@tensor_1/react 2.30.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.
- package/README.md +430 -0
- package/dist/attachment-labels.d.ts +16 -0
- package/dist/attachment-labels.js +9 -0
- package/dist/calendar-model.d.ts +141 -1
- package/dist/calendar-model.js +200 -11
- package/dist/chart-preset.cjs +127 -7
- package/dist/chart-preset.d.cts +2 -2
- package/dist/chart-preset.d.ts +1 -0
- package/dist/chart-preset.js +6 -1
- package/dist/chart-theme-Cl0IrkKQ.d.cts +112 -0
- package/dist/chart-theme.d.ts +51 -0
- package/dist/chart-theme.js +99 -0
- package/dist/chart.cjs +127 -7
- package/dist/chart.d.cts +2 -2
- package/dist/chart.d.ts +1 -0
- package/dist/chart.js +6 -2
- package/dist/components/Alert.d.ts +17 -1
- package/dist/components/Alert.js +9 -4
- package/dist/components/AlertDismiss.d.ts +14 -0
- package/dist/components/AlertDismiss.js +11 -0
- package/dist/components/Attachment.d.ts +58 -0
- package/dist/components/Attachment.js +69 -0
- package/dist/components/AttachmentParts.d.ts +24 -0
- package/dist/components/AttachmentParts.js +20 -0
- package/dist/components/Chart.d.ts +1 -0
- package/dist/components/Chart.js +6 -1
- package/dist/components/ChartCore.d.ts +6 -1
- package/dist/components/ChartCore.js +23 -5
- package/dist/components/Chip.d.ts +17 -2
- package/dist/components/Chip.js +35 -11
- package/dist/components/ChipRemove.d.ts +17 -0
- package/dist/components/ChipRemove.js +13 -0
- package/dist/components/CollapsibleParts.d.ts +29 -0
- package/dist/components/CollapsibleParts.js +20 -0
- package/dist/components/CollapsibleSection.d.ts +30 -3
- package/dist/components/CollapsibleSection.js +7 -11
- package/dist/components/CollapsibleSectionState.d.ts +1 -1
- package/dist/components/CollapsibleSectionState.js +54 -27
- package/dist/components/DashboardGrid.d.ts +60 -0
- package/dist/components/DashboardGrid.js +40 -0
- package/dist/components/DateRangePicker.d.ts +7 -1
- package/dist/components/DateRangePicker.js +5 -1
- package/dist/components/DescriptionList.d.ts +12 -1
- package/dist/components/DescriptionList.js +5 -5
- package/dist/components/Dialogs.d.ts +24 -3
- package/dist/components/Dialogs.js +74 -16
- package/dist/components/EntityPicker.d.ts +56 -0
- package/dist/components/EntityPicker.js +168 -0
- package/dist/components/FileIcon.d.ts +9 -0
- package/dist/components/FileIcon.js +17 -0
- package/dist/components/FileUpload.d.ts +95 -0
- package/dist/components/FileUpload.js +145 -0
- package/dist/components/FilterEditor.d.ts +55 -0
- package/dist/components/FilterEditor.js +186 -0
- package/dist/components/Heartbeat.d.ts +31 -0
- package/dist/components/Heartbeat.js +28 -0
- package/dist/components/Lightbox.d.ts +45 -0
- package/dist/components/Lightbox.js +102 -0
- package/dist/components/Menu.d.ts +53 -5
- package/dist/components/Menu.js +104 -4
- package/dist/components/Meter.d.ts +7 -3
- package/dist/components/Meter.js +2 -1
- package/dist/components/Popover.d.ts +21 -2
- package/dist/components/Popover.js +94 -17
- package/dist/components/Prose.d.ts +6 -2
- package/dist/components/Prose.js +1 -1
- package/dist/components/RuleBuilder.d.ts +52 -4
- package/dist/components/RuleBuilder.js +109 -24
- package/dist/components/SchedulingCalendar.d.ts +142 -11
- package/dist/components/SchedulingCalendar.js +234 -57
- package/dist/components/SchedulingTimeline.d.ts +1 -0
- package/dist/components/SearchSuggest.d.ts +6 -2
- package/dist/components/SearchSuggest.js +4 -0
- package/dist/components/Segmented.d.ts +17 -3
- package/dist/components/Segmented.js +59 -5
- package/dist/components/Select.d.ts +66 -2
- package/dist/components/Select.js +800 -10
- package/dist/components/Spinner.d.ts +2 -1
- package/dist/components/Spinner.js +1 -1
- package/dist/components/StatusPill.d.ts +8 -2
- package/dist/components/StatusPill.js +2 -2
- package/dist/components/Steps.d.ts +28 -4
- package/dist/components/Steps.js +46 -16
- package/dist/components/TableControls.js +4 -3
- package/dist/components/Tabs.d.ts +7 -2
- package/dist/components/Tabs.js +14 -1
- package/dist/components/TextDiff.d.ts +49 -0
- package/dist/components/TextDiff.js +124 -0
- package/dist/{emoji-image-6mPZ15Zy.d.cts → emoji-image-DjCKpdwE.d.cts} +1 -1
- package/dist/emoji.cjs +169 -27
- package/dist/emoji.d.cts +3 -3
- package/dist/file-model.d.ts +56 -0
- package/dist/file-model.js +52 -0
- package/dist/filter-model.d.ts +112 -0
- package/dist/filter-model.js +103 -0
- package/dist/hooks/useAnchoredPosition.d.ts +24 -1
- package/dist/hooks/useAnchoredPosition.js +58 -8
- package/dist/hooks/useVirtualRows.d.ts +17 -2
- package/dist/hooks/useVirtualRows.js +6 -5
- package/dist/icons/Icon.d.ts +14 -14
- package/dist/index.cjs +4938 -1631
- package/dist/index.d.cts +1622 -178
- package/dist/index.d.ts +20 -6
- package/dist/index.js +88 -4
- package/dist/labels-context.d.ts +28 -0
- package/dist/{overlay-position-Cjv5E7mn.d.cts → overlay-position-2Afke51l.d.cts} +20 -0
- package/dist/overlay-position.d.ts +20 -0
- package/dist/overlay-position.js +17 -2
- package/dist/rich-text.cjs +169 -27
- package/dist/rich-text.d.cts +3 -3
- package/dist/rule-builder-model.d.ts +34 -2
- package/dist/rule-builder-model.js +48 -12
- package/dist/scheduling-calendar-model.d.ts +66 -0
- package/dist/scheduling-calendar-model.js +82 -0
- package/dist/select-model.d.ts +80 -0
- package/dist/select-model.js +109 -0
- package/dist/table-engine.d.ts +7 -0
- package/dist/table-engine.js +2 -1
- package/dist/text-diff-model.d.ts +71 -0
- package/dist/text-diff-model.js +261 -0
- package/package.json +1 -1
- 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,
|
|
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
|
-
|
|
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
|
-
...
|
|
66
|
-
...labelOverrides,
|
|
67
|
-
|
|
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) ??
|
|
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
|
|
111
|
-
|
|
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,
|
|
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:
|
|
135
|
-
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: () => movePeriod(1), disabled: !onAnchorDateChange, children:
|
|
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
|
|
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(
|
|
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.
|
|
160
|
-
|
|
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__ */
|
|
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;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { ReactNode, HTMLAttributes, Ref } from 'react';
|
|
3
3
|
|
|
4
4
|
/** One result row. */
|
|
5
5
|
interface SearchSuggestOption {
|
|
@@ -74,6 +74,10 @@ interface SearchSuggestProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onCha
|
|
|
74
74
|
autoFocus?: boolean;
|
|
75
75
|
/** Form field name of the input. */
|
|
76
76
|
name?: string;
|
|
77
|
+
/** Id of the input itself (for a `<label for>` / `Field htmlFor`); `id` stays on the wrapper. */
|
|
78
|
+
inputId?: string;
|
|
79
|
+
/** `aria-describedby` of the input (hint and error text from a `Field`). */
|
|
80
|
+
inputDescribedBy?: string;
|
|
77
81
|
}
|
|
78
82
|
/**
|
|
79
83
|
* A search field with a suggestion list underneath: the "search and jump" pattern of Windows
|
|
@@ -82,6 +86,6 @@ interface SearchSuggestProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onCha
|
|
|
82
86
|
* (`aria-activedescendant`), Enter opens it (the first row when none is active), Escape closes the
|
|
83
87
|
* list and a second Escape clears the text.
|
|
84
88
|
*/
|
|
85
|
-
declare function SearchSuggest({ value, onValueChange, options, onSelect, label, placeholder, highlight, heading, headingAction, footer, loading, loadingLabel, empty, error, shortcutHint, clearLabel: clearLabelProp, status, size, open: openProp, strategy, onOpenChange, inputRef, autoFocus, name, className, onBlur, ...props }: SearchSuggestProps): react.JSX.Element;
|
|
89
|
+
declare function SearchSuggest({ value, onValueChange, options, onSelect, label, placeholder, highlight, heading, headingAction, footer, loading, loadingLabel, empty, error, shortcutHint, clearLabel: clearLabelProp, status, size, open: openProp, strategy, onOpenChange, inputRef, autoFocus, name, inputId, inputDescribedBy, className, onBlur, ...props }: SearchSuggestProps): react.JSX.Element;
|
|
86
90
|
|
|
87
91
|
export { SearchSuggest, type SearchSuggestAction, type SearchSuggestOption, type SearchSuggestProps };
|
|
@@ -43,6 +43,8 @@ function SearchSuggest({
|
|
|
43
43
|
inputRef,
|
|
44
44
|
autoFocus,
|
|
45
45
|
name,
|
|
46
|
+
inputId,
|
|
47
|
+
inputDescribedBy,
|
|
46
48
|
className,
|
|
47
49
|
onBlur,
|
|
48
50
|
...props
|
|
@@ -185,6 +187,8 @@ function SearchSuggest({
|
|
|
185
187
|
"input",
|
|
186
188
|
{
|
|
187
189
|
ref: fieldRef,
|
|
190
|
+
id: inputId,
|
|
191
|
+
"aria-describedby": inputDescribedBy,
|
|
188
192
|
className: "uix-input uix-search-suggest__input",
|
|
189
193
|
type: "text",
|
|
190
194
|
role: "combobox",
|
|
@@ -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
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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);
|