@tensor_1/react 2.47.0 → 2.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/README.md +470 -1
  2. package/dist/{SchedulingCalendar-B6TakO2h.d.ts → SchedulingCalendar-WWyFL3Eo.d.ts} +51 -2
  3. package/dist/announce.d.ts +10 -0
  4. package/dist/announce.js +30 -0
  5. package/dist/chart-analytic.cjs +33 -25
  6. package/dist/chart-preset.cjs +23 -15
  7. package/dist/chart.cjs +23 -15
  8. package/dist/combobox-model.d.ts +10 -1
  9. package/dist/combobox-model.js +15 -0
  10. package/dist/command-palette-model.d.ts +44 -0
  11. package/dist/command-palette-model.js +36 -0
  12. package/dist/components/AccountMenu.d.ts +44 -0
  13. package/dist/components/AccountMenu.js +43 -0
  14. package/dist/components/AppShell.d.ts +45 -2
  15. package/dist/components/AppShell.js +116 -5
  16. package/dist/components/AuditLog.d.ts +128 -0
  17. package/dist/components/AuditLog.js +196 -0
  18. package/dist/components/BrandProfiles.js +4 -3
  19. package/dist/components/Breadcrumbs.d.ts +9 -2
  20. package/dist/components/Breadcrumbs.js +3 -2
  21. package/dist/components/Button.d.ts +12 -5
  22. package/dist/components/Button.js +7 -3
  23. package/dist/components/Card.d.ts +18 -1
  24. package/dist/components/Card.js +5 -1
  25. package/dist/components/ChangeHighlight.d.ts +30 -0
  26. package/dist/components/ChangeHighlight.js +21 -0
  27. package/dist/components/ChartCore.js +2 -1
  28. package/dist/components/Checkbox.d.ts +27 -1
  29. package/dist/components/Checkbox.js +31 -2
  30. package/dist/components/CodeDisplay.d.ts +65 -0
  31. package/dist/components/CodeDisplay.js +124 -0
  32. package/dist/components/Combobox.d.ts +27 -6
  33. package/dist/components/Combobox.js +195 -58
  34. package/dist/components/CommandPalette.d.ts +36 -1
  35. package/dist/components/CommandPalette.js +254 -2
  36. package/dist/components/ContactCard.d.ts +66 -0
  37. package/dist/components/ContactCard.js +74 -0
  38. package/dist/components/Count.d.ts +24 -0
  39. package/dist/components/Count.js +32 -0
  40. package/dist/components/DataWorkspace.d.ts +167 -0
  41. package/dist/components/DataWorkspace.js +383 -0
  42. package/dist/components/DateFieldParts.d.ts +2 -1
  43. package/dist/components/DateFieldParts.js +7 -0
  44. package/dist/components/DatePicker.d.ts +9 -2
  45. package/dist/components/DatePicker.js +67 -28
  46. package/dist/components/DateRangePicker.d.ts +12 -2
  47. package/dist/components/DateRangePicker.js +13 -12
  48. package/dist/components/DetailPage.d.ts +7 -1
  49. package/dist/components/DetailPage.js +2 -1
  50. package/dist/components/Dialogs.d.ts +71 -10
  51. package/dist/components/Dialogs.js +130 -30
  52. package/dist/components/DiffViewer.d.ts +23 -3
  53. package/dist/components/DiffViewer.js +26 -12
  54. package/dist/components/DiscardPrompt.js +16 -0
  55. package/dist/components/DisplayPreferences.d.ts +83 -0
  56. package/dist/components/DisplayPreferences.js +185 -0
  57. package/dist/components/EditorialHome.d.ts +23 -3
  58. package/dist/components/EditorialHome.js +16 -11
  59. package/dist/components/EntityPicker.d.ts +20 -3
  60. package/dist/components/EntityPicker.js +32 -13
  61. package/dist/components/FilterEditor.d.ts +18 -4
  62. package/dist/components/FilterEditor.js +141 -19
  63. package/dist/components/InfoTip.js +11 -12
  64. package/dist/components/InlineEdit.d.ts +87 -20
  65. package/dist/components/InlineEdit.js +197 -54
  66. package/dist/components/Input.d.ts +12 -2
  67. package/dist/components/Input.js +5 -3
  68. package/dist/components/Kbd.d.ts +6 -1
  69. package/dist/components/Kbd.js +6 -6
  70. package/dist/components/Lightbox.d.ts +41 -5
  71. package/dist/components/Lightbox.js +95 -9
  72. package/dist/components/MatchReview.d.ts +40 -1
  73. package/dist/components/MatchReview.js +130 -4
  74. package/dist/components/MetricInput.js +3 -2
  75. package/dist/components/Modal.d.ts +2 -1
  76. package/dist/components/Modal.js +13 -4
  77. package/dist/components/NavFavourites.d.ts +5 -0
  78. package/dist/components/NavFavourites.js +7 -7
  79. package/dist/components/OtpInput.d.ts +33 -0
  80. package/dist/components/OtpInput.js +123 -0
  81. package/dist/components/PageHeader.d.ts +7 -1
  82. package/dist/components/PageHeader.js +2 -2
  83. package/dist/components/Pagination.js +6 -5
  84. package/dist/components/PasswordInput.d.ts +34 -0
  85. package/dist/components/PasswordInput.js +101 -0
  86. package/dist/components/Peek.js +3 -3
  87. package/dist/components/Popover.js +2 -0
  88. package/dist/components/PrintLayout.d.ts +63 -0
  89. package/dist/components/PrintLayout.js +114 -0
  90. package/dist/components/QrCode.d.ts +47 -0
  91. package/dist/components/QrCode.js +55 -0
  92. package/dist/components/QrScanner.d.ts +95 -0
  93. package/dist/components/QrScanner.js +266 -0
  94. package/dist/components/RelationshipGraph.d.ts +32 -1
  95. package/dist/components/RelationshipGraph.js +18 -5
  96. package/dist/components/RelationshipGraphLayered.d.ts +1 -1
  97. package/dist/components/RelationshipGraphLayered.js +190 -115
  98. package/dist/components/RelationshipGraphList.js +33 -6
  99. package/dist/components/RuleBuilder.js +39 -7
  100. package/dist/components/SchedulingAgenda.d.ts +3 -1
  101. package/dist/components/SchedulingCalendar.d.ts +3 -1
  102. package/dist/components/SchedulingCalendar.js +43 -4
  103. package/dist/components/SchedulingResourceDay.d.ts +57 -0
  104. package/dist/components/SchedulingResourceDay.js +287 -0
  105. package/dist/components/SchedulingTimeGrid.d.ts +3 -1
  106. package/dist/components/SchedulingTimeline.d.ts +3 -1
  107. package/dist/components/SearchSuggest.d.ts +23 -3
  108. package/dist/components/SearchSuggest.js +48 -17
  109. package/dist/components/Segmented.d.ts +39 -4
  110. package/dist/components/Segmented.js +31 -3
  111. package/dist/components/Select.d.ts +3 -2
  112. package/dist/components/Select.js +1 -1
  113. package/dist/components/ShortcutHint.d.ts +11 -10
  114. package/dist/components/ShortcutHint.js +44 -38
  115. package/dist/components/Sidebar.d.ts +36 -4
  116. package/dist/components/Sidebar.js +37 -12
  117. package/dist/components/SortableList.d.ts +45 -0
  118. package/dist/components/SortableList.js +134 -0
  119. package/dist/components/Stat.d.ts +28 -3
  120. package/dist/components/Stat.js +13 -3
  121. package/dist/components/StatusPill.d.ts +14 -3
  122. package/dist/components/StatusPill.js +11 -3
  123. package/dist/components/Steps.js +2 -1
  124. package/dist/components/Table.d.ts +9 -2
  125. package/dist/components/Table.js +4 -2
  126. package/dist/components/TableControls.js +18 -58
  127. package/dist/components/TableOverflowProbe.d.ts +12 -0
  128. package/dist/components/TableOverflowProbe.js +27 -0
  129. package/dist/components/Tabs.d.ts +2 -1
  130. package/dist/components/Tabs.js +1 -0
  131. package/dist/components/TagInput.d.ts +67 -0
  132. package/dist/components/TagInput.js +342 -0
  133. package/dist/components/Textarea.d.ts +5 -0
  134. package/dist/components/Textarea.js +2 -2
  135. package/dist/components/TimePicker.d.ts +80 -0
  136. package/dist/components/TimePicker.js +347 -0
  137. package/dist/components/Toast.js +3 -2
  138. package/dist/components/Tooltip.d.ts +8 -4
  139. package/dist/components/Tooltip.js +24 -21
  140. package/dist/components/Tree.d.ts +8 -0
  141. package/dist/components/Tree.js +11 -5
  142. package/dist/display-preferences-model.d.ts +53 -0
  143. package/dist/display-preferences-model.js +64 -0
  144. package/dist/emoji.cjs +28 -35
  145. package/dist/filter-model.d.ts +59 -3
  146. package/dist/filter-model.js +67 -2
  147. package/dist/hooks/useChangeHighlight.d.ts +33 -0
  148. package/dist/hooks/useChangeHighlight.js +44 -0
  149. package/dist/hooks/useDialog.d.ts +5 -0
  150. package/dist/hooks/useDialog.js +3 -2
  151. package/dist/hooks/useOptimisticValue.d.ts +51 -0
  152. package/dist/hooks/useOptimisticValue.js +61 -0
  153. package/dist/hooks/useReducedMotion.d.ts +4 -1
  154. package/dist/hooks/useReducedMotion.js +7 -1
  155. package/dist/hooks/useResolvedFilterLabels.d.ts +15 -0
  156. package/dist/hooks/useResolvedFilterLabels.js +34 -0
  157. package/dist/hooks/useVirtualRows.d.ts +21 -2
  158. package/dist/hooks/useVirtualRows.js +30 -3
  159. package/dist/icons/Icon.d.ts +1 -1
  160. package/dist/index.cjs +18644 -13210
  161. package/dist/index.d.cts +3003 -699
  162. package/dist/index.d.ts +42 -16
  163. package/dist/index.js +139 -7
  164. package/dist/labels-context.d.ts +90 -3
  165. package/dist/overlay-stack.d.ts +6 -0
  166. package/dist/overlay-stack.js +13 -0
  167. package/dist/qr-model.d.ts +65 -0
  168. package/dist/qr-model.js +323 -0
  169. package/dist/relationship-graph-model.d.ts +78 -3
  170. package/dist/relationship-graph-model.js +112 -5
  171. package/dist/reorder.d.ts +30 -0
  172. package/dist/reorder.js +74 -0
  173. package/dist/rich-text.cjs +4 -2
  174. package/dist/scheduling-resource-model.d.ts +76 -0
  175. package/dist/scheduling-resource-model.js +74 -0
  176. package/dist/search-suggest-model.js +3 -1
  177. package/dist/shell-context.d.ts +11 -0
  178. package/dist/shell-context.js +7 -0
  179. package/dist/time-field-model.d.ts +46 -0
  180. package/dist/time-field-model.js +152 -0
  181. package/dist/toast-store.d.ts +17 -1
  182. package/dist/toast-store.js +23 -7
  183. package/package.json +2 -1
