@tensor_1/react 2.31.0 → 2.33.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 (117) hide show
  1. package/README.md +367 -0
  2. package/dist/SchedulingCalendar-C4XVbqZr.d.ts +339 -0
  3. package/dist/attachment-labels.d.ts +16 -0
  4. package/dist/attachment-labels.js +9 -0
  5. package/dist/calendar-model.d.ts +141 -1
  6. package/dist/calendar-model.js +200 -11
  7. package/dist/chart-preset.cjs +127 -7
  8. package/dist/chart-preset.d.cts +2 -2
  9. package/dist/chart-preset.d.ts +1 -0
  10. package/dist/chart-preset.js +6 -1
  11. package/dist/chart-theme-Cl0IrkKQ.d.cts +112 -0
  12. package/dist/chart-theme.d.ts +51 -0
  13. package/dist/chart-theme.js +99 -0
  14. package/dist/chart.cjs +127 -7
  15. package/dist/chart.d.cts +2 -2
  16. package/dist/chart.d.ts +1 -0
  17. package/dist/chart.js +6 -2
  18. package/dist/components/Alert.d.ts +17 -1
  19. package/dist/components/Alert.js +9 -4
  20. package/dist/components/AlertDismiss.d.ts +14 -0
  21. package/dist/components/AlertDismiss.js +11 -0
  22. package/dist/components/Attachment.d.ts +15 -12
  23. package/dist/components/Attachment.js +17 -17
  24. package/dist/components/AttachmentParts.d.ts +24 -0
  25. package/dist/components/AttachmentParts.js +20 -0
  26. package/dist/components/Chart.d.ts +1 -0
  27. package/dist/components/Chart.js +6 -1
  28. package/dist/components/ChartCore.d.ts +6 -1
  29. package/dist/components/ChartCore.js +23 -5
  30. package/dist/components/Chip.d.ts +17 -2
  31. package/dist/components/Chip.js +35 -11
  32. package/dist/components/ChipRemove.d.ts +17 -0
  33. package/dist/components/ChipRemove.js +13 -0
  34. package/dist/components/CollapsibleParts.d.ts +29 -0
  35. package/dist/components/CollapsibleParts.js +20 -0
  36. package/dist/components/CollapsibleSection.d.ts +30 -3
  37. package/dist/components/CollapsibleSection.js +7 -11
  38. package/dist/components/CollapsibleSectionState.d.ts +1 -1
  39. package/dist/components/CollapsibleSectionState.js +54 -27
  40. package/dist/components/DashboardGrid.d.ts +60 -0
  41. package/dist/components/DashboardGrid.js +40 -0
  42. package/dist/components/DateRangePicker.d.ts +7 -1
  43. package/dist/components/DateRangePicker.js +5 -1
  44. package/dist/components/DescriptionList.d.ts +12 -1
  45. package/dist/components/DescriptionList.js +5 -5
  46. package/dist/components/Dialogs.d.ts +24 -3
  47. package/dist/components/Dialogs.js +74 -16
  48. package/dist/components/FileUpload.d.ts +10 -2
  49. package/dist/components/FileUpload.js +10 -4
  50. package/dist/components/FilterEditor.d.ts +4 -0
  51. package/dist/components/FilterEditor.js +13 -2
  52. package/dist/components/Heartbeat.d.ts +31 -0
  53. package/dist/components/Heartbeat.js +28 -0
  54. package/dist/components/Menu.d.ts +53 -5
  55. package/dist/components/Menu.js +104 -4
  56. package/dist/components/Meter.d.ts +7 -3
  57. package/dist/components/Meter.js +2 -1
  58. package/dist/components/Popover.d.ts +21 -2
  59. package/dist/components/Popover.js +94 -17
  60. package/dist/components/Prose.d.ts +6 -2
  61. package/dist/components/Prose.js +1 -1
  62. package/dist/components/RuleBuilder.d.ts +52 -4
  63. package/dist/components/RuleBuilder.js +109 -24
  64. package/dist/components/SchedulingCalendar.d.ts +4 -110
  65. package/dist/components/SchedulingCalendar.js +299 -63
  66. package/dist/components/SchedulingTimeGrid.d.ts +4 -0
  67. package/dist/components/SchedulingTimeGrid.js +521 -0
  68. package/dist/components/SchedulingTimeline.d.ts +2 -1
  69. package/dist/components/Segmented.d.ts +17 -3
  70. package/dist/components/Segmented.js +59 -5
  71. package/dist/components/Select.d.ts +66 -2
  72. package/dist/components/Select.js +800 -10
  73. package/dist/components/Spinner.d.ts +2 -1
  74. package/dist/components/Spinner.js +1 -1
  75. package/dist/components/StatusPill.d.ts +8 -2
  76. package/dist/components/StatusPill.js +2 -2
  77. package/dist/components/Steps.d.ts +28 -4
  78. package/dist/components/Steps.js +46 -16
  79. package/dist/components/TableControls.js +4 -3
  80. package/dist/components/Tabs.d.ts +7 -2
  81. package/dist/components/Tabs.js +14 -1
  82. package/dist/components/TextDiff.d.ts +49 -0
  83. package/dist/components/TextDiff.js +124 -0
  84. package/dist/{emoji-image-6mPZ15Zy.d.cts → emoji-image-DjCKpdwE.d.cts} +1 -1
  85. package/dist/emoji.cjs +169 -27
  86. package/dist/emoji.d.cts +3 -3
  87. package/dist/file-model.d.ts +22 -3
  88. package/dist/file-model.js +6 -4
  89. package/dist/filter-model.d.ts +26 -2
  90. package/dist/filter-model.js +13 -1
  91. package/dist/hooks/useAnchoredPosition.d.ts +24 -1
  92. package/dist/hooks/useAnchoredPosition.js +58 -8
  93. package/dist/hooks/useVirtualRows.d.ts +17 -2
  94. package/dist/hooks/useVirtualRows.js +6 -5
  95. package/dist/icons/Icon.d.ts +14 -14
  96. package/dist/index.cjs +4888 -1714
  97. package/dist/index.d.cts +1871 -658
  98. package/dist/index.d.ts +16 -9
  99. package/dist/index.js +67 -4
  100. package/dist/labels-context.d.ts +29 -1
  101. package/dist/{overlay-position-Cjv5E7mn.d.cts → overlay-position-2Afke51l.d.cts} +20 -0
  102. package/dist/overlay-position.d.ts +20 -0
  103. package/dist/overlay-position.js +17 -2
  104. package/dist/rich-text.cjs +169 -27
  105. package/dist/rich-text.d.cts +3 -3
  106. package/dist/rule-builder-model.d.ts +34 -2
  107. package/dist/rule-builder-model.js +48 -12
  108. package/dist/scheduling-calendar-model.d.ts +169 -0
  109. package/dist/scheduling-calendar-model.js +176 -0
  110. package/dist/select-model.d.ts +80 -0
  111. package/dist/select-model.js +109 -0
  112. package/dist/table-engine.d.ts +7 -0
  113. package/dist/table-engine.js +2 -1
  114. package/dist/text-diff-model.d.ts +71 -0
  115. package/dist/text-diff-model.js +261 -0
  116. package/package.json +1 -1
  117. package/dist/ChartCore-DzWe10bN.d.cts +0 -59
