@arun-dev/headless 4.9.0 → 4.10.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.
@@ -32,6 +32,8 @@ __export(index_parts_exports, {
32
32
  ChipRemove: () => ComboboxChipRemove,
33
33
  Clear: () => ComboboxClear,
34
34
  Empty: () => ComboboxEmpty,
35
+ Group: () => ComboboxGroup,
36
+ GroupLabel: () => ComboboxGroupLabel,
35
37
  Input: () => ComboboxInput,
36
38
  InputGroup: () => ComboboxInputGroup,
37
39
  Item: () => ComboboxItem,
@@ -169,6 +171,11 @@ function useComboboxRootContext(part) {
169
171
 
170
172
  // src/combobox/ComboboxRoot.tsx
171
173
  var import_jsx_runtime = require("react/jsx-runtime");
174
+ function isGroupList(items) {
175
+ return items.length > 0 && items.every(
176
+ (entry) => entry !== null && typeof entry === "object" && Array.isArray(entry.items)
177
+ );
178
+ }
172
179
  function defaultItemToString(item) {
173
180
  if (typeof item === "string") return item;
174
181
  if (item !== null && typeof item === "object" && "label" in item) return String(item.label);
@@ -193,6 +200,7 @@ function ComboboxRoot({
193
200
  onLoadMore,
194
201
  loading = false,
195
202
  disabled = false,
203
+ required = false,
196
204
  name,
197
205
  form,
198
206
  children
@@ -229,10 +237,17 @@ function ComboboxRoot({
229
237
  });
230
238
  const query = !multiple && inputValue === selectedLabel ? "" : inputValue;
231
239
  const deferredQuery = (0, import_react3.useDeferredValue)(query);
232
- const filteredItems = (0, import_react3.useMemo)(() => {
233
- if (filter === null) return items;
234
- const run = filter ?? defaultFilter;
235
- return run(items, deferredQuery, itemToString);
240
+ const { filteredItems, filteredGroups } = (0, import_react3.useMemo)(() => {
241
+ const run = filter === null ? (list) => list : (list) => (filter ?? defaultFilter)(
242
+ list,
243
+ deferredQuery,
244
+ itemToString
245
+ );
246
+ if (!isGroupList(items)) {
247
+ return { filteredItems: run(items), filteredGroups: null };
248
+ }
249
+ const groups = items.map((group) => ({ ...group, items: run(group.items) })).filter((group) => group.items.length > 0);
250
+ return { filteredItems: groups.flatMap((group) => group.items), filteredGroups: groups };
236
251
  }, [items, filter, deferredQuery, itemToString]);
237
252
  const indexByKey = (0, import_react3.useMemo)(
238
253
  () => new Map(filteredItems.map((item, index) => [itemToKey(item), index])),
@@ -377,9 +392,11 @@ function ComboboxRoot({
377
392
  open,
378
393
  multiple,
379
394
  disabled,
395
+ required,
380
396
  inputValue,
381
397
  loading,
382
398
  filteredItems,
399
+ filteredGroups,
383
400
  itemToString,
384
401
  itemToKey,
385
402
  indexByKey,
@@ -413,9 +430,11 @@ function ComboboxRoot({
413
430
  open,
414
431
  multiple,
415
432
  disabled,
433
+ required,
416
434
  inputValue,
417
435
  loading,
418
436
  filteredItems,
437
+ filteredGroups,
419
438
  itemToString,
420
439
  itemToKey,
421
440
  indexByKey,
@@ -578,6 +597,7 @@ function ComboboxInput({ className, render, ...rest }) {
578
597
  open,
579
598
  multiple,
580
599
  disabled,
600
+ required,
581
601
  inputValue,
582
602
  filteredItems,
583
603
  itemToKey,
@@ -606,6 +626,20 @@ function ComboboxInput({ className, render, ...rest }) {
606
626
  () => highlight.get().index,
607
627
  () => -1
608
628
  );
629
+ const missing = required && selectedItems.length === 0;
630
+ const textOnly = missing && inputValue !== "";
631
+ const customValidityRef = (0, import_react5.useRef)(false);
632
+ (0, import_react5.useLayoutEffect)(() => {
633
+ const input = inputRef.current;
634
+ if (!input || typeof input.setCustomValidity !== "function") return;
635
+ if (textOnly) {
636
+ input.setCustomValidity(valueMissingMessage());
637
+ customValidityRef.current = true;
638
+ } else if (customValidityRef.current) {
639
+ input.setCustomValidity("");
640
+ customValidityRef.current = false;
641
+ }
642
+ }, [textOnly, inputRef]);
609
643
  function enabledIndices() {
610
644
  const indices = [];
611
645
  filteredItems.forEach((item, index) => {
@@ -643,6 +677,9 @@ function ComboboxInput({ className, render, ...rest }) {
643
677
  "aria-controls": listId,
644
678
  "aria-activedescendant": open && highlighted >= 0 ? optionId(highlighted) : void 0,
645
679
  disabled: disabled || void 0,
680
+ required: missing || void 0,
681
+ // Stays when a selection lifts `required`: the field is still required.
682
+ "aria-required": required || void 0,
646
683
  value: inputValue,
647
684
  ...comboboxDataAttributes({ open, multiple, disabled }),
648
685
  // The anchor, unless an InputGroup around it is.
@@ -712,6 +749,11 @@ function ComboboxInput({ className, render, ...rest }) {
712
749
  consumerProps: rest
713
750
  });
714
751
  }
752
+ function valueMissingMessage() {
753
+ const probe = document.createElement("input");
754
+ probe.required = true;
755
+ return probe.validationMessage || "Please select an item in the list.";
756
+ }
715
757
 
716
758
  // src/combobox/ComboboxInputGroup.tsx
717
759
  var import_react6 = require("react");
@@ -872,8 +914,10 @@ function ComboboxList({
872
914
  render,
873
915
  ...rest
874
916
  }) {
875
- const { multiple, loading, filteredItems, onLoadMore, listId } = useComboboxRootContext("List");
876
- const content = typeof children === "function" ? filteredItems.map((item, index) => children(item, index)) : children;
917
+ const { multiple, loading, filteredItems, filteredGroups, onLoadMore, listId } = useComboboxRootContext("List");
918
+ const content = typeof children === "function" ? (filteredGroups ?? filteredItems).map(
919
+ (entry, index) => children(entry, index)
920
+ ) : children;
877
921
  return useRender({
878
922
  render,
879
923
  defaultTagName: "div",
@@ -909,10 +953,52 @@ function LoadMoreSentinel() {
909
953
  }
910
954
 
911
955
  // src/combobox/ComboboxItem.tsx
956
+ var import_react12 = require("react");
957
+
958
+ // src/combobox/ComboboxGroup.tsx
912
959
  var import_react11 = require("react");
960
+ var import_jsx_runtime3 = require("react/jsx-runtime");
961
+ var ComboboxGroupContext = (0, import_react11.createContext)(null);
962
+ function useComboboxGroupContext() {
963
+ return (0, import_react11.useContext)(ComboboxGroupContext);
964
+ }
965
+ function ComboboxGroup({
966
+ disabled = false,
967
+ className,
968
+ children,
969
+ render,
970
+ ...rest
971
+ }) {
972
+ useComboboxRootContext("Group");
973
+ const labelId = `${(0, import_react11.useId)()}-label`;
974
+ const [hasLabel, setHasLabel] = (0, import_react11.useState)(false);
975
+ const registerLabel = (0, import_react11.useCallback)(() => {
976
+ setHasLabel(true);
977
+ return () => setHasLabel(false);
978
+ }, []);
979
+ const context = (0, import_react11.useMemo)(
980
+ () => ({ disabled, labelId, registerLabel }),
981
+ [disabled, labelId, registerLabel]
982
+ );
983
+ const element = useRender({
984
+ render,
985
+ defaultTagName: "div",
986
+ props: {
987
+ role: "group",
988
+ "aria-labelledby": hasLabel ? labelId : void 0,
989
+ "data-disabled": disabled ? "" : void 0,
990
+ className,
991
+ children
992
+ },
993
+ consumerProps: rest
994
+ });
995
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ComboboxGroupContext.Provider, { value: context, children: element });
996
+ }
997
+
998
+ // src/combobox/ComboboxItem.tsx
913
999
  function ComboboxItem({
914
1000
  value,
915
- disabled = false,
1001
+ disabled: disabledProp = false,
916
1002
  className,
917
1003
  children,
918
1004
  render,
@@ -928,23 +1014,25 @@ function ComboboxItem({
928
1014
  select,
929
1015
  optionId
930
1016
  } = useComboboxRootContext("Item");
1017
+ const group = useComboboxGroupContext();
1018
+ const disabled = disabledProp || (group?.disabled ?? false);
931
1019
  const key = itemToKey(value);
932
1020
  const index = indexByKey.get(key) ?? -1;
933
1021
  const selected = selectedKeys.has(key);
934
- const highlighted = (0, import_react11.useSyncExternalStore)(
1022
+ const highlighted = (0, import_react12.useSyncExternalStore)(
935
1023
  highlight.subscribe,
936
1024
  () => index >= 0 && highlight.get().index === index,
937
1025
  () => false
938
1026
  );
939
- const elementRef = (0, import_react11.useRef)(null);
940
- (0, import_react11.useLayoutEffect)(() => {
1027
+ const elementRef = (0, import_react12.useRef)(null);
1028
+ (0, import_react12.useLayoutEffect)(() => {
941
1029
  if (!disabled) return;
942
1030
  disabledKeys.add(key);
943
1031
  return () => {
944
1032
  disabledKeys.delete(key);
945
1033
  };
946
1034
  }, [disabled, disabledKeys, key]);
947
- (0, import_react11.useEffect)(() => {
1035
+ (0, import_react12.useEffect)(() => {
948
1036
  if (highlighted && highlight.get().reason === "keyboard") {
949
1037
  elementRef.current?.scrollIntoView?.({ block: "nearest" });
950
1038
  }
@@ -975,6 +1063,28 @@ function ComboboxItem({
975
1063
  });
976
1064
  }
977
1065
 
1066
+ // src/combobox/ComboboxGroupLabel.tsx
1067
+ var import_react13 = require("react");
1068
+ function ComboboxGroupLabel({
1069
+ className,
1070
+ children,
1071
+ render,
1072
+ ...rest
1073
+ }) {
1074
+ const group = useComboboxGroupContext();
1075
+ if (group === null) {
1076
+ throw new Error("<Combobox.GroupLabel> must be rendered inside <Combobox.Group>.");
1077
+ }
1078
+ const { labelId, registerLabel } = group;
1079
+ (0, import_react13.useLayoutEffect)(registerLabel, [registerLabel]);
1080
+ return useRender({
1081
+ render,
1082
+ defaultTagName: "div",
1083
+ props: { id: labelId, className, children },
1084
+ consumerProps: rest
1085
+ });
1086
+ }
1087
+
978
1088
  // src/combobox/ComboboxEmpty.tsx
979
1089
  function ComboboxEmpty({ className, children, render, ...rest }) {
980
1090
  const { filteredItems, loading } = useComboboxRootContext("Empty");
@@ -999,11 +1109,11 @@ function ComboboxStatus({ className, children, render, ...rest }) {
999
1109
  }
1000
1110
 
1001
1111
  // src/combobox/ComboboxChip.tsx
1002
- var import_react12 = require("react");
1003
- var import_jsx_runtime3 = require("react/jsx-runtime");
1004
- var ComboboxChipContext = (0, import_react12.createContext)(null);
1112
+ var import_react14 = require("react");
1113
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1114
+ var ComboboxChipContext = (0, import_react14.createContext)(null);
1005
1115
  function useComboboxChipContext() {
1006
- const context = (0, import_react12.useContext)(ComboboxChipContext);
1116
+ const context = (0, import_react14.useContext)(ComboboxChipContext);
1007
1117
  if (context === null) {
1008
1118
  throw new Error("<Combobox.ChipRemove> must be rendered inside <Combobox.Chip>.");
1009
1119
  }
@@ -1018,9 +1128,9 @@ function ComboboxChip({
1018
1128
  }) {
1019
1129
  const { disabled, itemToKey, remove, registerChip, chips, inputRef } = useComboboxRootContext("Chip");
1020
1130
  const key = itemToKey(value);
1021
- const elementRef = (0, import_react12.useRef)(null);
1022
- (0, import_react12.useLayoutEffect)(() => registerChip({ key, ref: elementRef }), [registerChip, key]);
1023
- const chipContext = (0, import_react12.useMemo)(() => ({ value }), [value]);
1131
+ const elementRef = (0, import_react14.useRef)(null);
1132
+ (0, import_react14.useLayoutEffect)(() => registerChip({ key, ref: elementRef }), [registerChip, key]);
1133
+ const chipContext = (0, import_react14.useMemo)(() => ({ value }), [value]);
1024
1134
  function focusAt(index) {
1025
1135
  const chip = chips[index];
1026
1136
  if (chip) chip.ref.current?.focus();
@@ -1061,7 +1171,7 @@ function ComboboxChip({
1061
1171
  },
1062
1172
  consumerProps: rest
1063
1173
  });
1064
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ComboboxChipContext.Provider, { value: chipContext, children: element });
1174
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ComboboxChipContext.Provider, { value: chipContext, children: element });
1065
1175
  }
1066
1176
 
1067
1177
  // src/combobox/ComboboxChipRemove.tsx
@@ -1096,11 +1206,11 @@ function ComboboxChipRemove({
1096
1206
  }
1097
1207
 
1098
1208
  // src/combobox/ComboboxValue.tsx
1099
- var import_jsx_runtime4 = require("react/jsx-runtime");
1209
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1100
1210
  function ComboboxValue({ children }) {
1101
1211
  const { multiple, selectedItems, itemToString } = useComboboxRootContext("Value");
1102
1212
  const value = multiple ? selectedItems : selectedItems[0] ?? null;
1103
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: children(value, { itemToString }) });
1213
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: children(value, { itemToString }) });
1104
1214
  }
1105
1215
  // Annotate the CommonJS export names for ESM import in node:
1106
1216
  0 && (module.exports = {
@@ -24,9 +24,20 @@ type ComboboxState = {
24
24
  type ComboboxValue$1<T, Multiple extends boolean> = Multiple extends true ? T[] : T | null;
25
25
  /** Takes the whole list, so a filter can rank as well as narrow — a fuzzy search, say. */
26
26
  type ComboboxFilter<T> = (items: readonly T[], query: string, itemToString: (item: T) => string) => T[];
27
+ /**
28
+ * Items under a label, as an `<optgroup>`: any object with an `items` array — the rest, a
29
+ * label or an id, is yours to render.
30
+ */
31
+ type ComboboxGroup$1<T> = {
32
+ readonly items: readonly T[];
33
+ };
27
34
  type ComboboxRootProps<T, Multiple extends boolean = false> = {
28
- /** Every item the list can show. With server search, the current results. */
29
- items: readonly T[];
35
+ /**
36
+ * Every item the list can show. With server search, the current results. Or groups of
37
+ * them: each an object with an `items` array, which the filter searches and `List` renders
38
+ * one by one, leaving out the groups with no match.
39
+ */
40
+ items: readonly T[] | readonly ComboboxGroup$1<T>[];
30
41
  /**
31
42
  * An item's label: what the input shows once it is picked, and what the default filter
32
43
  * matches. Defaults to the item itself for strings, else its `label` property.
@@ -78,6 +89,11 @@ type ComboboxRootProps<T, Multiple extends boolean = false> = {
78
89
  loading?: boolean;
79
90
  /** Disables the input and every button. */
80
91
  disabled?: boolean;
92
+ /**
93
+ * Something must be selected before the form submits. The input is `required` while the
94
+ * selection is empty, so the browser's own validation reports it.
95
+ */
96
+ required?: boolean;
81
97
  /** Submits each selected item's key under this name, from hidden inputs. */
82
98
  name?: string;
83
99
  /** Associates the hidden inputs with a `<form>` by id, when rendered outside it. */
@@ -96,7 +112,7 @@ type ComboboxRootProps<T, Multiple extends boolean = false> = {
96
112
  *
97
113
  * Focus stays in the input; the highlighted option is its `aria-activedescendant`.
98
114
  */
99
- declare function ComboboxRoot<T, Multiple extends boolean = false>({ items, itemToString: itemToStringProp, itemToKey: itemToKeyProp, filter, multiple: multipleProp, value: valueProp, defaultValue, onValueChange, inputValue: inputValueProp, defaultInputValue, onInputValueChange, open: openProp, defaultOpen, onOpenChange, onItemHighlighted, onLoadMore, loading, disabled, name, form, children, }: ComboboxRootProps<T, Multiple>): react.JSX.Element;
115
+ declare function ComboboxRoot<T, Multiple extends boolean = false>({ items, itemToString: itemToStringProp, itemToKey: itemToKeyProp, filter, multiple: multipleProp, value: valueProp, defaultValue, onValueChange, inputValue: inputValueProp, defaultInputValue, onInputValueChange, open: openProp, defaultOpen, onOpenChange, onItemHighlighted, onLoadMore, loading, disabled, required, name, form, children, }: ComboboxRootProps<T, Multiple>): react.JSX.Element;
100
116
 
101
117
  type ComboboxInputOwnProps = {
102
118
  /** Element to render instead of the default `<input>`. Props and ref are merged onto it. */
@@ -114,6 +130,11 @@ type ComboboxInputProps = ComboboxInputOwnProps & Omit<ComponentPropsWithRef<'in
114
130
  * Enter picks the highlighted item; Esc closes. With `multiple`, Backspace in an empty input
115
131
  * removes the last selected item, and Left at the start of the text moves into the chips.
116
132
  *
133
+ * With the Root's `required`, the input is `required` while nothing is selected, so the
134
+ * browser's validation reports it, `:invalid` matches it, and `form.checkValidity()` fails.
135
+ * Text typed but not picked is still nothing selected: the input reports `valueMissing`'s
136
+ * message then, through `setCustomValidity`.
137
+ *
117
138
  * Leaving the input closes the list and puts the text back: the selected label, or empty
118
139
  * with `multiple`.
119
140
  */
@@ -194,7 +215,8 @@ declare function ComboboxPopup({ side, align, className, children, render, ...re
194
215
  type ComboboxListOwnProps<T> = {
195
216
  /**
196
217
  * A function rendering one `Combobox.Item` per filtered item, or any nodes. With a
197
- * virtualizer, render only the rows it asks for.
218
+ * virtualizer, render only the rows it asks for. With groups, it is called once per group
219
+ * the filter left an item in, with that group, and renders a `Combobox.Group`.
198
220
  */
199
221
  children?: ReactNode | ((item: T, index: number) => ReactNode);
200
222
  /** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
@@ -204,7 +226,8 @@ type ComboboxListOwnProps<T> = {
204
226
  };
205
227
  type ComboboxListProps<T = unknown> = ComboboxListOwnProps<T> & Omit<ComponentPropsWithRef<'div'>, keyof ComboboxListOwnProps<T>>;
206
228
  /**
207
- * The `role="listbox"`. Given a function, renders it once per item the filter left.
229
+ * The `role="listbox"`. Given a function, renders it once per item the filter left — or, when
230
+ * the Root's `items` are groups, once per group.
208
231
  *
209
232
  * With the Root's `onLoadMore`, an invisible sentinel follows the items; when it scrolls into
210
233
  * view and nothing is `loading`, `onLoadMore` is called. The list is `aria-busy` meanwhile.
@@ -214,7 +237,7 @@ declare function ComboboxList<T = unknown>({ className, children, render, ...res
214
237
  type ComboboxItemOwnProps<T> = {
215
238
  /** The item this option stands for — one of the Root's `items`. */
216
239
  value: T;
217
- /** Cannot be picked, and the arrow keys skip it. */
240
+ /** Cannot be picked, and the arrow keys skip it. A disabled Group disables it too. */
218
241
  disabled?: boolean;
219
242
  /** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
220
243
  render?: ReactElement;
@@ -229,7 +252,38 @@ type ComboboxItemProps<T = unknown> = ComboboxItemOwnProps<T> & Omit<ComponentPr
229
252
  * `data-selected` and `aria-selected` follow the Root's `value`; `data-highlighted` follows
230
253
  * the arrow keys and the pointer. Only the items whose highlight changes re-render.
231
254
  */
232
- declare function ComboboxItem<T = unknown>({ value, disabled, className, children, render, ...rest }: ComboboxItemProps<T>): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
255
+ declare function ComboboxItem<T = unknown>({ value, disabled: disabledProp, className, children, render, ...rest }: ComboboxItemProps<T>): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
256
+
257
+ type ComboboxGroupOwnProps = {
258
+ /** Disables every Item inside, as `disabled` on an `<optgroup>` does. */
259
+ disabled?: boolean;
260
+ /** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
261
+ render?: ReactElement;
262
+ /** Ref to the rendered element. Merged with any ref on the `render` element. */
263
+ ref?: Ref<HTMLElement>;
264
+ };
265
+ type ComboboxGroupProps = ComboboxGroupOwnProps & Omit<ComponentPropsWithRef<'div'>, keyof ComboboxGroupOwnProps>;
266
+ /**
267
+ * Items under a label, as an `<optgroup>`: `role="group"`, named by its GroupLabel. Render one
268
+ * from the List's function for each group of the Root's `items`, with an Item for each of the
269
+ * group's `items` — the filter has already narrowed them, and left out a group with none.
270
+ *
271
+ * The arrow keys run through every group's items as one list, past the labels.
272
+ */
273
+ declare function ComboboxGroup({ disabled, className, children, render, ...rest }: ComboboxGroupProps): react.JSX.Element;
274
+
275
+ type ComboboxGroupLabelOwnProps = {
276
+ /** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
277
+ render?: ReactElement;
278
+ /** Ref to the rendered element. Merged with any ref on the `render` element. */
279
+ ref?: Ref<HTMLElement>;
280
+ };
281
+ type ComboboxGroupLabelProps = ComboboxGroupLabelOwnProps & Omit<ComponentPropsWithRef<'div'>, keyof ComboboxGroupLabelOwnProps>;
282
+ /**
283
+ * Names its Group, as an `<optgroup>`'s `label` does. Shown in the list, but not an option:
284
+ * it cannot be picked, and the arrow keys pass it.
285
+ */
286
+ declare function ComboboxGroupLabel({ className, children, render, ...rest }: ComboboxGroupLabelProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
233
287
 
234
288
  type ComboboxEmptyOwnProps = {
235
289
  /** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
@@ -308,7 +362,7 @@ type ComboboxValueProps<T = unknown> = {
308
362
  declare function ComboboxValue<T = unknown>({ children }: ComboboxValueProps<T>): react.JSX.Element;
309
363
 
310
364
  declare namespace index_parts {
311
- export { ComboboxChip as Chip, ComboboxChipRemove as ChipRemove, ComboboxClear as Clear, ComboboxEmpty as Empty, ComboboxInput as Input, ComboboxInputGroup as InputGroup, ComboboxItem as Item, ComboboxList as List, ComboboxPopup as Popup, ComboboxRoot as Root, ComboboxStatus as Status, ComboboxTrigger as Trigger, ComboboxValue as Value };
365
+ export { ComboboxChip as Chip, ComboboxChipRemove as ChipRemove, ComboboxClear as Clear, ComboboxEmpty as Empty, ComboboxGroup as Group, ComboboxGroupLabel as GroupLabel, ComboboxInput as Input, ComboboxInputGroup as InputGroup, ComboboxItem as Item, ComboboxList as List, ComboboxPopup as Popup, ComboboxRoot as Root, ComboboxStatus as Status, ComboboxTrigger as Trigger, ComboboxValue as Value };
312
366
  }
313
367
 
314
368
  /**
@@ -319,4 +373,4 @@ declare namespace index_parts {
319
373
  */
320
374
  declare function defaultFilter<T>(items: readonly T[], query: string, itemToString: (item: T) => string): T[];
321
375
 
322
- export { index_parts as Combobox, type ComboboxAlign, type ComboboxChangeDetails, type ComboboxChangeReason, type ComboboxChipProps, type ComboboxChipRemoveProps, type ComboboxClearProps, type ComboboxEmptyProps, type ComboboxFilter, type ComboboxHighlightDetails, type ComboboxHighlightReason, type ComboboxInputGroupProps, type ComboboxInputProps, type ComboboxItemProps, type ComboboxListProps, type ComboboxPopupProps, type ComboboxRootProps, type ComboboxSide, type ComboboxState, type ComboboxStatusProps, type ComboboxTriggerProps, type ComboboxValue$1 as ComboboxValue, type ComboboxValueProps, defaultFilter };
376
+ export { index_parts as Combobox, type ComboboxAlign, type ComboboxChangeDetails, type ComboboxChangeReason, type ComboboxChipProps, type ComboboxChipRemoveProps, type ComboboxClearProps, type ComboboxEmptyProps, type ComboboxFilter, type ComboboxGroup$1 as ComboboxGroup, type ComboboxGroupLabelProps, type ComboboxGroupProps, type ComboboxHighlightDetails, type ComboboxHighlightReason, type ComboboxInputGroupProps, type ComboboxInputProps, type ComboboxItemProps, type ComboboxListProps, type ComboboxPopupProps, type ComboboxRootProps, type ComboboxSide, type ComboboxState, type ComboboxStatusProps, type ComboboxTriggerProps, type ComboboxValue$1 as ComboboxValue, type ComboboxValueProps, defaultFilter };
@@ -24,9 +24,20 @@ type ComboboxState = {
24
24
  type ComboboxValue$1<T, Multiple extends boolean> = Multiple extends true ? T[] : T | null;
25
25
  /** Takes the whole list, so a filter can rank as well as narrow — a fuzzy search, say. */
26
26
  type ComboboxFilter<T> = (items: readonly T[], query: string, itemToString: (item: T) => string) => T[];
27
+ /**
28
+ * Items under a label, as an `<optgroup>`: any object with an `items` array — the rest, a
29
+ * label or an id, is yours to render.
30
+ */
31
+ type ComboboxGroup$1<T> = {
32
+ readonly items: readonly T[];
33
+ };
27
34
  type ComboboxRootProps<T, Multiple extends boolean = false> = {
28
- /** Every item the list can show. With server search, the current results. */
29
- items: readonly T[];
35
+ /**
36
+ * Every item the list can show. With server search, the current results. Or groups of
37
+ * them: each an object with an `items` array, which the filter searches and `List` renders
38
+ * one by one, leaving out the groups with no match.
39
+ */
40
+ items: readonly T[] | readonly ComboboxGroup$1<T>[];
30
41
  /**
31
42
  * An item's label: what the input shows once it is picked, and what the default filter
32
43
  * matches. Defaults to the item itself for strings, else its `label` property.
@@ -78,6 +89,11 @@ type ComboboxRootProps<T, Multiple extends boolean = false> = {
78
89
  loading?: boolean;
79
90
  /** Disables the input and every button. */
80
91
  disabled?: boolean;
92
+ /**
93
+ * Something must be selected before the form submits. The input is `required` while the
94
+ * selection is empty, so the browser's own validation reports it.
95
+ */
96
+ required?: boolean;
81
97
  /** Submits each selected item's key under this name, from hidden inputs. */
82
98
  name?: string;
83
99
  /** Associates the hidden inputs with a `<form>` by id, when rendered outside it. */
@@ -96,7 +112,7 @@ type ComboboxRootProps<T, Multiple extends boolean = false> = {
96
112
  *
97
113
  * Focus stays in the input; the highlighted option is its `aria-activedescendant`.
98
114
  */
99
- declare function ComboboxRoot<T, Multiple extends boolean = false>({ items, itemToString: itemToStringProp, itemToKey: itemToKeyProp, filter, multiple: multipleProp, value: valueProp, defaultValue, onValueChange, inputValue: inputValueProp, defaultInputValue, onInputValueChange, open: openProp, defaultOpen, onOpenChange, onItemHighlighted, onLoadMore, loading, disabled, name, form, children, }: ComboboxRootProps<T, Multiple>): react.JSX.Element;
115
+ declare function ComboboxRoot<T, Multiple extends boolean = false>({ items, itemToString: itemToStringProp, itemToKey: itemToKeyProp, filter, multiple: multipleProp, value: valueProp, defaultValue, onValueChange, inputValue: inputValueProp, defaultInputValue, onInputValueChange, open: openProp, defaultOpen, onOpenChange, onItemHighlighted, onLoadMore, loading, disabled, required, name, form, children, }: ComboboxRootProps<T, Multiple>): react.JSX.Element;
100
116
 
101
117
  type ComboboxInputOwnProps = {
102
118
  /** Element to render instead of the default `<input>`. Props and ref are merged onto it. */
@@ -114,6 +130,11 @@ type ComboboxInputProps = ComboboxInputOwnProps & Omit<ComponentPropsWithRef<'in
114
130
  * Enter picks the highlighted item; Esc closes. With `multiple`, Backspace in an empty input
115
131
  * removes the last selected item, and Left at the start of the text moves into the chips.
116
132
  *
133
+ * With the Root's `required`, the input is `required` while nothing is selected, so the
134
+ * browser's validation reports it, `:invalid` matches it, and `form.checkValidity()` fails.
135
+ * Text typed but not picked is still nothing selected: the input reports `valueMissing`'s
136
+ * message then, through `setCustomValidity`.
137
+ *
117
138
  * Leaving the input closes the list and puts the text back: the selected label, or empty
118
139
  * with `multiple`.
119
140
  */
@@ -194,7 +215,8 @@ declare function ComboboxPopup({ side, align, className, children, render, ...re
194
215
  type ComboboxListOwnProps<T> = {
195
216
  /**
196
217
  * A function rendering one `Combobox.Item` per filtered item, or any nodes. With a
197
- * virtualizer, render only the rows it asks for.
218
+ * virtualizer, render only the rows it asks for. With groups, it is called once per group
219
+ * the filter left an item in, with that group, and renders a `Combobox.Group`.
198
220
  */
199
221
  children?: ReactNode | ((item: T, index: number) => ReactNode);
200
222
  /** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
@@ -204,7 +226,8 @@ type ComboboxListOwnProps<T> = {
204
226
  };
205
227
  type ComboboxListProps<T = unknown> = ComboboxListOwnProps<T> & Omit<ComponentPropsWithRef<'div'>, keyof ComboboxListOwnProps<T>>;
206
228
  /**
207
- * The `role="listbox"`. Given a function, renders it once per item the filter left.
229
+ * The `role="listbox"`. Given a function, renders it once per item the filter left — or, when
230
+ * the Root's `items` are groups, once per group.
208
231
  *
209
232
  * With the Root's `onLoadMore`, an invisible sentinel follows the items; when it scrolls into
210
233
  * view and nothing is `loading`, `onLoadMore` is called. The list is `aria-busy` meanwhile.
@@ -214,7 +237,7 @@ declare function ComboboxList<T = unknown>({ className, children, render, ...res
214
237
  type ComboboxItemOwnProps<T> = {
215
238
  /** The item this option stands for — one of the Root's `items`. */
216
239
  value: T;
217
- /** Cannot be picked, and the arrow keys skip it. */
240
+ /** Cannot be picked, and the arrow keys skip it. A disabled Group disables it too. */
218
241
  disabled?: boolean;
219
242
  /** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
220
243
  render?: ReactElement;
@@ -229,7 +252,38 @@ type ComboboxItemProps<T = unknown> = ComboboxItemOwnProps<T> & Omit<ComponentPr
229
252
  * `data-selected` and `aria-selected` follow the Root's `value`; `data-highlighted` follows
230
253
  * the arrow keys and the pointer. Only the items whose highlight changes re-render.
231
254
  */
232
- declare function ComboboxItem<T = unknown>({ value, disabled, className, children, render, ...rest }: ComboboxItemProps<T>): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
255
+ declare function ComboboxItem<T = unknown>({ value, disabled: disabledProp, className, children, render, ...rest }: ComboboxItemProps<T>): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
256
+
257
+ type ComboboxGroupOwnProps = {
258
+ /** Disables every Item inside, as `disabled` on an `<optgroup>` does. */
259
+ disabled?: boolean;
260
+ /** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
261
+ render?: ReactElement;
262
+ /** Ref to the rendered element. Merged with any ref on the `render` element. */
263
+ ref?: Ref<HTMLElement>;
264
+ };
265
+ type ComboboxGroupProps = ComboboxGroupOwnProps & Omit<ComponentPropsWithRef<'div'>, keyof ComboboxGroupOwnProps>;
266
+ /**
267
+ * Items under a label, as an `<optgroup>`: `role="group"`, named by its GroupLabel. Render one
268
+ * from the List's function for each group of the Root's `items`, with an Item for each of the
269
+ * group's `items` — the filter has already narrowed them, and left out a group with none.
270
+ *
271
+ * The arrow keys run through every group's items as one list, past the labels.
272
+ */
273
+ declare function ComboboxGroup({ disabled, className, children, render, ...rest }: ComboboxGroupProps): react.JSX.Element;
274
+
275
+ type ComboboxGroupLabelOwnProps = {
276
+ /** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
277
+ render?: ReactElement;
278
+ /** Ref to the rendered element. Merged with any ref on the `render` element. */
279
+ ref?: Ref<HTMLElement>;
280
+ };
281
+ type ComboboxGroupLabelProps = ComboboxGroupLabelOwnProps & Omit<ComponentPropsWithRef<'div'>, keyof ComboboxGroupLabelOwnProps>;
282
+ /**
283
+ * Names its Group, as an `<optgroup>`'s `label` does. Shown in the list, but not an option:
284
+ * it cannot be picked, and the arrow keys pass it.
285
+ */
286
+ declare function ComboboxGroupLabel({ className, children, render, ...rest }: ComboboxGroupLabelProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
233
287
 
234
288
  type ComboboxEmptyOwnProps = {
235
289
  /** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
@@ -308,7 +362,7 @@ type ComboboxValueProps<T = unknown> = {
308
362
  declare function ComboboxValue<T = unknown>({ children }: ComboboxValueProps<T>): react.JSX.Element;
309
363
 
310
364
  declare namespace index_parts {
311
- export { ComboboxChip as Chip, ComboboxChipRemove as ChipRemove, ComboboxClear as Clear, ComboboxEmpty as Empty, ComboboxInput as Input, ComboboxInputGroup as InputGroup, ComboboxItem as Item, ComboboxList as List, ComboboxPopup as Popup, ComboboxRoot as Root, ComboboxStatus as Status, ComboboxTrigger as Trigger, ComboboxValue as Value };
365
+ export { ComboboxChip as Chip, ComboboxChipRemove as ChipRemove, ComboboxClear as Clear, ComboboxEmpty as Empty, ComboboxGroup as Group, ComboboxGroupLabel as GroupLabel, ComboboxInput as Input, ComboboxInputGroup as InputGroup, ComboboxItem as Item, ComboboxList as List, ComboboxPopup as Popup, ComboboxRoot as Root, ComboboxStatus as Status, ComboboxTrigger as Trigger, ComboboxValue as Value };
312
366
  }
313
367
 
314
368
  /**
@@ -319,4 +373,4 @@ declare namespace index_parts {
319
373
  */
320
374
  declare function defaultFilter<T>(items: readonly T[], query: string, itemToString: (item: T) => string): T[];
321
375
 
322
- export { index_parts as Combobox, type ComboboxAlign, type ComboboxChangeDetails, type ComboboxChangeReason, type ComboboxChipProps, type ComboboxChipRemoveProps, type ComboboxClearProps, type ComboboxEmptyProps, type ComboboxFilter, type ComboboxHighlightDetails, type ComboboxHighlightReason, type ComboboxInputGroupProps, type ComboboxInputProps, type ComboboxItemProps, type ComboboxListProps, type ComboboxPopupProps, type ComboboxRootProps, type ComboboxSide, type ComboboxState, type ComboboxStatusProps, type ComboboxTriggerProps, type ComboboxValue$1 as ComboboxValue, type ComboboxValueProps, defaultFilter };
376
+ export { index_parts as Combobox, type ComboboxAlign, type ComboboxChangeDetails, type ComboboxChangeReason, type ComboboxChipProps, type ComboboxChipRemoveProps, type ComboboxClearProps, type ComboboxEmptyProps, type ComboboxFilter, type ComboboxGroup$1 as ComboboxGroup, type ComboboxGroupLabelProps, type ComboboxGroupProps, type ComboboxHighlightDetails, type ComboboxHighlightReason, type ComboboxInputGroupProps, type ComboboxInputProps, type ComboboxItemProps, type ComboboxListProps, type ComboboxPopupProps, type ComboboxRootProps, type ComboboxSide, type ComboboxState, type ComboboxStatusProps, type ComboboxTriggerProps, type ComboboxValue$1 as ComboboxValue, type ComboboxValueProps, defaultFilter };
@@ -22,6 +22,8 @@ __export(index_parts_exports, {
22
22
  ChipRemove: () => ComboboxChipRemove,
23
23
  Clear: () => ComboboxClear,
24
24
  Empty: () => ComboboxEmpty,
25
+ Group: () => ComboboxGroup,
26
+ GroupLabel: () => ComboboxGroupLabel,
25
27
  Input: () => ComboboxInput,
26
28
  InputGroup: () => ComboboxInputGroup,
27
29
  Item: () => ComboboxItem,
@@ -75,6 +77,11 @@ function useComboboxRootContext(part) {
75
77
 
76
78
  // src/combobox/ComboboxRoot.tsx
77
79
  import { jsx, jsxs } from "react/jsx-runtime";
80
+ function isGroupList(items) {
81
+ return items.length > 0 && items.every(
82
+ (entry) => entry !== null && typeof entry === "object" && Array.isArray(entry.items)
83
+ );
84
+ }
78
85
  function defaultItemToString(item) {
79
86
  if (typeof item === "string") return item;
80
87
  if (item !== null && typeof item === "object" && "label" in item) return String(item.label);
@@ -99,6 +106,7 @@ function ComboboxRoot({
99
106
  onLoadMore,
100
107
  loading = false,
101
108
  disabled = false,
109
+ required = false,
102
110
  name,
103
111
  form,
104
112
  children
@@ -135,10 +143,17 @@ function ComboboxRoot({
135
143
  });
136
144
  const query = !multiple && inputValue === selectedLabel ? "" : inputValue;
137
145
  const deferredQuery = useDeferredValue(query);
138
- const filteredItems = useMemo(() => {
139
- if (filter === null) return items;
140
- const run = filter ?? defaultFilter;
141
- return run(items, deferredQuery, itemToString);
146
+ const { filteredItems, filteredGroups } = useMemo(() => {
147
+ const run = filter === null ? (list) => list : (list) => (filter ?? defaultFilter)(
148
+ list,
149
+ deferredQuery,
150
+ itemToString
151
+ );
152
+ if (!isGroupList(items)) {
153
+ return { filteredItems: run(items), filteredGroups: null };
154
+ }
155
+ const groups = items.map((group) => ({ ...group, items: run(group.items) })).filter((group) => group.items.length > 0);
156
+ return { filteredItems: groups.flatMap((group) => group.items), filteredGroups: groups };
142
157
  }, [items, filter, deferredQuery, itemToString]);
143
158
  const indexByKey = useMemo(
144
159
  () => new Map(filteredItems.map((item, index) => [itemToKey(item), index])),
@@ -283,9 +298,11 @@ function ComboboxRoot({
283
298
  open,
284
299
  multiple,
285
300
  disabled,
301
+ required,
286
302
  inputValue,
287
303
  loading,
288
304
  filteredItems,
305
+ filteredGroups,
289
306
  itemToString,
290
307
  itemToKey,
291
308
  indexByKey,
@@ -319,9 +336,11 @@ function ComboboxRoot({
319
336
  open,
320
337
  multiple,
321
338
  disabled,
339
+ required,
322
340
  inputValue,
323
341
  loading,
324
342
  filteredItems,
343
+ filteredGroups,
325
344
  itemToString,
326
345
  itemToKey,
327
346
  indexByKey,
@@ -364,7 +383,7 @@ function ComboboxRoot({
364
383
  }
365
384
 
366
385
  // src/combobox/ComboboxInput.tsx
367
- import { useSyncExternalStore } from "react";
386
+ import { useLayoutEffect, useRef as useRef2, useSyncExternalStore } from "react";
368
387
 
369
388
  // src/combobox/comboboxDataAttributes.ts
370
389
  function comboboxDataAttributes({ open, disabled }) {
@@ -395,6 +414,7 @@ function ComboboxInput({ className, render, ...rest }) {
395
414
  open,
396
415
  multiple,
397
416
  disabled,
417
+ required,
398
418
  inputValue,
399
419
  filteredItems,
400
420
  itemToKey,
@@ -423,6 +443,20 @@ function ComboboxInput({ className, render, ...rest }) {
423
443
  () => highlight.get().index,
424
444
  () => -1
425
445
  );
446
+ const missing = required && selectedItems.length === 0;
447
+ const textOnly = missing && inputValue !== "";
448
+ const customValidityRef = useRef2(false);
449
+ useLayoutEffect(() => {
450
+ const input = inputRef.current;
451
+ if (!input || typeof input.setCustomValidity !== "function") return;
452
+ if (textOnly) {
453
+ input.setCustomValidity(valueMissingMessage());
454
+ customValidityRef.current = true;
455
+ } else if (customValidityRef.current) {
456
+ input.setCustomValidity("");
457
+ customValidityRef.current = false;
458
+ }
459
+ }, [textOnly, inputRef]);
426
460
  function enabledIndices() {
427
461
  const indices = [];
428
462
  filteredItems.forEach((item, index) => {
@@ -460,6 +494,9 @@ function ComboboxInput({ className, render, ...rest }) {
460
494
  "aria-controls": listId,
461
495
  "aria-activedescendant": open && highlighted >= 0 ? optionId(highlighted) : void 0,
462
496
  disabled: disabled || void 0,
497
+ required: missing || void 0,
498
+ // Stays when a selection lifts `required`: the field is still required.
499
+ "aria-required": required || void 0,
463
500
  value: inputValue,
464
501
  ...comboboxDataAttributes({ open, multiple, disabled }),
465
502
  // The anchor, unless an InputGroup around it is.
@@ -529,9 +566,14 @@ function ComboboxInput({ className, render, ...rest }) {
529
566
  consumerProps: rest
530
567
  });
531
568
  }
569
+ function valueMissingMessage() {
570
+ const probe = document.createElement("input");
571
+ probe.required = true;
572
+ return probe.validationMessage || "Please select an item in the list.";
573
+ }
532
574
 
533
575
  // src/combobox/ComboboxInputGroup.tsx
534
- import { useLayoutEffect, useRef as useRef2 } from "react";
576
+ import { useLayoutEffect as useLayoutEffect2, useRef as useRef3 } from "react";
535
577
  function ComboboxInputGroup({
536
578
  className,
537
579
  children,
@@ -539,9 +581,9 @@ function ComboboxInputGroup({
539
581
  ...rest
540
582
  }) {
541
583
  const { open, multiple, disabled, anchorName, registerGroup, registerInside, inputRef } = useComboboxRootContext("InputGroup");
542
- const elementRef = useRef2(null);
543
- useLayoutEffect(() => registerGroup(), [registerGroup]);
544
- useLayoutEffect(() => registerInside(elementRef), [registerInside]);
584
+ const elementRef = useRef3(null);
585
+ useLayoutEffect2(() => registerGroup(), [registerGroup]);
586
+ useLayoutEffect2(() => registerInside(elementRef), [registerInside]);
545
587
  return useRender({
546
588
  render,
547
589
  defaultTagName: "div",
@@ -562,11 +604,11 @@ function ComboboxInputGroup({
562
604
  }
563
605
 
564
606
  // src/combobox/ComboboxTrigger.tsx
565
- import { useLayoutEffect as useLayoutEffect2, useRef as useRef3 } from "react";
607
+ import { useLayoutEffect as useLayoutEffect3, useRef as useRef4 } from "react";
566
608
  function ComboboxTrigger({ className, children, render, ...rest }) {
567
609
  const { open, multiple, disabled, setOpen, close, listId, inputRef, registerInside } = useComboboxRootContext("Trigger");
568
- const elementRef = useRef3(null);
569
- useLayoutEffect2(() => registerInside(elementRef), [registerInside]);
610
+ const elementRef = useRef4(null);
611
+ useLayoutEffect3(() => registerInside(elementRef), [registerInside]);
570
612
  return useRender({
571
613
  render,
572
614
  defaultTagName: "button",
@@ -594,11 +636,11 @@ function ComboboxTrigger({ className, children, render, ...rest }) {
594
636
  }
595
637
 
596
638
  // src/combobox/ComboboxClear.tsx
597
- import { useLayoutEffect as useLayoutEffect3, useRef as useRef4 } from "react";
639
+ import { useLayoutEffect as useLayoutEffect4, useRef as useRef5 } from "react";
598
640
  function ComboboxClear({ className, children, render, ...rest }) {
599
641
  const { disabled, selectedItems, clear, inputRef, registerInside } = useComboboxRootContext("Clear");
600
- const elementRef = useRef4(null);
601
- useLayoutEffect3(() => registerInside(elementRef), [registerInside]);
642
+ const elementRef = useRef5(null);
643
+ useLayoutEffect4(() => registerInside(elementRef), [registerInside]);
602
644
  return useRender({
603
645
  render,
604
646
  defaultTagName: "button",
@@ -681,7 +723,7 @@ function ComboboxPopup({
681
723
  }
682
724
 
683
725
  // src/combobox/ComboboxList.tsx
684
- import { useEffect as useEffect3, useRef as useRef5 } from "react";
726
+ import { useEffect as useEffect3, useRef as useRef6 } from "react";
685
727
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
686
728
  function ComboboxList({
687
729
  className,
@@ -689,8 +731,10 @@ function ComboboxList({
689
731
  render,
690
732
  ...rest
691
733
  }) {
692
- const { multiple, loading, filteredItems, onLoadMore, listId } = useComboboxRootContext("List");
693
- const content = typeof children === "function" ? filteredItems.map((item, index) => children(item, index)) : children;
734
+ const { multiple, loading, filteredItems, filteredGroups, onLoadMore, listId } = useComboboxRootContext("List");
735
+ const content = typeof children === "function" ? (filteredGroups ?? filteredItems).map(
736
+ (entry, index) => children(entry, index)
737
+ ) : children;
694
738
  return useRender({
695
739
  render,
696
740
  defaultTagName: "div",
@@ -710,8 +754,8 @@ function ComboboxList({
710
754
  }
711
755
  function LoadMoreSentinel() {
712
756
  const { open, loading, filteredItems, onLoadMore } = useComboboxRootContext("List");
713
- const ref = useRef5(null);
714
- const onLoadMoreRef = useRef5(onLoadMore);
757
+ const ref = useRef6(null);
758
+ const onLoadMoreRef = useRef6(onLoadMore);
715
759
  onLoadMoreRef.current = onLoadMore;
716
760
  useEffect3(() => {
717
761
  const element = ref.current;
@@ -726,10 +770,52 @@ function LoadMoreSentinel() {
726
770
  }
727
771
 
728
772
  // src/combobox/ComboboxItem.tsx
729
- import { useEffect as useEffect4, useLayoutEffect as useLayoutEffect4, useRef as useRef6, useSyncExternalStore as useSyncExternalStore2 } from "react";
773
+ import { useEffect as useEffect4, useLayoutEffect as useLayoutEffect5, useRef as useRef7, useSyncExternalStore as useSyncExternalStore2 } from "react";
774
+
775
+ // src/combobox/ComboboxGroup.tsx
776
+ import { createContext as createContext2, useCallback as useCallback2, useContext as useContext2, useId as useId2, useMemo as useMemo2, useState as useState2 } from "react";
777
+ import { jsx as jsx3 } from "react/jsx-runtime";
778
+ var ComboboxGroupContext = createContext2(null);
779
+ function useComboboxGroupContext() {
780
+ return useContext2(ComboboxGroupContext);
781
+ }
782
+ function ComboboxGroup({
783
+ disabled = false,
784
+ className,
785
+ children,
786
+ render,
787
+ ...rest
788
+ }) {
789
+ useComboboxRootContext("Group");
790
+ const labelId = `${useId2()}-label`;
791
+ const [hasLabel, setHasLabel] = useState2(false);
792
+ const registerLabel = useCallback2(() => {
793
+ setHasLabel(true);
794
+ return () => setHasLabel(false);
795
+ }, []);
796
+ const context = useMemo2(
797
+ () => ({ disabled, labelId, registerLabel }),
798
+ [disabled, labelId, registerLabel]
799
+ );
800
+ const element = useRender({
801
+ render,
802
+ defaultTagName: "div",
803
+ props: {
804
+ role: "group",
805
+ "aria-labelledby": hasLabel ? labelId : void 0,
806
+ "data-disabled": disabled ? "" : void 0,
807
+ className,
808
+ children
809
+ },
810
+ consumerProps: rest
811
+ });
812
+ return /* @__PURE__ */ jsx3(ComboboxGroupContext.Provider, { value: context, children: element });
813
+ }
814
+
815
+ // src/combobox/ComboboxItem.tsx
730
816
  function ComboboxItem({
731
817
  value,
732
- disabled = false,
818
+ disabled: disabledProp = false,
733
819
  className,
734
820
  children,
735
821
  render,
@@ -745,6 +831,8 @@ function ComboboxItem({
745
831
  select,
746
832
  optionId
747
833
  } = useComboboxRootContext("Item");
834
+ const group = useComboboxGroupContext();
835
+ const disabled = disabledProp || (group?.disabled ?? false);
748
836
  const key = itemToKey(value);
749
837
  const index = indexByKey.get(key) ?? -1;
750
838
  const selected = selectedKeys.has(key);
@@ -753,8 +841,8 @@ function ComboboxItem({
753
841
  () => index >= 0 && highlight.get().index === index,
754
842
  () => false
755
843
  );
756
- const elementRef = useRef6(null);
757
- useLayoutEffect4(() => {
844
+ const elementRef = useRef7(null);
845
+ useLayoutEffect5(() => {
758
846
  if (!disabled) return;
759
847
  disabledKeys.add(key);
760
848
  return () => {
@@ -792,6 +880,28 @@ function ComboboxItem({
792
880
  });
793
881
  }
794
882
 
883
+ // src/combobox/ComboboxGroupLabel.tsx
884
+ import { useLayoutEffect as useLayoutEffect6 } from "react";
885
+ function ComboboxGroupLabel({
886
+ className,
887
+ children,
888
+ render,
889
+ ...rest
890
+ }) {
891
+ const group = useComboboxGroupContext();
892
+ if (group === null) {
893
+ throw new Error("<Combobox.GroupLabel> must be rendered inside <Combobox.Group>.");
894
+ }
895
+ const { labelId, registerLabel } = group;
896
+ useLayoutEffect6(registerLabel, [registerLabel]);
897
+ return useRender({
898
+ render,
899
+ defaultTagName: "div",
900
+ props: { id: labelId, className, children },
901
+ consumerProps: rest
902
+ });
903
+ }
904
+
795
905
  // src/combobox/ComboboxEmpty.tsx
796
906
  function ComboboxEmpty({ className, children, render, ...rest }) {
797
907
  const { filteredItems, loading } = useComboboxRootContext("Empty");
@@ -816,11 +926,11 @@ function ComboboxStatus({ className, children, render, ...rest }) {
816
926
  }
817
927
 
818
928
  // src/combobox/ComboboxChip.tsx
819
- import { createContext as createContext2, useContext as useContext2, useLayoutEffect as useLayoutEffect5, useMemo as useMemo2, useRef as useRef7 } from "react";
820
- import { jsx as jsx3 } from "react/jsx-runtime";
821
- var ComboboxChipContext = createContext2(null);
929
+ import { createContext as createContext3, useContext as useContext3, useLayoutEffect as useLayoutEffect7, useMemo as useMemo3, useRef as useRef8 } from "react";
930
+ import { jsx as jsx4 } from "react/jsx-runtime";
931
+ var ComboboxChipContext = createContext3(null);
822
932
  function useComboboxChipContext() {
823
- const context = useContext2(ComboboxChipContext);
933
+ const context = useContext3(ComboboxChipContext);
824
934
  if (context === null) {
825
935
  throw new Error("<Combobox.ChipRemove> must be rendered inside <Combobox.Chip>.");
826
936
  }
@@ -835,9 +945,9 @@ function ComboboxChip({
835
945
  }) {
836
946
  const { disabled, itemToKey, remove, registerChip, chips, inputRef } = useComboboxRootContext("Chip");
837
947
  const key = itemToKey(value);
838
- const elementRef = useRef7(null);
839
- useLayoutEffect5(() => registerChip({ key, ref: elementRef }), [registerChip, key]);
840
- const chipContext = useMemo2(() => ({ value }), [value]);
948
+ const elementRef = useRef8(null);
949
+ useLayoutEffect7(() => registerChip({ key, ref: elementRef }), [registerChip, key]);
950
+ const chipContext = useMemo3(() => ({ value }), [value]);
841
951
  function focusAt(index) {
842
952
  const chip = chips[index];
843
953
  if (chip) chip.ref.current?.focus();
@@ -878,7 +988,7 @@ function ComboboxChip({
878
988
  },
879
989
  consumerProps: rest
880
990
  });
881
- return /* @__PURE__ */ jsx3(ComboboxChipContext.Provider, { value: chipContext, children: element });
991
+ return /* @__PURE__ */ jsx4(ComboboxChipContext.Provider, { value: chipContext, children: element });
882
992
  }
883
993
 
884
994
  // src/combobox/ComboboxChipRemove.tsx
@@ -913,11 +1023,11 @@ function ComboboxChipRemove({
913
1023
  }
914
1024
 
915
1025
  // src/combobox/ComboboxValue.tsx
916
- import { Fragment as Fragment2, jsx as jsx4 } from "react/jsx-runtime";
1026
+ import { Fragment as Fragment2, jsx as jsx5 } from "react/jsx-runtime";
917
1027
  function ComboboxValue({ children }) {
918
1028
  const { multiple, selectedItems, itemToString } = useComboboxRootContext("Value");
919
1029
  const value = multiple ? selectedItems : selectedItems[0] ?? null;
920
- return /* @__PURE__ */ jsx4(Fragment2, { children: children(value, { itemToString }) });
1030
+ return /* @__PURE__ */ jsx5(Fragment2, { children: children(value, { itemToString }) });
921
1031
  }
922
1032
  export {
923
1033
  index_parts_exports as Combobox,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arun-dev/headless",
3
- "version": "4.9.0",
3
+ "version": "4.10.0",
4
4
  "description": "Unstyled React behaviour primitives — render engine, controlled/uncontrolled state, and data-* state projection. Ships no CSS and no class names.",
5
5
  "license": "MIT",
6
6
  "type": "module",