@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
@@ -2,12 +2,16 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useEffect, useId, useRef, useState } from "react";
4
4
  import { cx } from "../cx.js";
5
+ import { placeId, sameOrder, useRowDrag } from "../reorder.js";
5
6
  import { Button } from "./Button.js";
6
7
  import { Checkbox } from "./Checkbox.js";
8
+ import { DatePicker } from "./DatePicker.js";
7
9
  import { EditorIcon } from "./EditorIcons.js";
10
+ import { StarIcon } from "../icons/components.js";
8
11
  import { Input } from "./Input.js";
9
12
  import { Popover } from "./Popover.js";
10
13
  import { Segmented, SegmentedOption } from "./Segmented.js";
14
+ import { Select } from "./Select.js";
11
15
  import { Switch } from "./Switch.js";
12
16
  const VIEW_MENU_COLUMN_LABELS = {
13
17
  rowActions: "Column actions: {label}",
@@ -170,7 +174,7 @@ function ViewMenu({
170
174
  if (el && document.activeElement !== el) el.focus();
171
175
  });
172
176
  const commit = (next, id) => {
173
- setDraft(next);
177
+ setDraft(null);
174
178
  onReorder?.([...next]);
175
179
  const column = byId.get(id);
176
180
  if (column) setAnnouncement(fill(labels.moved, { label: columnText(column), position: next.indexOf(id) + 1, count: next.length }));
@@ -190,6 +194,11 @@ function ViewMenu({
190
194
  const start = dragStart.current;
191
195
  dragStart.current = null;
192
196
  if (start && !sameOrder(start, order)) commit(order, id);
197
+ else setDraft(null);
198
+ },
199
+ cancel: () => {
200
+ dragStart.current = null;
201
+ setDraft(null);
193
202
  }
194
203
  });
195
204
  const rowActions = (column, index) => [
@@ -288,7 +297,7 @@ function FilterPopover({ label, type = "text", value, onValueChange, options, ap
288
297
  return /* @__PURE__ */ jsxs("div", { className: "uix-filter-popover", children: [
289
298
  /* @__PURE__ */ jsxs("div", { className: "uix-field", children: [
290
299
  /* @__PURE__ */ jsx("label", { className: "uix-field__label", htmlFor: controlId, children: label }),
291
- type === "select" || type === "enum" || type === "boolean" ? /* @__PURE__ */ jsx("select", { id: controlId, className: "uix-select", value, onChange: (event) => onValueChange(event.currentTarget.value), children: options?.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value)) }) : /* @__PURE__ */ jsx(Input, { id: controlId, type, value, placeholder, onChange: (event) => onValueChange(event.currentTarget.value) })
300
+ type === "select" || type === "enum" || type === "boolean" ? /* @__PURE__ */ jsx(Select, { id: controlId, value, options: options ?? [], onValueChange: (next) => onValueChange(next) }) : type === "date" ? /* @__PURE__ */ jsx(DatePicker, { id: controlId, value: value || null, placeholder, onValueChange: (next) => onValueChange(next ?? "") }) : /* @__PURE__ */ jsx(Input, { id: controlId, type, value, placeholder, onChange: (event) => onValueChange(event.currentTarget.value) })
292
301
  ] }),
293
302
  /* @__PURE__ */ jsxs("div", { className: "uix-filter-popover__actions", children: [
294
303
  /* @__PURE__ */ jsx(Button, { type: "button", onClick: onClear, children: clearLabel }),
@@ -296,60 +305,6 @@ function FilterPopover({ label, type = "text", value, onValueChange, options, ap
296
305
  ] })
297
306
  ] });
298
307
  }
299
- const sameOrder = (a, b) => a.length === b.length && a.every((id, i) => id === b[i]);
300
- function dropIndex(rows, dragged, clientY) {
301
- let index = 0;
302
- for (const row of rows) {
303
- if (row === dragged) continue;
304
- const box = row.getBoundingClientRect();
305
- if (clientY > box.top + box.height / 2) index++;
306
- }
307
- return index;
308
- }
309
- function useRowDrag(rowSelector, handlers) {
310
- const [dragging, setDragging] = useState(null);
311
- const drag = useRef(null);
312
- const latest = useRef(handlers);
313
- latest.current = handlers;
314
- useEffect(() => {
315
- if (!dragging) return;
316
- const move = (event) => {
317
- const current = drag.current;
318
- if (!current) return;
319
- latest.current.over(current.id, dropIndex(Array.from(current.list.querySelectorAll(`:scope > ${rowSelector}`)), current.row, event.clientY));
320
- };
321
- const end = () => {
322
- const current = drag.current;
323
- drag.current = null;
324
- setDragging(null);
325
- if (current) latest.current.drop(current.id);
326
- };
327
- window.addEventListener("pointermove", move);
328
- window.addEventListener("pointerup", end);
329
- window.addEventListener("pointercancel", end);
330
- return () => {
331
- window.removeEventListener("pointermove", move);
332
- window.removeEventListener("pointerup", end);
333
- window.removeEventListener("pointercancel", end);
334
- };
335
- }, [dragging, rowSelector]);
336
- const start = (event, id) => {
337
- const row = event.currentTarget.closest("li");
338
- if (!row?.parentElement) return;
339
- event.currentTarget.setPointerCapture?.(event.pointerId);
340
- drag.current = { id, list: row.parentElement, row };
341
- setDragging(id);
342
- };
343
- return { dragging, start };
344
- }
345
- function placeId(ids, id, index) {
346
- const from = ids.indexOf(id);
347
- const to = Math.max(0, Math.min(index, ids.length - 1));
348
- if (from < 0 || from === to) return ids;
349
- const next = ids.filter((other) => other !== id);
350
- next.splice(to, 0, id);
351
- return next;
352
- }
353
308
  function SavedViewMenu({ items, sections, onSelect, onPinChange, pinLabel, unpinLabel, onReorder, reorderLabel, emptyLabel, actions, footer }) {
354
309
  const baseId = useId();
355
310
  const groups = sections ?? [{ id: void 0, items: items ?? [] }];
@@ -363,7 +318,7 @@ function SavedViewMenu({ items, sections, onSelect, onPinChange, pinLabel, unpin
363
318
  const current = orderOf(group);
364
319
  const next = placeId(current, id, current.indexOf(id) + delta);
365
320
  if (next === current) return;
366
- setDraft({ group: group.id, ids: next });
321
+ setDraft(null);
367
322
  onReorder?.([...next], group.id);
368
323
  };
369
324
  const { dragging, start: startDrag } = useRowDrag(".uix-saved-views__row", {
@@ -380,7 +335,12 @@ function SavedViewMenu({ items, sections, onSelect, onPinChange, pinLabel, unpin
380
335
  dragStart.current = null;
381
336
  if (!group) return;
382
337
  const end = orderOf(group);
338
+ setDraft(null);
383
339
  if (start && !sameOrder(start, end)) onReorder?.([...end], group.id);
340
+ },
341
+ cancel: () => {
342
+ dragStart.current = null;
343
+ setDraft(null);
384
344
  }
385
345
  });
386
346
  const visible = groups.filter((group) => group.items.length > 0);
@@ -415,7 +375,7 @@ function SavedViewMenu({ items, sections, onSelect, onPinChange, pinLabel, unpin
415
375
  }
416
376
  ),
417
377
  /* @__PURE__ */ jsx("button", { type: "button", id: nameId, className: "uix-menu__item", "data-active": item.active || void 0, "aria-current": item.active || void 0, title: typeof item.label === "string" ? item.label : void 0, onClick: () => onSelect(item.id), children: /* @__PURE__ */ jsx("span", { className: "uix-saved-views__name", children: item.label }) }),
418
- onPinChange && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-saved-views__pin", "aria-label": item.pinned ? unpinLabel : pinLabel, "data-on": item.pinned || void 0, onClick: () => onPinChange(item.id, !item.pinned), children: "\u2605" }),
378
+ onPinChange && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-saved-views__pin", "aria-label": item.pinned ? unpinLabel : pinLabel, "data-on": item.pinned || void 0, onClick: () => onPinChange(item.id, !item.pinned), children: /* @__PURE__ */ jsx(StarIcon, {}) }),
419
379
  extra != null && extra !== false && /* @__PURE__ */ jsx("span", { className: "uix-saved-views__actions", children: extra })
420
380
  ] }, item.id);
421
381
  });
@@ -0,0 +1,12 @@
1
+ import * as react from 'react';
2
+
3
+ /**
4
+ * Measures whether a `TableWrap`'s table is wider than the wrap and writes it to the wrap as
5
+ * `data-overflow="true" | "false"`, so frozen columns draw their dividers only where rows can
6
+ * scroll under them (PR #168 review). A hidden child, like BusyBar, so `TableWrap` itself stays a
7
+ * server component. Without ResizeObserver (or before it runs) there is no attribute and the
8
+ * dividers stay, as in plain HTML.
9
+ */
10
+ declare function TableOverflowProbe(): react.JSX.Element;
11
+
12
+ export { TableOverflowProbe };
@@ -0,0 +1,27 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { useLayoutEffect, useRef } from "react";
4
+ function TableOverflowProbe() {
5
+ const ref = useRef(null);
6
+ useLayoutEffect(() => {
7
+ const wrap = ref.current?.parentElement;
8
+ if (!wrap || typeof ResizeObserver === "undefined") return void 0;
9
+ const measure = () => {
10
+ const next = String(wrap.scrollWidth > wrap.clientWidth + 1);
11
+ if (wrap.getAttribute("data-overflow") !== next) wrap.setAttribute("data-overflow", next);
12
+ };
13
+ measure();
14
+ const observer = new ResizeObserver(measure);
15
+ observer.observe(wrap);
16
+ const table = wrap.querySelector("table");
17
+ if (table) observer.observe(table);
18
+ return () => {
19
+ observer.disconnect();
20
+ wrap.removeAttribute("data-overflow");
21
+ };
22
+ }, []);
23
+ return /* @__PURE__ */ jsx("span", { ref, hidden: true });
24
+ }
25
+ export {
26
+ TableOverflowProbe
27
+ };
@@ -13,7 +13,8 @@ interface TabsProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange' | 'r
13
13
  children?: ReactNode;
14
14
  className?: string;
15
15
  /**
16
- * What happens when the tabs don't fit. `'wrap'` (default) keeps today's layout.
16
+ * What happens when the tabs don't fit. `'wrap'` (default): the tab row wraps onto more rows,
17
+ * and no tab is wider than its row, so one long label wraps inside its tab (HAR-1754).
17
18
  * `'scroll'` keeps one row that scrolls sideways, with pointer-only edge buttons
18
19
  * shown on the side that has hidden tabs. The selected tab is always scrolled into view.
19
20
  */
@@ -151,6 +151,7 @@ function Tab({ value, children, disabled, className, onClick, ...props }) {
151
151
  return /* @__PURE__ */ jsx(
152
152
  "button",
153
153
  {
154
+ type: "button",
154
155
  role: "tab",
155
156
  id: ctx.baseId ? `${ctx.baseId}-tab-${value}` : void 0,
156
157
  "aria-controls": ctx.baseId ? `${ctx.baseId}-panel-${value}` : void 0,
@@ -0,0 +1,67 @@
1
+ import * as react from 'react';
2
+ import { AriaAttributes } from 'react';
3
+
4
+ /** Every word `TagInput` renders or announces. */
5
+ interface TagInputLabels {
6
+ /** Name of a chip's × button; `{tag}`. */
7
+ remove: string;
8
+ /** Announced after a tag was added; `{tag}`. */
9
+ added: string;
10
+ /** Announced after a tag was removed; `{tag}`. */
11
+ removed: string;
12
+ /** Shown when the tag is already there; `{tag}`. */
13
+ duplicate: string;
14
+ /** Shown when `maxTags` are there; `{max}`. */
15
+ max: string;
16
+ /** Name of the suggestion list. */
17
+ suggestions: string;
18
+ /** Name of the list of chips. */
19
+ list: string;
20
+ /** Part of the field's description: how many tags there are; `{count}`. */
21
+ count: string;
22
+ }
23
+ declare const DEFAULT_TAG_INPUT_LABELS: TagInputLabels;
24
+ interface TagInputProps {
25
+ /** The tags, in order. */
26
+ value: readonly string[];
27
+ onValueChange: (value: string[]) => void;
28
+ /** Offered while typing (those already added are left out); ArrowDown reaches them. */
29
+ suggestions?: readonly string[];
30
+ /**
31
+ * Check a tag before it is added: return a message to refuse it (shown under the field), or
32
+ * `null` to accept it. Runs after the duplicate and `maxTags` checks.
33
+ */
34
+ validate?: (tag: string, value: readonly string[]) => string | null | undefined;
35
+ /** The most tags; past it, adding is refused with the `max` message. */
36
+ maxTags?: number;
37
+ /** Keys that add the typed text. Default Enter and comma. */
38
+ separators?: readonly string[];
39
+ /** Submits each tag as its own value under this name (hidden inputs). */
40
+ name?: string;
41
+ /** Lands on the text input, so a `<label htmlFor>` or `<Field>` names it. */
42
+ id?: string;
43
+ placeholder?: string;
44
+ disabled?: boolean;
45
+ invalid?: boolean;
46
+ 'aria-label'?: string;
47
+ 'aria-labelledby'?: string;
48
+ 'aria-describedby'?: string;
49
+ 'aria-invalid'?: AriaAttributes['aria-invalid'];
50
+ 'aria-required'?: AriaAttributes['aria-required'];
51
+ /** Translatable words; English defaults in {@link DEFAULT_TAG_INPUT_LABELS}. */
52
+ labels?: Partial<TagInputLabels>;
53
+ className?: string;
54
+ }
55
+ /**
56
+ * Several short tags in one field (HAR-1377): type and press Enter (or a comma) to add a chip;
57
+ * pasting "a, b, c" adds three. A duplicate (ignoring case and accents), a refusal from `validate`
58
+ * or one past `maxTags` keeps the text and says why. Suggestions open under the field as a
59
+ * top-layer list once the person types (or presses ArrowDown): ArrowDown / ArrowUp move, Enter adds
60
+ * the active one, Escape closes it. Backspace in the empty field
61
+ * goes to the last chip; ArrowLeft / ArrowRight move between chips and back to the field;
62
+ * Backspace or Delete on a chip removes it. Each change is announced politely. Works in `Field`
63
+ * (the text input gets the label, hint and error) and submits each tag under `name`.
64
+ */
65
+ declare function TagInput({ value, onValueChange, suggestions, validate, maxTags, separators, name, id: idProp, placeholder, disabled, invalid, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy, 'aria-invalid': ariaInvalid, 'aria-required': ariaRequired, labels: labelOverrides, className, }: TagInputProps): react.JSX.Element;
66
+
67
+ export { DEFAULT_TAG_INPUT_LABELS, TagInput, type TagInputLabels, type TagInputProps };
@@ -0,0 +1,342 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
4
+ import { cx } from "../cx.js";
5
+ import { fillLabel } from "../fill-label.js";
6
+ import { useAnchoredPosition, zoomOf } from "../hooks/useAnchoredPosition.js";
7
+ import { XIcon } from "../icons/components.js";
8
+ import { useUixLabels } from "../labels-context.js";
9
+ import { foldForSearch } from "../search-suggest-model.js";
10
+ const useIsomorphicLayoutEffect = typeof document !== "undefined" ? useLayoutEffect : useEffect;
11
+ const DEFAULT_TAG_INPUT_LABELS = {
12
+ remove: "Remove {tag}",
13
+ added: "{tag} added.",
14
+ removed: "{tag} removed.",
15
+ duplicate: "{tag} is already added.",
16
+ max: "You can add up to {max}.",
17
+ suggestions: "Suggestions",
18
+ list: "Tags",
19
+ count: "{count} added"
20
+ };
21
+ const same = (a, b) => foldForSearch(a).text === foldForSearch(b).text;
22
+ function TagInput({
23
+ value,
24
+ onValueChange,
25
+ suggestions,
26
+ validate,
27
+ maxTags,
28
+ separators = ["Enter", ","],
29
+ name,
30
+ id: idProp,
31
+ placeholder,
32
+ disabled,
33
+ invalid,
34
+ "aria-label": ariaLabel,
35
+ "aria-labelledby": ariaLabelledBy,
36
+ "aria-describedby": ariaDescribedBy,
37
+ "aria-invalid": ariaInvalid,
38
+ "aria-required": ariaRequired,
39
+ labels: labelOverrides,
40
+ className
41
+ }) {
42
+ const labels = { ...DEFAULT_TAG_INPUT_LABELS, ...useUixLabels().tagInput, ...labelOverrides };
43
+ const uid = useId();
44
+ const id = idProp ?? `${uid}-input`;
45
+ const listId = `${uid}-suggestions`;
46
+ const messageId = `${uid}-message`;
47
+ const optionId = (index) => `${listId}-${index}`;
48
+ const boxRef = useRef(null);
49
+ const inputRef = useRef(null);
50
+ const popupRef = useRef(null);
51
+ const chipRefs = useRef([]);
52
+ const [text, setText] = useState("");
53
+ const [message, setMessage] = useState(null);
54
+ const [announcement, setAnnouncement] = useState("");
55
+ const [focusChip, setFocusChip] = useState(null);
56
+ const [open, setOpen] = useState(false);
57
+ const [active, setActive] = useState(-1);
58
+ const matches = useMemo(() => {
59
+ if (!suggestions?.length) return [];
60
+ const needle = foldForSearch(text.trim()).text;
61
+ return suggestions.filter((s) => !value.some((v) => same(v, s)) && (!needle || foldForSearch(s).text.includes(needle))).slice(0, 50);
62
+ }, [suggestions, text, value]);
63
+ const showing = open && matches.length > 0 && !disabled;
64
+ const refuse = (reason) => {
65
+ setMessage(reason);
66
+ setAnnouncement(reason);
67
+ };
68
+ const add = (raw) => {
69
+ let next = [...value];
70
+ const added = [];
71
+ let refusedAt = -1;
72
+ let reason = "";
73
+ for (let i = 0; i < raw.length; i += 1) {
74
+ const tag = raw[i].trim();
75
+ if (!tag) continue;
76
+ if (next.some((v) => same(v, tag))) reason = fillLabel(labels.duplicate, { tag });
77
+ else if (maxTags != null && next.length >= maxTags) reason = fillLabel(labels.max, { max: maxTags });
78
+ else reason = validate?.(tag, next) || "";
79
+ if (reason) {
80
+ refusedAt = i;
81
+ break;
82
+ }
83
+ next = [...next, tag];
84
+ added.push(tag);
85
+ }
86
+ if (added.length) {
87
+ setOpen(false);
88
+ setAnnouncement(fillLabel(labels.added, { tag: added.join(", ") }));
89
+ onValueChange(next);
90
+ }
91
+ if (refusedAt < 0) {
92
+ setMessage(null);
93
+ return "";
94
+ }
95
+ refuse(reason);
96
+ const joiner = separators.includes(",") ? ", " : " ";
97
+ return raw.slice(refusedAt).map((piece) => piece.trim()).filter(Boolean).join(joiner);
98
+ };
99
+ const remove = (index) => {
100
+ const tag = value[index];
101
+ if (tag == null || disabled) return;
102
+ onValueChange(value.filter((_, i) => i !== index));
103
+ setMessage(null);
104
+ setAnnouncement(fillLabel(labels.removed, { tag }));
105
+ setFocusChip(index > 0 ? index - 1 : null);
106
+ if (index === 0) inputRef.current?.focus();
107
+ };
108
+ useEffect(() => {
109
+ if (focusChip == null) return;
110
+ const chip = chipRefs.current[focusChip];
111
+ if (chip) chip.focus();
112
+ else inputRef.current?.focus();
113
+ setFocusChip(null);
114
+ }, [focusChip, value]);
115
+ const commitText = (extra = []) => {
116
+ setText(add([text, ...extra]));
117
+ setActive(-1);
118
+ };
119
+ const onInputKeyDown = (event) => {
120
+ if (event.nativeEvent.isComposing || event.keyCode === 229) return;
121
+ const { key } = event;
122
+ if (key === "ArrowDown" || key === "ArrowUp") {
123
+ if (!matches.length) return;
124
+ event.preventDefault();
125
+ if (!showing) {
126
+ setOpen(true);
127
+ setActive(key === "ArrowDown" ? 0 : matches.length - 1);
128
+ return;
129
+ }
130
+ setActive((i) => i < 0 ? key === "ArrowDown" ? 0 : matches.length - 1 : Math.max(0, Math.min(matches.length - 1, i + (key === "ArrowDown" ? 1 : -1))));
131
+ return;
132
+ }
133
+ if (key === "Escape" && showing) {
134
+ event.preventDefault();
135
+ event.stopPropagation();
136
+ setOpen(false);
137
+ setActive(-1);
138
+ return;
139
+ }
140
+ if (separators.includes(key)) {
141
+ if (key === "Enter" && showing && active >= 0) {
142
+ event.preventDefault();
143
+ if (!add([matches[active]])) {
144
+ setText("");
145
+ setActive(-1);
146
+ }
147
+ return;
148
+ }
149
+ if (key === "Enter" && !text.trim()) return;
150
+ event.preventDefault();
151
+ commitText();
152
+ return;
153
+ }
154
+ const caretAtStart = event.currentTarget.selectionStart === 0 && event.currentTarget.selectionEnd === 0;
155
+ if (key === "Backspace" && !text || key === "ArrowLeft" && caretAtStart) {
156
+ if (!value.length) return;
157
+ event.preventDefault();
158
+ setOpen(false);
159
+ chipRefs.current[value.length - 1]?.focus();
160
+ }
161
+ };
162
+ const onChipKeyDown = (event, index) => {
163
+ const { key } = event;
164
+ if (key === "Backspace" || key === "Delete") {
165
+ event.preventDefault();
166
+ remove(index);
167
+ return;
168
+ }
169
+ if (key === "ArrowLeft") {
170
+ event.preventDefault();
171
+ chipRefs.current[Math.max(0, index - 1)]?.focus();
172
+ return;
173
+ }
174
+ if (key === "ArrowRight") {
175
+ event.preventDefault();
176
+ if (index < value.length - 1) chipRefs.current[index + 1]?.focus();
177
+ else inputRef.current?.focus();
178
+ return;
179
+ }
180
+ if (key === "Escape") {
181
+ event.preventDefault();
182
+ inputRef.current?.focus();
183
+ }
184
+ };
185
+ const onPaste = (event) => {
186
+ const pasted = event.clipboardData?.getData("text") ?? "";
187
+ const chars = separators.filter((s) => s.length === 1).map((s) => s.replace(/[\\^\]-]/g, "\\$&")).join("");
188
+ const split = new RegExp(`[\\n\\r${chars}]+`);
189
+ if (!split.test(pasted)) return;
190
+ event.preventDefault();
191
+ const pieces = pasted.split(split);
192
+ setText(add([`${text}${pieces[0] ?? ""}`, ...pieces.slice(1)]));
193
+ };
194
+ useIsomorphicLayoutEffect(() => {
195
+ const popup = popupRef.current;
196
+ const box = boxRef.current;
197
+ if (!popup || !box) return;
198
+ const isOpen = () => {
199
+ try {
200
+ return popup.matches(":popover-open");
201
+ } catch {
202
+ return false;
203
+ }
204
+ };
205
+ if (showing) {
206
+ const zoom = zoomOf(box);
207
+ popup.style.minWidth = `${Math.round(box.getBoundingClientRect().width / zoom)}px`;
208
+ try {
209
+ if (!isOpen()) popup.showPopover();
210
+ } catch {
211
+ }
212
+ } else {
213
+ try {
214
+ if (isOpen()) popup.hidePopover();
215
+ } catch {
216
+ }
217
+ }
218
+ }, [showing]);
219
+ useAnchoredPosition(boxRef, popupRef, { open: showing, placement: "bottom-start", offset: 4, padding: 8 });
220
+ useIsomorphicLayoutEffect(() => {
221
+ if (!showing || active < 0) return;
222
+ popupRef.current?.ownerDocument.getElementById(optionId(active))?.scrollIntoView?.({ block: "nearest" });
223
+ }, [showing, active]);
224
+ const isInvalid = !!invalid || ariaInvalid === true || ariaInvalid === "true";
225
+ const countId = `${uid}-count`;
226
+ const describedBy = [ariaDescribedBy, message ? messageId : void 0, value.length ? countId : void 0].filter(Boolean).join(" ") || void 0;
227
+ const popoverAttr = { popover: "manual" };
228
+ return /* @__PURE__ */ jsxs("div", { className: cx("uix-taginput-field", className), children: [
229
+ /* @__PURE__ */ jsxs(
230
+ "div",
231
+ {
232
+ ref: boxRef,
233
+ className: "uix-taginput",
234
+ "data-invalid": isInvalid || void 0,
235
+ "data-disabled": disabled || void 0,
236
+ onMouseDown: (event) => {
237
+ if (event.target === event.currentTarget) {
238
+ event.preventDefault();
239
+ inputRef.current?.focus();
240
+ }
241
+ },
242
+ onBlur: (event) => {
243
+ if (!boxRef.current?.contains(event.relatedTarget)) {
244
+ setOpen(false);
245
+ setActive(-1);
246
+ }
247
+ },
248
+ children: [
249
+ value.length > 0 && /* @__PURE__ */ jsx("span", { role: "list", "aria-label": labels.list, className: "uix-taginput__tags", children: value.map((tag, index) => /* @__PURE__ */ jsxs("span", { className: "uix-tag", role: "listitem", children: [
250
+ tag,
251
+ !disabled && /* @__PURE__ */ jsx(
252
+ "button",
253
+ {
254
+ ref: (el) => {
255
+ chipRefs.current[index] = el;
256
+ },
257
+ type: "button",
258
+ className: "uix-tag__remove",
259
+ tabIndex: -1,
260
+ "aria-label": fillLabel(labels.remove, { tag }),
261
+ onMouseDown: (event) => event.preventDefault(),
262
+ onClick: () => remove(index),
263
+ onKeyDown: (event) => onChipKeyDown(event, index),
264
+ children: /* @__PURE__ */ jsx(XIcon, {})
265
+ }
266
+ )
267
+ ] }, `${tag}-${index}`)) }),
268
+ /* @__PURE__ */ jsx(
269
+ "input",
270
+ {
271
+ ref: inputRef,
272
+ id,
273
+ className: "uix-taginput__field",
274
+ type: "text",
275
+ role: suggestions ? "combobox" : void 0,
276
+ "aria-autocomplete": suggestions ? "list" : void 0,
277
+ "aria-expanded": suggestions ? showing : void 0,
278
+ "aria-controls": suggestions ? listId : void 0,
279
+ "aria-activedescendant": showing && active >= 0 ? optionId(active) : void 0,
280
+ "aria-label": ariaLabel,
281
+ "aria-labelledby": ariaLabelledBy,
282
+ "aria-describedby": describedBy,
283
+ "aria-invalid": isInvalid || !!message || void 0,
284
+ "aria-required": ariaRequired,
285
+ autoComplete: "off",
286
+ spellCheck: false,
287
+ enterKeyHint: "enter",
288
+ placeholder,
289
+ disabled,
290
+ value: text,
291
+ onChange: (event) => {
292
+ const next = event.target.value;
293
+ if (separators.includes(",") && next.includes(",")) {
294
+ const pieces = next.split(",");
295
+ if (!add([pieces[0]])) setText(pieces.slice(1).join(",").trimStart());
296
+ else setText(next.replace(/,/g, ""));
297
+ return;
298
+ }
299
+ setText(next);
300
+ if (message) setMessage(null);
301
+ setOpen(next.trim().length > 0);
302
+ setActive(-1);
303
+ },
304
+ onKeyDown: onInputKeyDown,
305
+ onPaste
306
+ }
307
+ )
308
+ ]
309
+ }
310
+ ),
311
+ name && value.map((tag, index) => /* @__PURE__ */ jsx("input", { type: "hidden", name, value: tag, disabled }, `${tag}-${index}`)),
312
+ message && /* @__PURE__ */ jsx("span", { id: messageId, className: "uix-field__warning uix-taginput__message", children: message }),
313
+ value.length > 0 && /* @__PURE__ */ jsx("span", { id: countId, className: "uix-visually-hidden", children: fillLabel(labels.count, { count: value.length }) }),
314
+ /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", role: "status", "aria-live": "polite", children: announcement }),
315
+ suggestions && /* @__PURE__ */ jsx("div", { ref: popupRef, className: "uix-select__popup uix-taginput__popup", hidden: !showing, ...popoverAttr, onMouseDown: (event) => event.preventDefault(), children: /* @__PURE__ */ jsx("div", { id: listId, role: "listbox", "aria-label": labels.suggestions, className: "uix-listbox uix-select__listbox", children: matches.map((suggestion, index) => /* @__PURE__ */ jsx(
316
+ "div",
317
+ {
318
+ id: optionId(index),
319
+ role: "option",
320
+ "aria-selected": index === active,
321
+ className: "uix-listbox__option",
322
+ "data-active": index === active || void 0,
323
+ onPointerMove: () => {
324
+ if (index !== active) setActive(index);
325
+ },
326
+ onClick: () => {
327
+ if (!add([suggestion])) {
328
+ setText("");
329
+ setActive(-1);
330
+ }
331
+ inputRef.current?.focus();
332
+ },
333
+ children: /* @__PURE__ */ jsx("span", { className: "uix-listbox__label", children: suggestion })
334
+ },
335
+ suggestion
336
+ )) }) })
337
+ ] });
338
+ }
339
+ export {
340
+ DEFAULT_TAG_INPUT_LABELS,
341
+ TagInput
342
+ };
@@ -3,6 +3,11 @@ import { TextareaHTMLAttributes } from 'react';
3
3
 
4
4
  interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
5
5
  invalid?: boolean;
6
+ /**
7
+ * `lg`: the public-page size beside `Input size="lg"` and `Button size="lg"` (HAR-1812): larger
8
+ * text and padding, never shorter than 44 px. It still grows with `rows` and the resize handle.
9
+ */
10
+ size?: 'md' | 'lg';
6
11
  }
7
12
  declare const Textarea: react.ForwardRefExoticComponent<TextareaProps & react.RefAttributes<HTMLTextAreaElement>>;
8
13
 
@@ -2,11 +2,11 @@ import { jsx } from "react/jsx-runtime";
2
2
  import { forwardRef } from "react";
3
3
  import { cx } from "../cx.js";
4
4
  const Textarea = forwardRef(
5
- ({ invalid, className, ...props }, ref) => /* @__PURE__ */ jsx(
5
+ ({ invalid, size = "md", className, ...props }, ref) => /* @__PURE__ */ jsx(
6
6
  "textarea",
7
7
  {
8
8
  ref,
9
- className: cx("uix-textarea", className),
9
+ className: cx("uix-textarea", size === "lg" && "uix-textarea--lg", className),
10
10
  "aria-invalid": invalid || void 0,
11
11
  ...props
12
12
  }