@@ -16,6 +16,7 @@ const DEFAULT_DATE_RANGE_PICKER_LABELS = {
16
16
  rangeEnd: "end of selected range",
17
17
  inRange: "in selected range"
18
18
  };
19
+ const localDateKey = (now = /* @__PURE__ */ new Date()) => `${now.getFullYear().toString().padStart(4, "0")}-${(now.getMonth() + 1).toString().padStart(2, "0")}-${now.getDate().toString().padStart(2, "0")}`;
19
20
  const monthLabel = (date, locale) => new Intl.DateTimeFormat(locale, { month: "long", year: "numeric", timeZone: "UTC" }).format(/* @__PURE__ */ new Date(`${date}T00:00:00Z`));
20
21
  const dateLabel = (date, locale) => new Intl.DateTimeFormat(locale, { dateStyle: "full", timeZone: "UTC" }).format(/* @__PURE__ */ new Date(`${date}T00:00:00Z`));
21
22
  const weekdayNames = (locale) => {
@@ -37,7 +38,8 @@ function DateRangePicker({
37
38
  invalidMessage = "The selected date range is invalid.",
38
39
  disabled,
39
40
  className,
40
- labels: labelOverrides
41
+ labels: labelOverrides,
42
+ today
41
43
  }) {
42
44
  const id = useId();
43
45
  const uixLabels = useUixLabels();
@@ -53,6 +55,7 @@ function DateRangePicker({
53
55
  const month = startOfMonth(addCalendarMonths(visibleMonth, index));
54
56
  return { month, days: buildMonthGrid(month).filter((day) => day.inMonth) };
55
57
  }), [visibleMonth, count]);
58
+ const todayDate = today === void 0 ? localDateKey() : today;
56
59
  const rangeInvalid = invalid || !!value.start && !!value.end && value.start > value.end;
57
60
  useEffect(() => {
58
61
  const first = calendars[0]?.days[0]?.date;
@@ -123,6 +126,7 @@ function DateRangePicker({
123
126
  "data-in-range": isDateInRange(day.date, value) || void 0,
124
127
  "aria-pressed": edge != null || isDateInRange(day.date, value),
125
128
  "aria-label": `${dateLabel(day.date, locale)}${edge ? `, ${edge === "start" ? labels.rangeStart : labels.rangeEnd}` : isDateInRange(day.date, value) ? `, ${labels.inRange}` : ""}`,
129
+ "aria-current": day.date === todayDate ? "date" : void 0,
126
130
  tabIndex: activeDate === day.date ? 0 : -1,
127
131
  disabled: unavailable,
128
132
  onFocus: () => setActiveDate(day.date),
@@ -26,6 +26,10 @@ interface DescriptionListItem {
26
26
  id?: string;
27
27
  term: ReactNode;
28
28
  description: ReactNode;
29
+ /** Attributes for this row's `<dt>` (an `id` to anchor to, `data-*`). */
30
+ termProps?: HTMLAttributes<HTMLElement>;
31
+ /** Attributes for this row's `<dd>`. */
32
+ descriptionProps?: HTMLAttributes<HTMLElement>;
29
33
  }
30
34
  interface DescriptionListProps extends HTMLAttributes<HTMLDListElement> {
31
35
  items?: DescriptionListItem[];
@@ -35,12 +39,19 @@ declare function DescriptionList({ items, children, className, ...props }: Descr
35
39
  interface DescriptionItemProps {
36
40
  term: ReactNode;
37
41
  children?: ReactNode;
42
+ /**
43
+ * Attributes for the `<dt>`: an `id` a deep link or `aria-describedby` points at, `data-*`,
44
+ * a `className` (HAR-1628). The pair has no wrapper element, so this is where they go.
45
+ */
46
+ termProps?: HTMLAttributes<HTMLElement>;
47
+ /** Attributes for the `<dd>`. */
48
+ descriptionProps?: HTMLAttributes<HTMLElement>;
38
49
  }
39
50
  /**
40
51
  * One hand-authored row. Renders a `<dt>`/`<dd>` fragment so the pair lands as
41
52
  * direct grid children of the enclosing `<DescriptionList>` (never wrap it in
42
53
  * an element — that breaks the two-column alignment).
43
54
  */
44
- declare function DescriptionItem({ term, children }: DescriptionItemProps): react.JSX.Element;
55
+ declare function DescriptionItem({ term, children, termProps, descriptionProps }: DescriptionItemProps): react.JSX.Element;
45
56
 
46
57
  export { DescriptionItem, type DescriptionItemProps, DescriptionList, type DescriptionListItem, type DescriptionListProps };
@@ -4,16 +4,16 @@ import { cx } from "../cx.js";
4
4
  function DescriptionList({ items, children, className, ...props }) {
5
5
  return /* @__PURE__ */ jsxs("dl", { className: cx("uix-dl", className), ...props, children: [
6
6
  items?.map((item, i) => /* @__PURE__ */ jsxs(Fragment2, { children: [
7
- /* @__PURE__ */ jsx("dt", { children: item.term }),
8
- /* @__PURE__ */ jsx("dd", { children: item.description })
7
+ /* @__PURE__ */ jsx("dt", { ...item.termProps, children: item.term }),
8
+ /* @__PURE__ */ jsx("dd", { ...item.descriptionProps, children: item.description })
9
9
  ] }, item.id ?? i)),
10
10
  children
11
11
  ] });
12
12
  }
13
- function DescriptionItem({ term, children }) {
13
+ function DescriptionItem({ term, children, termProps, descriptionProps }) {
14
14
  return /* @__PURE__ */ jsxs(Fragment, { children: [
15
- /* @__PURE__ */ jsx("dt", { children: term }),
16
- /* @__PURE__ */ jsx("dd", { children })
15
+ /* @__PURE__ */ jsx("dt", { ...termProps, children: term }),
16
+ /* @__PURE__ */ jsx("dd", { ...descriptionProps, children })
17
17
  ] });
18
18
  }
19
19
  export {
@@ -8,7 +8,8 @@ interface ConfirmDialogProps {
8
8
  description?: ReactNode;
9
9
  confirmLabel: ReactNode;
10
10
  cancelLabel: ReactNode;
11
- closeLabel: string;
11
+ /** Accessible name of the close button. Default: `UixLabelsProvider` `modal.close`, then "Close dialog". */
12
+ closeLabel?: string;
12
13
  onConfirm: () => void | Promise<void>;
13
14
  onCancel: () => void;
14
15
  pending?: boolean;
@@ -75,12 +76,32 @@ interface PromptDialogProps {
75
76
  placeholder?: string;
76
77
  submitLabel: ReactNode;
77
78
  cancelLabel: ReactNode;
78
- closeLabel: string;
79
+ /** Accessible name of the close button. Default: `UixLabelsProvider` `modal.close`, then "Close dialog". */
80
+ closeLabel?: string;
79
81
  onSubmit: (value: string) => void | Promise<void>;
80
82
  onCancel: () => void;
81
83
  validate?: (value: string) => ReactNode | undefined;
82
84
  pending?: boolean;
85
+ /**
86
+ * Ask for a longer answer in a textarea (a reason, a note); a number sets its rows, `true`
87
+ * is 4. Enter adds a line there, so the form is sent with the submit button or Ctrl/⌘+Enter
88
+ * (HAR-1614).
89
+ */
90
+ multiline?: boolean | number;
91
+ /**
92
+ * The answer confirms something that cannot be taken back (a forced import): the submit
93
+ * button is the danger button and the dialog is an `alertdialog`.
94
+ */
95
+ destructive?: boolean;
96
+ /**
97
+ * Why the last submit failed, from the server. Shown under the field (`role="alert"`) and
98
+ * tied to it; the dialog stays open. A message from `validate` takes its place while the
99
+ * value is invalid.
100
+ */
101
+ error?: ReactNode;
102
+ /** Default: `'alertdialog'` when `destructive`, else `'dialog'`. */
103
+ role?: 'dialog' | 'alertdialog';
83
104
  }
84
- declare function PromptDialog({ open, title, description, inputLabel, defaultValue, placeholder, submitLabel, cancelLabel, closeLabel, onSubmit, onCancel, validate, pending }: PromptDialogProps): react.JSX.Element;
105
+ 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;
85
106
 
86
107
  export { ConfirmDialog, type ConfirmDialogProps, Popconfirm, type PopconfirmProps, PromptDialog, type PromptDialogProps };
@@ -4,6 +4,7 @@ import { useEffect, useId, useRef, useState } from "react";
4
4
  import { Button } from "./Button.js";
5
5
  import { Input } from "./Input.js";
6
6
  import { Modal } from "./Modal.js";
7
+ import { Textarea } from "./Textarea.js";
7
8
  import { Popover } from "./Popover.js";
8
9
  import { fillLabel } from "../fill-label.js";
9
10
  import { resolveAnchor } from "../hooks/useAnchoredPosition.js";
@@ -181,33 +182,90 @@ function Popconfirm({
181
182
  }
182
183
  );
183
184
  }
184
- function PromptDialog({ open, title, description, inputLabel, defaultValue = "", placeholder, submitLabel, cancelLabel, closeLabel, onSubmit, onCancel, validate, pending }) {
185
+ function PromptDialog({
186
+ open,
187
+ title,
188
+ description,
189
+ inputLabel,
190
+ defaultValue = "",
191
+ placeholder,
192
+ submitLabel,
193
+ cancelLabel,
194
+ closeLabel,
195
+ onSubmit,
196
+ onCancel,
197
+ validate,
198
+ pending,
199
+ multiline = false,
200
+ destructive = false,
201
+ error: submitError,
202
+ role
203
+ }) {
185
204
  const inputId = useId();
186
205
  const errorId = `${inputId}-error`;
206
+ const descriptionId = `${inputId}-description`;
207
+ const formRef = useRef(null);
208
+ const fieldRef = useRef(null);
187
209
  const [value, setValue] = useState(defaultValue);
188
- const [error, setError] = useState();
210
+ const [issue, setIssue] = useState();
211
+ const error = issue ?? submitError;
212
+ useEffect(() => {
213
+ if (open) fieldRef.current?.focus();
214
+ }, [open]);
189
215
  useEffect(() => {
190
216
  if (open) {
191
217
  setValue(defaultValue);
192
- setError(void 0);
218
+ setIssue(void 0);
193
219
  }
194
220
  }, [defaultValue, open]);
195
221
  const handleSubmit = (event) => {
196
222
  event.preventDefault();
197
- const issue = validate?.(value);
198
- setError(issue);
199
- if (issue == null) void onSubmit(value);
223
+ const found = validate?.(value);
224
+ setIssue(found);
225
+ if (found == null) void onSubmit(value);
200
226
  };
201
- return /* @__PURE__ */ jsx(Modal, { open, onClose: onCancel, title, closeLabel, children: /* @__PURE__ */ jsxs("form", { className: "uix-prompt", onSubmit: handleSubmit, children: [
202
- description != null && /* @__PURE__ */ jsx("div", { children: description }),
203
- /* @__PURE__ */ jsx("label", { className: "uix-field__label", htmlFor: inputId, children: inputLabel }),
204
- /* @__PURE__ */ jsx(Input, { id: inputId, value, placeholder, "aria-describedby": error ? errorId : void 0, invalid: error != null, onChange: (event) => setValue(event.currentTarget.value), autoFocus: true }),
205
- error != null && /* @__PURE__ */ jsx("div", { id: errorId, className: "uix-field__error", role: "alert", children: error }),
206
- /* @__PURE__ */ jsxs("div", { className: "uix-dialog__footer uix-prompt__actions", children: [
207
- /* @__PURE__ */ jsx(Button, { type: "button", onClick: onCancel, disabled: pending, children: cancelLabel }),
208
- /* @__PURE__ */ jsx(Button, { type: "submit", variant: "primary", loading: pending, children: submitLabel })
209
- ] })
210
- ] }) });
227
+ const field = {
228
+ id: inputId,
229
+ value,
230
+ placeholder,
231
+ "aria-describedby": error != null ? errorId : void 0,
232
+ invalid: error != null,
233
+ ref: fieldRef
234
+ };
235
+ return /* @__PURE__ */ jsx(
236
+ Modal,
237
+ {
238
+ open,
239
+ onClose: onCancel,
240
+ title,
241
+ closeLabel,
242
+ role: role ?? (destructive ? "alertdialog" : "dialog"),
243
+ "aria-describedby": description != null ? descriptionId : void 0,
244
+ children: /* @__PURE__ */ jsxs("form", { ref: formRef, className: "uix-prompt", onSubmit: handleSubmit, children: [
245
+ description != null && /* @__PURE__ */ jsx("div", { id: descriptionId, children: description }),
246
+ /* @__PURE__ */ jsx("label", { className: "uix-field__label", htmlFor: inputId, children: inputLabel }),
247
+ multiline ? /* @__PURE__ */ jsx(
248
+ Textarea,
249
+ {
250
+ ...field,
251
+ rows: typeof multiline === "number" ? multiline : 4,
252
+ onChange: (event) => setValue(event.currentTarget.value),
253
+ onKeyDown: (event) => {
254
+ if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) {
255
+ event.preventDefault();
256
+ formRef.current?.requestSubmit();
257
+ }
258
+ }
259
+ }
260
+ ) : /* @__PURE__ */ jsx(Input, { ...field, onChange: (event) => setValue(event.currentTarget.value) }),
261
+ error != null && /* @__PURE__ */ jsx("div", { id: errorId, className: "uix-field__error", role: "alert", children: error }),
262
+ /* @__PURE__ */ jsxs("div", { className: "uix-dialog__footer uix-prompt__actions", children: [
263
+ /* @__PURE__ */ jsx(Button, { type: "button", onClick: onCancel, disabled: pending, children: cancelLabel }),
264
+ /* @__PURE__ */ jsx(Button, { type: "submit", variant: destructive ? "danger" : "primary", loading: pending, children: submitLabel })
265
+ ] })
266
+ ] })
267
+ }
268
+ );
211
269
  }
212
270
  export {
213
271
  ConfirmDialog,
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { FileRejection } from '../file-model.js';
3
+ import { FileRejection, FileSizeFormatter } from '../file-model.js';
4
4
 
5
5
  type FileUploadStatus = 'queued' | 'uploading' | 'done' | 'error';
6
6
  interface FileUploadItem {
@@ -74,6 +74,14 @@ interface FileUploadProps {
74
74
  /** Second line of the drop zone, e.g. "PDF or images, up to 20 MB". */
75
75
  hint?: ReactNode;
76
76
  locale?: string;
77
+ /** Count sizes in 1024s (1 MB = 1,048,576 bytes) instead of 1000s. Default `1000`. */
78
+ sizeBase?: 1000 | 1024;
79
+ /** The product's own size formatter; it replaces `formatFileSize` (and `sizeBase`). */
80
+ formatSize?: FileSizeFormatter;
81
+ /**
82
+ * Words for this instance. Each one defaults to `UixLabelsProvider` `fileUpload`, then to
83
+ * English, so one provider translates every upload below it (HAR-1630).
84
+ */
77
85
  labels?: Partial<FileUploadLabels>;
78
86
  className?: string;
79
87
  }
@@ -82,6 +90,6 @@ interface FileUploadProps {
82
90
  * product owns the upload: it receives validated `File`s and reports progress, success and
83
91
  * failure through `items`. Keyboard users get a real button; drop is an extra, not the only way.
84
92
  */
85
- declare function FileUpload({ items, onFilesAdded, onRemove, onRetry, onAltChange, accept, maxSize, maxFiles, multiple, capture, disabled, hint, locale, labels: labelOverrides, className, }: FileUploadProps): react.JSX.Element;
93
+ declare function FileUpload({ items, onFilesAdded, onRemove, onRetry, onAltChange, accept, maxSize, maxFiles, multiple, capture, disabled, hint, locale, sizeBase, formatSize, labels: labelOverrides, className, }: FileUploadProps): react.JSX.Element;
86
94
 
87
95
  export { DEFAULT_FILE_UPLOAD_LABELS, FileUpload, type FileUploadItem, type FileUploadLabels, type FileUploadProps, type FileUploadStatus };
@@ -4,6 +4,7 @@ import { useId, useRef, useState } from "react";
4
4
  import { cx } from "../cx.js";
5
5
  import { fillLabel } from "../fill-label.js";
6
6
  import { formatFileSize, partitionFiles } from "../file-model.js";
7
+ import { useUixLabels } from "../labels-context.js";
7
8
  import { Button } from "./Button.js";
8
9
  import { Input } from "./Input.js";
9
10
  import { Progress } from "./Progress.js";
@@ -40,10 +41,13 @@ function FileUpload({
40
41
  disabled,
41
42
  hint,
42
43
  locale,
44
+ sizeBase,
45
+ formatSize,
43
46
  labels: labelOverrides,
44
47
  className
45
48
  }) {
46
- const labels = { ...DEFAULT_FILE_UPLOAD_LABELS, ...labelOverrides };
49
+ const labels = { ...DEFAULT_FILE_UPLOAD_LABELS, ...useUixLabels().fileUpload, ...labelOverrides };
50
+ const sizeText = (bytes) => formatSize ? formatSize(bytes, locale) : formatFileSize(bytes, locale, { base: sizeBase });
47
51
  const id = useId();
48
52
  const inputRef = useRef(null);
49
53
  const [over, setOver] = useState(false);
@@ -53,7 +57,7 @@ function FileUpload({
53
57
  if (!list || disabled) return;
54
58
  const files = Array.from(list);
55
59
  const { accepted, rejected } = partitionFiles(files, { accept, maxSize, maxFiles, existing: items.length });
56
- const max = maxSize !== void 0 ? formatFileSize(maxSize, locale) : "";
60
+ const max = maxSize !== void 0 ? sizeText(maxSize) : "";
57
61
  setMessages(rejected.map(({ file, reason }) => fillLabel(
58
62
  reason === "type" ? labels.wrongType : reason === "size" ? labels.tooLarge : labels.tooMany,
59
63
  { name: file.name, accept: accept ?? "", max: reason === "count" ? String(maxFiles) : max }
@@ -108,7 +112,9 @@ function FileUpload({
108
112
  ]
109
113
  }
110
114
  ),
111
- messages.length > 0 && /* @__PURE__ */ jsx("ul", { className: "uix-file-upload__errors", role: "alert", children: messages.map((m, i) => /* @__PURE__ */ jsx("li", { children: m }, i)) }),
115
+ messages.length > 0 && // The alert wraps the list: role="alert" on the <ul> itself replaced its list role and
116
+ // left the <li>s outside any list (axe `listitem`).
117
+ /* @__PURE__ */ jsx("div", { role: "alert", children: /* @__PURE__ */ jsx("ul", { className: "uix-file-upload__errors", children: messages.map((m, i) => /* @__PURE__ */ jsx("li", { children: m }, i)) }) }),
112
118
  /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", role: "status", "aria-live": "polite", children: announce }),
113
119
  items.length > 0 && /* @__PURE__ */ jsx("ul", { className: "uix-filelist uix-file-upload__list", "aria-label": labels.list, children: items.map((item) => {
114
120
  const statusText = item.status === "uploading" ? fillLabel(labels.uploading, { progress: item.progress ?? 0 }) : item.status === "done" ? labels.done : item.status === "error" ? labels.failed : labels.queued;
@@ -118,7 +124,7 @@ function FileUpload({
118
124
  /* @__PURE__ */ jsxs("div", { className: "uix-file-upload__body", children: [
119
125
  /* @__PURE__ */ jsx("span", { className: "uix-file-upload__name", children: item.name }),
120
126
  /* @__PURE__ */ jsxs("span", { className: "uix-file-upload__meta", children: [
121
- /* @__PURE__ */ jsx("span", { children: formatFileSize(item.size, locale) }),
127
+ /* @__PURE__ */ jsx("span", { children: sizeText(item.size) }),
122
128
  item.status !== "uploading" && /* @__PURE__ */ jsx("span", { className: "uix-file-upload__status", children: statusText })
123
129
  ] }),
124
130
  item.status === "uploading" && /* @__PURE__ */ jsx(Progress, { value: item.progress, indeterminate: item.progress === void 0, max: 100, label: statusText }),
@@ -24,6 +24,8 @@ interface FilterEditorLabels {
24
24
  selected: string;
25
25
  /** Remove one chosen record. `{label}`. */
26
26
  remove: string;
27
+ /** Names the row of `field.presets` chips. Optional so existing label objects still type-check; default "Presets". */
28
+ presets?: string;
27
29
  operators: Record<TextFilterOperator | NumberFilterOperator, string>;
28
30
  }
29
31
  declare const DEFAULT_FILTER_EDITOR_LABELS: FilterEditorLabels;
@@ -45,6 +47,8 @@ interface FilterEditorProps {
45
47
  * multi-select, text or number with a condition, a date range, yes/no, or records found by
46
48
  * an async search. The product owns the popover trigger and the query; `summarizeFilter`
47
49
  * turns the applied value into the chip text. `FilterPopover` stays for the one-string case.
50
+ * An enum field's `presets` (HAR-1505) render as toggle chips above its options. Words come
51
+ * from `labels`, or from `UixLabelsProvider`'s `filterEditor` entry.
48
52
  */
49
53
  declare function FilterEditor({ field, value: valueProp, onValueChange, onApply, onClear, labels: labelOverrides, searchThreshold, searchDelay }: FilterEditorProps): react.JSX.Element;
50
54
 
@@ -8,8 +8,9 @@ import { Radio, RadioGroup } from "./Radio.js";
8
8
  import { Select } from "./Select.js";
9
9
  import { Chip } from "./Chip.js";
10
10
  import { fillLabel } from "../fill-label.js";
11
+ import { useUixLabels } from "../labels-context.js";
11
12
  import { foldForSearch } from "../search-suggest-model.js";
12
- import { NUMBER_FILTER_OPERATORS, TEXT_FILTER_OPERATORS, emptyFilterValue } from "../filter-model.js";
13
+ import { NUMBER_FILTER_OPERATORS, TEXT_FILTER_OPERATORS, emptyFilterValue, matchFilterPreset } from "../filter-model.js";
13
14
  const DEFAULT_FILTER_EDITOR_LABELS = {
14
15
  apply: "Apply",
15
16
  clear: "Clear",
@@ -30,6 +31,7 @@ const DEFAULT_FILTER_EDITOR_LABELS = {
30
31
  selectNone: "Clear selection",
31
32
  selected: "{count} selected",
32
33
  remove: "Remove {label}",
34
+ presets: "Presets",
33
35
  operators: {
34
36
  contains: "contains",
35
37
  "not-contains": "does not contain",
@@ -46,7 +48,13 @@ const DEFAULT_FILTER_EDITOR_LABELS = {
46
48
  }
47
49
  };
48
50
  function FilterEditor({ field, value: valueProp, onValueChange, onApply, onClear, labels: labelOverrides, searchThreshold = 8, searchDelay = 250 }) {
49
- const labels = { ...DEFAULT_FILTER_EDITOR_LABELS, ...labelOverrides, operators: { ...DEFAULT_FILTER_EDITOR_LABELS.operators, ...labelOverrides?.operators } };
51
+ const provided = useUixLabels().filterEditor;
52
+ const labels = {
53
+ ...DEFAULT_FILTER_EDITOR_LABELS,
54
+ ...provided,
55
+ ...labelOverrides,
56
+ operators: { ...DEFAULT_FILTER_EDITOR_LABELS.operators, ...provided?.operators, ...labelOverrides?.operators }
57
+ };
50
58
  const id = useId();
51
59
  const value = valueProp?.kind === field.kind ? valueProp : emptyFilterValue(field.kind);
52
60
  let body = null;
@@ -121,7 +129,10 @@ function EnumBody({ field, value, onChange, labels, threshold }) {
121
129
  }, [options, query]);
122
130
  const selected = new Set(value.values);
123
131
  const toggle = (v, on) => onChange({ kind: "enum", values: on ? [...value.values, v] : value.values.filter((x) => x !== v) });
132
+ const presets = field.presets ?? [];
133
+ const active = matchFilterPreset(field, value);
124
134
  return /* @__PURE__ */ jsxs(Fragment, { children: [
135
+ presets.length > 0 && /* @__PURE__ */ jsx("div", { className: "uix-chip-group uix-filter-editor__presets", role: "group", "aria-label": labels.presets ?? DEFAULT_FILTER_EDITOR_LABELS.presets, children: presets.map((p) => /* @__PURE__ */ jsx(Chip, { pressed: p === active, onPressedChange: () => onChange({ kind: "enum", values: [...p.values] }), children: p.label }, p.id)) }),
125
136
  options.length > threshold && /* @__PURE__ */ jsx(Input, { type: "search", size: "sm", "aria-label": labels.search, placeholder: labels.search, value: query, onChange: (e) => setQuery(e.currentTarget.value), autoFocus: true }),
126
137
  /* @__PURE__ */ jsxs("div", { className: "uix-filter-editor__bulk", children: [
127
138
  /* @__PURE__ */ jsx("span", { className: "uix-filter-editor__count", "aria-live": "polite", children: fillLabel(labels.selected, { count: value.values.length }) }),
@@ -0,0 +1,31 @@
1
+ import * as react from 'react';
2
+ import { HTMLAttributes, ReactNode } from 'react';
3
+
4
+ type HeartbeatState = 'live' | 'idle' | 'warning' | 'danger';
5
+ interface HeartbeatProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'children'> {
6
+ /**
7
+ * `live` (default): a green dot with a pulsing ring. `warning` / `danger`: the same in that
8
+ * tone. `idle`: a grey dot, no ring.
9
+ */
10
+ state?: HeartbeatState;
11
+ /**
12
+ * What the dot means, for assistive technology ("Live", "Connection lost"). Without it the
13
+ * dot is decorative (`aria-hidden`): say the state in text beside it, as `LiveIndicator` does.
14
+ */
15
+ label?: string;
16
+ }
17
+ /**
18
+ * The pulsing status dot over `.uix-heartbeat` (HAR-1606), so products stop hand-writing its
19
+ * three spans. Decorative unless it has a `label`. The ring stops under
20
+ * `prefers-reduced-motion` (the kit's global motion guard); the dot and its colour stay.
21
+ */
22
+ declare function Heartbeat({ state, label, className, ...props }: HeartbeatProps): react.JSX.Element;
23
+ interface LiveIndicatorProps extends HTMLAttributes<HTMLSpanElement> {
24
+ state?: HeartbeatState;
25
+ /** The state in words: "Live", "Paused", "Reconnecting…". It is what a screen reader gets. */
26
+ children?: ReactNode;
27
+ }
28
+ /** A `Heartbeat` with its state in text beside it, over `.uix-live`. */
29
+ declare function LiveIndicator({ state, children, className, ...props }: LiveIndicatorProps): react.JSX.Element;
30
+
31
+ export { Heartbeat, type HeartbeatProps, type HeartbeatState, LiveIndicator, type LiveIndicatorProps };
@@ -0,0 +1,28 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { cx } from "../cx.js";
3
+ function Heartbeat({ state = "live", label, className, ...props }) {
4
+ return /* @__PURE__ */ jsxs(
5
+ "span",
6
+ {
7
+ className: cx("uix-heartbeat", state !== "live" && `uix-heartbeat--${state}`, className),
8
+ role: label ? "img" : void 0,
9
+ "aria-label": label,
10
+ "aria-hidden": label ? void 0 : true,
11
+ ...props,
12
+ children: [
13
+ /* @__PURE__ */ jsx("span", { className: "uix-heartbeat__ping" }),
14
+ /* @__PURE__ */ jsx("span", { className: "uix-heartbeat__dot" })
15
+ ]
16
+ }
17
+ );
18
+ }
19
+ function LiveIndicator({ state = "live", children, className, ...props }) {
20
+ return /* @__PURE__ */ jsxs("span", { className: cx("uix-live", className), ...props, children: [
21
+ /* @__PURE__ */ jsx(Heartbeat, { state }),
22
+ children
23
+ ] });
24
+ }
25
+ export {
26
+ Heartbeat,
27
+ LiveIndicator
28
+ };
@@ -3,13 +3,18 @@ import { ReactElement, ReactNode } from 'react';
3
3
  import { UixRenderLink } from '../link.js';
4
4
  import { Placement } from '../overlay-position.js';
5
5
 
6
+ /** `id` and `data-*` for the item's own element (HAR-1629): a stable hook for tests, journeys and analytics. */
7
+ interface MenuItemAttributes {
8
+ id?: string;
9
+ [dataAttribute: `data-${string}`]: string | number | boolean | undefined;
10
+ }
6
11
  interface MenuProps {
7
12
  /**
8
13
  * The menu button, usually a `Button`. It receives `aria-haspopup="menu"`,
9
14
  * `aria-expanded`, `aria-controls` and the open/keyboard handlers.
10
15
  */
11
16
  trigger: ReactElement;
12
- /** `MenuItem`, `MenuGroup` and `MenuSeparator`. */
17
+ /** `MenuItem`, `MenuItemRadio`, `MenuItemCheckbox`, `MenuRadioGroup`, `MenuGroup` and `MenuSeparator`. */
13
18
  children?: ReactNode;
14
19
  /** Controlled open state; pair with `onOpenChange`. */
15
20
  open?: boolean;
@@ -23,10 +28,11 @@ interface MenuProps {
23
28
  /**
24
29
  * A menu button with an APG menu (HAR-1359; TENSOR C1, MOTUS B-A14): ↓/↑ move, Home/End,
25
30
  * type-to-find, Enter/Space activate, Esc and Tab close, focus returns to the trigger.
26
- * The surface is the kit `Popover`, so it escapes clipping containers.
31
+ * The surface is the kit `Popover`, so it escapes clipping containers; a menu taller than
32
+ * the viewport is capped to it and scrolls (HAR-1613).
27
33
  */
28
34
  declare function Menu({ trigger, children, open: controlledOpen, defaultOpen, onOpenChange, label, placement, className }: MenuProps): react.JSX.Element;
29
- interface MenuItemProps {
35
+ interface MenuItemProps extends MenuItemAttributes {
30
36
  children?: ReactNode;
31
37
  /** Runs on click, Enter or Space; the menu then closes and focus returns to the trigger. */
32
38
  onSelect?: () => void;
@@ -40,7 +46,49 @@ interface MenuItemProps {
40
46
  disabled?: boolean;
41
47
  className?: string;
42
48
  }
43
- declare function MenuItem({ children, onSelect, href, renderLink, icon, shortcut, tone, disabled, className }: MenuItemProps): react.JSX.Element;
49
+ declare function MenuItem({ children, onSelect, href, renderLink, icon, shortcut, tone, disabled, className, ...rest }: MenuItemProps): react.JSX.Element;
50
+ interface MenuRadioGroupProps {
51
+ /** The `value` of the checked `MenuItemRadio`. */
52
+ value?: string;
53
+ onValueChange?: (value: string) => void;
54
+ /** Visible group heading; it also names the group. */
55
+ label?: ReactNode;
56
+ /** Names the group when there is no visible `label`. */
57
+ 'aria-label'?: string;
58
+ children?: ReactNode;
59
+ }
60
+ /** One choice out of several inside a `Menu`: wraps `MenuItemRadio` rows and holds their value (HAR-1629). */
61
+ declare function MenuRadioGroup({ value, onValueChange, label, 'aria-label': ariaLabel, children }: MenuRadioGroupProps): react.JSX.Element;
62
+ interface MenuItemRadioProps extends MenuItemAttributes {
63
+ /** Checked when it equals the surrounding `MenuRadioGroup`'s `value`. */
64
+ value: string;
65
+ /** For a radio item used without a `MenuRadioGroup`. */
66
+ checked?: boolean;
67
+ /** Runs when the item is chosen, after the group's `onValueChange`. */
68
+ onSelect?: () => void;
69
+ /** Close the menu after a click or Enter. Default `true`. Space never closes it. */
70
+ closeOnSelect?: boolean;
71
+ children?: ReactNode;
72
+ icon?: ReactNode;
73
+ shortcut?: ReactNode;
74
+ disabled?: boolean;
75
+ className?: string;
76
+ }
77
+ /** A `role="menuitemradio"` row with `aria-checked` and a check mark for the current choice. */
78
+ declare function MenuItemRadio({ value, checked, onSelect, closeOnSelect, ...rest }: MenuItemRadioProps): react.JSX.Element;
79
+ interface MenuItemCheckboxProps extends MenuItemAttributes {
80
+ checked: boolean;
81
+ onCheckedChange?: (checked: boolean) => void;
82
+ /** Close the menu after a click or Enter. Default `false`: several boxes are usually ticked in a row. */
83
+ closeOnSelect?: boolean;
84
+ children?: ReactNode;
85
+ icon?: ReactNode;
86
+ shortcut?: ReactNode;
87
+ disabled?: boolean;
88
+ className?: string;
89
+ }
90
+ /** A `role="menuitemcheckbox"` row with `aria-checked`; the menu stays open while boxes are ticked. */
91
+ declare function MenuItemCheckbox({ checked, onCheckedChange, closeOnSelect, ...rest }: MenuItemCheckboxProps): react.JSX.Element;
44
92
  interface MenuGroupProps {
45
93
  label: ReactNode;
46
94
  children?: ReactNode;
@@ -48,4 +96,4 @@ interface MenuGroupProps {
48
96
  declare function MenuGroup({ label, children }: MenuGroupProps): react.JSX.Element;
49
97
  declare function MenuSeparator(): react.JSX.Element;
50
98
 
51
- export { Menu, MenuGroup, type MenuGroupProps, MenuItem, type MenuItemProps, type MenuProps, MenuSeparator };
99
+ export { Menu, MenuGroup, type MenuGroupProps, MenuItem, type MenuItemAttributes, MenuItemCheckbox, type MenuItemCheckboxProps, type MenuItemProps, MenuItemRadio, type MenuItemRadioProps, type MenuProps, MenuRadioGroup, type MenuRadioGroupProps, MenuSeparator };