@tensor_1/react 2.47.0 → 2.48.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 (183) hide show
  1. package/README.md +470 -1
  2. package/dist/{SchedulingCalendar-B6TakO2h.d.ts → SchedulingCalendar-WWyFL3Eo.d.ts} +51 -2
  3. package/dist/announce.d.ts +10 -0
  4. package/dist/announce.js +30 -0
  5. package/dist/chart-analytic.cjs +33 -25
  6. package/dist/chart-preset.cjs +23 -15
  7. package/dist/chart.cjs +23 -15
  8. package/dist/combobox-model.d.ts +10 -1
  9. package/dist/combobox-model.js +15 -0
  10. package/dist/command-palette-model.d.ts +44 -0
  11. package/dist/command-palette-model.js +36 -0
  12. package/dist/components/AccountMenu.d.ts +44 -0
  13. package/dist/components/AccountMenu.js +43 -0
  14. package/dist/components/AppShell.d.ts +45 -2
  15. package/dist/components/AppShell.js +116 -5
  16. package/dist/components/AuditLog.d.ts +128 -0
  17. package/dist/components/AuditLog.js +196 -0
  18. package/dist/components/BrandProfiles.js +4 -3
  19. package/dist/components/Breadcrumbs.d.ts +9 -2
  20. package/dist/components/Breadcrumbs.js +3 -2
  21. package/dist/components/Button.d.ts +12 -5
  22. package/dist/components/Button.js +7 -3
  23. package/dist/components/Card.d.ts +18 -1
  24. package/dist/components/Card.js +5 -1
  25. package/dist/components/ChangeHighlight.d.ts +30 -0
  26. package/dist/components/ChangeHighlight.js +21 -0
  27. package/dist/components/ChartCore.js +2 -1
  28. package/dist/components/Checkbox.d.ts +27 -1
  29. package/dist/components/Checkbox.js +31 -2
  30. package/dist/components/CodeDisplay.d.ts +65 -0
  31. package/dist/components/CodeDisplay.js +124 -0
  32. package/dist/components/Combobox.d.ts +27 -6
  33. package/dist/components/Combobox.js +195 -58
  34. package/dist/components/CommandPalette.d.ts +36 -1
  35. package/dist/components/CommandPalette.js +254 -2
  36. package/dist/components/ContactCard.d.ts +66 -0
  37. package/dist/components/ContactCard.js +74 -0
  38. package/dist/components/Count.d.ts +24 -0
  39. package/dist/components/Count.js +32 -0
  40. package/dist/components/DataWorkspace.d.ts +167 -0
  41. package/dist/components/DataWorkspace.js +383 -0
  42. package/dist/components/DateFieldParts.d.ts +2 -1
  43. package/dist/components/DateFieldParts.js +7 -0
  44. package/dist/components/DatePicker.d.ts +9 -2
  45. package/dist/components/DatePicker.js +67 -28
  46. package/dist/components/DateRangePicker.d.ts +12 -2
  47. package/dist/components/DateRangePicker.js +13 -12
  48. package/dist/components/DetailPage.d.ts +7 -1
  49. package/dist/components/DetailPage.js +2 -1
  50. package/dist/components/Dialogs.d.ts +71 -10
  51. package/dist/components/Dialogs.js +130 -30
  52. package/dist/components/DiffViewer.d.ts +23 -3
  53. package/dist/components/DiffViewer.js +26 -12
  54. package/dist/components/DiscardPrompt.js +16 -0
  55. package/dist/components/DisplayPreferences.d.ts +83 -0
  56. package/dist/components/DisplayPreferences.js +185 -0
  57. package/dist/components/EditorialHome.d.ts +23 -3
  58. package/dist/components/EditorialHome.js +16 -11
  59. package/dist/components/EntityPicker.d.ts +20 -3
  60. package/dist/components/EntityPicker.js +32 -13
  61. package/dist/components/FilterEditor.d.ts +18 -4
  62. package/dist/components/FilterEditor.js +141 -19
  63. package/dist/components/InfoTip.js +11 -12
  64. package/dist/components/InlineEdit.d.ts +87 -20
  65. package/dist/components/InlineEdit.js +197 -54
  66. package/dist/components/Input.d.ts +12 -2
  67. package/dist/components/Input.js +5 -3
  68. package/dist/components/Kbd.d.ts +6 -1
  69. package/dist/components/Kbd.js +6 -6
  70. package/dist/components/Lightbox.d.ts +41 -5
  71. package/dist/components/Lightbox.js +95 -9
  72. package/dist/components/MatchReview.d.ts +40 -1
  73. package/dist/components/MatchReview.js +130 -4
  74. package/dist/components/MetricInput.js +3 -2
  75. package/dist/components/Modal.d.ts +2 -1
  76. package/dist/components/Modal.js +13 -4
  77. package/dist/components/NavFavourites.d.ts +5 -0
  78. package/dist/components/NavFavourites.js +7 -7
  79. package/dist/components/OtpInput.d.ts +33 -0
  80. package/dist/components/OtpInput.js +123 -0
  81. package/dist/components/PageHeader.d.ts +7 -1
  82. package/dist/components/PageHeader.js +2 -2
  83. package/dist/components/Pagination.js +6 -5
  84. package/dist/components/PasswordInput.d.ts +34 -0
  85. package/dist/components/PasswordInput.js +101 -0
  86. package/dist/components/Peek.js +3 -3
  87. package/dist/components/Popover.js +2 -0
  88. package/dist/components/PrintLayout.d.ts +63 -0
  89. package/dist/components/PrintLayout.js +114 -0
  90. package/dist/components/QrCode.d.ts +47 -0
  91. package/dist/components/QrCode.js +55 -0
  92. package/dist/components/QrScanner.d.ts +95 -0
  93. package/dist/components/QrScanner.js +266 -0
  94. package/dist/components/RelationshipGraph.d.ts +32 -1
  95. package/dist/components/RelationshipGraph.js +18 -5
  96. package/dist/components/RelationshipGraphLayered.d.ts +1 -1
  97. package/dist/components/RelationshipGraphLayered.js +190 -115
  98. package/dist/components/RelationshipGraphList.js +33 -6
  99. package/dist/components/RuleBuilder.js +39 -7
  100. package/dist/components/SchedulingAgenda.d.ts +3 -1
  101. package/dist/components/SchedulingCalendar.d.ts +3 -1
  102. package/dist/components/SchedulingCalendar.js +43 -4
  103. package/dist/components/SchedulingResourceDay.d.ts +57 -0
  104. package/dist/components/SchedulingResourceDay.js +287 -0
  105. package/dist/components/SchedulingTimeGrid.d.ts +3 -1
  106. package/dist/components/SchedulingTimeline.d.ts +3 -1
  107. package/dist/components/SearchSuggest.d.ts +23 -3
  108. package/dist/components/SearchSuggest.js +48 -17
  109. package/dist/components/Segmented.d.ts +39 -4
  110. package/dist/components/Segmented.js +31 -3
  111. package/dist/components/Select.d.ts +3 -2
  112. package/dist/components/Select.js +1 -1
  113. package/dist/components/ShortcutHint.d.ts +11 -10
  114. package/dist/components/ShortcutHint.js +44 -38
  115. package/dist/components/Sidebar.d.ts +36 -4
  116. package/dist/components/Sidebar.js +37 -12
  117. package/dist/components/SortableList.d.ts +45 -0
  118. package/dist/components/SortableList.js +134 -0
  119. package/dist/components/Stat.d.ts +28 -3
  120. package/dist/components/Stat.js +13 -3
  121. package/dist/components/StatusPill.d.ts +14 -3
  122. package/dist/components/StatusPill.js +11 -3
  123. package/dist/components/Steps.js +2 -1
  124. package/dist/components/Table.d.ts +9 -2
  125. package/dist/components/Table.js +4 -2
  126. package/dist/components/TableControls.js +18 -58
  127. package/dist/components/TableOverflowProbe.d.ts +12 -0
  128. package/dist/components/TableOverflowProbe.js +27 -0
  129. package/dist/components/Tabs.d.ts +2 -1
  130. package/dist/components/Tabs.js +1 -0
  131. package/dist/components/TagInput.d.ts +67 -0
  132. package/dist/components/TagInput.js +342 -0
  133. package/dist/components/Textarea.d.ts +5 -0
  134. package/dist/components/Textarea.js +2 -2
  135. package/dist/components/TimePicker.d.ts +80 -0
  136. package/dist/components/TimePicker.js +347 -0
  137. package/dist/components/Toast.js +3 -2
  138. package/dist/components/Tooltip.d.ts +8 -4
  139. package/dist/components/Tooltip.js +24 -21
  140. package/dist/components/Tree.d.ts +8 -0
  141. package/dist/components/Tree.js +11 -5
  142. package/dist/display-preferences-model.d.ts +53 -0
  143. package/dist/display-preferences-model.js +64 -0
  144. package/dist/emoji.cjs +28 -35
  145. package/dist/filter-model.d.ts +59 -3
  146. package/dist/filter-model.js +67 -2
  147. package/dist/hooks/useChangeHighlight.d.ts +33 -0
  148. package/dist/hooks/useChangeHighlight.js +44 -0
  149. package/dist/hooks/useDialog.d.ts +5 -0
  150. package/dist/hooks/useDialog.js +3 -2
  151. package/dist/hooks/useOptimisticValue.d.ts +51 -0
  152. package/dist/hooks/useOptimisticValue.js +61 -0
  153. package/dist/hooks/useReducedMotion.d.ts +4 -1
  154. package/dist/hooks/useReducedMotion.js +7 -1
  155. package/dist/hooks/useResolvedFilterLabels.d.ts +15 -0
  156. package/dist/hooks/useResolvedFilterLabels.js +34 -0
  157. package/dist/hooks/useVirtualRows.d.ts +21 -2
  158. package/dist/hooks/useVirtualRows.js +30 -3
  159. package/dist/icons/Icon.d.ts +1 -1
  160. package/dist/index.cjs +18644 -13210
  161. package/dist/index.d.cts +3003 -699
  162. package/dist/index.d.ts +42 -16
  163. package/dist/index.js +139 -7
  164. package/dist/labels-context.d.ts +90 -3
  165. package/dist/overlay-stack.d.ts +6 -0
  166. package/dist/overlay-stack.js +13 -0
  167. package/dist/qr-model.d.ts +65 -0
  168. package/dist/qr-model.js +323 -0
  169. package/dist/relationship-graph-model.d.ts +78 -3
  170. package/dist/relationship-graph-model.js +112 -5
  171. package/dist/reorder.d.ts +30 -0
  172. package/dist/reorder.js +74 -0
  173. package/dist/rich-text.cjs +4 -2
  174. package/dist/scheduling-resource-model.d.ts +76 -0
  175. package/dist/scheduling-resource-model.js +74 -0
  176. package/dist/search-suggest-model.js +3 -1
  177. package/dist/shell-context.d.ts +11 -0
  178. package/dist/shell-context.js +7 -0
  179. package/dist/time-field-model.d.ts +46 -0
  180. package/dist/time-field-model.js +152 -0
  181. package/dist/toast-store.d.ts +17 -1
  182. package/dist/toast-store.js +23 -7
  183. package/package.json +2 -1
