@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
@@ -1,9 +1,10 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
3
- import { useEffect, useId, useRef, useState } from "react";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useId, useLayoutEffect, useRef, useState } from "react";
4
4
  import { cx } from "../cx.js";
5
5
  import { fillLabel } from "../fill-label.js";
6
6
  import { useUixLabels } from "../labels-context.js";
7
+ import { useDelayedPending } from "../hooks/useDelayedPending.js";
7
8
  import { PencilIcon } from "../icons/components.js";
8
9
  import { Button } from "./Button.js";
9
10
  import { Input } from "./Input.js";
@@ -15,8 +16,10 @@ const DEFAULT_INLINE_EDIT_LABELS = {
15
16
  saving: "Saving\u2026",
16
17
  saved: "Saved",
17
18
  failed: "Could not save.",
18
- empty: "Not set"
19
+ empty: "Not set",
20
+ readOnly: "Read-only:"
19
21
  };
22
+ const defaultEmpty = (value) => value == null || value === "";
20
23
  function InlineEdit({
21
24
  value,
22
25
  onSave,
@@ -25,22 +28,45 @@ function InlineEdit({
25
28
  renderEditor,
26
29
  multiline = false,
27
30
  validate,
31
+ equals = Object.is,
32
+ isEmpty = defaultEmpty,
33
+ commit = "explicit",
28
34
  placeholder,
29
35
  disabled = false,
36
+ readOnlyReason,
37
+ trigger = "value",
38
+ editing: editingProp,
39
+ onEditingChange,
40
+ draft: draftProp,
41
+ onDraftChange,
42
+ renderError,
43
+ onStateChange,
30
44
  labels: labelOverrides,
31
45
  className
32
46
  }) {
33
47
  const labels = { ...DEFAULT_INLINE_EDIT_LABELS, ...useUixLabels().inlineEdit, ...labelOverrides };
34
48
  const id = useId();
35
49
  const errorId = `${id}-error`;
36
- const [editing, setEditing] = useState(false);
37
- const [draft, setDraft] = useState(value);
50
+ const reasonId = `${id}-reason`;
51
+ const [editingState, setEditingState] = useState(false);
52
+ const isEditing = editingProp ?? editingState;
53
+ const [draftState, setDraftState] = useState(value);
54
+ const draft = draftProp !== void 0 ? draftProp : draftState;
38
55
  const [pending, setPending] = useState(false);
56
+ const pendingVisible = useDelayedPending(pending) && pending;
39
57
  const [error, setError] = useState();
40
58
  const [status, setStatus] = useState("");
59
+ const [saved, setSaved] = useState(false);
60
+ const [accepted, setAccepted] = useState(null);
41
61
  const viewRef = useRef(null);
42
62
  const editorRef = useRef(null);
43
- const focusNext = useRef(null);
63
+ const focusView = useRef(false);
64
+ const editingRef = useRef(isEditing);
65
+ editingRef.current = isEditing;
66
+ const inFlight = useRef(false);
67
+ const lastSent = useRef(null);
68
+ const session = useRef(0);
69
+ const pressInside = useRef(false);
44
70
  const mounted = useRef(true);
45
71
  useEffect(() => {
46
72
  mounted.current = true;
@@ -48,83 +74,143 @@ function InlineEdit({
48
74
  mounted.current = false;
49
75
  };
50
76
  }, []);
77
+ const lastValue = useRef(value);
78
+ useLayoutEffect(() => {
79
+ if (equals(lastValue.current, value)) return;
80
+ lastValue.current = value;
81
+ setAccepted(null);
82
+ });
83
+ const shown = accepted ? accepted.value : value;
51
84
  useEffect(() => {
52
- const target = focusNext.current;
53
- if (!target) return;
54
- focusNext.current = null;
55
- if (target === "view") {
56
- viewRef.current?.focus();
85
+ if (pendingVisible && pending) setStatus(labels.saving);
86
+ }, [pendingVisible, pending]);
87
+ const state = pending ? "saving" : error?.failure ? "error" : isEditing ? "editing" : saved ? "saved" : "idle";
88
+ const onStateChangeRef = useRef(onStateChange);
89
+ onStateChangeRef.current = onStateChange;
90
+ const reported = useRef("idle");
91
+ useEffect(() => {
92
+ if (reported.current === state) return;
93
+ reported.current = state;
94
+ onStateChangeRef.current?.(state);
95
+ }, [state]);
96
+ const setEditing = (next) => {
97
+ editingRef.current = next;
98
+ if (editingProp === void 0) setEditingState(next);
99
+ onEditingChange?.(next);
100
+ };
101
+ const setDraft = (next) => {
102
+ if (draftProp === void 0) setDraftState(next);
103
+ onDraftChange?.(next);
104
+ };
105
+ useEffect(() => {
106
+ if (isEditing) {
107
+ const control = editorRef.current?.querySelector("input, textarea, select, button, [tabindex]");
108
+ control?.focus();
109
+ if (control instanceof HTMLInputElement || control instanceof HTMLTextAreaElement) control.select();
57
110
  return;
58
111
  }
59
- const control = editorRef.current?.querySelector("input, textarea, select, button, [tabindex]");
60
- control?.focus();
61
- if (control instanceof HTMLInputElement || control instanceof HTMLTextAreaElement) control.select();
62
- }, [editing]);
63
- const start = () => {
64
- setDraft(value);
112
+ if (focusView.current) {
113
+ focusView.current = false;
114
+ viewRef.current?.focus();
115
+ }
116
+ }, [isEditing]);
117
+ const wasEditing = useRef(isEditing);
118
+ useLayoutEffect(() => {
119
+ if (isEditing && !wasEditing.current && editingProp !== void 0) resetForOpen();
120
+ wasEditing.current = isEditing;
121
+ });
122
+ function resetForOpen() {
123
+ setDraft(shown);
65
124
  setError(void 0);
66
125
  setStatus("");
67
- focusNext.current = "editor";
126
+ setSaved(false);
127
+ lastSent.current = null;
128
+ }
129
+ const start = () => {
130
+ session.current += 1;
131
+ resetForOpen();
68
132
  setEditing(true);
69
133
  };
70
- const close = () => {
71
- focusNext.current = "view";
134
+ const close = (returnFocus) => {
135
+ focusView.current = returnFocus;
136
+ inFlight.current = false;
72
137
  setEditing(false);
73
138
  };
74
139
  const cancel = () => {
75
- if (pending) return;
140
+ if (inFlight.current) return;
76
141
  setError(void 0);
77
- close();
142
+ setDraft(shown);
143
+ close(true);
78
144
  };
79
- const save = () => {
80
- if (pending) return;
81
- const issue = validate?.(draft);
145
+ const failureMessage = (reason) => typeof reason === "string" && reason ? reason : reason instanceof Error && reason.message ? reason.message : labels.failed;
146
+ const send = (next, explicit) => {
147
+ if (!editingRef.current || inFlight.current) return;
148
+ if (!explicit && error?.failure && lastSent.current && equals(lastSent.current.value, next)) return;
149
+ const issue = validate?.(next);
82
150
  if (issue != null) {
83
- setError(issue);
151
+ setError({ message: issue });
84
152
  return;
85
153
  }
86
- if (draft === value) {
87
- close();
154
+ if (equals(next, shown)) {
155
+ setError(void 0);
156
+ close(explicit);
88
157
  return;
89
158
  }
90
159
  setError(void 0);
160
+ lastSent.current = { value: next };
161
+ const mine = session.current;
91
162
  let result;
92
163
  try {
93
- result = onSave(draft);
164
+ result = onSave(next);
94
165
  } catch (thrown) {
95
- setError(thrown instanceof Error && thrown.message ? thrown.message : labels.failed);
166
+ setError({ message: failureMessage(thrown), failure: { reason: thrown } });
96
167
  return;
97
168
  }
98
- if (!result || typeof result.then !== "function") {
169
+ const done = () => {
170
+ setAccepted({ value: next });
99
171
  setStatus(labels.saved);
100
- close();
172
+ setSaved(true);
173
+ if (editingRef.current && session.current === mine) close(explicit);
174
+ };
175
+ if (!result || typeof result.then !== "function") {
176
+ done();
101
177
  return;
102
178
  }
179
+ inFlight.current = true;
103
180
  setPending(true);
104
- setStatus(labels.saving);
105
181
  result.then(
106
182
  () => {
107
183
  if (!mounted.current) return;
184
+ inFlight.current = false;
108
185
  setPending(false);
109
- setStatus(labels.saved);
110
- close();
186
+ done();
111
187
  },
112
188
  (reason) => {
113
189
  if (!mounted.current) return;
190
+ inFlight.current = false;
114
191
  setPending(false);
192
+ if (session.current !== mine) return;
115
193
  setStatus("");
116
- setError(typeof reason === "string" && reason ? reason : reason instanceof Error && reason.message ? reason.message : labels.failed);
117
- focusNext.current = null;
194
+ setError({ message: failureMessage(reason), failure: { reason } });
118
195
  editorRef.current?.querySelector("input, textarea, select")?.focus();
119
196
  }
120
197
  );
121
198
  };
199
+ const save = (next) => send(next === void 0 ? draft : next, true);
200
+ const retry = () => send(lastSent.current && equals(lastSent.current.value, draft) ? lastSent.current.value : draft, true);
201
+ const change = (next) => {
202
+ if (inFlight.current) return;
203
+ setDraft(next);
204
+ if (error && !error.failure) setError(void 0);
205
+ if (commit === "change") send(next, true);
206
+ };
122
207
  const onEditorKeyDown = (event) => {
123
208
  if (event.key === "Escape") {
209
+ if (event.defaultPrevented || event.target.closest?.('[aria-expanded="true"]')) return;
124
210
  event.preventDefault();
125
211
  event.stopPropagation();
126
212
  cancel();
127
- } else if (event.key === "Enter" && !event.defaultPrevented) {
213
+ } else if (event.key === "Enter" && !event.defaultPrevented && commit !== "change") {
128
214
  const inField = event.target.matches("input, textarea");
129
215
  if (!inField) return;
130
216
  const isTextarea = event.target.tagName === "TEXTAREA";
@@ -133,15 +219,56 @@ function InlineEdit({
133
219
  save();
134
220
  }
135
221
  };
222
+ const onEditorBlur = (event) => {
223
+ if (commit === "explicit") return;
224
+ const next = event.relatedTarget;
225
+ if (next && event.currentTarget.contains(next)) return;
226
+ if (pressInside.current) return;
227
+ if (next) {
228
+ send(draft, false);
229
+ return;
230
+ }
231
+ const draftNow = draft;
232
+ setTimeout(() => {
233
+ if (!editingRef.current || pressInside.current) return;
234
+ if (editorRef.current?.contains(document.activeElement)) return;
235
+ send(draftNow, false);
236
+ }, 0);
237
+ };
238
+ const onEditorPointerDown = () => {
239
+ pressInside.current = true;
240
+ const release = () => {
241
+ setTimeout(() => {
242
+ pressInside.current = false;
243
+ }, 0);
244
+ window.removeEventListener("pointerup", release, true);
245
+ window.removeEventListener("pointercancel", release, true);
246
+ };
247
+ window.addEventListener("pointerup", release, true);
248
+ window.addEventListener("pointercancel", release, true);
249
+ };
136
250
  const liveRegion = /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", role: "status", "aria-live": "polite", children: status });
137
- const shown = value === "" ? /* @__PURE__ */ jsx("span", { className: "uix-inline-edit__placeholder", children: placeholder ?? labels.empty }) : renderView?.(value) ?? value;
138
- if (disabled) {
139
- return /* @__PURE__ */ jsx("span", { className: cx("uix-inline-edit", "uix-inline-edit--readonly", className), children: /* @__PURE__ */ jsx("span", { className: "uix-inline-edit__value", children: shown }) });
251
+ const empty = isEmpty(shown);
252
+ const display = empty ? /* @__PURE__ */ jsx("span", { className: "uix-inline-edit__placeholder", children: placeholder ?? labels.empty }) : renderView?.(shown) ?? String(shown);
253
+ if (disabled || readOnlyReason != null) {
254
+ return /* @__PURE__ */ jsxs("span", { className: cx("uix-inline-edit", "uix-inline-edit--readonly", readOnlyReason != null && "uix-inline-edit--reason", className), children: [
255
+ /* @__PURE__ */ jsx("span", { className: "uix-inline-edit__value", "aria-describedby": readOnlyReason != null ? reasonId : void 0, children: display }),
256
+ readOnlyReason != null && /* @__PURE__ */ jsxs("span", { id: reasonId, className: "uix-inline-edit__reason", children: [
257
+ /* @__PURE__ */ jsxs("span", { className: "uix-visually-hidden", children: [
258
+ labels.readOnly,
259
+ " "
260
+ ] }),
261
+ readOnlyReason
262
+ ] })
263
+ ] });
140
264
  }
141
- if (!editing) {
142
- return /* @__PURE__ */ jsxs("span", { className: cx("uix-inline-edit", className), children: [
143
- /* @__PURE__ */ jsxs("button", { ref: viewRef, type: "button", className: "uix-inline-edit__view", onClick: start, children: [
144
- /* @__PURE__ */ jsx("span", { className: "uix-inline-edit__value", children: shown }),
265
+ if (!isEditing) {
266
+ return /* @__PURE__ */ jsxs("span", { className: cx("uix-inline-edit", trigger === "icon" && "uix-inline-edit--icon-trigger", className), children: [
267
+ trigger === "icon" ? /* @__PURE__ */ jsxs(Fragment, { children: [
268
+ /* @__PURE__ */ jsx("span", { className: "uix-inline-edit__value", children: display }),
269
+ /* @__PURE__ */ jsx("button", { ref: viewRef, type: "button", className: "uix-inline-edit__trigger", "aria-label": fillLabel(labels.edit, { label }), onClick: start, children: /* @__PURE__ */ jsx(PencilIcon, { size: "sm" }) })
270
+ ] }) : /* @__PURE__ */ jsxs("button", { ref: viewRef, type: "button", className: "uix-inline-edit__view", onClick: start, children: [
271
+ /* @__PURE__ */ jsx("span", { className: "uix-inline-edit__value", children: display }),
145
272
  /* @__PURE__ */ jsxs("span", { className: "uix-visually-hidden", children: [
146
273
  ", ",
147
274
  fillLabel(labels.edit, { label })
@@ -156,17 +283,33 @@ function InlineEdit({
156
283
  "aria-label": label,
157
284
  "aria-describedby": error != null ? errorId : void 0,
158
285
  "aria-invalid": error != null ? true : void 0,
159
- disabled: pending
286
+ disabled: pendingVisible,
287
+ readOnly: pendingVisible
160
288
  };
289
+ const textDraft = typeof draft === "string" ? draft : String(draft ?? "");
290
+ const errorBody = error && (error.failure && renderError ? renderError(error.failure.reason, { message: error.message, draft, retry, cancel }) : error.message);
161
291
  return /* @__PURE__ */ jsxs("span", { className: cx("uix-inline-edit", "uix-inline-edit--editing", className), children: [
162
- /* @__PURE__ */ jsxs("div", { ref: editorRef, className: "uix-inline-edit__editor", role: "group", "aria-label": fillLabel(labels.edit, { label }), "aria-busy": pending || void 0, onKeyDown: onEditorKeyDown, children: [
163
- /* @__PURE__ */ jsx("div", { className: "uix-inline-edit__control", children: renderEditor ? renderEditor({ value: draft, onChange: setDraft, save, cancel, field }) : multiline ? /* @__PURE__ */ jsx(Textarea, { ...field, rows: 3, value: draft, invalid: error != null, onChange: (event) => setDraft(event.currentTarget.value) }) : /* @__PURE__ */ jsx(Input, { ...field, size: "sm", value: draft, invalid: error != null, onChange: (event) => setDraft(event.currentTarget.value) }) }),
164
- /* @__PURE__ */ jsxs("span", { className: "uix-inline-edit__actions", children: [
165
- /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "primary", loading: pending, onClick: save, children: labels.save }),
166
- /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", disabled: pending, onClick: cancel, children: labels.cancel })
167
- ] }),
168
- error != null && /* @__PURE__ */ jsx("div", { id: errorId, className: "uix-field__error uix-inline-edit__error", role: "alert", children: error })
169
- ] }),
292
+ /* @__PURE__ */ jsxs(
293
+ "div",
294
+ {
295
+ ref: editorRef,
296
+ className: "uix-inline-edit__editor",
297
+ role: "group",
298
+ "aria-label": fillLabel(labels.edit, { label }),
299
+ "aria-busy": pending || void 0,
300
+ onKeyDown: onEditorKeyDown,
301
+ onBlur: onEditorBlur,
302
+ onPointerDownCapture: onEditorPointerDown,
303
+ children: [
304
+ /* @__PURE__ */ jsx("div", { className: "uix-inline-edit__control", children: renderEditor ? renderEditor({ value: draft, onChange: change, save, cancel, field }) : multiline ? /* @__PURE__ */ jsx(Textarea, { ...field, disabled: false, rows: 3, value: textDraft, invalid: error != null, onChange: (event) => change(event.currentTarget.value) }) : /* @__PURE__ */ jsx(Input, { ...field, disabled: false, size: "sm", value: textDraft, invalid: error != null, onChange: (event) => change(event.currentTarget.value) }) }),
305
+ /* @__PURE__ */ jsxs("span", { className: "uix-inline-edit__actions", children: [
306
+ commit !== "change" && /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "primary", loading: pending, onMouseDown: (event) => event.preventDefault(), onClick: () => save(), children: labels.save }),
307
+ /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", disabled: pendingVisible, onMouseDown: (event) => event.preventDefault(), onClick: cancel, children: labels.cancel })
308
+ ] }),
309
+ errorBody != null && /* @__PURE__ */ jsx("div", { id: errorId, className: "uix-field__error uix-inline-edit__error", role: "alert", children: errorBody })
310
+ ]
311
+ }
312
+ ),
170
313
  liveRegion
171
314
  ] });
172
315
  }
@@ -2,7 +2,11 @@ import * as react from 'react';
2
2
  import { InputHTMLAttributes, ReactNode, AriaAttributes } from 'react';
3
3
 
4
4
  interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size'> {
5
- size?: 'sm' | 'md';
5
+ /**
6
+ * `sm` 28 px, `md` 36 px (default), `lg` 44 px: the touch-target floor of a public page, the
7
+ * same height as `Button size="lg"` (HAR-1812).
8
+ */
9
+ size?: 'sm' | 'md' | 'lg';
6
10
  invalid?: boolean;
7
11
  valid?: boolean;
8
12
  /**
@@ -16,6 +20,12 @@ interface InputGroupProps {
16
20
  children: ReactNode;
17
21
  leadingIcon?: ReactNode;
18
22
  trailingIcon?: ReactNode;
23
+ /**
24
+ * A control at the end of the field, inside its border: a reveal toggle, a clear or a copy
25
+ * button (HAR-1376). Give it the class `uix-input-group__action` (an icon button sized to the
26
+ * field) and an accessible name. It takes the place of `trailingIcon`.
27
+ */
28
+ trailingAction?: ReactNode;
19
29
  className?: string;
20
30
  /** Injected by `<Field>` when it wraps the group; forwarded onto the child control. */
21
31
  id?: string;
@@ -29,6 +39,6 @@ interface InputGroupProps {
29
39
  * single child element (the child's own props win) so label/message association survives
30
40
  * the extra div (UIX-A11Y-3).
31
41
  */
32
- declare function InputGroup({ children, leadingIcon, trailingIcon, className, id, 'aria-describedby': describedBy, 'aria-invalid': invalid, 'aria-required': required, }: InputGroupProps): react.JSX.Element;
42
+ declare function InputGroup({ children, leadingIcon, trailingIcon, trailingAction, className, id, 'aria-describedby': describedBy, 'aria-invalid': invalid, 'aria-required': required, }: InputGroupProps): react.JSX.Element;
33
43
 
34
44
  export { Input, InputGroup, type InputGroupProps, type InputProps };
@@ -6,7 +6,7 @@ const Input = forwardRef(
6
6
  "input",
7
7
  {
8
8
  ref,
9
- className: cx("uix-input", size === "sm" && "uix-input--sm", className),
9
+ className: cx("uix-input", size !== "md" && `uix-input--${size}`, className),
10
10
  "aria-invalid": invalid || void 0,
11
11
  "data-valid": valid || void 0,
12
12
  "data-warning": warning || void 0,
@@ -19,6 +19,7 @@ function InputGroup({
19
19
  children,
20
20
  leadingIcon,
21
21
  trailingIcon,
22
+ trailingAction,
22
23
  className,
23
24
  id,
24
25
  "aria-describedby": describedBy,
@@ -37,13 +38,14 @@ function InputGroup({
37
38
  className: cx(
38
39
  "uix-input-group",
39
40
  leadingIcon && "uix-input-group--has-leading",
40
- trailingIcon && "uix-input-group--has-trailing",
41
+ (trailingIcon || trailingAction) && "uix-input-group--has-trailing",
42
+ trailingAction && "uix-input-group--has-action",
41
43
  className
42
44
  ),
43
45
  children: [
44
46
  leadingIcon && /* @__PURE__ */ jsx("span", { className: "uix-input-group__icon", "aria-hidden": "true", children: leadingIcon }),
45
47
  control,
46
- trailingIcon && /* @__PURE__ */ jsx("span", { className: "uix-input-group__icon uix-input-group__icon--trailing", "aria-hidden": "true", children: trailingIcon })
48
+ trailingAction ?? (trailingIcon && /* @__PURE__ */ jsx("span", { className: "uix-input-group__icon uix-input-group__icon--trailing", "aria-hidden": "true", children: trailingIcon }))
47
49
  ]
48
50
  }
49
51
  );
@@ -26,6 +26,11 @@ type KeyShortcut = readonly string[];
26
26
  declare function ariaKeyShortcuts(keys: KeyShortcut, platform: KbdPlatform): string;
27
27
  /** A `shortcut` prop that is key names rather than a node (HAR-2031). */
28
28
  declare const isKeyShortcut: (value: unknown) => value is KeyShortcut;
29
+ /**
30
+ * What a screen reader should hear for a shortcut: the key names joined with "+", e.g.
31
+ * "Control+K" or "Command+K" (HAR-2416). The same text `KbdCombo` speaks. Internal.
32
+ */
33
+ declare function spokenKeys(keys: KeyShortcut, platform: KbdPlatform, keyLabels?: Partial<Record<string, string>>): string;
29
34
  interface KbdComboProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'children'> {
30
35
  /**
31
36
  * Keys in order, e.g. `['Mod', 'K']`. `Mod` is ⌘ on Apple and Ctrl elsewhere; `Shift`,
@@ -43,4 +48,4 @@ interface KbdComboProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'children'
43
48
  */
44
49
  declare function KbdCombo({ keys, platform, keyLabels, className, ...props }: KbdComboProps): react.JSX.Element;
45
50
 
46
- export { Kbd, KbdCombo, type KbdComboProps, type KbdPlatform, type KbdProps, type KeyShortcut, ariaKeyShortcuts, isKeyShortcut, useKbdPlatform };
51
+ export { Kbd, KbdCombo, type KbdComboProps, type KbdPlatform, type KbdProps, type KeyShortcut, ariaKeyShortcuts, isKeyShortcut, spokenKeys, useKbdPlatform };
@@ -48,15 +48,14 @@ function ariaKeyShortcuts(keys, platform) {
48
48
  return keys.map((key) => ARIA_KEYS[key]?.[platform] ?? (key.length === 1 ? key.toUpperCase() : key)).join("+");
49
49
  }
50
50
  const isKeyShortcut = (value) => Array.isArray(value) && value.length > 0 && value.every((key) => typeof key === "string");
51
+ function spokenKeys(keys, platform, keyLabels) {
52
+ return keys.map((key) => keyLabels?.[key] ?? KEYS[key]?.[platform]?.[1] ?? key).join("+");
53
+ }
51
54
  function KbdCombo({ keys, platform, keyLabels, className, ...props }) {
52
55
  const active = useKbdPlatform(platform);
53
- const parts = keys.map((key) => {
54
- const known = KEYS[key]?.[active];
55
- return { glyph: known?.[0] ?? key, spoken: keyLabels?.[key] ?? known?.[1] ?? key };
56
- });
57
56
  return /* @__PURE__ */ jsxs("span", { className: cx("uix-kbd-combo", className), ...props, children: [
58
- /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: parts.map((p) => p.spoken).join("+") }),
59
- /* @__PURE__ */ jsx("span", { className: "uix-kbd-combo__keys", "aria-hidden": "true", children: parts.map((p, i) => /* @__PURE__ */ jsx("kbd", { className: "uix-kbd", children: p.glyph }, `${keys[i]}-${i}`)) })
57
+ /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: spokenKeys(keys, active, keyLabels) }),
58
+ /* @__PURE__ */ jsx("span", { className: "uix-kbd-combo__keys", "aria-hidden": "true", children: keys.map((key, i) => /* @__PURE__ */ jsx("kbd", { className: "uix-kbd", children: KEYS[key]?.[active]?.[0] ?? key }, `${key}-${i}`)) })
60
59
  ] });
61
60
  }
62
61
  export {
@@ -64,5 +63,6 @@ export {
64
63
  KbdCombo,
65
64
  ariaKeyShortcuts,
66
65
  isKeyShortcut,
66
+ spokenKeys,
67
67
  useKbdPlatform
68
68
  };
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { ReactNode } from 'react';
2
+ import { ReactNode, KeyboardEvent } from 'react';
3
3
 
4
4
  interface LightboxItem {
5
5
  src: string;
@@ -22,6 +22,21 @@ interface LightboxLabels {
22
22
  zoomOut: string;
23
23
  }
24
24
  declare const DEFAULT_LIGHTBOX_LABELS: LightboxLabels;
25
+ /** What `renderOverlay` receives (HAR-1826). */
26
+ interface LightboxOverlayContext {
27
+ /** The item on show (0-based) and the item itself. */
28
+ index: number;
29
+ item: LightboxItem;
30
+ /** The rendered `<img>` or `<video>`; `naturalWidth` / `videoWidth` give the bitmap size. */
31
+ media: HTMLImageElement | HTMLVideoElement | null;
32
+ /**
33
+ * The overlay's size in CSS pixels. The overlay covers exactly the picture (not the letterbox
34
+ * around a fitted one), so a point at `(x, y)` in it is bitmap pixel
35
+ * `(x / width * naturalWidth, y / height * naturalHeight)`. 0 until the media has loaded.
36
+ */
37
+ width: number;
38
+ height: number;
39
+ }
25
40
  interface LightboxProps {
26
41
  items: readonly LightboxItem[];
27
42
  /** The item on show (0-based). */
@@ -33,13 +48,34 @@ interface LightboxProps {
33
48
  loop?: boolean;
34
49
  labels?: Partial<LightboxLabels>;
35
50
  className?: string;
51
+ /**
52
+ * Buttons for the toolbar beside Close (HAR-1826), e.g. "Hide area" and "Undo" on a viewer
53
+ * that redacts a screenshot. Tab order: these, then Close. They sit inside the dialog.
54
+ */
55
+ actions?: ReactNode;
56
+ /**
57
+ * Content laid over the picture, in the picture's own box: it moves and scales with the
58
+ * fit / actual-size toggle. Pointer events on it never swipe or page, so it can draw. Each
59
+ * child positions itself (`position: absolute` in `%`, or px of `width` / `height`).
60
+ */
61
+ renderOverlay?: (context: LightboxOverlayContext) => ReactNode;
62
+ /**
63
+ * Runs first for every key in the viewer. Call `event.preventDefault()` to claim the key:
64
+ * the viewer then neither pages (arrows, Home, End) nor closes (Escape). For a draft that
65
+ * arrow keys move and Escape cancels.
66
+ */
67
+ onKeyDown?: (event: KeyboardEvent<HTMLDialogElement>) => void;
68
+ /** Escape only: `event.preventDefault()` keeps the viewer open. */
69
+ onEscapeKeyDown?: (event: KeyboardEvent<HTMLDialogElement>) => void;
36
70
  }
37
71
  /**
38
72
  * A full-screen media viewer over `.uix-lightbox` (HAR-1367; TENSOR C12 `capture-viewer.tsx`,
39
73
  * MOTUS C-8 programme gallery): previous/next buttons, ←/→ and Home/End, swipe on touch, an
40
- * announced "n of total" counter, a caption, and a fit/actual-size toggle. Native `<dialog>`,
41
- * so focus stays inside and Escape closes; focus returns to the control that opened it.
74
+ * announced "n of total" counter (not for a single item), a caption, and a fit/actual-size
75
+ * toggle. Native `<dialog>`, so focus stays inside and Escape closes; focus returns to the
76
+ * control that opened it. An annotating viewer (HAR-1826) adds toolbar `actions`, draws in
77
+ * `renderOverlay`, and claims Escape and the arrows with `onKeyDown` while a draft is open.
42
78
  */
43
- declare function Lightbox({ items, index, onIndexChange, open, onClose, loop, labels: labelOverrides, className }: LightboxProps): react.JSX.Element;
79
+ declare function Lightbox({ items, index, onIndexChange, open, onClose, loop, labels: labelOverrides, className, actions, renderOverlay, onKeyDown: onKeyDownProp, onEscapeKeyDown, }: LightboxProps): react.JSX.Element;
44
80
 
45
- export { DEFAULT_LIGHTBOX_LABELS, Lightbox, type LightboxItem, type LightboxLabels, type LightboxProps };
81
+ export { DEFAULT_LIGHTBOX_LABELS, Lightbox, type LightboxItem, type LightboxLabels, type LightboxOverlayContext, type LightboxProps };