@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
@@ -24,6 +24,13 @@ interface DateRangePickerLabels {
24
24
  fieldStart: string;
25
25
  /** Field mode: the trigger's text for a range; `{start}`, `{end}`. */
26
26
  fieldRange: string;
27
+ /**
28
+ * The picker's name: the heading of the inline months, the field's hidden name and its popover's
29
+ * `aria-label` (HAR-2432). The `label` prop wins.
30
+ */
31
+ label: string;
32
+ /** Shown when the range is invalid (an end before the start, or `invalid`). The `invalidMessage` prop wins. */
33
+ invalidMessage: string;
27
34
  }
28
35
  declare const DEFAULT_DATE_RANGE_PICKER_LABELS: DateRangePickerLabels;
29
36
  interface DateRangePickerProps {
@@ -35,9 +42,12 @@ interface DateRangePickerProps {
35
42
  min?: string;
36
43
  max?: string;
37
44
  isDateDisabled?: (date: string) => boolean;
45
+ /** Default: `UixLabelsProvider` `dateFormat.locale`, then the viewer's. */
38
46
  locale?: string;
47
+ /** Default: `labels.label` ("Choose date range"). */
39
48
  label?: string;
40
49
  invalid?: boolean;
50
+ /** Default: `labels.invalidMessage`. */
41
51
  invalidMessage?: string;
42
52
  disabled?: boolean;
43
53
  className?: string;
@@ -57,7 +67,7 @@ interface DateRangePickerProps {
57
67
  * bars. It closes once the range has both ends; Escape closes it and returns focus.
58
68
  */
59
69
  mode?: 'inline' | 'field';
60
- /** Field mode: how the trigger writes a date. Default: the locale's numeric date. */
70
+ /** Field mode: how the trigger writes a date. Default: `UixLabelsProvider` `dateFormat.formatDate`, then the locale's numeric date. */
61
71
  formatDate?: (date: string) => string;
62
72
  /** Field mode: lands on the trigger, so a `<label htmlFor>` or `<Field>` names it. */
63
73
  id?: string;
@@ -74,6 +84,6 @@ interface DateRangePickerProps {
74
84
  * Controlled date-range picker using the UIx calendar language: two months inline, or with
75
85
  * `mode="field"` a form control that opens them in a popover.
76
86
  */
77
- declare function DateRangePicker({ labels: labelOverrides, ...props }: DateRangePickerProps): react.JSX.Element;
87
+ declare function DateRangePicker({ labels: labelOverrides, ...rest }: DateRangePickerProps): react.JSX.Element;
78
88
 
79
89
  export { DEFAULT_DATE_RANGE_PICKER_LABELS, DateRangePicker, type DateRangePickerLabels, type DateRangePickerProps };
@@ -21,7 +21,9 @@ const DEFAULT_DATE_RANGE_PICKER_LABELS = {
21
21
  inRange: "in selected range",
22
22
  placeholder: "Choose dates",
23
23
  fieldStart: "{start} \u2013",
24
- fieldRange: "{start} \u2013 {end}"
24
+ fieldRange: "{start} \u2013 {end}",
25
+ label: "Choose date range",
26
+ invalidMessage: "The selected date range is invalid."
25
27
  };
26
28
  const monthLabel = (date, locale) => new Intl.DateTimeFormat(locale, { month: "long", year: "numeric", timeZone: "UTC" }).format(/* @__PURE__ */ new Date(`${date}T00:00:00Z`));
27
29
  const dateLabel = (date, locale) => new Intl.DateTimeFormat(locale, { dateStyle: "full", timeZone: "UTC" }).format(/* @__PURE__ */ new Date(`${date}T00:00:00Z`));
@@ -35,9 +37,9 @@ function RangeCalendar({
35
37
  max,
36
38
  isDateDisabled,
37
39
  locale,
38
- label = "Choose date range",
40
+ label: labelProp,
39
41
  invalid,
40
- invalidMessage = "The selected date range is invalid.",
42
+ invalidMessage: invalidMessageProp,
41
43
  disabled,
42
44
  className,
43
45
  words: labels,
@@ -46,6 +48,8 @@ function RangeCalendar({
46
48
  autoFocus = false
47
49
  }) {
48
50
  const id = useId();
51
+ const label = labelProp ?? labels.label;
52
+ const invalidMessage = invalidMessageProp ?? labels.invalidMessage;
49
53
  const weekdays = useMemo(() => weekdayNames(locale, weekStartsOn), [locale, weekStartsOn]);
50
54
  const [internalMonth, setInternalMonth] = useState(() => startOfMonth(controlledMonth ?? value.start ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)));
51
55
  const visibleMonth = startOfMonth(controlledMonth ?? internalMonth);
@@ -145,9 +149,10 @@ function RangeCalendar({
145
149
  rangeInvalid && /* @__PURE__ */ jsx("p", { className: "uix-field__error", role: "alert", children: invalidMessage })
146
150
  ] });
147
151
  }
148
- function DateRangePicker({ labels: labelOverrides, ...props }) {
152
+ function DateRangePicker({ labels: labelOverrides, ...rest }) {
149
153
  const uixLabels = useUixLabels();
150
154
  const words = { ...DEFAULT_DATE_RANGE_PICKER_LABELS, ...uixLabels.dateRangePicker, ...labelOverrides };
155
+ const props = { ...rest, locale: rest.locale ?? uixLabels.dateFormat?.locale, formatDate: rest.formatDate ?? uixLabels.dateFormat?.formatDate };
151
156
  if (props.mode === "field") return /* @__PURE__ */ jsx(DateRangeField, { ...props, words });
152
157
  return /* @__PURE__ */ jsx(RangeCalendar, { ...props, words });
153
158
  }
@@ -156,7 +161,7 @@ function DateRangeField(props) {
156
161
  value,
157
162
  onChange,
158
163
  locale,
159
- label = "Choose date range",
164
+ label: labelProp,
160
165
  invalid,
161
166
  disabled,
162
167
  className,
@@ -170,12 +175,13 @@ function DateRangeField(props) {
170
175
  "aria-invalid": ariaInvalid
171
176
  } = props;
172
177
  const uid = useId();
178
+ const label = labelProp ?? words.label;
173
179
  const id = idProp ?? `${uid}-trigger`;
174
180
  const popoverId = `${uid}-months`;
175
181
  const valueId = `${uid}-value`;
176
182
  const anchorRef = useRef(null);
177
183
  const triggerRef = useRef(null);
178
- const { open, hide, triggerProps } = useFieldPopover(popoverId, () => triggerRef.current?.focus({ preventScroll: true }), onOpenChange);
184
+ const { open, hide, onFieldKeyDown, triggerProps } = useFieldPopover(popoverId, () => triggerRef.current?.focus({ preventScroll: true }), onOpenChange);
179
185
  const [labelled, setLabelled] = useState(false);
180
186
  useEffect(() => {
181
187
  setLabelled((triggerRef.current?.labels?.length ?? 0) > 0);
@@ -189,12 +195,7 @@ function DateRangeField(props) {
189
195
  {
190
196
  ref: anchorRef,
191
197
  className: cx("uix-date-range-field", className),
192
- onKeyDown: (event) => {
193
- if (event.key !== "Escape" || !open) return;
194
- event.stopPropagation();
195
- event.preventDefault();
196
- hide();
197
- },
198
+ onKeyDown: onFieldKeyDown,
198
199
  children: [
199
200
  /* @__PURE__ */ jsxs(
200
201
  "button",
@@ -45,8 +45,14 @@ interface DetailPageProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {
45
45
  * modified click, no JS).
46
46
  */
47
47
  onTabSelect?: (id: string) => void;
48
+ /**
49
+ * `staff` is the staff type preset (HAR-1815): the title at h2 with its 1.15 leading, the back
50
+ * link an inline hushed link without an underline, and each metric's label an eyebrow over an
51
+ * h3-size, regular-weight, tabular value. `default` is the plain record page.
52
+ */
53
+ variant?: 'default' | 'staff';
48
54
  }
49
55
  /** Framework-neutral record-detail scaffold; routing and data policy stay with the application. */
50
- declare function DetailPage({ back, eyebrow, title, subtitle, actions, tabs, metrics, side, children, tabsLabel, renderLink, onTabSelect, className, ...props }: DetailPageProps): react.JSX.Element;
56
+ declare function DetailPage({ back, eyebrow, title, subtitle, actions, tabs, metrics, side, children, tabsLabel, renderLink, onTabSelect, variant, className, ...props }: DetailPageProps): react.JSX.Element;
51
57
 
52
58
  export { DetailPage, type DetailPageLinkProps, type DetailPageMetric, type DetailPageProps, type DetailPageTab };
@@ -18,10 +18,11 @@ function DetailPage({
18
18
  tabsLabel,
19
19
  renderLink = plainLink,
20
20
  onTabSelect,
21
+ variant = "default",
21
22
  className,
22
23
  ...props
23
24
  }) {
24
- return /* @__PURE__ */ jsxs("section", { className: cx("uix-detail-page", className), ...props, children: [
25
+ return /* @__PURE__ */ jsxs("section", { className: cx("uix-detail-page", variant === "staff" && "uix-detail-page--staff", className), ...props, children: [
25
26
  back && renderLink({ href: back.href, className: "uix-link uix-link--quiet uix-detail-page__back", children: back.label }),
26
27
  /* @__PURE__ */ jsx(PageHeader, { eyebrow, title, subtitle, actions }),
27
28
  metrics && metrics.length > 0 && /* @__PURE__ */ jsx("dl", { className: "uix-detail-page__metrics", children: metrics.map((metric) => /* @__PURE__ */ jsxs("div", { children: [
@@ -1,7 +1,15 @@
1
1
  import * as react from 'react';
2
- import { ReactNode, RefObject } from 'react';
2
+ import { ButtonHTMLAttributes, ReactNode, RefObject } from 'react';
3
3
  import { Placement } from '../overlay-position.js';
4
4
 
5
+ /**
6
+ * Attributes for a dialog's confirm button (HAR-1831): `data-*` (TENSOR puts `data-action-id`
7
+ * there), `id`, `className`, an `aria-describedby`. Its `onClick` runs first; `preventDefault()`
8
+ * there stops the confirm. `disabled` adds to the dialog's own reasons to disable it.
9
+ */
10
+ type ConfirmButtonProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'children' | 'type'> & {
11
+ [dataAttribute: `data-${string}`]: string | number | boolean | undefined;
12
+ };
5
13
  interface ConfirmDialogProps {
6
14
  open: boolean;
7
15
  title: ReactNode;
@@ -12,6 +20,11 @@ interface ConfirmDialogProps {
12
20
  closeLabel?: string;
13
21
  onConfirm: () => void | Promise<void>;
14
22
  onCancel: () => void;
23
+ /**
24
+ * The confirmed action is running: Confirm shows its loading state, Cancel is disabled, and
25
+ * Escape, the × and a press on Cancel do nothing until it settles (HAR-1831), so the person
26
+ * cannot walk away from an action that is still being carried out.
27
+ */
15
28
  pending?: boolean;
16
29
  destructive?: boolean;
17
30
  /**
@@ -34,21 +47,47 @@ interface ConfirmDialogProps {
34
47
  * this text, e.g. the record's name.
35
48
  */
36
49
  typeToConfirm?: string;
37
- /** Label of the type-to-confirm field. `{value}` is replaced by `typeToConfirm`. */
38
- typeToConfirmLabel?: string;
50
+ /**
51
+ * Label of the type-to-confirm field. In a string, `{value}` is replaced by `typeToConfirm`;
52
+ * pass a node for markup, e.g. the value in `<code>` (HAR-1831).
53
+ */
54
+ typeToConfirmLabel?: ReactNode;
55
+ /** Placeholder of the type-to-confirm field, e.g. the value itself (HAR-1831). */
56
+ typeToConfirmPlaceholder?: string;
57
+ /**
58
+ * Shown under the field (and its description) once the typed text stops matching; `{value}` is
59
+ * replaced by `typeToConfirm` (HAR-1831). Default: `UixLabelsProvider` `confirmDialog`, then
60
+ * "That does not match {value}."
61
+ */
62
+ typeToConfirmMismatch?: string;
39
63
  /** How the action is undone or compensated, shown as its own labelled block (TENSOR C2). */
40
64
  compensation?: ReactNode;
41
65
  /** Heading of the compensation block. Default "How to undo this". */
42
66
  compensationLabel?: string;
67
+ /** Attributes for the confirm button, e.g. `{ 'data-action-id': 'ci.delete' }` (HAR-1831). */
68
+ confirmProps?: ConfirmButtonProps;
43
69
  }
44
- /** Generic confirmation shell. Audit, authorization and compensation policy remain application-owned. */
45
- declare function ConfirmDialog({ open, title, description, confirmLabel, cancelLabel, closeLabel, onConfirm, onCancel, pending, destructive, children, error, initialFocus, tertiaryLabel, onTertiary, typeToConfirm, typeToConfirmLabel: typeToConfirmLabelProp, compensation, compensationLabel: compensationLabelProp, }: ConfirmDialogProps): react.JSX.Element;
70
+ /**
71
+ * Generic confirmation shell. Audit, authorization and compensation policy remain application-owned.
72
+ * The ref reaches the `<dialog>` (HAR-1831).
73
+ */
74
+ declare const ConfirmDialog: react.ForwardRefExoticComponent<ConfirmDialogProps & react.RefAttributes<HTMLDialogElement>>;
46
75
  interface PopconfirmProps {
47
76
  open: boolean;
48
77
  /** The control that asked for confirmation; the step is placed against it and focus returns to it. */
49
78
  anchor: RefObject<HTMLElement | null> | HTMLElement | null;
50
- title: ReactNode;
79
+ /**
80
+ * The question. Optional (HAR-1831): without it the step is named by its confirm button
81
+ * ("Delete"), which then has to say what it does.
82
+ */
83
+ title?: ReactNode;
51
84
  description?: ReactNode;
85
+ /**
86
+ * Content between the text and the buttons, e.g. a required reason field (HAR-1831). When it
87
+ * holds a field, that field has focus when the step opens, and the step takes its full width.
88
+ * Pair a required field with `confirmDisabled` until it is filled.
89
+ */
90
+ children?: ReactNode;
52
91
  confirmLabel: ReactNode;
53
92
  cancelLabel: ReactNode;
54
93
  onConfirm: () => void | Promise<void>;
@@ -56,17 +95,39 @@ interface PopconfirmProps {
56
95
  onCancel: () => void;
57
96
  pending?: boolean;
58
97
  destructive?: boolean;
98
+ /** Confirm cannot be pressed yet, e.g. while a required reason is empty (HAR-1831). */
99
+ confirmDisabled?: boolean;
100
+ /** Attributes for the confirm button, as on `ConfirmDialog` (HAR-1831). */
101
+ confirmProps?: ConfirmButtonProps;
59
102
  /** How the action is undone, under its own heading (TENSOR C2 "single_click" tier). */
60
103
  compensation?: ReactNode;
61
104
  compensationLabel?: string;
62
105
  placement?: Placement;
106
+ /** Default: `'alertdialog'` when `destructive`, else `'dialog'` (HAR-1831). */
107
+ role?: 'dialog' | 'alertdialog';
108
+ /**
109
+ * What names the step: its `title` (default when there is one) or its confirm button
110
+ * (default without a title) (HAR-1831).
111
+ */
112
+ labelledBy?: 'title' | 'confirm';
113
+ /**
114
+ * Called with the Escape keydown before the step cancels; `event.preventDefault()` keeps it open
115
+ * (HAR-1831). The step only takes an Escape that is its own: not while a popup inside it is open
116
+ * (a Select's list), not one meant for a dialog opened above it, and only the newest open step.
117
+ */
118
+ onEscapeKeyDown?: (event: KeyboardEvent) => void;
119
+ /**
120
+ * Cancel when the anchor has scrolled out of view (outside the viewport or a clipping ancestor),
121
+ * rather than leave the step floating over unrelated content (HAR-1831). Default `false`.
122
+ */
123
+ closeWhenAnchorHidden?: boolean;
63
124
  }
64
125
  /**
65
126
  * An anchored, single-step confirmation for low-risk actions (HAR-1356; TENSOR C2): a small
66
- * `role="dialog"` on the kit `Popover`, focus on Cancel, Escape or a press outside cancels,
67
- * and focus returns to the anchor. Use `ConfirmDialog` when the action is hard to undo.
127
+ * dialog on the kit `Popover`, focus on Cancel (or on a field in its content), Escape or a press
128
+ * outside cancels, and focus returns to the anchor. Use `ConfirmDialog` when the action is hard to undo.
68
129
  */
69
- declare function Popconfirm({ open, anchor, title, description, confirmLabel, cancelLabel, onConfirm, onCancel, pending, destructive, compensation, compensationLabel: compensationLabelProp, placement, }: PopconfirmProps): react.JSX.Element;
130
+ declare function Popconfirm({ open, anchor, title, description, children, confirmLabel, cancelLabel, onConfirm, onCancel, pending, destructive, confirmDisabled, confirmProps, compensation, compensationLabel: compensationLabelProp, placement, role, labelledBy, onEscapeKeyDown, closeWhenAnchorHidden, }: PopconfirmProps): react.JSX.Element;
70
131
  interface PromptDialogProps {
71
132
  open: boolean;
72
133
  title: ReactNode;
@@ -104,4 +165,4 @@ interface PromptDialogProps {
104
165
  }
105
166
  declare function PromptDialog({ open, title, description, inputLabel, defaultValue, placeholder, submitLabel, cancelLabel, closeLabel, onSubmit, onCancel, validate, pending, multiline, destructive, error: submitError, role, }: PromptDialogProps): react.JSX.Element;
106
167
 
107
- export { ConfirmDialog, type ConfirmDialogProps, Popconfirm, type PopconfirmProps, PromptDialog, type PromptDialogProps };
168
+ export { type ConfirmButtonProps, ConfirmDialog, type ConfirmDialogProps, Popconfirm, type PopconfirmProps, PromptDialog, type PromptDialogProps };
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- import { useEffect, useId, useRef, useState } from "react";
3
+ import { forwardRef, useEffect, useId, useRef, useState } from "react";
4
+ import { cx } from "../cx.js";
4
5
  import { Button } from "./Button.js";
5
6
  import { Input } from "./Input.js";
6
7
  import { Modal } from "./Modal.js";
@@ -8,8 +9,21 @@ import { Textarea } from "./Textarea.js";
8
9
  import { Popover } from "./Popover.js";
9
10
  import { fillLabel } from "../fill-label.js";
10
11
  import { resolveAnchor } from "../hooks/useAnchoredPosition.js";
12
+ import { isTopLayer, pushLayer } from "../overlay-stack.js";
11
13
  import { useUixLabels } from "../labels-context.js";
12
- function ConfirmDialog({
14
+ const confirmButtonProps = (extra, own) => {
15
+ const { onClick, disabled, className, ...rest } = extra ?? {};
16
+ return {
17
+ ...rest,
18
+ className,
19
+ disabled: own.disabled || !!disabled,
20
+ onClick: (event) => {
21
+ onClick?.(event);
22
+ if (!event.defaultPrevented) own.onConfirm();
23
+ }
24
+ };
25
+ };
26
+ const ConfirmDialog = forwardRef(function ConfirmDialog2({
13
27
  open,
14
28
  title,
15
29
  description,
@@ -18,7 +32,7 @@ function ConfirmDialog({
18
32
  closeLabel,
19
33
  onConfirm,
20
34
  onCancel,
21
- pending,
35
+ pending = false,
22
36
  destructive,
23
37
  children,
24
38
  error,
@@ -27,20 +41,26 @@ function ConfirmDialog({
27
41
  onTertiary,
28
42
  typeToConfirm,
29
43
  typeToConfirmLabel: typeToConfirmLabelProp,
44
+ typeToConfirmPlaceholder,
45
+ typeToConfirmMismatch: mismatchProp,
30
46
  compensation,
31
- compensationLabel: compensationLabelProp
32
- }) {
47
+ compensationLabel: compensationLabelProp,
48
+ confirmProps
49
+ }, ref) {
33
50
  const uixLabels = useUixLabels();
34
51
  const typeToConfirmLabel = typeToConfirmLabelProp ?? uixLabels.confirmDialog?.typeToConfirm ?? "Type {value} to confirm";
35
52
  const compensationLabel = compensationLabelProp ?? uixLabels.confirmDialog?.compensation ?? "How to undo this";
53
+ const mismatch = mismatchProp ?? uixLabels.confirmDialog?.typeToConfirmMismatch ?? "That does not match {value}.";
36
54
  const id = useId();
37
55
  const bodyId = `${id}-body`;
38
56
  const typedId = `${id}-typed`;
57
+ const typedMsgId = `${id}-typed-msg`;
39
58
  const cancelRef = useRef(null);
40
59
  const confirmRef = useRef(null);
41
60
  const typedRef = useRef(null);
42
61
  const [typed, setTyped] = useState("");
43
62
  const typedMatches = typeToConfirm == null || typed === typeToConfirm;
63
+ const typedWrong = typeToConfirm != null && typed !== "" && !typeToConfirm.startsWith(typed);
44
64
  const hasBody = description != null || children != null || compensation != null;
45
65
  useEffect(() => {
46
66
  if (open) setTyped("");
@@ -51,11 +71,19 @@ function ConfirmDialog({
51
71
  const target = typeToConfirm != null ? typedRef.current : focus === "cancel" ? cancelRef.current : focus === "confirm" ? confirmRef.current : null;
52
72
  target?.focus();
53
73
  }, [open, initialFocus, destructive, typeToConfirm]);
74
+ const confirm = confirmButtonProps(confirmProps, {
75
+ disabled: !typedMatches,
76
+ onConfirm: () => {
77
+ if (typedMatches && !pending) void onConfirm();
78
+ }
79
+ });
54
80
  return /* @__PURE__ */ jsx(
55
81
  Modal,
56
82
  {
83
+ ref,
57
84
  open,
58
85
  onClose: onCancel,
86
+ pending,
59
87
  title,
60
88
  closeLabel,
61
89
  role: "alertdialog",
@@ -63,9 +91,7 @@ function ConfirmDialog({
63
91
  footer: /* @__PURE__ */ jsxs(Fragment, { children: [
64
92
  tertiaryLabel != null && onTertiary && /* @__PURE__ */ jsx(Button, { type: "button", className: "uix-confirm__tertiary", onClick: () => void onTertiary(), disabled: pending, children: tertiaryLabel }),
65
93
  /* @__PURE__ */ jsx(Button, { ref: cancelRef, type: "button", onClick: onCancel, disabled: pending, children: cancelLabel }),
66
- /* @__PURE__ */ jsx(Button, { ref: confirmRef, type: "button", variant: destructive ? "danger" : "primary", onClick: () => {
67
- if (typedMatches) void onConfirm();
68
- }, loading: pending, disabled: !typedMatches, children: confirmLabel })
94
+ /* @__PURE__ */ jsx(Button, { ref: confirmRef, type: "button", variant: destructive ? "danger" : "primary", loading: pending, ...confirm, children: confirmLabel })
69
95
  ] }),
70
96
  children: /* @__PURE__ */ jsxs("div", { className: "uix-confirm", children: [
71
97
  hasBody && /* @__PURE__ */ jsxs("div", { id: bodyId, className: "uix-confirm__body", children: [
@@ -77,40 +103,80 @@ function ConfirmDialog({
77
103
  ] })
78
104
  ] }),
79
105
  typeToConfirm != null && /* @__PURE__ */ jsxs("div", { className: "uix-field", children: [
80
- /* @__PURE__ */ jsx("label", { className: "uix-field__label", htmlFor: typedId, children: fillLabel(typeToConfirmLabel, { value: typeToConfirm }) }),
81
- /* @__PURE__ */ jsx(Input, { ref: typedRef, id: typedId, value: typed, autoComplete: "off", spellCheck: false, onChange: (event) => setTyped(event.currentTarget.value) })
106
+ /* @__PURE__ */ jsx("label", { className: "uix-field__label", htmlFor: typedId, children: typeof typeToConfirmLabel === "string" ? fillLabel(typeToConfirmLabel, { value: typeToConfirm }) : typeToConfirmLabel }),
107
+ /* @__PURE__ */ jsx(
108
+ Input,
109
+ {
110
+ ref: typedRef,
111
+ id: typedId,
112
+ value: typed,
113
+ autoComplete: "off",
114
+ spellCheck: false,
115
+ autoCapitalize: "none",
116
+ autoCorrect: "off",
117
+ placeholder: typeToConfirmPlaceholder,
118
+ invalid: typedWrong,
119
+ disabled: pending,
120
+ "aria-describedby": typedWrong ? typedMsgId : void 0,
121
+ onChange: (event) => setTyped(event.currentTarget.value)
122
+ }
123
+ ),
124
+ /* @__PURE__ */ jsxs("div", { className: "uix-field__msg uix-confirm__typed-msg", children: [
125
+ /* @__PURE__ */ jsx("span", { className: "uix-field__error", "aria-hidden": "true", children: fillLabel(mismatch, { value: typeToConfirm }) }),
126
+ typedWrong && /* @__PURE__ */ jsx("span", { id: typedMsgId, className: "uix-field__error", role: "alert", children: fillLabel(mismatch, { value: typeToConfirm }) })
127
+ ] })
82
128
  ] }),
83
129
  error != null && /* @__PURE__ */ jsx("div", { className: "uix-confirm__error", role: "alert", children: error })
84
130
  ] })
85
131
  }
86
132
  );
87
- }
133
+ });
134
+ ConfirmDialog.displayName = "ConfirmDialog";
135
+ const FIELD = 'input:not([type="hidden"]):not(:disabled), textarea:not(:disabled), select:not(:disabled), [contenteditable="true"]';
136
+ const isOwnEscape = (event, token) => event.key === "Escape" && !event.defaultPrevented && !event.isComposing && event.keyCode !== 229 && isTopLayer(token);
88
137
  function Popconfirm({
89
138
  open,
90
139
  anchor,
91
140
  title,
92
141
  description,
142
+ children,
93
143
  confirmLabel,
94
144
  cancelLabel,
95
145
  onConfirm,
96
146
  onCancel,
97
147
  pending,
98
148
  destructive,
149
+ confirmDisabled = false,
150
+ confirmProps,
99
151
  compensation,
100
152
  compensationLabel: compensationLabelProp,
101
- placement = "bottom-start"
153
+ placement = "bottom-start",
154
+ role,
155
+ labelledBy,
156
+ onEscapeKeyDown,
157
+ closeWhenAnchorHidden = false
102
158
  }) {
103
159
  const uixLabels = useUixLabels();
104
160
  const compensationLabel = compensationLabelProp ?? uixLabels.confirmDialog?.compensation ?? "How to undo this";
105
161
  const id = useId();
106
162
  const panelId = `${id}-popconfirm`;
163
+ const confirmId = confirmProps?.id ?? `${id}-confirm`;
107
164
  const cancelRef = useRef(null);
108
- const onCancelRef = useRef(onCancel);
109
- onCancelRef.current = onCancel;
165
+ const contentRef = useRef(null);
166
+ const latest = useRef({ onCancel, onEscapeKeyDown });
167
+ latest.current = { onCancel, onEscapeKeyDown };
168
+ const layer = useRef(null);
169
+ const [holdsInput, setHoldsInput] = useState(false);
170
+ const readContent = () => {
171
+ const fields = Array.from(contentRef.current?.querySelectorAll('input:not([type="hidden"]), textarea') ?? []);
172
+ setHoldsInput(fields.some((field) => field.value.trim() !== ""));
173
+ };
110
174
  const hasBody = description != null || compensation != null;
175
+ const hasTitle = title != null;
176
+ const nameBy = labelledBy ?? (hasTitle ? "title" : "confirm");
111
177
  useEffect(() => {
112
178
  const panel = document.getElementById(panelId);
113
- if (!panel) return;
179
+ if (!panel) return void 0;
114
180
  const shown = () => {
115
181
  try {
116
182
  return panel.matches(":popover-open");
@@ -127,7 +193,7 @@ function Popconfirm({
127
193
  }
128
194
  if (focusInside) resolveAnchor(anchor)?.focus();
129
195
  }
130
- return;
196
+ return void 0;
131
197
  }
132
198
  if (!shown()) {
133
199
  try {
@@ -135,25 +201,45 @@ function Popconfirm({
135
201
  } catch {
136
202
  }
137
203
  }
138
- cancelRef.current?.focus();
139
- const onKey = (event) => {
140
- if (event.key !== "Escape") return;
141
- event.preventDefault();
142
- event.stopPropagation();
143
- onCancelRef.current();
204
+ (contentRef.current?.querySelector(FIELD) ?? cancelRef.current)?.focus();
205
+ const token = {};
206
+ layer.current = token;
207
+ const pop = pushLayer(token);
208
+ const onDocumentKey = (event) => {
209
+ if (panel.contains(event.target) || !isOwnEscape(event, token)) return;
210
+ if (panel.querySelector('[aria-expanded="true"][aria-haspopup]')) return;
211
+ const dialog = event.target?.closest?.("dialog");
212
+ if (dialog && !dialog.contains(panel)) return;
213
+ cancelOnEscape(event);
144
214
  };
145
215
  const onPointer = (event) => {
146
216
  const target = event.target;
147
217
  if (panel.contains(target) || resolveAnchor(anchor)?.contains(target)) return;
148
- onCancelRef.current();
218
+ if (!isTopLayer(token)) return;
219
+ latest.current.onCancel();
149
220
  };
150
- document.addEventListener("keydown", onKey, true);
221
+ document.addEventListener("keydown", onDocumentKey, true);
151
222
  document.addEventListener("pointerdown", onPointer, true);
152
223
  return () => {
153
- document.removeEventListener("keydown", onKey, true);
224
+ pop();
225
+ layer.current = null;
226
+ document.removeEventListener("keydown", onDocumentKey, true);
154
227
  document.removeEventListener("pointerdown", onPointer, true);
155
228
  };
156
229
  }, [open, anchor, panelId]);
230
+ const cancelOnEscape = (event) => {
231
+ latest.current.onEscapeKeyDown?.(event);
232
+ event.stopPropagation();
233
+ if (event.defaultPrevented) return;
234
+ event.preventDefault();
235
+ latest.current.onCancel();
236
+ };
237
+ const confirm = confirmButtonProps(confirmProps, {
238
+ disabled: confirmDisabled,
239
+ onConfirm: () => {
240
+ if (!pending) void onConfirm();
241
+ }
242
+ });
157
243
  return /* @__PURE__ */ jsxs(
158
244
  Popover,
159
245
  {
@@ -161,12 +247,25 @@ function Popconfirm({
161
247
  popover: "manual",
162
248
  anchor,
163
249
  placement,
164
- role: "dialog",
165
- "aria-labelledby": `${id}-title`,
250
+ role: role ?? (destructive ? "alertdialog" : "dialog"),
251
+ "aria-labelledby": nameBy === "title" && hasTitle ? `${id}-title` : confirmId,
166
252
  "aria-describedby": hasBody ? `${id}-body` : void 0,
167
- className: "uix-popconfirm",
253
+ className: cx("uix-popconfirm", children != null && "uix-popconfirm--content"),
254
+ closeWhenAnchorHidden: closeWhenAnchorHidden && !holdsInput,
255
+ onAnchorHidden: closeWhenAnchorHidden ? () => {
256
+ const panel = document.getElementById(panelId);
257
+ if (panel?.contains(document.activeElement)) resolveAnchor(anchor)?.focus({ preventScroll: true });
258
+ latest.current.onCancel();
259
+ } : void 0,
260
+ onKeyDown: (event) => {
261
+ const token = layer.current;
262
+ if (!token || !isOwnEscape(event.nativeEvent, token)) return;
263
+ cancelOnEscape(event.nativeEvent);
264
+ event.stopPropagation();
265
+ },
266
+ onInput: readContent,
168
267
  children: [
169
- /* @__PURE__ */ jsx("div", { id: `${id}-title`, className: "uix-popconfirm__title", children: title }),
268
+ hasTitle && /* @__PURE__ */ jsx("div", { id: `${id}-title`, className: "uix-popconfirm__title", children: title }),
170
269
  hasBody && /* @__PURE__ */ jsxs("div", { id: `${id}-body`, className: "uix-popconfirm__body", children: [
171
270
  description != null && /* @__PURE__ */ jsx("div", { children: description }),
172
271
  compensation != null && /* @__PURE__ */ jsxs("div", { className: "uix-confirm__compensation", children: [
@@ -174,9 +273,10 @@ function Popconfirm({
174
273
  /* @__PURE__ */ jsx("div", { children: compensation })
175
274
  ] })
176
275
  ] }),
276
+ children != null && /* @__PURE__ */ jsx("div", { ref: contentRef, className: "uix-popconfirm__content", children }),
177
277
  /* @__PURE__ */ jsxs("div", { className: "uix-popconfirm__actions", children: [
178
278
  /* @__PURE__ */ jsx(Button, { ref: cancelRef, type: "button", size: "sm", onClick: onCancel, disabled: pending, children: cancelLabel }),
179
- /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: destructive ? "danger" : "primary", onClick: () => void onConfirm(), loading: pending, children: confirmLabel })
279
+ /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: destructive ? "danger" : "primary", loading: pending, ...confirm, id: confirmId, children: confirmLabel })
180
280
  ] })
181
281
  ]
182
282
  }
@@ -1,4 +1,5 @@
1
1
  import * as react from 'react';
2
+ import { ReactNode } from 'react';
2
3
  import { JsonValue } from '../json-value.js';
3
4
  import { DiffEntry, DiffResolution } from '../diff-model.js';
4
5
 
@@ -56,8 +57,27 @@ interface DiffViewerProps {
56
57
  */
57
58
  controlSize?: 'sm' | 'md' | 'lg';
58
59
  className?: string;
60
+ /**
61
+ * The name of an entry for people (HAR-1369): "First author" for `$.authors[0].name`. Shown
62
+ * instead of the path; a string also names the entry's regions and buttons (anything else
63
+ * leaves the path there). Default: the path.
64
+ */
65
+ labelFor?: (path: string) => ReactNode;
66
+ /**
67
+ * How a value shows: a name, a date, a chip, instead of `JSON.stringify`. `undefined` is a
68
+ * value that is not there in that version. Default: the JSON, or `labels.notPresent`.
69
+ */
70
+ renderValue?: (value: JsonValue | undefined, path: string, side: DiffSide) => ReactNode;
71
+ /** Where a version's value came from, shown under it ("Edited by Ana, 12 Oct"). Nothing for none. */
72
+ provenance?: (path: string, side: DiffSide) => ReactNode;
59
73
  }
60
- /** Three-way configuration diff with per-entry keyboard-operable resolution. */
61
- declare function DiffViewer({ base, current, incoming, entries: providedEntries, resolutions: controlledResolutions, onResolutionChange, labels: labelOverrides, controlSize, className, }: DiffViewerProps): react.JSX.Element;
74
+ /** One of the three versions a `DiffViewer` entry compares. */
75
+ type DiffSide = 'base' | 'current' | 'incoming';
76
+ /**
77
+ * Three-way configuration diff with per-entry keyboard-operable resolution. For people who do
78
+ * not read JSON paths (HAR-1369, MOTUS B-A9): `labelFor` names an entry, `renderValue` shows a
79
+ * value as a name, a date or a chip, and `provenance` says where each version came from.
80
+ */
81
+ declare function DiffViewer({ base, current, incoming, entries: providedEntries, resolutions: controlledResolutions, onResolutionChange, labels: labelOverrides, controlSize, className, labelFor, renderValue, provenance, }: DiffViewerProps): react.JSX.Element;
62
82
 
63
- export { DEFAULT_DIFF_VIEWER_LABELS, DiffViewer, type DiffViewerLabels, type DiffViewerProps };
83
+ export { DEFAULT_DIFF_VIEWER_LABELS, type DiffSide, DiffViewer, type DiffViewerLabels, type DiffViewerProps };