@@ -0,0 +1,80 @@
1
+ import * as react from 'react';
2
+ import { AriaAttributes, CSSProperties } from 'react';
3
+ import { Placement } from '../overlay-position.js';
4
+
5
+ /** Every word `TimePicker` renders or announces. */
6
+ interface TimePickerLabels {
7
+ /** The list button's name while no time is set. */
8
+ open: string;
9
+ /** The list button's name with a time set; `{time}` is the time as the field shows it. */
10
+ openSelected: string;
11
+ /** The list of times (the popover's and the listbox's name). */
12
+ list: string;
13
+ /** Shown when the typed text is not a time; `{example}` is a time in the field's own shape. */
14
+ invalidTime: string;
15
+ /** Shown when the typed time is before `min`; `{min}`. */
16
+ tooEarly: string;
17
+ /** Shown when the typed time is after `max`; `{max}`. */
18
+ tooLate: string;
19
+ /** Shown when the typed time is outside an overnight range (`min` after `max`); `{min}`, `{max}`. */
20
+ outside: string;
21
+ }
22
+ declare const DEFAULT_TIME_PICKER_LABELS: TimePickerLabels;
23
+ /** Why typed text was refused: not a time, or a time before `min` / after `max`. */
24
+ type TimeInputProblem = 'format' | 'early' | 'late' | 'outside';
25
+ interface TimePickerProps {
26
+ /** The time as `HH:mm` (24-hour), or `null` for none. */
27
+ value: string | null;
28
+ onValueChange: (value: string | null) => void;
29
+ /** Minutes the arrow keys step the minutes by, and between the times in the list. Default 15. */
30
+ minuteStep?: number;
31
+ /**
32
+ * First and last time that can be chosen (`HH:mm`, inclusive). A `min` after `max` (22:00 to
33
+ * 06:00) is an overnight range: it wraps round midnight, in the keys, the list and the check.
34
+ */
35
+ min?: string;
36
+ max?: string;
37
+ /**
38
+ * The locale that writes the time: 14:30 in `de`, 2:30 PM in `en-US`. Default: `UixLabelsProvider`
39
+ * `dateFormat.locale`, then the viewer's.
40
+ */
41
+ locale?: string;
42
+ /** `true` writes 2:30 PM, `false` 14:30, whatever the locale. */
43
+ hour12?: boolean;
44
+ placeholder?: string;
45
+ size?: 'sm' | 'md';
46
+ disabled?: boolean;
47
+ readOnly?: boolean;
48
+ required?: boolean;
49
+ invalid?: boolean;
50
+ /** Submits the `HH:mm` value under this name (a hidden input), whatever the field shows. */
51
+ name?: string;
52
+ /** Lands on the text input, so a `<label htmlFor>` or `<Field>` names it. */
53
+ id?: string;
54
+ 'aria-label'?: string;
55
+ 'aria-labelledby'?: string;
56
+ 'aria-describedby'?: string;
57
+ 'aria-invalid'?: AriaAttributes['aria-invalid'];
58
+ 'aria-required'?: AriaAttributes['aria-required'];
59
+ /** Where the list opens. Default `'bottom-start'`; it flips and shifts to stay on screen. */
60
+ placement?: Placement;
61
+ /** Told when typed text is refused, and `null` once the field is fine again. */
62
+ onInputProblem?: (problem: TimeInputProblem | null) => void;
63
+ onOpenChange?: (open: boolean) => void;
64
+ /** Translatable words; English defaults in {@link DEFAULT_TIME_PICKER_LABELS}. */
65
+ labels?: Partial<TimePickerLabels>;
66
+ className?: string;
67
+ style?: CSSProperties;
68
+ }
69
+ /**
70
+ * A time of day as a form field (HAR-1835), beside `DatePicker`: a text input that shows the time
71
+ * in the locale's shape (14:30 in German, 2:30 PM in en-US) and reads a typed one (`1430`, `9`,
72
+ * `2:30 pm`) on Enter and when focus leaves it; a clock button opens the times at `minuteStep` in a
73
+ * list. ArrowUp / ArrowDown step the part the caret is in (the hour, or the minutes by
74
+ * `minuteStep`), PageUp / PageDown step the hour, Alt+ArrowDown opens the list. Text that is not a
75
+ * time, or a time outside `min` / `max`, marks the field invalid, says why and leaves `value` alone.
76
+ * The value is `HH:mm`, what `<input type="time">` holds.
77
+ */
78
+ declare const TimePicker: react.ForwardRefExoticComponent<TimePickerProps & react.RefAttributes<HTMLInputElement>>;
79
+
80
+ export { DEFAULT_TIME_PICKER_LABELS, type TimeInputProblem, TimePicker, type TimePickerLabels, type TimePickerProps };
@@ -0,0 +1,347 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { forwardRef, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
4
+ import { cx } from "../cx.js";
5
+ import { fillLabel } from "../fill-label.js";
6
+ import { ClockIcon } from "../icons/components.js";
7
+ import { useUixLabels } from "../labels-context.js";
8
+ import {
9
+ formatTimeKey,
10
+ isOvernightRange,
11
+ isTimeOutOfRange,
12
+ isValidTimeKey,
13
+ parseTimeInput,
14
+ stepTime,
15
+ timePartEnds,
16
+ timeSlots,
17
+ timeToMinutes
18
+ } from "../time-field-model.js";
19
+ import { useFieldPopover } from "./DateFieldParts.js";
20
+ import { Popover } from "./Popover.js";
21
+ const useIsomorphicLayoutEffect = typeof document !== "undefined" ? useLayoutEffect : useEffect;
22
+ const DEFAULT_TIME_PICKER_LABELS = {
23
+ open: "Choose time",
24
+ openSelected: "Change time, {time}",
25
+ list: "Times",
26
+ invalidTime: "Enter a time like {example}.",
27
+ tooEarly: "Choose {min} or later.",
28
+ tooLate: "Choose {max} or earlier.",
29
+ outside: "Choose a time from {min} to {max}."
30
+ };
31
+ const TimePicker = forwardRef(function TimePicker2({
32
+ value,
33
+ onValueChange,
34
+ minuteStep = 15,
35
+ min,
36
+ max,
37
+ locale: localeProp,
38
+ hour12,
39
+ placeholder,
40
+ size = "md",
41
+ disabled,
42
+ readOnly,
43
+ required,
44
+ invalid,
45
+ name,
46
+ id: idProp,
47
+ "aria-label": ariaLabel,
48
+ "aria-labelledby": ariaLabelledBy,
49
+ "aria-describedby": ariaDescribedBy,
50
+ "aria-invalid": ariaInvalid,
51
+ "aria-required": ariaRequired,
52
+ placement = "bottom-start",
53
+ onInputProblem,
54
+ onOpenChange,
55
+ labels: labelOverrides,
56
+ className,
57
+ style
58
+ }, forwardedRef) {
59
+ const uid = useId();
60
+ const id = idProp ?? `${uid}-input`;
61
+ const popoverId = `${uid}-times`;
62
+ const listId = `${uid}-list`;
63
+ const errorId = `${uid}-error`;
64
+ const uixLabels = useUixLabels();
65
+ const labels = { ...DEFAULT_TIME_PICKER_LABELS, ...uixLabels.timePicker, ...labelOverrides };
66
+ const locale = localeProp ?? uixLabels.dateFormat?.locale;
67
+ const anchorRef = useRef(null);
68
+ const inputRef = useRef(null);
69
+ const listRef = useRef(null);
70
+ useImperativeHandle(forwardedRef, () => inputRef.current);
71
+ const step = Math.max(1, Math.round(minuteStep));
72
+ const current = isValidTimeKey(value) ? value : null;
73
+ const [mounted, setMounted] = useState(false);
74
+ useEffect(() => setMounted(true), []);
75
+ const shownLocale = locale ?? (mounted ? void 0 : "en-GB");
76
+ const shownHour12 = hour12 ?? (locale || mounted ? void 0 : false);
77
+ const format = (time) => formatTimeKey(time, shownLocale, shownHour12);
78
+ const [text, setText] = useState(() => current ? format(current) : "");
79
+ const [problem, setProblemState] = useState(null);
80
+ const reported = useRef(null);
81
+ const setProblem = (next) => {
82
+ if (next !== reported.current) {
83
+ reported.current = next;
84
+ onInputProblem?.(next);
85
+ }
86
+ setProblemState(next);
87
+ };
88
+ useEffect(() => {
89
+ setText(current ? format(current) : "");
90
+ setProblem(null);
91
+ }, [current, locale, hour12, mounted]);
92
+ const { open, show, hide, triggerProps } = useFieldPopover(popoverId, () => inputRef.current?.focus({ preventScroll: true }), onOpenChange);
93
+ const locked = !!disabled || !!readOnly;
94
+ const commit = () => {
95
+ const typed = text.trim();
96
+ if (!typed) {
97
+ setProblem(null);
98
+ if (text) setText("");
99
+ if (value !== null) onValueChange(null);
100
+ return true;
101
+ }
102
+ if (current && typed === format(current)) {
103
+ setProblem(null);
104
+ return true;
105
+ }
106
+ const parsed = parseTimeInput(typed, locale);
107
+ if (!parsed) {
108
+ setProblem("format");
109
+ return false;
110
+ }
111
+ if (isTimeOutOfRange(parsed, min, max)) {
112
+ setProblem(isOvernightRange(min, max) ? "outside" : min && isValidTimeKey(min) && parsed < min ? "early" : "late");
113
+ return false;
114
+ }
115
+ setProblem(null);
116
+ setText(format(parsed));
117
+ if (parsed !== value) onValueChange(parsed);
118
+ return true;
119
+ };
120
+ const choose = (time) => {
121
+ setProblem(null);
122
+ setText(format(time));
123
+ if (time !== value) onValueChange(time);
124
+ };
125
+ const onInputKeyDown = (event) => {
126
+ if (locked || event.nativeEvent.isComposing || event.keyCode === 229) return;
127
+ const { key, altKey } = event;
128
+ if (key === "ArrowDown" && altKey) {
129
+ event.preventDefault();
130
+ show();
131
+ return;
132
+ }
133
+ if (key === "ArrowUp" || key === "ArrowDown" || key === "PageUp" || key === "PageDown") {
134
+ event.preventDefault();
135
+ const base = parseTimeInput(text, locale) ?? current;
136
+ const input = event.currentTarget;
137
+ const caret = input.selectionStart ?? 0;
138
+ const ends = base && input.value === format(base) ? timePartEnds(base, shownLocale, shownHour12) : null;
139
+ const hourEnd = ends?.hour ?? /^\s*\d{1,2}/.exec(input.value)?.[0].length ?? 0;
140
+ const onHour = key === "PageUp" || key === "PageDown" || !!base && caret <= hourEnd;
141
+ const sign = key === "ArrowUp" || key === "PageUp" ? 1 : -1;
142
+ let next;
143
+ if (!base) next = stepTime(null, 0, min, max);
144
+ else if (onHour) next = stepTime(base, sign * 60, min, max);
145
+ else {
146
+ const minutes = timeToMinutes(base);
147
+ const snapped = sign > 0 ? Math.floor(minutes / step) * step + step : Math.ceil(minutes / step) * step - step;
148
+ next = stepTime(base, snapped - minutes, min, max);
149
+ }
150
+ choose(next);
151
+ requestAnimationFrame(() => {
152
+ const el = inputRef.current;
153
+ if (!el || el.ownerDocument.activeElement !== el) return;
154
+ const moved = timePartEnds(next, shownLocale, shownHour12);
155
+ const at = moved ? onHour ? moved.hour : moved.minute : el.value.length;
156
+ try {
157
+ el.setSelectionRange(at, at);
158
+ } catch {
159
+ }
160
+ });
161
+ return;
162
+ }
163
+ if (key === "Enter" && text.trim() !== (current ? format(current) : "")) {
164
+ event.preventDefault();
165
+ commit();
166
+ }
167
+ };
168
+ const pick = (time) => {
169
+ choose(time);
170
+ hide(false);
171
+ inputRef.current?.focus({ preventScroll: true });
172
+ };
173
+ const describedBy = [ariaDescribedBy, problem ? errorId : void 0].filter(Boolean).join(" ") || void 0;
174
+ const isInvalid = !!invalid || !!problem || ariaInvalid === true || ariaInvalid === "true";
175
+ const example = format(min && isValidTimeKey(min) && min > "14:30" ? min : "14:30");
176
+ return /* @__PURE__ */ jsxs(
177
+ "div",
178
+ {
179
+ ref: anchorRef,
180
+ className: cx("uix-date-picker", "uix-time-picker", size === "sm" && "uix-date-picker--sm", className),
181
+ style,
182
+ onKeyDown: (event) => {
183
+ if (event.key !== "Escape" || !open) return;
184
+ event.stopPropagation();
185
+ event.preventDefault();
186
+ hide(false);
187
+ inputRef.current?.focus({ preventScroll: true });
188
+ },
189
+ children: [
190
+ /* @__PURE__ */ jsxs("div", { className: "uix-date-picker__control", children: [
191
+ /* @__PURE__ */ jsx(
192
+ "input",
193
+ {
194
+ ref: inputRef,
195
+ id,
196
+ type: "text",
197
+ autoComplete: "off",
198
+ spellCheck: false,
199
+ className: cx("uix-input", size === "sm" && "uix-input--sm", "uix-date-picker__input", "uix-time-picker__input"),
200
+ value: text,
201
+ placeholder: placeholder ?? example,
202
+ disabled,
203
+ readOnly,
204
+ required,
205
+ "aria-label": ariaLabel,
206
+ "aria-labelledby": ariaLabelledBy,
207
+ "aria-describedby": describedBy,
208
+ "aria-invalid": isInvalid || void 0,
209
+ "aria-required": ariaRequired,
210
+ onChange: (event) => {
211
+ setText(event.target.value);
212
+ if (problem) setProblem(null);
213
+ },
214
+ onBlur: () => {
215
+ if (!locked) commit();
216
+ },
217
+ onKeyDown: onInputKeyDown
218
+ }
219
+ ),
220
+ /* @__PURE__ */ jsx(
221
+ "button",
222
+ {
223
+ type: "button",
224
+ className: "uix-date-picker__toggle",
225
+ disabled: locked,
226
+ "aria-label": current ? fillLabel(labels.openSelected, { time: format(current) }) : labels.open,
227
+ ...triggerProps,
228
+ children: /* @__PURE__ */ jsx(ClockIcon, { size: "sm" })
229
+ }
230
+ )
231
+ ] }),
232
+ name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: current ?? "", disabled }),
233
+ problem && /* @__PURE__ */ jsx("span", { id: errorId, className: "uix-field__error uix-date-picker__error", role: "alert", children: problem === "format" ? fillLabel(labels.invalidTime, { example }) : problem === "outside" ? fillLabel(labels.outside, { min: format(min ?? ""), max: format(max ?? "") }) : problem === "early" ? fillLabel(labels.tooEarly, { min: format(min ?? "") }) : fillLabel(labels.tooLate, { max: format(max ?? "") }) }),
234
+ /* @__PURE__ */ jsx(
235
+ Popover,
236
+ {
237
+ id: popoverId,
238
+ anchor: anchorRef,
239
+ placement,
240
+ className: "uix-time-picker__popover",
241
+ role: "dialog",
242
+ "aria-label": labels.list,
243
+ capHeight: true,
244
+ closeWhenAnchorHidden: true,
245
+ children: open && /* @__PURE__ */ jsx(
246
+ TimeList,
247
+ {
248
+ ref: listRef,
249
+ id: listId,
250
+ value: current,
251
+ step,
252
+ min,
253
+ max,
254
+ label: labels.list,
255
+ format,
256
+ onPick: pick
257
+ }
258
+ )
259
+ }
260
+ )
261
+ ]
262
+ }
263
+ );
264
+ });
265
+ TimePicker.displayName = "TimePicker";
266
+ const TimeList = forwardRef(function TimeList2({ id, value, step, min, max, label, format, onPick }, ref) {
267
+ const slots = useMemo(() => timeSlots(step, min, max), [step, min, max]);
268
+ const nearest = () => {
269
+ if (!slots.length) return -1;
270
+ if (!value) return 0;
271
+ const exact = slots.indexOf(value);
272
+ if (exact >= 0) return exact;
273
+ const at = slots.findIndex((slot) => slot > value);
274
+ return at < 0 ? slots.length - 1 : Math.max(0, at - 1);
275
+ };
276
+ const [active, setActive] = useState(nearest);
277
+ const listRef = useRef(null);
278
+ useImperativeHandle(ref, () => listRef.current);
279
+ const optionId = (index) => `${id}-${index}`;
280
+ useEffect(() => {
281
+ listRef.current?.focus({ preventScroll: true });
282
+ }, []);
283
+ const placed = useRef(false);
284
+ useIsomorphicLayoutEffect(() => {
285
+ const list = listRef.current;
286
+ const el = list?.ownerDocument.getElementById(optionId(active));
287
+ if (!list || !el) return;
288
+ const top = el.offsetTop;
289
+ const bottom = top + el.offsetHeight;
290
+ if (!placed.current) {
291
+ placed.current = true;
292
+ list.scrollTop = top - (list.clientHeight - el.offsetHeight) / 2;
293
+ return;
294
+ }
295
+ if (top < list.scrollTop) list.scrollTop = top;
296
+ else if (bottom > list.scrollTop + list.clientHeight) list.scrollTop = bottom - list.clientHeight;
297
+ }, [active]);
298
+ const perHour = Math.max(1, Math.round(60 / step));
299
+ const onKeyDown = (event) => {
300
+ const last = slots.length - 1;
301
+ const moves = { ArrowDown: 1, ArrowUp: -1, PageDown: perHour, PageUp: -perHour };
302
+ if (event.key in moves) {
303
+ event.preventDefault();
304
+ setActive((index) => Math.max(0, Math.min(last, index + moves[event.key])));
305
+ } else if (event.key === "Home" || event.key === "End") {
306
+ event.preventDefault();
307
+ setActive(event.key === "Home" ? 0 : last);
308
+ } else if (event.key === "Enter" || event.key === " ") {
309
+ event.preventDefault();
310
+ const slot = slots[active];
311
+ if (slot) onPick(slot);
312
+ }
313
+ };
314
+ return /* @__PURE__ */ jsx(
315
+ "div",
316
+ {
317
+ ref: listRef,
318
+ id,
319
+ role: "listbox",
320
+ tabIndex: 0,
321
+ "aria-label": label,
322
+ className: "uix-listbox uix-time-picker__list",
323
+ "aria-activedescendant": active >= 0 ? optionId(active) : void 0,
324
+ onKeyDown,
325
+ children: slots.map((slot, index) => /* @__PURE__ */ jsx(
326
+ "div",
327
+ {
328
+ id: optionId(index),
329
+ role: "option",
330
+ className: "uix-listbox__option",
331
+ "aria-selected": slot === value,
332
+ "data-active": index === active || void 0,
333
+ onPointerMove: () => {
334
+ if (index !== active) setActive(index);
335
+ },
336
+ onClick: () => onPick(slot),
337
+ children: /* @__PURE__ */ jsx("span", { className: "uix-listbox__label", children: format(slot) })
338
+ },
339
+ slot
340
+ ))
341
+ }
342
+ );
343
+ });
344
+ export {
345
+ DEFAULT_TIME_PICKER_LABELS,
346
+ TimePicker
347
+ };
@@ -3,6 +3,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
4
4
  import { cx } from "../cx.js";