@@ -1,8 +1,15 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { useEffect, useId, useMemo, useRef, useState } from "react";
3
+ import { Fragment, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
4
4
  import { cx } from "../cx.js";
5
- import { filterComboboxOptions } from "../combobox-model.js";
5
+ import { filterComboboxItems } from "../combobox-model.js";
6
+ import { useAnchoredPosition, zoomOf } from "../hooks/useAnchoredPosition.js";
7
+ import { CheckIcon } from "../icons/components.js";
8
+ import { buildSelectModel, firstEnabled, lastEnabled, moveActive } from "../select-model.js";
9
+ const useIsomorphicLayoutEffect = typeof document !== "undefined" ? useLayoutEffect : useEffect;
10
+ const OFFSET = 4;
11
+ const EDGE = 8;
12
+ const MAX_HEIGHT = 320;
6
13
  function Combobox({
7
14
  options,
8
15
  value,
@@ -14,105 +21,235 @@ function Combobox({
14
21
  name,
15
22
  disabled,
16
23
  className,
24
+ "aria-labelledby": ariaLabelledBy,
25
+ "aria-describedby": ariaDescribedBy,
26
+ "aria-invalid": ariaInvalid,
27
+ inputId,
28
+ placement = "bottom-start",
17
29
  ...props
18
30
  }) {
19
- const generatedId = useId();
20
- const listboxId = `${generatedId}-listbox`;
31
+ const uid = useId();
32
+ const listboxId = `${uid}-listbox`;
33
+ const optionId = (index) => `${listboxId}-${index}`;
21
34
  const rootRef = useRef(null);
35
+ const inputRef = useRef(null);
36
+ const popupRef = useRef(null);
37
+ const listRef = useRef(null);
22
38
  const [internalValue, setInternalValue] = useState(defaultValue ?? "");
23
39
  const selectedValue = value ?? internalValue;
24
- const selected = options.find((option) => option.value === selectedValue);
40
+ const all = useMemo(() => buildSelectModel(options).all, [options]);
41
+ const selected = all.find((entry) => entry.option.value === selectedValue)?.option;
25
42
  const [query, setQuery] = useState(selected?.label ?? "");
43
+ const [filter, setFilter] = useState("");
26
44
  const [open, setOpen] = useState(false);
27
- const [activeIndex, setActiveIndex] = useState(0);
28
- const filtered = useMemo(() => filterComboboxOptions(options, open ? query : ""), [open, options, query]);
29
- const activeOption = filtered[activeIndex];
45
+ const [activeIndex, setActiveIndex] = useState(-1);
46
+ const model = useMemo(() => buildSelectModel(filterComboboxItems(options, open ? filter : "")), [open, options, filter]);
47
+ const entries = model.entries;
48
+ const active = activeIndex >= 0 ? entries[activeIndex] : void 0;
49
+ const selectedLabel = () => all.find((entry) => entry.option.value === selectedValue)?.option.label ?? "";
30
50
  useEffect(() => {
31
- if (!open) setQuery(options.find((option) => option.value === selectedValue)?.label ?? "");
51
+ if (!open) setQuery(selectedLabel());
32
52
  }, [open, options, selectedValue]);
33
- const select = (option) => {
34
- if (option.disabled) return;
53
+ const openList = (target = "selected", list = entries) => {
54
+ if (disabled) return;
55
+ const chosen = list.findIndex((entry) => entry.option.value === selectedValue && !entry.disabled);
56
+ setActiveIndex(target === "last" ? lastEnabled(list) : target === "first" || chosen < 0 ? firstEnabled(list) : chosen);
57
+ setOpen(true);
58
+ };
59
+ const select = (entry) => {
60
+ if (!entry || entry.disabled) return;
61
+ const option = entry.option;
35
62
  if (value == null) setInternalValue(option.value);
36
63
  setQuery(option.label);
64
+ setFilter("");
37
65
  setOpen(false);
66
+ setActiveIndex(-1);
38
67
  onValueChange?.(option.value, option);
39
68
  };
40
- const move = (step) => {
41
- if (!filtered.length) return;
42
- let next = activeIndex;
43
- for (let attempts = 0; attempts < filtered.length; attempts += 1) {
44
- next = (next + step + filtered.length) % filtered.length;
45
- if (!filtered[next]?.disabled) break;
46
- }
47
- setActiveIndex(next);
69
+ const restore = () => {
70
+ setFilter("");
71
+ setOpen(false);
72
+ setActiveIndex(-1);
73
+ setQuery(selectedLabel());
48
74
  };
49
75
  const handleKeyDown = (event) => {
50
- if (event.key === "ArrowDown" || event.key === "ArrowUp") {
51
- event.preventDefault();
52
- if (!open) setOpen(true);
53
- else move(event.key === "ArrowDown" ? 1 : -1);
54
- } else if (event.key === "Enter" && open && activeOption) {
55
- event.preventDefault();
56
- select(activeOption);
57
- } else if (event.key === "Escape") {
58
- setOpen(false);
59
- setQuery(selected?.label ?? "");
76
+ if (event.nativeEvent.isComposing || event.keyCode === 229) return;
77
+ const { key, altKey } = event;
78
+ switch (key) {
79
+ case "ArrowDown":
80
+ case "ArrowUp":
81
+ event.preventDefault();
82
+ if (!open) {
83
+ openList(key === "ArrowUp" && !altKey ? "last" : "selected");
84
+ return;
85
+ }
86
+ if (altKey) return;
87
+ setActiveIndex((index) => moveActive(entries, index, key === "ArrowDown" ? 1 : -1));
88
+ return;
89
+ case "PageDown":
90
+ case "PageUp":
91
+ if (!open) return;
92
+ event.preventDefault();
93
+ setActiveIndex((index) => moveActive(entries, index, key === "PageDown" ? 10 : -10));
94
+ return;
95
+ case "Home":
96
+ case "End":
97
+ if (!open || activeIndex < 0) return;
98
+ event.preventDefault();
99
+ setActiveIndex(key === "Home" ? firstEnabled(entries) : lastEnabled(entries));
100
+ return;
101
+ case "Enter":
102
+ if (open && active) {
103
+ event.preventDefault();
104
+ select(active);
105
+ }
106
+ return;
107
+ case "Escape":
108
+ if (open || query !== selectedLabel()) {
109
+ event.preventDefault();
110
+ event.stopPropagation();
111
+ restore();
112
+ }
113
+ return;
114
+ default:
60
115
  }
61
116
  };
62
117
  const handleBlur = (event) => {
63
- if (!rootRef.current?.contains(event.relatedTarget)) {
64
- setOpen(false);
65
- setQuery(options.find((option) => option.value === selectedValue)?.label ?? "");
66
- }
118
+ const next = event.relatedTarget;
119
+ if (!rootRef.current?.contains(next)) restore();
67
120
  };
68
121
  const handleChange = (event) => {
69
- setQuery(event.target.value);
70
- setActiveIndex(0);
122
+ const text = event.target.value;
123
+ setQuery(text);
124
+ setFilter(text);
125
+ const list = buildSelectModel(filterComboboxItems(options, text)).entries;
126
+ setActiveIndex(firstEnabled(list));
71
127
  setOpen(true);
72
128
  };
129
+ useIsomorphicLayoutEffect(() => {
130
+ if (open && activeIndex >= entries.length) setActiveIndex(firstEnabled(entries));
131
+ }, [open, entries.length]);
132
+ useIsomorphicLayoutEffect(() => {
133
+ const popup = popupRef.current;
134
+ const input = inputRef.current;
135
+ if (!popup || !input) return;
136
+ const isOpen = () => {
137
+ try {
138
+ return popup.matches(":popover-open");
139
+ } catch {
140
+ return false;
141
+ }
142
+ };
143
+ if (open) {
144
+ const zoom = zoomOf(input);
145
+ const r = input.getBoundingClientRect();
146
+ const room = Math.max(r.top, window.innerHeight - r.bottom) / zoom - OFFSET - EDGE;
147
+ popup.style.minWidth = `${Math.round(r.width / zoom)}px`;
148
+ popup.style.maxHeight = `${Math.max(96, Math.min(MAX_HEIGHT, Math.floor(room)))}px`;
149
+ try {
150
+ if (!isOpen()) popup.showPopover();
151
+ } catch {
152
+ }
153
+ } else {
154
+ try {
155
+ if (isOpen()) popup.hidePopover();
156
+ } catch {
157
+ }
158
+ }
159
+ }, [open]);
160
+ useAnchoredPosition(inputRef, popupRef, { open, placement, offset: OFFSET, padding: EDGE });
161
+ useIsomorphicLayoutEffect(() => {
162
+ const list = listRef.current;
163
+ if (!open || !list || activeIndex < 0) return;
164
+ const el = list.ownerDocument.getElementById(optionId(activeIndex));
165
+ if (!el || !list.contains(el)) return;
166
+ const top = el.offsetTop;
167
+ const bottom = top + el.offsetHeight;
168
+ if (top < list.scrollTop) list.scrollTop = top;
169
+ else if (bottom > list.scrollTop + list.clientHeight) list.scrollTop = bottom - list.clientHeight;
170
+ }, [open, activeIndex]);
171
+ const popoverAttr = { popover: "manual" };
172
+ const invalid = ariaInvalid != null && ariaInvalid !== false && ariaInvalid !== "false";
173
+ const renderEntry = (entry) => /* @__PURE__ */ jsxs(
174
+ "div",
175
+ {
176
+ id: optionId(entry.index),
177
+ className: "uix-listbox__option",
178
+ role: "option",
179
+ "aria-selected": entry.option.value === selectedValue,
180
+ "aria-disabled": entry.disabled || void 0,
181
+ "data-active": open && entry.index === activeIndex || void 0,
182
+ onMouseDown: (event) => event.preventDefault(),
183
+ onPointerMove: () => {
184
+ if (entry.index !== activeIndex && !entry.disabled) setActiveIndex(entry.index);
185
+ },
186
+ onClick: () => select(entry),
187
+ children: [
188
+ /* @__PURE__ */ jsx("span", { className: "uix-listbox__text", children: /* @__PURE__ */ jsx("span", { className: "uix-listbox__label", children: entry.option.label }) }),
189
+ /* @__PURE__ */ jsx("span", { className: "uix-listbox__check", "aria-hidden": "true", children: entry.option.value === selectedValue && /* @__PURE__ */ jsx(CheckIcon, { size: "sm" }) })
190
+ ]
191
+ },
192
+ `${entry.option.value}-${entry.index}`
193
+ );
73
194
  return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: cx("uix-combobox", className), onBlur: handleBlur, ...props, children: [
74
195
  name && /* @__PURE__ */ jsx("input", { type: "hidden", name, value: selectedValue }),
75
196
  /* @__PURE__ */ jsx(
76
197
  "input",
77
198
  {
199
+ ref: inputRef,
200
+ id: inputId,
78
201
  className: "uix-input",
79
202
  role: "combobox",
80
- "aria-label": label,
203
+ "aria-label": ariaLabelledBy ? void 0 : label,
204
+ "aria-labelledby": ariaLabelledBy,
205
+ "aria-describedby": ariaDescribedBy,
206
+ "aria-invalid": invalid || void 0,
81
207
  "aria-autocomplete": "list",
82
208
  "aria-expanded": open,
83
209
  "aria-controls": listboxId,
84
- "aria-activedescendant": open && activeOption ? `${listboxId}-${activeIndex}` : void 0,
210
+ "aria-activedescendant": open && active ? optionId(activeIndex) : void 0,
211
+ autoComplete: "off",
212
+ spellCheck: false,
85
213
  value: query,
86
214
  placeholder,
87
215
  disabled,
88
216
  onChange: handleChange,
89
217
  onFocus: () => {
90
218
  setQuery("");
91
- setOpen(true);
92
- setActiveIndex(Math.max(0, options.findIndex((option) => option.value === selectedValue)));
219
+ openList("selected", buildSelectModel(options).entries);
93
220
  },
94
221
  onKeyDown: handleKeyDown
95
222
  }
96
223
  ),
97
- open && /* @__PURE__ */ jsxs("ul", { id: listboxId, className: "uix-listbox uix-combobox__listbox", role: "listbox", children: [
98
- filtered.length === 0 && /* @__PURE__ */ jsx("li", { className: "uix-listbox__empty", children: emptyLabel }),
99
- filtered.map((option, index) => /* @__PURE__ */ jsx(
100
- "li",
101
- {
102
- id: `${listboxId}-${index}`,
103
- className: "uix-listbox__option",
104
- role: "option",
105
- "aria-selected": option.value === selectedValue,
106
- "aria-disabled": option.disabled || void 0,
107
- "data-active": index === activeIndex || void 0,
108
- onMouseDown: (event) => event.preventDefault(),
109
- onMouseEnter: () => setActiveIndex(index),
110
- onClick: () => select(option),
111
- children: option.label
112
- },
113
- option.value
114
- ))
115
- ] })
224
+ /* @__PURE__ */ jsxs(
225
+ "div",
226
+ {
227
+ ref: popupRef,
228
+ className: "uix-select__popup uix-combobox__popup",
229
+ hidden: !open,
230
+ ...popoverAttr,
231
+ onMouseDown: (event) => event.preventDefault(),
232
+ children: [
233
+ /* @__PURE__ */ jsx(
234
+ "div",
235
+ {
236
+ ref: listRef,
237
+ id: listboxId,
238
+ role: "listbox",
239
+ "aria-label": label,
240
+ className: "uix-listbox uix-select__listbox",
241
+ hidden: entries.length === 0 || void 0,
242
+ children: model.sections.map((section, i) => section.label != null ? /* @__PURE__ */ jsxs("div", { role: "group", className: "uix-listbox__group", "aria-labelledby": `${uid}-group-${i}`, "aria-disabled": section.disabled || void 0, children: [
243
+ /* @__PURE__ */ jsx("div", { id: `${uid}-group-${i}`, role: "presentation", className: "uix-listbox__group-label", children: section.label }),
244
+ section.entries.map(renderEntry)
245
+ ] }, `g${i}`) : /* @__PURE__ */ jsx(Fragment, { children: section.entries.map(renderEntry) }, `s${i}`))
246
+ }
247
+ ),
248
+ entries.length === 0 && /* @__PURE__ */ jsx("div", { className: "uix-select__state", children: emptyLabel })
249
+ ]
250
+ }
251
+ ),
252
+ /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", role: "status", "aria-live": "polite", children: open && entries.length === 0 ? emptyLabel : "" })
116
253
  ] });
117
254
  }
118
255
  export {
@@ -1,5 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, HTMLAttributes, InputHTMLAttributes } from 'react';
3
+ import { CommandPaletteItem } from '../command-palette-model.js';
3
4
 
4
5
  interface CommandPaletteProps extends HTMLAttributes<HTMLDivElement> {
5
6
  /** TENSOR RX-125 (UIX-04): translatable; English default. */
@@ -12,6 +13,40 @@ interface CommandPaletteProps extends HTMLAttributes<HTMLDivElement> {
12
13
  /** Props for the search input (value/onChange/placeholder wired by the consumer). */
13
14
  inputProps?: InputHTMLAttributes<HTMLInputElement>;
14
15
  children?: ReactNode;
16
+ /**
17
+ * Items mode (HAR-1378): pass the commands and the palette filters them itself (case and
18
+ * diacritics folded, best match first), groups them, moves the active one with ↑ / ↓ (Home / End
19
+ * while the query is empty) through `aria-activedescendant`, and runs it on Enter or a click.
20
+ * Without `items`, `children` render as before and the consumer wires everything.
21
+ */
22
+ items?: readonly CommandPaletteItem[];
23
+ /** Items mode: the query (controlled). Pair with `onQueryChange`. */
24
+ query?: string;
25
+ /** Items mode: the query as it changes (also when the palette clears it on close). */
26
+ onQueryChange?: (query: string) => void;
27
+ /** Items mode: what an empty query shows above all items, under the `recentLabel` heading. */
28
+ recent?: readonly CommandPaletteItem[];
29
+ /** Items mode: your own matching instead of the built-in one (`filterCommandItems`). */
30
+ filter?: (items: readonly CommandPaletteItem[], query: string) => CommandPaletteItem[];
31
+ /** Items mode: called with every chosen item, after its own `onSelect`, e.g. to record it as recent. */
32
+ onItemSelect?: (item: CommandPaletteItem) => void;
33
+ /** Items mode: follow an item's `href` with your router. Default: the browser goes there. */
34
+ onNavigate?: (href: string, item: CommandPaletteItem) => void;
35
+ /** Items mode: shown when nothing matches. Default: `UixLabelsProvider` `commandPalette.empty`, then "No results". */
36
+ emptyLabel?: ReactNode;
37
+ /** Items mode: the heading of `recent`. Default: `commandPalette.recent`, then "Recent". */
38
+ recentLabel?: string;
39
+ /** Items mode: the input's placeholder. */
40
+ placeholder?: string;
41
+ /**
42
+ * Items mode: host the palette in a modal native `<dialog>` (focus stays inside, Escape and a
43
+ * click outside close it, focus returns to the opener). Closing it clears the query. Without
44
+ * `open`, the palette renders in place.
45
+ */
46
+ open?: boolean;
47
+ onOpenChange?: (open: boolean) => void;
48
+ /** Items mode: render at most this many found items (the count still says how many were found). Default 100. */
49
+ limit?: number;
15
50
  }
16
51
  /**
17
52
  * ⌘K command palette surface over `.uix-cmdk`. Presentational: mount it inside a
@@ -23,7 +58,7 @@ interface CommandPaletteProps extends HTMLAttributes<HTMLDivElement> {
23
58
  * popover it sits in `[data-uix-instant]`: it opens and closes with no animation, without the
24
59
  * consumer knowing the attribute (or the older `data-uix-cmdk-dialog`).
25
60
  */
26
- declare function CommandPalette({ inputProps, children, inputLabel: inputLabelProp, resultsLabel, className, ...props }: CommandPaletteProps): react.JSX.Element;
61
+ declare function CommandPalette(props: CommandPaletteProps): react.JSX.Element;
27
62
  interface CommandGroupProps {
28
63
  label?: ReactNode;
29
64
  children?: ReactNode;
@@ -1,12 +1,40 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { createContext, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
4
4
  import { cx } from "../cx.js";
5
5
  import { fillLabel } from "../fill-label.js";
6
6
  import { useUixLabels } from "../labels-context.js";
7
+ import { useDialog } from "../hooks/useDialog.js";
8
+ import { filterCommandItems, groupCommandItems } from "../command-palette-model.js";
9
+ import { searchSegments } from "../search-suggest-model.js";
10
+ import { KbdCombo, ariaKeyShortcuts, useKbdPlatform } from "./Kbd.js";
7
11
  const CmdkContext = createContext(null);
8
12
  const useIsomorphicLayoutEffect = typeof document !== "undefined" ? useLayoutEffect : useEffect;
9
- function CommandPalette({ inputProps, children, inputLabel: inputLabelProp, resultsLabel, className, ...props }) {
13
+ function CommandPalette(props) {
14
+ if (props.items) return /* @__PURE__ */ jsx(CommandPaletteItems, { ...props, items: props.items });
15
+ return /* @__PURE__ */ jsx(CommandPaletteChildren, { ...props });
16
+ }
17
+ function CommandPaletteChildren({
18
+ inputProps,
19
+ children,
20
+ inputLabel: inputLabelProp,
21
+ resultsLabel,
22
+ className,
23
+ items: _items,
24
+ query: _query,
25
+ onQueryChange: _onQueryChange,
26
+ recent: _recent,
27
+ filter: _filter,
28
+ onItemSelect: _onItemSelect,
29
+ onNavigate: _onNavigate,
30
+ emptyLabel: _emptyLabel,
31
+ recentLabel: _recentLabel,
32
+ placeholder: _placeholder,
33
+ open: _open,
34
+ onOpenChange: _onOpenChange,
35
+ limit: _limit,
36
+ ...props
37
+ }) {
10
38
  const uixLabels = useUixLabels();
11
39
  const inputLabel = inputLabelProp ?? uixLabels.commandPalette?.input ?? "Command palette";
12
40
  const resultsOne = uixLabels.commandPalette?.resultsOne ?? "{count} result";
@@ -89,6 +117,230 @@ function CommandItem({ icon, shortcut, active, children, className, id, ...props
89
117
  }
90
118
  );
91
119
  }
120
+ const optionId = (listId, item, recent) => `${listId}-${recent ? "r-" : ""}${item.id.replace(/[^\w-]/g, "_")}`;
121
+ function Highlighted({ text, query }) {
122
+ return /* @__PURE__ */ jsx(Fragment, { children: searchSegments(text, query).map((segment, index) => segment.match ? /* @__PURE__ */ jsx("mark", { className: "uix-cmdk__mark", children: segment.text }, index) : segment.text) });
123
+ }
124
+ function CommandPaletteItems({
125
+ items,
126
+ query: queryProp,
127
+ onQueryChange,
128
+ recent,
129
+ filter,
130
+ onItemSelect,
131
+ onNavigate,
132
+ emptyLabel,
133
+ recentLabel,
134
+ placeholder,
135
+ open,
136
+ onOpenChange,
137
+ limit = 100,
138
+ inputLabel: inputLabelProp,
139
+ resultsLabel,
140
+ inputProps,
141
+ className,
142
+ children: _children,
143
+ ...props
144
+ }) {
145
+ const uixLabels = useUixLabels();
146
+ const labels = uixLabels.commandPalette;
147
+ const inputLabel = inputLabelProp ?? labels?.input ?? "Command palette";
148
+ const empty = emptyLabel ?? labels?.empty ?? "No results";
149
+ const recentHeading = recentLabel ?? labels?.recent ?? "Recent";
150
+ const resultsText = (count2) => resultsLabel?.(count2) ?? fillLabel(count2 === 1 ? labels?.resultsOne ?? "{count} result" : labels?.resultsMany ?? "{count} results", { count: count2 });
151
+ const resultsTextRef = useRef(resultsText);
152
+ resultsTextRef.current = resultsText;
153
+ const platform = useKbdPlatform();
154
+ const listId = useId();
155
+ const [queryState, setQueryState] = useState("");
156
+ const query = queryProp ?? queryState;
157
+ const setQuery = (next) => {
158
+ if (queryProp === void 0) setQueryState(next);
159
+ onQueryChange?.(next);
160
+ };
161
+ const inDialog = open !== void 0;
162
+ const ref = useDialog(inDialog ? open : false, () => onOpenChange?.(false));
163
+ const inputRef = useRef(null);
164
+ const listRef = useRef(null);
165
+ const returnTo = useRef(null);
166
+ const pressedBackdrop = useRef(false);
167
+ const found = useMemo(() => (filter ?? filterCommandItems)(items, query), [filter, items, query]);
168
+ const showRecent = query.trim() === "" && (recent?.length ?? 0) > 0;
169
+ const groups = useMemo(() => groupCommandItems(found.slice(0, Math.max(0, limit))), [found, limit]);
170
+ const rows = useMemo(() => [
171
+ ...showRecent ? recent.map((item) => ({ item, id: optionId(listId, item, true) })) : [],
172
+ ...groups.flatMap((group) => group.items.map((item) => ({ item, id: optionId(listId, item, false) })))
173
+ ], [showRecent, recent, groups, listId]);
174
+ const enabled = rows.filter((row) => !row.item.disabled);
175
+ const [activeState, setActiveState] = useState(null);
176
+ const activeId = enabled.some((row) => row.id === activeState) ? activeState : enabled[0]?.id ?? null;
177
+ const active = rows.find((row) => row.id === activeId);
178
+ useLayoutEffect(() => {
179
+ if (!activeId) return;
180
+ const el = listRef.current?.querySelector(`[id="${activeId}"]`);
181
+ el?.scrollIntoView?.({ block: "nearest" });
182
+ }, [activeId]);
183
+ const [announced, setAnnounced] = useState("");
184
+ const count = found.length;
185
+ useEffect(() => {
186
+ const t = window.setTimeout(() => setAnnounced(resultsTextRef.current(count)), 150);
187
+ return () => window.clearTimeout(t);
188
+ }, [count]);
189
+ useEffect(() => {
190
+ if (!inDialog) return;
191
+ if (open) {
192
+ returnTo.current = document.activeElement;
193
+ inputRef.current?.focus();
194
+ } else {
195
+ if (query) setQuery("");
196
+ setActiveState(null);
197
+ returnTo.current?.focus?.();
198
+ returnTo.current = null;
199
+ }
200
+ }, [open, inDialog]);
201
+ const choose = (item) => {
202
+ if (item.disabled) return;
203
+ item.onSelect?.();
204
+ if (item.href) {
205
+ if (onNavigate) onNavigate(item.href, item);
206
+ else window.location.assign(item.href);
207
+ }
208
+ onItemSelect?.(item);
209
+ if (inDialog) onOpenChange?.(false);
210
+ };
211
+ const move = (to) => {
212
+ if (enabled.length === 0) return;
213
+ const index = (to + enabled.length) % enabled.length;
214
+ setActiveState(enabled[index].id);
215
+ };
216
+ const onKeyDown = (event) => {
217
+ inputProps?.onKeyDown?.(event);
218
+ if (event.defaultPrevented || event.nativeEvent.isComposing) return;
219
+ const at = enabled.findIndex((row) => row.id === activeId);
220
+ switch (event.key) {
221
+ case "ArrowDown":
222
+ event.preventDefault();
223
+ move(at + 1);
224
+ break;
225
+ case "ArrowUp":
226
+ event.preventDefault();
227
+ move(at - 1);
228
+ break;
229
+ // Home / End move the caret while there is text to edit; in an empty field they jump.
230
+ case "Home":
231
+ if (query === "") {
232
+ event.preventDefault();
233
+ move(0);
234
+ }
235
+ break;
236
+ case "End":
237
+ if (query === "") {
238
+ event.preventDefault();
239
+ move(enabled.length - 1);
240
+ }
241
+ break;
242
+ case "Enter":
243
+ if (active) {
244
+ event.preventDefault();
245
+ choose(active.item);
246
+ }
247
+ break;
248
+ default:
249
+ }
250
+ };
251
+ const renderRow = ({ item, id }) => {
252
+ const isActive = id === activeId;
253
+ return /* @__PURE__ */ jsxs(
254
+ "div",
255
+ {
256
+ id,
257
+ role: "option",
258
+ className: "uix-cmdk__item",
259
+ "data-active": isActive || void 0,
260
+ "aria-selected": isActive,
261
+ "aria-disabled": item.disabled || void 0,
262
+ "aria-keyshortcuts": item.shortcut?.length ? ariaKeyShortcuts(item.shortcut, platform) : void 0,
263
+ onPointerMove: () => {
264
+ if (!item.disabled && !isActive) setActiveState(id);
265
+ },
266
+ onMouseDown: (event) => event.preventDefault(),
267
+ onClick: () => choose(item),
268
+ children: [
269
+ item.icon != null && /* @__PURE__ */ jsx("span", { className: "uix-cmdk__icon", "aria-hidden": "true", children: item.icon }),
270
+ /* @__PURE__ */ jsxs("span", { className: "uix-cmdk__text", children: [
271
+ /* @__PURE__ */ jsx("span", { className: "uix-cmdk__label", children: /* @__PURE__ */ jsx(Highlighted, { text: item.label, query }) }),
272
+ item.description && /* @__PURE__ */ jsx("span", { className: "uix-cmdk__desc", children: item.description })
273
+ ] }),
274
+ item.shortcut?.length ? /* @__PURE__ */ jsx("span", { className: "uix-cmdk__shortcut", children: /* @__PURE__ */ jsx(KbdCombo, { keys: [...item.shortcut], platform }) }) : null
275
+ ]
276
+ },
277
+ id
278
+ );
279
+ };
280
+ const recentRows = showRecent ? rows.slice(0, recent.length) : [];
281
+ let cursor = recentRows.length;
282
+ const palette = /* @__PURE__ */ jsxs("div", { className: cx("uix-cmdk", className), "data-items": "", ...props, children: [
283
+ /* @__PURE__ */ jsx(
284
+ "input",
285
+ {
286
+ ref: inputRef,
287
+ className: "uix-cmdk__input",
288
+ role: "combobox",
289
+ "aria-expanded": "true",
290
+ "aria-autocomplete": "list",
291
+ "aria-controls": listId,
292
+ "aria-activedescendant": activeId ?? void 0,
293
+ "aria-label": inputLabel,
294
+ placeholder,
295
+ autoComplete: "off",
296
+ spellCheck: false,
297
+ ...inputProps,
298
+ value: query,
299
+ onChange: (event) => {
300
+ inputProps?.onChange?.(event);
301
+ setQuery(event.currentTarget.value);
302
+ },
303
+ onKeyDown
304
+ }
305
+ ),
306
+ /* @__PURE__ */ jsxs("div", { ref: listRef, className: "uix-cmdk__list", role: "listbox", id: listId, "aria-label": inputLabel, hidden: rows.length === 0, children: [
307
+ recentRows.length > 0 && /* @__PURE__ */ jsxs("div", { role: "group", "aria-labelledby": `${listId}-recent`, children: [
308
+ /* @__PURE__ */ jsx("div", { className: "uix-cmdk__group", id: `${listId}-recent`, role: "presentation", children: recentHeading }),
309
+ recentRows.map(renderRow)
310
+ ] }),
311
+ groups.map((group, index) => {
312
+ const groupRows = rows.slice(cursor, cursor + group.items.length);
313
+ cursor += group.items.length;
314
+ const headingId = `${listId}-g${index}`;
315
+ return /* @__PURE__ */ jsxs("div", { role: "group", "aria-labelledby": group.label != null ? headingId : void 0, children: [
316
+ group.label != null && /* @__PURE__ */ jsx("div", { className: "uix-cmdk__group", id: headingId, role: "presentation", children: group.label }),
317
+ groupRows.map(renderRow)
318
+ ] }, group.label ?? `__${index}`);
319
+ })
320
+ ] }),
321
+ rows.length === 0 && /* @__PURE__ */ jsx("p", { className: "uix-cmdk__empty", children: empty }),
322
+ /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", role: "status", children: announced })
323
+ ] });
324
+ if (!inDialog) return palette;
325
+ return /* @__PURE__ */ jsx(
326
+ "dialog",
327
+ {
328
+ ref,
329
+ className: "uix-dialog uix-cmdk-dialog",
330
+ "data-uix-cmdk-dialog": "",
331
+ "data-uix-instant": "",
332
+ "aria-label": inputLabel,
333
+ onPointerDown: (event) => {
334
+ pressedBackdrop.current = event.target === event.currentTarget;
335
+ },
336
+ onClick: (event) => {
337
+ if (event.target === event.currentTarget && pressedBackdrop.current) onOpenChange?.(false);
338
+ pressedBackdrop.current = false;
339
+ },
340
+ children: palette
341
+ }
342
+ );
343
+ }
92
344
  export {
93
345
  CommandGroup,
94
346
  CommandItem,