@arun-dev/headless 4.8.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.
- package/dist/combobox/index.cjs +198 -36
- package/dist/combobox/index.d.cts +102 -14
- package/dist/combobox/index.d.ts +102 -14
- package/dist/combobox/index.js +192 -30
- package/package.json +1 -1
package/dist/combobox/index.cjs
CHANGED
|
@@ -32,13 +32,17 @@ __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,
|
|
38
|
+
InputGroup: () => ComboboxInputGroup,
|
|
36
39
|
Item: () => ComboboxItem,
|
|
37
40
|
List: () => ComboboxList,
|
|
38
41
|
Popup: () => ComboboxPopup,
|
|
39
42
|
Root: () => ComboboxRoot,
|
|
40
43
|
Status: () => ComboboxStatus,
|
|
41
|
-
Trigger: () => ComboboxTrigger
|
|
44
|
+
Trigger: () => ComboboxTrigger,
|
|
45
|
+
Value: () => ComboboxValue
|
|
42
46
|
});
|
|
43
47
|
|
|
44
48
|
// src/combobox/ComboboxRoot.tsx
|
|
@@ -167,6 +171,11 @@ function useComboboxRootContext(part) {
|
|
|
167
171
|
|
|
168
172
|
// src/combobox/ComboboxRoot.tsx
|
|
169
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
|
+
}
|
|
170
179
|
function defaultItemToString(item) {
|
|
171
180
|
if (typeof item === "string") return item;
|
|
172
181
|
if (item !== null && typeof item === "object" && "label" in item) return String(item.label);
|
|
@@ -191,6 +200,7 @@ function ComboboxRoot({
|
|
|
191
200
|
onLoadMore,
|
|
192
201
|
loading = false,
|
|
193
202
|
disabled = false,
|
|
203
|
+
required = false,
|
|
194
204
|
name,
|
|
195
205
|
form,
|
|
196
206
|
children
|
|
@@ -227,10 +237,17 @@ function ComboboxRoot({
|
|
|
227
237
|
});
|
|
228
238
|
const query = !multiple && inputValue === selectedLabel ? "" : inputValue;
|
|
229
239
|
const deferredQuery = (0, import_react3.useDeferredValue)(query);
|
|
230
|
-
const filteredItems = (0, import_react3.useMemo)(() => {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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 };
|
|
234
251
|
}, [items, filter, deferredQuery, itemToString]);
|
|
235
252
|
const indexByKey = (0, import_react3.useMemo)(
|
|
236
253
|
() => new Map(filteredItems.map((item, index) => [itemToKey(item), index])),
|
|
@@ -342,6 +359,11 @@ function ComboboxRoot({
|
|
|
342
359
|
},
|
|
343
360
|
[insideRefs]
|
|
344
361
|
);
|
|
362
|
+
const [hasGroup, setHasGroup] = (0, import_react3.useState)(false);
|
|
363
|
+
const registerGroup = (0, import_react3.useCallback)(() => {
|
|
364
|
+
setHasGroup(true);
|
|
365
|
+
return () => setHasGroup(false);
|
|
366
|
+
}, []);
|
|
345
367
|
const inputRef = (0, import_react3.useRef)(null);
|
|
346
368
|
const popupRef = (0, import_react3.useRef)(null);
|
|
347
369
|
const { current: initial } = (0, import_react3.useRef)({ value, inputValue });
|
|
@@ -370,9 +392,11 @@ function ComboboxRoot({
|
|
|
370
392
|
open,
|
|
371
393
|
multiple,
|
|
372
394
|
disabled,
|
|
395
|
+
required,
|
|
373
396
|
inputValue,
|
|
374
397
|
loading,
|
|
375
398
|
filteredItems,
|
|
399
|
+
filteredGroups,
|
|
376
400
|
itemToString,
|
|
377
401
|
itemToKey,
|
|
378
402
|
indexByKey,
|
|
@@ -392,6 +416,8 @@ function ComboboxRoot({
|
|
|
392
416
|
chips,
|
|
393
417
|
registerInside,
|
|
394
418
|
isInside,
|
|
419
|
+
hasGroup,
|
|
420
|
+
registerGroup,
|
|
395
421
|
onLoadMore,
|
|
396
422
|
inputId: `${id}-input`,
|
|
397
423
|
listId: `${id}-list`,
|
|
@@ -404,9 +430,11 @@ function ComboboxRoot({
|
|
|
404
430
|
open,
|
|
405
431
|
multiple,
|
|
406
432
|
disabled,
|
|
433
|
+
required,
|
|
407
434
|
inputValue,
|
|
408
435
|
loading,
|
|
409
436
|
filteredItems,
|
|
437
|
+
filteredGroups,
|
|
410
438
|
itemToString,
|
|
411
439
|
itemToKey,
|
|
412
440
|
indexByKey,
|
|
@@ -426,6 +454,8 @@ function ComboboxRoot({
|
|
|
426
454
|
chips,
|
|
427
455
|
registerInside,
|
|
428
456
|
isInside,
|
|
457
|
+
hasGroup,
|
|
458
|
+
registerGroup,
|
|
429
459
|
onLoadMore,
|
|
430
460
|
id
|
|
431
461
|
]
|
|
@@ -567,6 +597,7 @@ function ComboboxInput({ className, render, ...rest }) {
|
|
|
567
597
|
open,
|
|
568
598
|
multiple,
|
|
569
599
|
disabled,
|
|
600
|
+
required,
|
|
570
601
|
inputValue,
|
|
571
602
|
filteredItems,
|
|
572
603
|
itemToKey,
|
|
@@ -586,6 +617,7 @@ function ComboboxInput({ className, render, ...rest }) {
|
|
|
586
617
|
listId,
|
|
587
618
|
optionId,
|
|
588
619
|
anchorName,
|
|
620
|
+
hasGroup,
|
|
589
621
|
inputRef,
|
|
590
622
|
popupRef
|
|
591
623
|
} = useComboboxRootContext("Input");
|
|
@@ -594,6 +626,20 @@ function ComboboxInput({ className, render, ...rest }) {
|
|
|
594
626
|
() => highlight.get().index,
|
|
595
627
|
() => -1
|
|
596
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]);
|
|
597
643
|
function enabledIndices() {
|
|
598
644
|
const indices = [];
|
|
599
645
|
filteredItems.forEach((item, index) => {
|
|
@@ -631,9 +677,13 @@ function ComboboxInput({ className, render, ...rest }) {
|
|
|
631
677
|
"aria-controls": listId,
|
|
632
678
|
"aria-activedescendant": open && highlighted >= 0 ? optionId(highlighted) : void 0,
|
|
633
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,
|
|
634
683
|
value: inputValue,
|
|
635
684
|
...comboboxDataAttributes({ open, multiple, disabled }),
|
|
636
|
-
|
|
685
|
+
// The anchor, unless an InputGroup around it is.
|
|
686
|
+
style: hasGroup ? void 0 : { anchorName },
|
|
637
687
|
className,
|
|
638
688
|
ref: inputRef,
|
|
639
689
|
onChange(event) {
|
|
@@ -699,13 +749,49 @@ function ComboboxInput({ className, render, ...rest }) {
|
|
|
699
749
|
consumerProps: rest
|
|
700
750
|
});
|
|
701
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
|
+
}
|
|
702
757
|
|
|
703
|
-
// src/combobox/
|
|
758
|
+
// src/combobox/ComboboxInputGroup.tsx
|
|
704
759
|
var import_react6 = require("react");
|
|
705
|
-
function
|
|
706
|
-
|
|
760
|
+
function ComboboxInputGroup({
|
|
761
|
+
className,
|
|
762
|
+
children,
|
|
763
|
+
render,
|
|
764
|
+
...rest
|
|
765
|
+
}) {
|
|
766
|
+
const { open, multiple, disabled, anchorName, registerGroup, registerInside, inputRef } = useComboboxRootContext("InputGroup");
|
|
707
767
|
const elementRef = (0, import_react6.useRef)(null);
|
|
768
|
+
(0, import_react6.useLayoutEffect)(() => registerGroup(), [registerGroup]);
|
|
708
769
|
(0, import_react6.useLayoutEffect)(() => registerInside(elementRef), [registerInside]);
|
|
770
|
+
return useRender({
|
|
771
|
+
render,
|
|
772
|
+
defaultTagName: "div",
|
|
773
|
+
props: {
|
|
774
|
+
...comboboxDataAttributes({ open, multiple, disabled }),
|
|
775
|
+
style: { anchorName },
|
|
776
|
+
className,
|
|
777
|
+
children,
|
|
778
|
+
ref: elementRef,
|
|
779
|
+
onMouseDown(event) {
|
|
780
|
+
if (event.target !== event.currentTarget || disabled) return;
|
|
781
|
+
event.preventDefault();
|
|
782
|
+
inputRef.current?.focus();
|
|
783
|
+
}
|
|
784
|
+
},
|
|
785
|
+
consumerProps: rest
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// src/combobox/ComboboxTrigger.tsx
|
|
790
|
+
var import_react7 = require("react");
|
|
791
|
+
function ComboboxTrigger({ className, children, render, ...rest }) {
|
|
792
|
+
const { open, multiple, disabled, setOpen, close, listId, inputRef, registerInside } = useComboboxRootContext("Trigger");
|
|
793
|
+
const elementRef = (0, import_react7.useRef)(null);
|
|
794
|
+
(0, import_react7.useLayoutEffect)(() => registerInside(elementRef), [registerInside]);
|
|
709
795
|
return useRender({
|
|
710
796
|
render,
|
|
711
797
|
defaultTagName: "button",
|
|
@@ -733,11 +819,11 @@ function ComboboxTrigger({ className, children, render, ...rest }) {
|
|
|
733
819
|
}
|
|
734
820
|
|
|
735
821
|
// src/combobox/ComboboxClear.tsx
|
|
736
|
-
var
|
|
822
|
+
var import_react8 = require("react");
|
|
737
823
|
function ComboboxClear({ className, children, render, ...rest }) {
|
|
738
824
|
const { disabled, selectedItems, clear, inputRef, registerInside } = useComboboxRootContext("Clear");
|
|
739
|
-
const elementRef = (0,
|
|
740
|
-
(0,
|
|
825
|
+
const elementRef = (0, import_react8.useRef)(null);
|
|
826
|
+
(0, import_react8.useLayoutEffect)(() => registerInside(elementRef), [registerInside]);
|
|
741
827
|
return useRender({
|
|
742
828
|
render,
|
|
743
829
|
defaultTagName: "button",
|
|
@@ -763,7 +849,7 @@ function ComboboxClear({ className, children, render, ...rest }) {
|
|
|
763
849
|
}
|
|
764
850
|
|
|
765
851
|
// src/combobox/ComboboxPopup.tsx
|
|
766
|
-
var
|
|
852
|
+
var import_react9 = require("react");
|
|
767
853
|
function ComboboxPopup({
|
|
768
854
|
side = "bottom",
|
|
769
855
|
align = "start",
|
|
@@ -773,7 +859,7 @@ function ComboboxPopup({
|
|
|
773
859
|
...rest
|
|
774
860
|
}) {
|
|
775
861
|
const { open, multiple, disabled, setOpen, close, isInside, anchorName, inputRef, popupRef } = useComboboxRootContext("Popup");
|
|
776
|
-
(0,
|
|
862
|
+
(0, import_react9.useEffect)(() => {
|
|
777
863
|
if (!open) return;
|
|
778
864
|
function onPointerDown(event) {
|
|
779
865
|
if (event.target instanceof Node && isInside(event.target)) return;
|
|
@@ -782,8 +868,8 @@ function ComboboxPopup({
|
|
|
782
868
|
document.addEventListener("pointerdown", onPointerDown, true);
|
|
783
869
|
return () => document.removeEventListener("pointerdown", onPointerDown, true);
|
|
784
870
|
}, [open, isInside, close]);
|
|
785
|
-
const [, resync] = (0,
|
|
786
|
-
(0,
|
|
871
|
+
const [, resync] = (0, import_react9.useReducer)((n) => n + 1, 0);
|
|
872
|
+
(0, import_react9.useEffect)(() => {
|
|
787
873
|
const popup = popupRef.current;
|
|
788
874
|
if (!popup || typeof popup.showPopover !== "function") return;
|
|
789
875
|
const shown = popup.matches(":popover-open");
|
|
@@ -820,7 +906,7 @@ function ComboboxPopup({
|
|
|
820
906
|
}
|
|
821
907
|
|
|
822
908
|
// src/combobox/ComboboxList.tsx
|
|
823
|
-
var
|
|
909
|
+
var import_react10 = require("react");
|
|
824
910
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
825
911
|
function ComboboxList({
|
|
826
912
|
className,
|
|
@@ -828,8 +914,10 @@ function ComboboxList({
|
|
|
828
914
|
render,
|
|
829
915
|
...rest
|
|
830
916
|
}) {
|
|
831
|
-
const { multiple, loading, filteredItems, onLoadMore, listId } = useComboboxRootContext("List");
|
|
832
|
-
const content = typeof children === "function" ? filteredItems.map(
|
|
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;
|
|
833
921
|
return useRender({
|
|
834
922
|
render,
|
|
835
923
|
defaultTagName: "div",
|
|
@@ -849,10 +937,10 @@ function ComboboxList({
|
|
|
849
937
|
}
|
|
850
938
|
function LoadMoreSentinel() {
|
|
851
939
|
const { open, loading, filteredItems, onLoadMore } = useComboboxRootContext("List");
|
|
852
|
-
const ref = (0,
|
|
853
|
-
const onLoadMoreRef = (0,
|
|
940
|
+
const ref = (0, import_react10.useRef)(null);
|
|
941
|
+
const onLoadMoreRef = (0, import_react10.useRef)(onLoadMore);
|
|
854
942
|
onLoadMoreRef.current = onLoadMore;
|
|
855
|
-
(0,
|
|
943
|
+
(0, import_react10.useEffect)(() => {
|
|
856
944
|
const element = ref.current;
|
|
857
945
|
if (!element || !open || loading || typeof IntersectionObserver === "undefined") return;
|
|
858
946
|
const observer = new IntersectionObserver((entries) => {
|
|
@@ -865,10 +953,52 @@ function LoadMoreSentinel() {
|
|
|
865
953
|
}
|
|
866
954
|
|
|
867
955
|
// src/combobox/ComboboxItem.tsx
|
|
868
|
-
var
|
|
956
|
+
var import_react12 = require("react");
|
|
957
|
+
|
|
958
|
+
// src/combobox/ComboboxGroup.tsx
|
|
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
|
|
869
999
|
function ComboboxItem({
|
|
870
1000
|
value,
|
|
871
|
-
disabled = false,
|
|
1001
|
+
disabled: disabledProp = false,
|
|
872
1002
|
className,
|
|
873
1003
|
children,
|
|
874
1004
|
render,
|
|
@@ -884,23 +1014,25 @@ function ComboboxItem({
|
|
|
884
1014
|
select,
|
|
885
1015
|
optionId
|
|
886
1016
|
} = useComboboxRootContext("Item");
|
|
1017
|
+
const group = useComboboxGroupContext();
|
|
1018
|
+
const disabled = disabledProp || (group?.disabled ?? false);
|
|
887
1019
|
const key = itemToKey(value);
|
|
888
1020
|
const index = indexByKey.get(key) ?? -1;
|
|
889
1021
|
const selected = selectedKeys.has(key);
|
|
890
|
-
const highlighted = (0,
|
|
1022
|
+
const highlighted = (0, import_react12.useSyncExternalStore)(
|
|
891
1023
|
highlight.subscribe,
|
|
892
1024
|
() => index >= 0 && highlight.get().index === index,
|
|
893
1025
|
() => false
|
|
894
1026
|
);
|
|
895
|
-
const elementRef = (0,
|
|
896
|
-
(0,
|
|
1027
|
+
const elementRef = (0, import_react12.useRef)(null);
|
|
1028
|
+
(0, import_react12.useLayoutEffect)(() => {
|
|
897
1029
|
if (!disabled) return;
|
|
898
1030
|
disabledKeys.add(key);
|
|
899
1031
|
return () => {
|
|
900
1032
|
disabledKeys.delete(key);
|
|
901
1033
|
};
|
|
902
1034
|
}, [disabled, disabledKeys, key]);
|
|
903
|
-
(0,
|
|
1035
|
+
(0, import_react12.useEffect)(() => {
|
|
904
1036
|
if (highlighted && highlight.get().reason === "keyboard") {
|
|
905
1037
|
elementRef.current?.scrollIntoView?.({ block: "nearest" });
|
|
906
1038
|
}
|
|
@@ -931,6 +1063,28 @@ function ComboboxItem({
|
|
|
931
1063
|
});
|
|
932
1064
|
}
|
|
933
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
|
+
|
|
934
1088
|
// src/combobox/ComboboxEmpty.tsx
|
|
935
1089
|
function ComboboxEmpty({ className, children, render, ...rest }) {
|
|
936
1090
|
const { filteredItems, loading } = useComboboxRootContext("Empty");
|
|
@@ -955,11 +1109,11 @@ function ComboboxStatus({ className, children, render, ...rest }) {
|
|
|
955
1109
|
}
|
|
956
1110
|
|
|
957
1111
|
// src/combobox/ComboboxChip.tsx
|
|
958
|
-
var
|
|
959
|
-
var
|
|
960
|
-
var ComboboxChipContext = (0,
|
|
1112
|
+
var import_react14 = require("react");
|
|
1113
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
1114
|
+
var ComboboxChipContext = (0, import_react14.createContext)(null);
|
|
961
1115
|
function useComboboxChipContext() {
|
|
962
|
-
const context = (0,
|
|
1116
|
+
const context = (0, import_react14.useContext)(ComboboxChipContext);
|
|
963
1117
|
if (context === null) {
|
|
964
1118
|
throw new Error("<Combobox.ChipRemove> must be rendered inside <Combobox.Chip>.");
|
|
965
1119
|
}
|
|
@@ -974,9 +1128,9 @@ function ComboboxChip({
|
|
|
974
1128
|
}) {
|
|
975
1129
|
const { disabled, itemToKey, remove, registerChip, chips, inputRef } = useComboboxRootContext("Chip");
|
|
976
1130
|
const key = itemToKey(value);
|
|
977
|
-
const elementRef = (0,
|
|
978
|
-
(0,
|
|
979
|
-
const chipContext = (0,
|
|
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]);
|
|
980
1134
|
function focusAt(index) {
|
|
981
1135
|
const chip = chips[index];
|
|
982
1136
|
if (chip) chip.ref.current?.focus();
|
|
@@ -1017,7 +1171,7 @@ function ComboboxChip({
|
|
|
1017
1171
|
},
|
|
1018
1172
|
consumerProps: rest
|
|
1019
1173
|
});
|
|
1020
|
-
return /* @__PURE__ */ (0,
|
|
1174
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ComboboxChipContext.Provider, { value: chipContext, children: element });
|
|
1021
1175
|
}
|
|
1022
1176
|
|
|
1023
1177
|
// src/combobox/ComboboxChipRemove.tsx
|
|
@@ -1050,6 +1204,14 @@ function ComboboxChipRemove({
|
|
|
1050
1204
|
consumerProps: rest
|
|
1051
1205
|
});
|
|
1052
1206
|
}
|
|
1207
|
+
|
|
1208
|
+
// src/combobox/ComboboxValue.tsx
|
|
1209
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
1210
|
+
function ComboboxValue({ children }) {
|
|
1211
|
+
const { multiple, selectedItems, itemToString } = useComboboxRootContext("Value");
|
|
1212
|
+
const value = multiple ? selectedItems : selectedItems[0] ?? null;
|
|
1213
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_jsx_runtime5.Fragment, { children: children(value, { itemToString }) });
|
|
1214
|
+
}
|
|
1053
1215
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1054
1216
|
0 && (module.exports = {
|
|
1055
1217
|
Combobox,
|
|
@@ -21,12 +21,23 @@ type ComboboxState = {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
/** The selection: one item or `null`, or an array with `multiple`. */
|
|
24
|
-
type ComboboxValue<T, Multiple extends boolean> = Multiple extends true ? T[] : T | null;
|
|
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
|
-
/**
|
|
29
|
-
|
|
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.
|
|
@@ -51,10 +62,10 @@ type ComboboxRootProps<T, Multiple extends boolean = false> = {
|
|
|
51
62
|
* Never `undefined` once mounted: the mode is latched at mount. Use `null` — or `[]` with
|
|
52
63
|
* `multiple` — for nothing selected.
|
|
53
64
|
*/
|
|
54
|
-
value?: ComboboxValue<T, Multiple>;
|
|
65
|
+
value?: ComboboxValue$1<T, Multiple>;
|
|
55
66
|
/** Initial selection when uncontrolled. Read once, at mount. */
|
|
56
|
-
defaultValue?: ComboboxValue<T, Multiple>;
|
|
57
|
-
onValueChange?: (value: ComboboxValue<T, Multiple>, details: ComboboxChangeDetails) => void;
|
|
67
|
+
defaultValue?: ComboboxValue$1<T, Multiple>;
|
|
68
|
+
onValueChange?: (value: ComboboxValue$1<T, Multiple>, details: ComboboxChangeDetails) => void;
|
|
58
69
|
/** Controlled text of the input. Provide `onInputValueChange` alongside it. */
|
|
59
70
|
inputValue?: string;
|
|
60
71
|
/** Initial text when uncontrolled. Defaults to the selected item's label, without `multiple`. */
|
|
@@ -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. */
|
|
@@ -107,18 +123,39 @@ type ComboboxInputOwnProps = {
|
|
|
107
123
|
type ComboboxInputProps = ComboboxInputOwnProps & Omit<ComponentPropsWithRef<'input'>, keyof ComboboxInputOwnProps | 'value' | 'defaultValue'>;
|
|
108
124
|
/**
|
|
109
125
|
* The text input: `role="combobox"`, and the element focus stays on. Its text is the Root's
|
|
110
|
-
* `inputValue`. It is what the popup is anchored to.
|
|
126
|
+
* `inputValue`. It is what the popup is anchored to, unless an InputGroup wraps it.
|
|
111
127
|
*
|
|
112
128
|
* Typing opens the list and filters it. Up and Down open it, or move the highlight, wrapping;
|
|
113
129
|
* Home and End jump to the ends while it is open; Alt+Down opens it without highlighting.
|
|
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
|
*/
|
|
120
141
|
declare function ComboboxInput({ className, render, ...rest }: ComboboxInputProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
121
142
|
|
|
143
|
+
type ComboboxInputGroupOwnProps = {
|
|
144
|
+
/** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
|
|
145
|
+
render?: ReactElement;
|
|
146
|
+
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
147
|
+
ref?: Ref<HTMLElement>;
|
|
148
|
+
};
|
|
149
|
+
type ComboboxInputGroupProps = ComboboxInputGroupOwnProps & Omit<ComponentPropsWithRef<'div'>, keyof ComboboxInputGroupOwnProps>;
|
|
150
|
+
/**
|
|
151
|
+
* The field around the Input — chips, Clear and Trigger with it — when they are drawn as one
|
|
152
|
+
* box. It takes over as the popup's anchor, so the list lines up with the whole field rather
|
|
153
|
+
* than the text alone, and a press on it is not a press outside.
|
|
154
|
+
*
|
|
155
|
+
* A press on the box itself, between its children, focuses the input.
|
|
156
|
+
*/
|
|
157
|
+
declare function ComboboxInputGroup({ className, children, render, ...rest }: ComboboxInputGroupProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
158
|
+
|
|
122
159
|
type ComboboxTriggerOwnProps = {
|
|
123
160
|
/** Element to render instead of the default `<button>`. Props and ref are merged onto it. */
|
|
124
161
|
render?: ReactElement;
|
|
@@ -178,7 +215,8 @@ declare function ComboboxPopup({ side, align, className, children, render, ...re
|
|
|
178
215
|
type ComboboxListOwnProps<T> = {
|
|
179
216
|
/**
|
|
180
217
|
* A function rendering one `Combobox.Item` per filtered item, or any nodes. With a
|
|
181
|
-
* 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`.
|
|
182
220
|
*/
|
|
183
221
|
children?: ReactNode | ((item: T, index: number) => ReactNode);
|
|
184
222
|
/** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
|
|
@@ -188,7 +226,8 @@ type ComboboxListOwnProps<T> = {
|
|
|
188
226
|
};
|
|
189
227
|
type ComboboxListProps<T = unknown> = ComboboxListOwnProps<T> & Omit<ComponentPropsWithRef<'div'>, keyof ComboboxListOwnProps<T>>;
|
|
190
228
|
/**
|
|
191
|
-
* 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.
|
|
192
231
|
*
|
|
193
232
|
* With the Root's `onLoadMore`, an invisible sentinel follows the items; when it scrolls into
|
|
194
233
|
* view and nothing is `loading`, `onLoadMore` is called. The list is `aria-busy` meanwhile.
|
|
@@ -198,7 +237,7 @@ declare function ComboboxList<T = unknown>({ className, children, render, ...res
|
|
|
198
237
|
type ComboboxItemOwnProps<T> = {
|
|
199
238
|
/** The item this option stands for — one of the Root's `items`. */
|
|
200
239
|
value: T;
|
|
201
|
-
/** 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. */
|
|
202
241
|
disabled?: boolean;
|
|
203
242
|
/** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
|
|
204
243
|
render?: ReactElement;
|
|
@@ -213,7 +252,38 @@ type ComboboxItemProps<T = unknown> = ComboboxItemOwnProps<T> & Omit<ComponentPr
|
|
|
213
252
|
* `data-selected` and `aria-selected` follow the Root's `value`; `data-highlighted` follows
|
|
214
253
|
* the arrow keys and the pointer. Only the items whose highlight changes re-render.
|
|
215
254
|
*/
|
|
216
|
-
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>>;
|
|
217
287
|
|
|
218
288
|
type ComboboxEmptyOwnProps = {
|
|
219
289
|
/** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
|
|
@@ -273,8 +343,26 @@ type ComboboxChipRemoveProps = ComboboxChipRemoveOwnProps & Omit<ComponentPropsW
|
|
|
273
343
|
*/
|
|
274
344
|
declare function ComboboxChipRemove({ className, children, render, ...rest }: ComboboxChipRemoveProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
275
345
|
|
|
346
|
+
type ComboboxValueProps<T = unknown> = {
|
|
347
|
+
/**
|
|
348
|
+
* Renders the selection: `T | null`, or `T[]` with `multiple`. The Root's `itemToString` is
|
|
349
|
+
* passed along for labels — a chip's text, say.
|
|
350
|
+
*/
|
|
351
|
+
children: (value: T, details: {
|
|
352
|
+
itemToString: (item: unknown) => string;
|
|
353
|
+
}) => ReactNode;
|
|
354
|
+
};
|
|
355
|
+
/**
|
|
356
|
+
* The current selection, for whatever shows it outside the input — chips, most often. Renders
|
|
357
|
+
* no element of its own.
|
|
358
|
+
*
|
|
359
|
+
* It exists so that a separate styling package can show the selection without owning a copy
|
|
360
|
+
* of it (decision 9): the value stays the Root's.
|
|
361
|
+
*/
|
|
362
|
+
declare function ComboboxValue<T = unknown>({ children }: ComboboxValueProps<T>): react.JSX.Element;
|
|
363
|
+
|
|
276
364
|
declare namespace index_parts {
|
|
277
|
-
export { ComboboxChip as Chip, ComboboxChipRemove as ChipRemove, ComboboxClear as Clear, ComboboxEmpty as Empty, ComboboxInput as Input, ComboboxItem as Item, ComboboxList as List, ComboboxPopup as Popup, ComboboxRoot as Root, ComboboxStatus as Status, ComboboxTrigger as Trigger };
|
|
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 };
|
|
278
366
|
}
|
|
279
367
|
|
|
280
368
|
/**
|
|
@@ -285,4 +373,4 @@ declare namespace index_parts {
|
|
|
285
373
|
*/
|
|
286
374
|
declare function defaultFilter<T>(items: readonly T[], query: string, itemToString: (item: T) => string): T[];
|
|
287
375
|
|
|
288
|
-
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 ComboboxInputProps, type ComboboxItemProps, type ComboboxListProps, type ComboboxPopupProps, type ComboboxRootProps, type ComboboxSide, type ComboboxState, type ComboboxStatusProps, type ComboboxTriggerProps, type ComboboxValue, 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 };
|
package/dist/combobox/index.d.ts
CHANGED
|
@@ -21,12 +21,23 @@ type ComboboxState = {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
/** The selection: one item or `null`, or an array with `multiple`. */
|
|
24
|
-
type ComboboxValue<T, Multiple extends boolean> = Multiple extends true ? T[] : T | null;
|
|
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
|
-
/**
|
|
29
|
-
|
|
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.
|
|
@@ -51,10 +62,10 @@ type ComboboxRootProps<T, Multiple extends boolean = false> = {
|
|
|
51
62
|
* Never `undefined` once mounted: the mode is latched at mount. Use `null` — or `[]` with
|
|
52
63
|
* `multiple` — for nothing selected.
|
|
53
64
|
*/
|
|
54
|
-
value?: ComboboxValue<T, Multiple>;
|
|
65
|
+
value?: ComboboxValue$1<T, Multiple>;
|
|
55
66
|
/** Initial selection when uncontrolled. Read once, at mount. */
|
|
56
|
-
defaultValue?: ComboboxValue<T, Multiple>;
|
|
57
|
-
onValueChange?: (value: ComboboxValue<T, Multiple>, details: ComboboxChangeDetails) => void;
|
|
67
|
+
defaultValue?: ComboboxValue$1<T, Multiple>;
|
|
68
|
+
onValueChange?: (value: ComboboxValue$1<T, Multiple>, details: ComboboxChangeDetails) => void;
|
|
58
69
|
/** Controlled text of the input. Provide `onInputValueChange` alongside it. */
|
|
59
70
|
inputValue?: string;
|
|
60
71
|
/** Initial text when uncontrolled. Defaults to the selected item's label, without `multiple`. */
|
|
@@ -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. */
|
|
@@ -107,18 +123,39 @@ type ComboboxInputOwnProps = {
|
|
|
107
123
|
type ComboboxInputProps = ComboboxInputOwnProps & Omit<ComponentPropsWithRef<'input'>, keyof ComboboxInputOwnProps | 'value' | 'defaultValue'>;
|
|
108
124
|
/**
|
|
109
125
|
* The text input: `role="combobox"`, and the element focus stays on. Its text is the Root's
|
|
110
|
-
* `inputValue`. It is what the popup is anchored to.
|
|
126
|
+
* `inputValue`. It is what the popup is anchored to, unless an InputGroup wraps it.
|
|
111
127
|
*
|
|
112
128
|
* Typing opens the list and filters it. Up and Down open it, or move the highlight, wrapping;
|
|
113
129
|
* Home and End jump to the ends while it is open; Alt+Down opens it without highlighting.
|
|
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
|
*/
|
|
120
141
|
declare function ComboboxInput({ className, render, ...rest }: ComboboxInputProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
121
142
|
|
|
143
|
+
type ComboboxInputGroupOwnProps = {
|
|
144
|
+
/** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
|
|
145
|
+
render?: ReactElement;
|
|
146
|
+
/** Ref to the rendered element. Merged with any ref on the `render` element. */
|
|
147
|
+
ref?: Ref<HTMLElement>;
|
|
148
|
+
};
|
|
149
|
+
type ComboboxInputGroupProps = ComboboxInputGroupOwnProps & Omit<ComponentPropsWithRef<'div'>, keyof ComboboxInputGroupOwnProps>;
|
|
150
|
+
/**
|
|
151
|
+
* The field around the Input — chips, Clear and Trigger with it — when they are drawn as one
|
|
152
|
+
* box. It takes over as the popup's anchor, so the list lines up with the whole field rather
|
|
153
|
+
* than the text alone, and a press on it is not a press outside.
|
|
154
|
+
*
|
|
155
|
+
* A press on the box itself, between its children, focuses the input.
|
|
156
|
+
*/
|
|
157
|
+
declare function ComboboxInputGroup({ className, children, render, ...rest }: ComboboxInputGroupProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
158
|
+
|
|
122
159
|
type ComboboxTriggerOwnProps = {
|
|
123
160
|
/** Element to render instead of the default `<button>`. Props and ref are merged onto it. */
|
|
124
161
|
render?: ReactElement;
|
|
@@ -178,7 +215,8 @@ declare function ComboboxPopup({ side, align, className, children, render, ...re
|
|
|
178
215
|
type ComboboxListOwnProps<T> = {
|
|
179
216
|
/**
|
|
180
217
|
* A function rendering one `Combobox.Item` per filtered item, or any nodes. With a
|
|
181
|
-
* 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`.
|
|
182
220
|
*/
|
|
183
221
|
children?: ReactNode | ((item: T, index: number) => ReactNode);
|
|
184
222
|
/** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
|
|
@@ -188,7 +226,8 @@ type ComboboxListOwnProps<T> = {
|
|
|
188
226
|
};
|
|
189
227
|
type ComboboxListProps<T = unknown> = ComboboxListOwnProps<T> & Omit<ComponentPropsWithRef<'div'>, keyof ComboboxListOwnProps<T>>;
|
|
190
228
|
/**
|
|
191
|
-
* 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.
|
|
192
231
|
*
|
|
193
232
|
* With the Root's `onLoadMore`, an invisible sentinel follows the items; when it scrolls into
|
|
194
233
|
* view and nothing is `loading`, `onLoadMore` is called. The list is `aria-busy` meanwhile.
|
|
@@ -198,7 +237,7 @@ declare function ComboboxList<T = unknown>({ className, children, render, ...res
|
|
|
198
237
|
type ComboboxItemOwnProps<T> = {
|
|
199
238
|
/** The item this option stands for — one of the Root's `items`. */
|
|
200
239
|
value: T;
|
|
201
|
-
/** 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. */
|
|
202
241
|
disabled?: boolean;
|
|
203
242
|
/** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
|
|
204
243
|
render?: ReactElement;
|
|
@@ -213,7 +252,38 @@ type ComboboxItemProps<T = unknown> = ComboboxItemOwnProps<T> & Omit<ComponentPr
|
|
|
213
252
|
* `data-selected` and `aria-selected` follow the Root's `value`; `data-highlighted` follows
|
|
214
253
|
* the arrow keys and the pointer. Only the items whose highlight changes re-render.
|
|
215
254
|
*/
|
|
216
|
-
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>>;
|
|
217
287
|
|
|
218
288
|
type ComboboxEmptyOwnProps = {
|
|
219
289
|
/** Element to render instead of the default `<div>`. Props and ref are merged onto it. */
|
|
@@ -273,8 +343,26 @@ type ComboboxChipRemoveProps = ComboboxChipRemoveOwnProps & Omit<ComponentPropsW
|
|
|
273
343
|
*/
|
|
274
344
|
declare function ComboboxChipRemove({ className, children, render, ...rest }: ComboboxChipRemoveProps): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
|
|
275
345
|
|
|
346
|
+
type ComboboxValueProps<T = unknown> = {
|
|
347
|
+
/**
|
|
348
|
+
* Renders the selection: `T | null`, or `T[]` with `multiple`. The Root's `itemToString` is
|
|
349
|
+
* passed along for labels — a chip's text, say.
|
|
350
|
+
*/
|
|
351
|
+
children: (value: T, details: {
|
|
352
|
+
itemToString: (item: unknown) => string;
|
|
353
|
+
}) => ReactNode;
|
|
354
|
+
};
|
|
355
|
+
/**
|
|
356
|
+
* The current selection, for whatever shows it outside the input — chips, most often. Renders
|
|
357
|
+
* no element of its own.
|
|
358
|
+
*
|
|
359
|
+
* It exists so that a separate styling package can show the selection without owning a copy
|
|
360
|
+
* of it (decision 9): the value stays the Root's.
|
|
361
|
+
*/
|
|
362
|
+
declare function ComboboxValue<T = unknown>({ children }: ComboboxValueProps<T>): react.JSX.Element;
|
|
363
|
+
|
|
276
364
|
declare namespace index_parts {
|
|
277
|
-
export { ComboboxChip as Chip, ComboboxChipRemove as ChipRemove, ComboboxClear as Clear, ComboboxEmpty as Empty, ComboboxInput as Input, ComboboxItem as Item, ComboboxList as List, ComboboxPopup as Popup, ComboboxRoot as Root, ComboboxStatus as Status, ComboboxTrigger as Trigger };
|
|
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 };
|
|
278
366
|
}
|
|
279
367
|
|
|
280
368
|
/**
|
|
@@ -285,4 +373,4 @@ declare namespace index_parts {
|
|
|
285
373
|
*/
|
|
286
374
|
declare function defaultFilter<T>(items: readonly T[], query: string, itemToString: (item: T) => string): T[];
|
|
287
375
|
|
|
288
|
-
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 ComboboxInputProps, type ComboboxItemProps, type ComboboxListProps, type ComboboxPopupProps, type ComboboxRootProps, type ComboboxSide, type ComboboxState, type ComboboxStatusProps, type ComboboxTriggerProps, type ComboboxValue, 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 };
|
package/dist/combobox/index.js
CHANGED
|
@@ -22,13 +22,17 @@ __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,
|
|
28
|
+
InputGroup: () => ComboboxInputGroup,
|
|
26
29
|
Item: () => ComboboxItem,
|
|
27
30
|
List: () => ComboboxList,
|
|
28
31
|
Popup: () => ComboboxPopup,
|
|
29
32
|
Root: () => ComboboxRoot,
|
|
30
33
|
Status: () => ComboboxStatus,
|
|
31
|
-
Trigger: () => ComboboxTrigger
|
|
34
|
+
Trigger: () => ComboboxTrigger,
|
|
35
|
+
Value: () => ComboboxValue
|
|
32
36
|
});
|
|
33
37
|
|
|
34
38
|
// src/combobox/ComboboxRoot.tsx
|
|
@@ -73,6 +77,11 @@ function useComboboxRootContext(part) {
|
|
|
73
77
|
|
|
74
78
|
// src/combobox/ComboboxRoot.tsx
|
|
75
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
|
+
}
|
|
76
85
|
function defaultItemToString(item) {
|
|
77
86
|
if (typeof item === "string") return item;
|
|
78
87
|
if (item !== null && typeof item === "object" && "label" in item) return String(item.label);
|
|
@@ -97,6 +106,7 @@ function ComboboxRoot({
|
|
|
97
106
|
onLoadMore,
|
|
98
107
|
loading = false,
|
|
99
108
|
disabled = false,
|
|
109
|
+
required = false,
|
|
100
110
|
name,
|
|
101
111
|
form,
|
|
102
112
|
children
|
|
@@ -133,10 +143,17 @@ function ComboboxRoot({
|
|
|
133
143
|
});
|
|
134
144
|
const query = !multiple && inputValue === selectedLabel ? "" : inputValue;
|
|
135
145
|
const deferredQuery = useDeferredValue(query);
|
|
136
|
-
const filteredItems = useMemo(() => {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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 };
|
|
140
157
|
}, [items, filter, deferredQuery, itemToString]);
|
|
141
158
|
const indexByKey = useMemo(
|
|
142
159
|
() => new Map(filteredItems.map((item, index) => [itemToKey(item), index])),
|
|
@@ -248,6 +265,11 @@ function ComboboxRoot({
|
|
|
248
265
|
},
|
|
249
266
|
[insideRefs]
|
|
250
267
|
);
|
|
268
|
+
const [hasGroup, setHasGroup] = useState(false);
|
|
269
|
+
const registerGroup = useCallback(() => {
|
|
270
|
+
setHasGroup(true);
|
|
271
|
+
return () => setHasGroup(false);
|
|
272
|
+
}, []);
|
|
251
273
|
const inputRef = useRef(null);
|
|
252
274
|
const popupRef = useRef(null);
|
|
253
275
|
const { current: initial } = useRef({ value, inputValue });
|
|
@@ -276,9 +298,11 @@ function ComboboxRoot({
|
|
|
276
298
|
open,
|
|
277
299
|
multiple,
|
|
278
300
|
disabled,
|
|
301
|
+
required,
|
|
279
302
|
inputValue,
|
|
280
303
|
loading,
|
|
281
304
|
filteredItems,
|
|
305
|
+
filteredGroups,
|
|
282
306
|
itemToString,
|
|
283
307
|
itemToKey,
|
|
284
308
|
indexByKey,
|
|
@@ -298,6 +322,8 @@ function ComboboxRoot({
|
|
|
298
322
|
chips,
|
|
299
323
|
registerInside,
|
|
300
324
|
isInside,
|
|
325
|
+
hasGroup,
|
|
326
|
+
registerGroup,
|
|
301
327
|
onLoadMore,
|
|
302
328
|
inputId: `${id}-input`,
|
|
303
329
|
listId: `${id}-list`,
|
|
@@ -310,9 +336,11 @@ function ComboboxRoot({
|
|
|
310
336
|
open,
|
|
311
337
|
multiple,
|
|
312
338
|
disabled,
|
|
339
|
+
required,
|
|
313
340
|
inputValue,
|
|
314
341
|
loading,
|
|
315
342
|
filteredItems,
|
|
343
|
+
filteredGroups,
|
|
316
344
|
itemToString,
|
|
317
345
|
itemToKey,
|
|
318
346
|
indexByKey,
|
|
@@ -332,6 +360,8 @@ function ComboboxRoot({
|
|
|
332
360
|
chips,
|
|
333
361
|
registerInside,
|
|
334
362
|
isInside,
|
|
363
|
+
hasGroup,
|
|
364
|
+
registerGroup,
|
|
335
365
|
onLoadMore,
|
|
336
366
|
id
|
|
337
367
|
]
|
|
@@ -353,7 +383,7 @@ function ComboboxRoot({
|
|
|
353
383
|
}
|
|
354
384
|
|
|
355
385
|
// src/combobox/ComboboxInput.tsx
|
|
356
|
-
import { useSyncExternalStore } from "react";
|
|
386
|
+
import { useLayoutEffect, useRef as useRef2, useSyncExternalStore } from "react";
|
|
357
387
|
|
|
358
388
|
// src/combobox/comboboxDataAttributes.ts
|
|
359
389
|
function comboboxDataAttributes({ open, disabled }) {
|
|
@@ -384,6 +414,7 @@ function ComboboxInput({ className, render, ...rest }) {
|
|
|
384
414
|
open,
|
|
385
415
|
multiple,
|
|
386
416
|
disabled,
|
|
417
|
+
required,
|
|
387
418
|
inputValue,
|
|
388
419
|
filteredItems,
|
|
389
420
|
itemToKey,
|
|
@@ -403,6 +434,7 @@ function ComboboxInput({ className, render, ...rest }) {
|
|
|
403
434
|
listId,
|
|
404
435
|
optionId,
|
|
405
436
|
anchorName,
|
|
437
|
+
hasGroup,
|
|
406
438
|
inputRef,
|
|
407
439
|
popupRef
|
|
408
440
|
} = useComboboxRootContext("Input");
|
|
@@ -411,6 +443,20 @@ function ComboboxInput({ className, render, ...rest }) {
|
|
|
411
443
|
() => highlight.get().index,
|
|
412
444
|
() => -1
|
|
413
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]);
|
|
414
460
|
function enabledIndices() {
|
|
415
461
|
const indices = [];
|
|
416
462
|
filteredItems.forEach((item, index) => {
|
|
@@ -448,9 +494,13 @@ function ComboboxInput({ className, render, ...rest }) {
|
|
|
448
494
|
"aria-controls": listId,
|
|
449
495
|
"aria-activedescendant": open && highlighted >= 0 ? optionId(highlighted) : void 0,
|
|
450
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,
|
|
451
500
|
value: inputValue,
|
|
452
501
|
...comboboxDataAttributes({ open, multiple, disabled }),
|
|
453
|
-
|
|
502
|
+
// The anchor, unless an InputGroup around it is.
|
|
503
|
+
style: hasGroup ? void 0 : { anchorName },
|
|
454
504
|
className,
|
|
455
505
|
ref: inputRef,
|
|
456
506
|
onChange(event) {
|
|
@@ -516,13 +566,49 @@ function ComboboxInput({ className, render, ...rest }) {
|
|
|
516
566
|
consumerProps: rest
|
|
517
567
|
});
|
|
518
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
|
+
}
|
|
574
|
+
|
|
575
|
+
// src/combobox/ComboboxInputGroup.tsx
|
|
576
|
+
import { useLayoutEffect as useLayoutEffect2, useRef as useRef3 } from "react";
|
|
577
|
+
function ComboboxInputGroup({
|
|
578
|
+
className,
|
|
579
|
+
children,
|
|
580
|
+
render,
|
|
581
|
+
...rest
|
|
582
|
+
}) {
|
|
583
|
+
const { open, multiple, disabled, anchorName, registerGroup, registerInside, inputRef } = useComboboxRootContext("InputGroup");
|
|
584
|
+
const elementRef = useRef3(null);
|
|
585
|
+
useLayoutEffect2(() => registerGroup(), [registerGroup]);
|
|
586
|
+
useLayoutEffect2(() => registerInside(elementRef), [registerInside]);
|
|
587
|
+
return useRender({
|
|
588
|
+
render,
|
|
589
|
+
defaultTagName: "div",
|
|
590
|
+
props: {
|
|
591
|
+
...comboboxDataAttributes({ open, multiple, disabled }),
|
|
592
|
+
style: { anchorName },
|
|
593
|
+
className,
|
|
594
|
+
children,
|
|
595
|
+
ref: elementRef,
|
|
596
|
+
onMouseDown(event) {
|
|
597
|
+
if (event.target !== event.currentTarget || disabled) return;
|
|
598
|
+
event.preventDefault();
|
|
599
|
+
inputRef.current?.focus();
|
|
600
|
+
}
|
|
601
|
+
},
|
|
602
|
+
consumerProps: rest
|
|
603
|
+
});
|
|
604
|
+
}
|
|
519
605
|
|
|
520
606
|
// src/combobox/ComboboxTrigger.tsx
|
|
521
|
-
import { useLayoutEffect, useRef as
|
|
607
|
+
import { useLayoutEffect as useLayoutEffect3, useRef as useRef4 } from "react";
|
|
522
608
|
function ComboboxTrigger({ className, children, render, ...rest }) {
|
|
523
609
|
const { open, multiple, disabled, setOpen, close, listId, inputRef, registerInside } = useComboboxRootContext("Trigger");
|
|
524
|
-
const elementRef =
|
|
525
|
-
|
|
610
|
+
const elementRef = useRef4(null);
|
|
611
|
+
useLayoutEffect3(() => registerInside(elementRef), [registerInside]);
|
|
526
612
|
return useRender({
|
|
527
613
|
render,
|
|
528
614
|
defaultTagName: "button",
|
|
@@ -550,11 +636,11 @@ function ComboboxTrigger({ className, children, render, ...rest }) {
|
|
|
550
636
|
}
|
|
551
637
|
|
|
552
638
|
// src/combobox/ComboboxClear.tsx
|
|
553
|
-
import { useLayoutEffect as
|
|
639
|
+
import { useLayoutEffect as useLayoutEffect4, useRef as useRef5 } from "react";
|
|
554
640
|
function ComboboxClear({ className, children, render, ...rest }) {
|
|
555
641
|
const { disabled, selectedItems, clear, inputRef, registerInside } = useComboboxRootContext("Clear");
|
|
556
|
-
const elementRef =
|
|
557
|
-
|
|
642
|
+
const elementRef = useRef5(null);
|
|
643
|
+
useLayoutEffect4(() => registerInside(elementRef), [registerInside]);
|
|
558
644
|
return useRender({
|
|
559
645
|
render,
|
|
560
646
|
defaultTagName: "button",
|
|
@@ -637,7 +723,7 @@ function ComboboxPopup({
|
|
|
637
723
|
}
|
|
638
724
|
|
|
639
725
|
// src/combobox/ComboboxList.tsx
|
|
640
|
-
import { useEffect as useEffect3, useRef as
|
|
726
|
+
import { useEffect as useEffect3, useRef as useRef6 } from "react";
|
|
641
727
|
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
642
728
|
function ComboboxList({
|
|
643
729
|
className,
|
|
@@ -645,8 +731,10 @@ function ComboboxList({
|
|
|
645
731
|
render,
|
|
646
732
|
...rest
|
|
647
733
|
}) {
|
|
648
|
-
const { multiple, loading, filteredItems, onLoadMore, listId } = useComboboxRootContext("List");
|
|
649
|
-
const content = typeof children === "function" ? filteredItems.map(
|
|
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;
|
|
650
738
|
return useRender({
|
|
651
739
|
render,
|
|
652
740
|
defaultTagName: "div",
|
|
@@ -666,8 +754,8 @@ function ComboboxList({
|
|
|
666
754
|
}
|
|
667
755
|
function LoadMoreSentinel() {
|
|
668
756
|
const { open, loading, filteredItems, onLoadMore } = useComboboxRootContext("List");
|
|
669
|
-
const ref =
|
|
670
|
-
const onLoadMoreRef =
|
|
757
|
+
const ref = useRef6(null);
|
|
758
|
+
const onLoadMoreRef = useRef6(onLoadMore);
|
|
671
759
|
onLoadMoreRef.current = onLoadMore;
|
|
672
760
|
useEffect3(() => {
|
|
673
761
|
const element = ref.current;
|
|
@@ -682,10 +770,52 @@ function LoadMoreSentinel() {
|
|
|
682
770
|
}
|
|
683
771
|
|
|
684
772
|
// src/combobox/ComboboxItem.tsx
|
|
685
|
-
import { useEffect as useEffect4, useLayoutEffect as
|
|
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
|
|
686
816
|
function ComboboxItem({
|
|
687
817
|
value,
|
|
688
|
-
disabled = false,
|
|
818
|
+
disabled: disabledProp = false,
|
|
689
819
|
className,
|
|
690
820
|
children,
|
|
691
821
|
render,
|
|
@@ -701,6 +831,8 @@ function ComboboxItem({
|
|
|
701
831
|
select,
|
|
702
832
|
optionId
|
|
703
833
|
} = useComboboxRootContext("Item");
|
|
834
|
+
const group = useComboboxGroupContext();
|
|
835
|
+
const disabled = disabledProp || (group?.disabled ?? false);
|
|
704
836
|
const key = itemToKey(value);
|
|
705
837
|
const index = indexByKey.get(key) ?? -1;
|
|
706
838
|
const selected = selectedKeys.has(key);
|
|
@@ -709,8 +841,8 @@ function ComboboxItem({
|
|
|
709
841
|
() => index >= 0 && highlight.get().index === index,
|
|
710
842
|
() => false
|
|
711
843
|
);
|
|
712
|
-
const elementRef =
|
|
713
|
-
|
|
844
|
+
const elementRef = useRef7(null);
|
|
845
|
+
useLayoutEffect5(() => {
|
|
714
846
|
if (!disabled) return;
|
|
715
847
|
disabledKeys.add(key);
|
|
716
848
|
return () => {
|
|
@@ -748,6 +880,28 @@ function ComboboxItem({
|
|
|
748
880
|
});
|
|
749
881
|
}
|
|
750
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
|
+
|
|
751
905
|
// src/combobox/ComboboxEmpty.tsx
|
|
752
906
|
function ComboboxEmpty({ className, children, render, ...rest }) {
|
|
753
907
|
const { filteredItems, loading } = useComboboxRootContext("Empty");
|
|
@@ -772,11 +926,11 @@ function ComboboxStatus({ className, children, render, ...rest }) {
|
|
|
772
926
|
}
|
|
773
927
|
|
|
774
928
|
// src/combobox/ComboboxChip.tsx
|
|
775
|
-
import { createContext as
|
|
776
|
-
import { jsx as
|
|
777
|
-
var ComboboxChipContext =
|
|
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);
|
|
778
932
|
function useComboboxChipContext() {
|
|
779
|
-
const context =
|
|
933
|
+
const context = useContext3(ComboboxChipContext);
|
|
780
934
|
if (context === null) {
|
|
781
935
|
throw new Error("<Combobox.ChipRemove> must be rendered inside <Combobox.Chip>.");
|
|
782
936
|
}
|
|
@@ -791,9 +945,9 @@ function ComboboxChip({
|
|
|
791
945
|
}) {
|
|
792
946
|
const { disabled, itemToKey, remove, registerChip, chips, inputRef } = useComboboxRootContext("Chip");
|
|
793
947
|
const key = itemToKey(value);
|
|
794
|
-
const elementRef =
|
|
795
|
-
|
|
796
|
-
const chipContext =
|
|
948
|
+
const elementRef = useRef8(null);
|
|
949
|
+
useLayoutEffect7(() => registerChip({ key, ref: elementRef }), [registerChip, key]);
|
|
950
|
+
const chipContext = useMemo3(() => ({ value }), [value]);
|
|
797
951
|
function focusAt(index) {
|
|
798
952
|
const chip = chips[index];
|
|
799
953
|
if (chip) chip.ref.current?.focus();
|
|
@@ -834,7 +988,7 @@ function ComboboxChip({
|
|
|
834
988
|
},
|
|
835
989
|
consumerProps: rest
|
|
836
990
|
});
|
|
837
|
-
return /* @__PURE__ */
|
|
991
|
+
return /* @__PURE__ */ jsx4(ComboboxChipContext.Provider, { value: chipContext, children: element });
|
|
838
992
|
}
|
|
839
993
|
|
|
840
994
|
// src/combobox/ComboboxChipRemove.tsx
|
|
@@ -867,6 +1021,14 @@ function ComboboxChipRemove({
|
|
|
867
1021
|
consumerProps: rest
|
|
868
1022
|
});
|
|
869
1023
|
}
|
|
1024
|
+
|
|
1025
|
+
// src/combobox/ComboboxValue.tsx
|
|
1026
|
+
import { Fragment as Fragment2, jsx as jsx5 } from "react/jsx-runtime";
|
|
1027
|
+
function ComboboxValue({ children }) {
|
|
1028
|
+
const { multiple, selectedItems, itemToString } = useComboboxRootContext("Value");
|
|
1029
|
+
const value = multiple ? selectedItems : selectedItems[0] ?? null;
|
|
1030
|
+
return /* @__PURE__ */ jsx5(Fragment2, { children: children(value, { itemToString }) });
|
|
1031
|
+
}
|
|
870
1032
|
export {
|
|
871
1033
|
index_parts_exports as Combobox,
|
|
872
1034
|
defaultFilter
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arun-dev/headless",
|
|
3
|
-
"version": "4.
|
|
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",
|