5
5
  import { useUixLabels } from "../labels-context.js";
6
+ import { XIcon } from "../icons/components.js";
6
7
  import { toast as defaultToast } from "../toast-store.js";
7
8
  const ToasterContext = createContext(null);
8
9
  function Toast({ title, message, tone, icon, action, onClose, leaving, dismissLabel: dismissLabelProp, className, ...props }) {
@@ -44,7 +45,7 @@ function Toast({ title, message, tone, icon, action, onClose, leaving, dismissLa
44
45
  message && /* @__PURE__ */ jsx("div", { className: "uix-toast__msg", children: message })
45
46
  ] }),
46
47
  action != null && /* @__PURE__ */ jsx("div", { className: "uix-toast__action-slot", children: action }),
47
- onClose && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-toast__close", onClick: handleClose, "aria-label": dismissLabel, children: "\xD7" })
48
+ onClose && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-toast__close", onClick: handleClose, "aria-label": dismissLabel, children: /* @__PURE__ */ jsx(XIcon, {}) })
48
49
  ]
49
50
  }
50
51
  );
@@ -124,7 +125,7 @@ function useToastTimers(store, visible, paused) {
124
125
  timer = { version: record.version, remaining: record.duration, start: now };
125
126
  map.set(record.id, timer);
126
127
  }
127
- if (paused) {
128
+ if (paused && record.pauseOnHover !== false) {
128
129
  if (timer.handle != null) {
129
130
  clearTimeout(timer.handle);
130
131
  timer.handle = void 0;
@@ -16,10 +16,11 @@ interface TooltipProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'content'>
16
16
  placement?: Placement;
17
17
  /**
18
18
  * The trigger's keyboard shortcut, in `KbdCombo` key names, e.g. `['Mod', 'K']` (HAR-2031):
19
- * shown with `Kbd` after the text, and set as the trigger's `aria-keyshortcuts` for the
20
- * viewer's platform. Default: the `shortcut` of a `Button` child, so a Tooltip around a Button
21
- * with a shortcut shows one bubble with the label and the keys. The kit only shows it: the
22
- * product binds the key.
19
+ * shown with `Kbd` after the text. Screen readers hear it once, in the trigger's description
20
+ * after the text ("Keyboard shortcuts, Shift+?"), the same rule as a `Button` with `shortcut`
21
+ * (HAR-2416). Default: the `shortcut` of a `Button` child, so a Tooltip around a Button with a
22
+ * shortcut shows one bubble with the label and the keys. The kit only shows it: the product
23
+ * binds the key.
23
24
  */
24
25
  shortcut?: KeyShortcut;
25
26
  children?: ReactNode;
@@ -31,6 +32,9 @@ interface TooltipProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'content'>
31
32
  * (flip/shift), shown on hover and keyboard focus, dismissed on blur or Escape
32
33
  * (document-level, per WCAG 1.4.13), and hoverable: the bubble stays open while
33
34
  * the pointer is over it (UIX-A11Y-1).
35
+ *
36
+ * While the bubble is open it takes the first Escape (HAR-2415), also on a combobox-style trigger
37
+ * whose list is open (a Select, a date field): one Escape closes the bubble, the next one the list.
34
38
  */
35
39
  declare function Tooltip({ label, content, placement, shortcut, className, children, ...props }: TooltipProps): react.JSX.Element;
36
40
 
@@ -1,9 +1,10 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
3
- import { cloneElement, isValidElement, useEffect, useId, useRef, useState } from "react";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { cloneElement, isValidElement, useEffect, useId, useLayoutEffect, useRef, useState } from "react";
4
4
  import { cx } from "../cx.js";
5
5
  import { useAnchoredPosition } from "../hooks/useAnchoredPosition.js";
6
- import { KbdCombo, ariaKeyShortcuts, isKeyShortcut, useKbdPlatform } from "./Kbd.js";
6
+ import { KbdCombo, isKeyShortcut, spokenKeys, useKbdPlatform } from "./Kbd.js";
7
+ const useIsomorphicLayoutEffect = typeof document !== "undefined" ? useLayoutEffect : useEffect;
7
8
  function Tooltip({ label, content, placement = "top", shortcut, className, children, ...props }) {
8
9
  const triggerRef = useRef(null);
9
10
  const bubbleRef = useRef(null);
@@ -14,7 +15,6 @@ function Tooltip({ label, content, placement = "top", shortcut, className, child
14
15
  const childShortcut = isValidElement(children) ? children.props.shortcut : void 0;
15
16
  const own = shortcut ?? (isKeyShortcut(childShortcut) ? childShortcut : void 0);
16
17
  const keys = own && own.length > 0 ? own : null;
17
- const keyshortcuts = keys ? ariaKeyShortcuts(keys, platform) : void 0;
18
18
  useAnchoredPosition(triggerRef, bubbleRef, { open, placement, offset: 6 });
19
19
  const cancelClose = () => {
20
20
  if (closeTimer.current != null) {
@@ -48,13 +48,16 @@ function Tooltip({ label, content, placement = "top", shortcut, className, child
48
48
  cancelClose();
49
49
  closeTimer.current = window.setTimeout(hide, 100);
50
50
  };
51
- useEffect(() => {
52
- if (!open) return;
51
+ useIsomorphicLayoutEffect(() => {
52
+ if (!open) return void 0;
53
53
  const onKey = (e) => {
54
- if (e.key === "Escape") hide();
54
+ if (e.key !== "Escape") return;
55
+ e.preventDefault();
56
+ e.stopPropagation();
57
+ hide();
55
58
  };
56
- document.addEventListener("keydown", onKey);
57
- return () => document.removeEventListener("keydown", onKey);
59
+ document.addEventListener("keydown", onKey, true);
60
+ return () => document.removeEventListener("keydown", onKey, true);
58
61
  }, [open]);
59
62
  useEffect(() => () => {
60
63
  if (closeTimer.current != null) clearTimeout(closeTimer.current);
@@ -67,30 +70,27 @@ function Tooltip({ label, content, placement = "top", shortcut, className, child
67
70
  }
68
71
  }, []);
69
72
  const bubbleAttr = { popover: "manual" };
73
+ const text = content ?? label;
74
+ const description = keys ? [text != null ? `${id}-text` : null, `${id}-keys`].filter(Boolean).join(" ") : id;
70
75
  const single = isValidElement(children);
71
76
  const child = single ? cloneElement(children, {
72
- "aria-describedby": [children.props["aria-describedby"], id].filter(Boolean).join(" "),
73
- ...keyshortcuts ? { "aria-keyshortcuts": keyshortcuts } : {}
77
+ "aria-describedby": [children.props["aria-describedby"], description].filter(Boolean).join(" ")
74
78
  }) : children;
75
79
  return /* @__PURE__ */ jsxs(
76
80
  "span",
77
81
  {
78
82
  ref: triggerRef,
79
83
  className: cx("uix-tooltip", className),
80
- "aria-describedby": single ? void 0 : id,
81
- "aria-keyshortcuts": single ? void 0 : keyshortcuts,
84
+ "aria-describedby": single ? void 0 : description,
82
85
  tabIndex: single ? void 0 : 0,
83
86
  onMouseEnter: show,
84
87
  onMouseLeave: scheduleHide,
85
88
  onFocus: show,
86
89
  onBlur: hide,
87
- onKeyDown: (e) => {
88
- if (e.key === "Escape") hide();
89
- },
90
90
  ...props,
91
91
  children: [
92
92
  child,
93
- /* @__PURE__ */ jsxs(
93
+ /* @__PURE__ */ jsx(
94
94
  "span",
95
95
  {
96
96
  ref: bubbleRef,
@@ -100,10 +100,13 @@ function Tooltip({ label, content, placement = "top", shortcut, className, child
100
100
  onMouseEnter: cancelClose,
101
101
  onMouseLeave: scheduleHide,
102
102
  ...bubbleAttr,
103
- children: [
104
- content ?? label,
105
- keys && /* @__PURE__ */ jsx("span", { className: "uix-tooltip__shortcut", children: /* @__PURE__ */ jsx(KbdCombo, { keys: [...keys], platform }) })
106
- ]
103
+ children: keys ? /* @__PURE__ */ jsxs(Fragment, { children: [
104
+ text != null && /* @__PURE__ */ jsx("span", { id: `${id}-text`, children: text }),
105
+ /* @__PURE__ */ jsxs("span", { className: "uix-tooltip__shortcut", children: [
106
+ /* @__PURE__ */ jsx("span", { id: `${id}-keys`, className: "uix-visually-hidden", children: spokenKeys(keys, platform) }),
107
+ /* @__PURE__ */ jsx(KbdCombo, { keys: [...keys], platform, "aria-hidden": "true" })
108
+ ] })
109
+ ] }) : text
107
110
  }
108
111
  )
109
112
  ]
@@ -6,6 +6,12 @@ interface TreeNodeData {
6
6
  label: ReactNode;
7
7
  /** Plain-text label for keyboard typeahead when `label` is not a string (UIX-A11Y-2). */
8
8
  typeaheadLabel?: string;
9
+ /**
10
+ * The row's accessible name when `label` is rich (an avatar, a name and a meta line) and only
11
+ * part of it should be spoken, e.g. the person's name (HAR-1828). By default a row is named by
12
+ * its own `label`, never by its children.
13
+ */
14
+ ariaLabel?: string;
9
15
  icon?: ReactNode;
10
16
  children?: TreeNodeData[];
11
17
  /**
@@ -39,6 +45,8 @@ interface TreeNodeProps {
39
45
  rowToggles?: boolean;
40
46
  /** Ids of nodes whose children are loading (`aria-busy`). */
41
47
  busy?: ReadonlySet<string>;
48
+ /** Scopes the ids that name each row by its own label (HAR-1828). Set by `Tree`. */
49
+ treeId?: string;
42
50
  }
43
51
  interface TreeProps extends Omit<HTMLAttributes<HTMLUListElement>, 'onSelect' | 'onChange'> {
44
52
  nodes: TreeNodeData[];
@@ -15,13 +15,16 @@ const LOADING = "::uix-loading";
15
15
  const FAILED = "::uix-error";
16
16
  const syntheticOf = (id) => id.endsWith(LOADING) ? { parent: id.slice(0, -LOADING.length), kind: "loading" } : id.endsWith(FAILED) ? { parent: id.slice(0, -FAILED.length), kind: "error" } : null;
17
17
  const Ring = () => /* @__PURE__ */ jsx("span", { className: "uix-spinner uix-spinner--sm", "aria-hidden": "true" });
18
- function TreeNode({ node, expanded, selected, selectable, tabbableId, onToggle, onSelect, onFocusNode, level = 1, rowToggles = true, busy }) {
18
+ const labelId = (treeId, nodeId) => `${treeId}-label-${encodeURIComponent(nodeId)}`;
19
+ const rowName = (treeId, node) => node.ariaLabel ? { "aria-label": node.ariaLabel } : { "aria-labelledby": labelId(treeId, node.id) };
20
+ function TreeNode({ node, expanded, selected, selectable, tabbableId, onToggle, onSelect, onFocusNode, level = 1, rowToggles = true, busy, treeId = "uix-tree" }) {
19
21
  const hasChildren = Array.isArray(node.children) && node.children.length > 0;
20
22
  const isExpanded = expanded.has(node.id);
21
23
  return /* @__PURE__ */ jsxs(
22
24
  "li",
23
25
  {
24
26
  role: "treeitem",
27
+ ...rowName(treeId, node),
25
28
  "aria-level": level,
26
29
  "aria-expanded": hasChildren ? isExpanded : void 0,
27
30
  "aria-selected": selectable && !syntheticOf(node.id) ? selected === node.id : void 0,
@@ -61,7 +64,7 @@ function TreeNode({ node, expanded, selected, selectable, tabbableId, onToggle,
61
64
  )
62
65
  ) : /* @__PURE__ */ jsx("span", { className: "uix-tree__toggle" }),
63
66
  node.icon && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: node.icon }),
64
- /* @__PURE__ */ jsx("span", { className: "uix-tree__label", children: node.label })
67
+ /* @__PURE__ */ jsx("span", { id: labelId(treeId, node.id), className: "uix-tree__label", children: node.label })
65
68
  ] }),
66
69
  hasChildren && isExpanded && /* @__PURE__ */ jsx("ul", { role: "group", className: "uix-tree__group", children: node.children.map((child) => /* @__PURE__ */ jsx(
67
70
  TreeNode,
@@ -76,7 +79,8 @@ function TreeNode({ node, expanded, selected, selectable, tabbableId, onToggle,
76
79
  onFocusNode,
77
80
  level: level + 1,
78
81
  rowToggles,
79
- busy
82
+ busy,
83
+ treeId
80
84
  },
81
85
  child.id
82
86
  )) })
@@ -317,7 +321,8 @@ function Tree({
317
321
  onFocusNode: setFocusedId,
318
322
  level: 1,
319
323
  rowToggles,
320
- busy: loading
324
+ busy: loading,
325
+ treeId
321
326
  },
322
327
  node.id
323
328
  )) });
@@ -418,6 +423,7 @@ function VirtualTreeView({
418
423
  "li",
419
424
  {
420
425
  role: "treeitem",
426
+ ...rowName(treeId, f.node),
421
427
  "aria-level": f.level,
422
428
  "aria-setsize": f.setsize,
423
429
  "aria-posinset": f.posinset,
@@ -458,7 +464,7 @@ function VirtualTreeView({
458
464
  )
459
465
  ) : /* @__PURE__ */ jsx("span", { className: "uix-tree__toggle" }),
460
466
  f.node.icon && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: f.node.icon }),
461
- /* @__PURE__ */ jsx("span", { className: "uix-tree__label", children: f.node.label })
467
+ /* @__PURE__ */ jsx("span", { id: labelId(treeId, f.node.id), className: "uix-tree__label", children: f.node.label })
462
468
  ] })
463
469
  },
464
470
  f.node.id