@arun-dev/headless 4.7.0 → 4.9.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.
@@ -0,0 +1,925 @@
1
+ import {
2
+ byDocumentOrder,
3
+ rovingIndex
4
+ } from "../chunk-2S4XG4VD.js";
5
+ import {
6
+ anchorNameFor,
7
+ anchoredDataAttributes,
8
+ anchoredPopupStyle
9
+ } from "../chunk-HCOCPQXS.js";
10
+ import {
11
+ useControlled
12
+ } from "../chunk-UEIB3I3T.js";
13
+ import {
14
+ __export,
15
+ useRender
16
+ } from "../chunk-OQTWWZ4B.js";
17
+
18
+ // src/combobox/index.parts.ts
19
+ var index_parts_exports = {};
20
+ __export(index_parts_exports, {
21
+ Chip: () => ComboboxChip,
22
+ ChipRemove: () => ComboboxChipRemove,
23
+ Clear: () => ComboboxClear,
24
+ Empty: () => ComboboxEmpty,
25
+ Input: () => ComboboxInput,
26
+ InputGroup: () => ComboboxInputGroup,
27
+ Item: () => ComboboxItem,
28
+ List: () => ComboboxList,
29
+ Popup: () => ComboboxPopup,
30
+ Root: () => ComboboxRoot,
31
+ Status: () => ComboboxStatus,
32
+ Trigger: () => ComboboxTrigger,
33
+ Value: () => ComboboxValue
34
+ });
35
+
36
+ // src/combobox/ComboboxRoot.tsx
37
+ import { useCallback, useDeferredValue, useEffect, useId, useMemo, useRef, useState } from "react";
38
+
39
+ // src/combobox/defaultFilter.ts
40
+ function fold(text) {
41
+ return text.normalize("NFD").replace(/\p{M}/gu, "").toLocaleLowerCase();
42
+ }
43
+ function defaultFilter(items, query, itemToString) {
44
+ const needle = fold(query.trim());
45
+ if (needle === "") return [...items];
46
+ return items.filter((item) => fold(itemToString(item)).includes(needle));
47
+ }
48
+
49
+ // src/combobox/ComboboxRootContext.ts
50
+ import { createContext, useContext } from "react";
51
+ function createHighlightStore() {
52
+ let current = { index: -1, reason: "none" };
53
+ const listeners = /* @__PURE__ */ new Set();
54
+ return {
55
+ get: () => current,
56
+ set(next) {
57
+ if (next.index === current.index && next.reason === current.reason) return;
58
+ current = next;
59
+ for (const listener of listeners) listener();
60
+ },
61
+ subscribe(listener) {
62
+ listeners.add(listener);
63
+ return () => listeners.delete(listener);
64
+ }
65
+ };
66
+ }
67
+ var ComboboxRootContext = createContext(null);
68
+ function useComboboxRootContext(part) {
69
+ const context = useContext(ComboboxRootContext);
70
+ if (context === null) {
71
+ throw new Error(`<Combobox.${part}> must be rendered inside <Combobox.Root>.`);
72
+ }
73
+ return context;
74
+ }
75
+
76
+ // src/combobox/ComboboxRoot.tsx
77
+ import { jsx, jsxs } from "react/jsx-runtime";
78
+ function defaultItemToString(item) {
79
+ if (typeof item === "string") return item;
80
+ if (item !== null && typeof item === "object" && "label" in item) return String(item.label);
81
+ return String(item);
82
+ }
83
+ function ComboboxRoot({
84
+ items,
85
+ itemToString: itemToStringProp,
86
+ itemToKey: itemToKeyProp,
87
+ filter,
88
+ multiple: multipleProp,
89
+ value: valueProp,
90
+ defaultValue,
91
+ onValueChange,
92
+ inputValue: inputValueProp,
93
+ defaultInputValue,
94
+ onInputValueChange,
95
+ open: openProp,
96
+ defaultOpen,
97
+ onOpenChange,
98
+ onItemHighlighted,
99
+ onLoadMore,
100
+ loading = false,
101
+ disabled = false,
102
+ name,
103
+ form,
104
+ children
105
+ }) {
106
+ const multiple = multipleProp === true;
107
+ const itemToString = itemToStringProp ?? defaultItemToString;
108
+ const itemToKey = itemToKeyProp ?? itemToString;
109
+ const [value, setValueState] = useControlled({
110
+ controlled: valueProp,
111
+ default: defaultValue ?? (multiple ? [] : null),
112
+ name: "Combobox.Root",
113
+ state: "value"
114
+ });
115
+ const selectedItems = useMemo(
116
+ () => multiple ? value : value == null ? [] : [value],
117
+ [multiple, value]
118
+ );
119
+ const selectedKeys = useMemo(
120
+ () => new Set(selectedItems.map((item) => itemToKey(item))),
121
+ [selectedItems, itemToKey]
122
+ );
123
+ const selectedLabel = !multiple && selectedItems[0] != null ? itemToString(selectedItems[0]) : "";
124
+ const [inputValue, setInputValueState] = useControlled({
125
+ controlled: inputValueProp,
126
+ default: defaultInputValue ?? selectedLabel,
127
+ name: "Combobox.Root",
128
+ state: "inputValue"
129
+ });
130
+ const [open, setOpenState] = useControlled({
131
+ controlled: openProp,
132
+ default: defaultOpen ?? false,
133
+ name: "Combobox.Root",
134
+ state: "open"
135
+ });
136
+ const query = !multiple && inputValue === selectedLabel ? "" : inputValue;
137
+ 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);
142
+ }, [items, filter, deferredQuery, itemToString]);
143
+ const indexByKey = useMemo(
144
+ () => new Map(filteredItems.map((item, index) => [itemToKey(item), index])),
145
+ [filteredItems, itemToKey]
146
+ );
147
+ const [highlight] = useState(createHighlightStore);
148
+ const setHighlight = useCallback(
149
+ (index, reason) => {
150
+ if (highlight.get().index === index && highlight.get().reason === reason) return;
151
+ highlight.set({ index, reason });
152
+ onItemHighlighted?.(filteredItems[index], { index, reason });
153
+ },
154
+ [highlight, filteredItems, onItemHighlighted]
155
+ );
156
+ useEffect(() => {
157
+ if (highlight.get().index !== -1) setHighlight(-1, "none");
158
+ }, [filteredItems]);
159
+ const setValue = useCallback(
160
+ (next, reason) => {
161
+ setValueState(next);
162
+ onValueChange?.(next, {
163
+ reason
164
+ });
165
+ },
166
+ [setValueState, onValueChange]
167
+ );
168
+ const setInputValue = useCallback(
169
+ (next, reason) => {
170
+ if (next === inputValue) return;
171
+ setInputValueState(next);
172
+ onInputValueChange?.(next, { reason });
173
+ },
174
+ [inputValue, setInputValueState, onInputValueChange]
175
+ );
176
+ const setOpen = useCallback(
177
+ (next, reason) => {
178
+ if (next === open) return;
179
+ setOpenState(next);
180
+ onOpenChange?.(next, { reason });
181
+ if (!next) setHighlight(-1, "none");
182
+ },
183
+ [open, setOpenState, onOpenChange, setHighlight]
184
+ );
185
+ const close = useCallback(
186
+ (reason) => {
187
+ setOpen(false, reason);
188
+ setInputValue(selectedLabel, reason);
189
+ },
190
+ [setOpen, setInputValue, selectedLabel]
191
+ );
192
+ const select = useCallback(
193
+ (item) => {
194
+ const key = itemToKey(item);
195
+ if (multiple) {
196
+ const current = value;
197
+ setValue(
198
+ selectedKeys.has(key) ? current.filter((i) => itemToKey(i) !== key) : [...current, item],
199
+ "item-press"
200
+ );
201
+ setInputValue("", "item-press");
202
+ } else {
203
+ if (!selectedKeys.has(key)) setValue(item, "item-press");
204
+ setInputValue(itemToString(item), "item-press");
205
+ }
206
+ setOpen(false, "item-press");
207
+ },
208
+ [multiple, value, selectedKeys, itemToKey, itemToString, setValue, setInputValue, setOpen]
209
+ );
210
+ const remove = useCallback(
211
+ (item, reason) => {
212
+ if (!multiple) return;
213
+ const key = itemToKey(item);
214
+ setValue(
215
+ value.filter((i) => itemToKey(i) !== key),
216
+ reason
217
+ );
218
+ },
219
+ [multiple, value, itemToKey, setValue]
220
+ );
221
+ const clearSelection = useCallback(
222
+ (reason) => setValue(multiple ? [] : null, reason),
223
+ [multiple, setValue]
224
+ );
225
+ const clear = useCallback(() => {
226
+ setInputValue("", "clear");
227
+ clearSelection("clear");
228
+ setHighlight(-1, "none");
229
+ }, [setInputValue, clearSelection, setHighlight]);
230
+ const selectedKey = !multiple && selectedItems[0] != null ? itemToKey(selectedItems[0]) : null;
231
+ const lastSelectedKeyRef = useRef(selectedKey);
232
+ useEffect(() => {
233
+ if (lastSelectedKeyRef.current === selectedKey) return;
234
+ lastSelectedKeyRef.current = selectedKey;
235
+ setInputValueState(selectedLabel);
236
+ }, [selectedKey]);
237
+ const [chips, setChips] = useState([]);
238
+ const registerChip = useCallback((entry) => {
239
+ setChips((current) => [...current, entry].sort(byDocumentOrder));
240
+ return () => setChips((current) => current.filter((c) => c !== entry));
241
+ }, []);
242
+ const [disabledKeys] = useState(() => /* @__PURE__ */ new Set());
243
+ const [insideRefs] = useState(() => /* @__PURE__ */ new Set());
244
+ const registerInside = useCallback(
245
+ (ref) => {
246
+ insideRefs.add(ref);
247
+ return () => {
248
+ insideRefs.delete(ref);
249
+ };
250
+ },
251
+ [insideRefs]
252
+ );
253
+ const [hasGroup, setHasGroup] = useState(false);
254
+ const registerGroup = useCallback(() => {
255
+ setHasGroup(true);
256
+ return () => setHasGroup(false);
257
+ }, []);
258
+ const inputRef = useRef(null);
259
+ const popupRef = useRef(null);
260
+ const { current: initial } = useRef({ value, inputValue });
261
+ useEffect(() => {
262
+ const owner = inputRef.current?.form;
263
+ if (!owner) return;
264
+ function onReset(event) {
265
+ queueMicrotask(() => {
266
+ if (event.defaultPrevented) return;
267
+ if (value !== initial.value) setValue(initial.value, "form-reset");
268
+ setInputValue(initial.inputValue, "form-reset");
269
+ });
270
+ }
271
+ owner.addEventListener("reset", onReset);
272
+ return () => owner.removeEventListener("reset", onReset);
273
+ }, [value, initial, setValue, setInputValue]);
274
+ const isInside = useCallback(
275
+ (node) => [inputRef, popupRef, ...insideRefs, ...chips.map((chip) => chip.ref)].some(
276
+ (ref) => ref.current?.contains(node) ?? false
277
+ ),
278
+ [insideRefs, chips]
279
+ );
280
+ const id = useId();
281
+ const context = useMemo(
282
+ () => ({
283
+ open,
284
+ multiple,
285
+ disabled,
286
+ inputValue,
287
+ loading,
288
+ filteredItems,
289
+ itemToString,
290
+ itemToKey,
291
+ indexByKey,
292
+ selectedKeys,
293
+ selectedItems,
294
+ disabledKeys,
295
+ highlight,
296
+ setHighlight,
297
+ setOpen,
298
+ setInputValue,
299
+ select,
300
+ remove,
301
+ clearSelection,
302
+ clear,
303
+ close,
304
+ registerChip,
305
+ chips,
306
+ registerInside,
307
+ isInside,
308
+ hasGroup,
309
+ registerGroup,
310
+ onLoadMore,
311
+ inputId: `${id}-input`,
312
+ listId: `${id}-list`,
313
+ optionId: (index) => `${id}-option-${index}`,
314
+ anchorName: anchorNameFor(id),
315
+ inputRef,
316
+ popupRef
317
+ }),
318
+ [
319
+ open,
320
+ multiple,
321
+ disabled,
322
+ inputValue,
323
+ loading,
324
+ filteredItems,
325
+ itemToString,
326
+ itemToKey,
327
+ indexByKey,
328
+ selectedKeys,
329
+ selectedItems,
330
+ disabledKeys,
331
+ highlight,
332
+ setHighlight,
333
+ setOpen,
334
+ setInputValue,
335
+ select,
336
+ remove,
337
+ clearSelection,
338
+ clear,
339
+ close,
340
+ registerChip,
341
+ chips,
342
+ registerInside,
343
+ isInside,
344
+ hasGroup,
345
+ registerGroup,
346
+ onLoadMore,
347
+ id
348
+ ]
349
+ );
350
+ return /* @__PURE__ */ jsxs(ComboboxRootContext.Provider, { value: context, children: [
351
+ children,
352
+ name !== void 0 && (multiple ? selectedItems : [selectedItems[0] ?? null]).map((item, index) => /* @__PURE__ */ jsx(
353
+ "input",
354
+ {
355
+ type: "hidden",
356
+ name,
357
+ form,
358
+ disabled,
359
+ value: item === null ? "" : itemToKey(item)
360
+ },
361
+ item === null ? index : itemToKey(item)
362
+ ))
363
+ ] });
364
+ }
365
+
366
+ // src/combobox/ComboboxInput.tsx
367
+ import { useSyncExternalStore } from "react";
368
+
369
+ // src/combobox/comboboxDataAttributes.ts
370
+ function comboboxDataAttributes({ open, disabled }) {
371
+ return {
372
+ "data-open": open ? "" : void 0,
373
+ "data-closed": open ? void 0 : "",
374
+ "data-disabled": disabled ? "" : void 0
375
+ };
376
+ }
377
+ function comboboxItemDataAttributes({
378
+ selected,
379
+ highlighted,
380
+ disabled
381
+ }) {
382
+ return {
383
+ "data-selected": selected ? "" : void 0,
384
+ "data-highlighted": highlighted ? "" : void 0,
385
+ "data-disabled": disabled ? "" : void 0
386
+ };
387
+ }
388
+ function comboboxClearDataAttributes(visible) {
389
+ return { "data-visible": visible ? "" : void 0 };
390
+ }
391
+
392
+ // src/combobox/ComboboxInput.tsx
393
+ function ComboboxInput({ className, render, ...rest }) {
394
+ const {
395
+ open,
396
+ multiple,
397
+ disabled,
398
+ inputValue,
399
+ filteredItems,
400
+ itemToKey,
401
+ selectedItems,
402
+ selectedKeys,
403
+ disabledKeys,
404
+ highlight,
405
+ setHighlight,
406
+ setOpen,
407
+ setInputValue,
408
+ select,
409
+ remove,
410
+ clearSelection,
411
+ close,
412
+ chips,
413
+ inputId,
414
+ listId,
415
+ optionId,
416
+ anchorName,
417
+ hasGroup,
418
+ inputRef,
419
+ popupRef
420
+ } = useComboboxRootContext("Input");
421
+ const highlighted = useSyncExternalStore(
422
+ highlight.subscribe,
423
+ () => highlight.get().index,
424
+ () => -1
425
+ );
426
+ function enabledIndices() {
427
+ const indices = [];
428
+ filteredItems.forEach((item, index) => {
429
+ if (!disabledKeys.has(itemToKey(item))) indices.push(index);
430
+ });
431
+ return indices;
432
+ }
433
+ function openingIndex(key, enabled) {
434
+ if (!multiple && selectedItems[0] != null) {
435
+ const selected = enabled.find((i) => selectedKeys.has(itemToKey(filteredItems[i])));
436
+ if (selected !== void 0) return selected;
437
+ }
438
+ return (key === "ArrowDown" ? enabled[0] : enabled.at(-1)) ?? -1;
439
+ }
440
+ function moveHighlight(key) {
441
+ const enabled = enabledIndices();
442
+ const next = rovingIndex(key, {
443
+ count: enabled.length,
444
+ current: enabled.indexOf(highlighted),
445
+ orientation: "vertical",
446
+ rtl: false
447
+ });
448
+ if (next !== null) setHighlight(enabled[next] ?? -1, "keyboard");
449
+ }
450
+ return useRender({
451
+ render,
452
+ defaultTagName: "input",
453
+ props: {
454
+ id: inputId,
455
+ type: "text",
456
+ role: "combobox",
457
+ autoComplete: "off",
458
+ "aria-autocomplete": "list",
459
+ "aria-expanded": open,
460
+ "aria-controls": listId,
461
+ "aria-activedescendant": open && highlighted >= 0 ? optionId(highlighted) : void 0,
462
+ disabled: disabled || void 0,
463
+ value: inputValue,
464
+ ...comboboxDataAttributes({ open, multiple, disabled }),
465
+ // The anchor, unless an InputGroup around it is.
466
+ style: hasGroup ? void 0 : { anchorName },
467
+ className,
468
+ ref: inputRef,
469
+ onChange(event) {
470
+ const text = event.target.value;
471
+ setInputValue(text, "input");
472
+ if (!multiple && text === "" && selectedItems.length > 0) clearSelection("input");
473
+ setOpen(true, "input");
474
+ },
475
+ onClick() {
476
+ if (!disabled) setOpen(true, "input-press");
477
+ },
478
+ onKeyDown(event) {
479
+ const { key } = event;
480
+ if (key === "ArrowDown" || key === "ArrowUp") {
481
+ event.preventDefault();
482
+ if (!open) {
483
+ setOpen(true, "keyboard");
484
+ if (!event.altKey) setHighlight(openingIndex(key, enabledIndices()), "keyboard");
485
+ return;
486
+ }
487
+ moveHighlight(key);
488
+ return;
489
+ }
490
+ if ((key === "Home" || key === "End") && open) {
491
+ event.preventDefault();
492
+ moveHighlight(key);
493
+ return;
494
+ }
495
+ if (key === "Enter") {
496
+ if (!open || highlighted < 0) return;
497
+ event.preventDefault();
498
+ const item = filteredItems[highlighted];
499
+ if (!disabledKeys.has(itemToKey(item))) select(item);
500
+ return;
501
+ }
502
+ if (key === "Escape") {
503
+ if (!open) return;
504
+ event.preventDefault();
505
+ close("escape");
506
+ return;
507
+ }
508
+ if (!multiple) return;
509
+ if (key === "Backspace" && inputValue === "" && selectedItems.length > 0) {
510
+ remove(selectedItems.at(-1), "chip-remove");
511
+ return;
512
+ }
513
+ const input = event.currentTarget;
514
+ if (key === "ArrowLeft" && chips.length > 0 && input.selectionStart === 0 && input.selectionEnd === 0) {
515
+ event.preventDefault();
516
+ chips.at(-1)?.ref.current?.focus();
517
+ }
518
+ },
519
+ onBlur(event) {
520
+ const next = event.relatedTarget;
521
+ if (next && popupRef.current?.contains(next)) return;
522
+ if (next && chips.some((chip) => chip.ref.current === next)) {
523
+ setOpen(false, "blur");
524
+ return;
525
+ }
526
+ close("blur");
527
+ }
528
+ },
529
+ consumerProps: rest
530
+ });
531
+ }
532
+
533
+ // src/combobox/ComboboxInputGroup.tsx
534
+ import { useLayoutEffect, useRef as useRef2 } from "react";
535
+ function ComboboxInputGroup({
536
+ className,
537
+ children,
538
+ render,
539
+ ...rest
540
+ }) {
541
+ const { open, multiple, disabled, anchorName, registerGroup, registerInside, inputRef } = useComboboxRootContext("InputGroup");
542
+ const elementRef = useRef2(null);
543
+ useLayoutEffect(() => registerGroup(), [registerGroup]);
544
+ useLayoutEffect(() => registerInside(elementRef), [registerInside]);
545
+ return useRender({
546
+ render,
547
+ defaultTagName: "div",
548
+ props: {
549
+ ...comboboxDataAttributes({ open, multiple, disabled }),
550
+ style: { anchorName },
551
+ className,
552
+ children,
553
+ ref: elementRef,
554
+ onMouseDown(event) {
555
+ if (event.target !== event.currentTarget || disabled) return;
556
+ event.preventDefault();
557
+ inputRef.current?.focus();
558
+ }
559
+ },
560
+ consumerProps: rest
561
+ });
562
+ }
563
+
564
+ // src/combobox/ComboboxTrigger.tsx
565
+ import { useLayoutEffect as useLayoutEffect2, useRef as useRef3 } from "react";
566
+ function ComboboxTrigger({ className, children, render, ...rest }) {
567
+ const { open, multiple, disabled, setOpen, close, listId, inputRef, registerInside } = useComboboxRootContext("Trigger");
568
+ const elementRef = useRef3(null);
569
+ useLayoutEffect2(() => registerInside(elementRef), [registerInside]);
570
+ return useRender({
571
+ render,
572
+ defaultTagName: "button",
573
+ props: {
574
+ type: "button",
575
+ tabIndex: -1,
576
+ "aria-expanded": open,
577
+ "aria-controls": listId,
578
+ disabled: disabled || void 0,
579
+ ...comboboxDataAttributes({ open, multiple, disabled }),
580
+ className,
581
+ children,
582
+ ref: elementRef,
583
+ onMouseDown(event) {
584
+ event.preventDefault();
585
+ },
586
+ onClick() {
587
+ if (open) close("trigger-press");
588
+ else setOpen(true, "trigger-press");
589
+ inputRef.current?.focus();
590
+ }
591
+ },
592
+ consumerProps: rest
593
+ });
594
+ }
595
+
596
+ // src/combobox/ComboboxClear.tsx
597
+ import { useLayoutEffect as useLayoutEffect3, useRef as useRef4 } from "react";
598
+ function ComboboxClear({ className, children, render, ...rest }) {
599
+ const { disabled, selectedItems, clear, inputRef, registerInside } = useComboboxRootContext("Clear");
600
+ const elementRef = useRef4(null);
601
+ useLayoutEffect3(() => registerInside(elementRef), [registerInside]);
602
+ return useRender({
603
+ render,
604
+ defaultTagName: "button",
605
+ props: {
606
+ type: "button",
607
+ tabIndex: -1,
608
+ disabled: disabled || void 0,
609
+ ...comboboxClearDataAttributes(selectedItems.length > 0),
610
+ className,
611
+ children,
612
+ ref: elementRef,
613
+ onMouseDown(event) {
614
+ event.preventDefault();
615
+ },
616
+ onClick() {
617
+ if (disabled) return;
618
+ clear();
619
+ inputRef.current?.focus();
620
+ }
621
+ },
622
+ consumerProps: rest
623
+ });
624
+ }
625
+
626
+ // src/combobox/ComboboxPopup.tsx
627
+ import { useEffect as useEffect2, useReducer } from "react";
628
+ function ComboboxPopup({
629
+ side = "bottom",
630
+ align = "start",
631
+ className,
632
+ children,
633
+ render,
634
+ ...rest
635
+ }) {
636
+ const { open, multiple, disabled, setOpen, close, isInside, anchorName, inputRef, popupRef } = useComboboxRootContext("Popup");
637
+ useEffect2(() => {
638
+ if (!open) return;
639
+ function onPointerDown(event) {
640
+ if (event.target instanceof Node && isInside(event.target)) return;
641
+ close("outside-press");
642
+ }
643
+ document.addEventListener("pointerdown", onPointerDown, true);
644
+ return () => document.removeEventListener("pointerdown", onPointerDown, true);
645
+ }, [open, isInside, close]);
646
+ const [, resync] = useReducer((n) => n + 1, 0);
647
+ useEffect2(() => {
648
+ const popup = popupRef.current;
649
+ if (!popup || typeof popup.showPopover !== "function") return;
650
+ const shown = popup.matches(":popover-open");
651
+ if (open && !shown) {
652
+ popup.showPopover({ source: inputRef.current ?? void 0 });
653
+ if (!popup.matches(":popover-open")) setOpen(false, "outside-press");
654
+ } else if (!open && shown) {
655
+ popup.hidePopover();
656
+ }
657
+ });
658
+ return useRender({
659
+ render,
660
+ defaultTagName: "div",
661
+ props: {
662
+ popover: "manual",
663
+ ...anchoredDataAttributes(side, align),
664
+ ...comboboxDataAttributes({ open, multiple, disabled }),
665
+ style: anchoredPopupStyle(anchorName, side, align),
666
+ className,
667
+ children,
668
+ ref: popupRef,
669
+ onMouseDown(event) {
670
+ event.preventDefault();
671
+ },
672
+ onToggle(event) {
673
+ if (event.target !== event.currentTarget) return;
674
+ if (event.nativeEvent.newState !== "closed") return;
675
+ close("outside-press");
676
+ resync();
677
+ }
678
+ },
679
+ consumerProps: rest
680
+ });
681
+ }
682
+
683
+ // src/combobox/ComboboxList.tsx
684
+ import { useEffect as useEffect3, useRef as useRef5 } from "react";
685
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
686
+ function ComboboxList({
687
+ className,
688
+ children,
689
+ render,
690
+ ...rest
691
+ }) {
692
+ const { multiple, loading, filteredItems, onLoadMore, listId } = useComboboxRootContext("List");
693
+ const content = typeof children === "function" ? filteredItems.map((item, index) => children(item, index)) : children;
694
+ return useRender({
695
+ render,
696
+ defaultTagName: "div",
697
+ props: {
698
+ id: listId,
699
+ role: "listbox",
700
+ "aria-multiselectable": multiple || void 0,
701
+ "aria-busy": loading || void 0,
702
+ className,
703
+ children: /* @__PURE__ */ jsxs2(Fragment, { children: [
704
+ content,
705
+ onLoadMore && /* @__PURE__ */ jsx2(LoadMoreSentinel, {})
706
+ ] })
707
+ },
708
+ consumerProps: rest
709
+ });
710
+ }
711
+ function LoadMoreSentinel() {
712
+ const { open, loading, filteredItems, onLoadMore } = useComboboxRootContext("List");
713
+ const ref = useRef5(null);
714
+ const onLoadMoreRef = useRef5(onLoadMore);
715
+ onLoadMoreRef.current = onLoadMore;
716
+ useEffect3(() => {
717
+ const element = ref.current;
718
+ if (!element || !open || loading || typeof IntersectionObserver === "undefined") return;
719
+ const observer = new IntersectionObserver((entries) => {
720
+ if (entries.some((entry) => entry.isIntersecting)) onLoadMoreRef.current?.();
721
+ });
722
+ observer.observe(element);
723
+ return () => observer.disconnect();
724
+ }, [open, loading, filteredItems.length]);
725
+ return /* @__PURE__ */ jsx2("div", { ref, "aria-hidden": "true", "data-load-more": "", style: { blockSize: 1 } });
726
+ }
727
+
728
+ // src/combobox/ComboboxItem.tsx
729
+ import { useEffect as useEffect4, useLayoutEffect as useLayoutEffect4, useRef as useRef6, useSyncExternalStore as useSyncExternalStore2 } from "react";
730
+ function ComboboxItem({
731
+ value,
732
+ disabled = false,
733
+ className,
734
+ children,
735
+ render,
736
+ ...rest
737
+ }) {
738
+ const {
739
+ itemToKey,
740
+ indexByKey,
741
+ selectedKeys,
742
+ disabledKeys,
743
+ highlight,
744
+ setHighlight,
745
+ select,
746
+ optionId
747
+ } = useComboboxRootContext("Item");
748
+ const key = itemToKey(value);
749
+ const index = indexByKey.get(key) ?? -1;
750
+ const selected = selectedKeys.has(key);
751
+ const highlighted = useSyncExternalStore2(
752
+ highlight.subscribe,
753
+ () => index >= 0 && highlight.get().index === index,
754
+ () => false
755
+ );
756
+ const elementRef = useRef6(null);
757
+ useLayoutEffect4(() => {
758
+ if (!disabled) return;
759
+ disabledKeys.add(key);
760
+ return () => {
761
+ disabledKeys.delete(key);
762
+ };
763
+ }, [disabled, disabledKeys, key]);
764
+ useEffect4(() => {
765
+ if (highlighted && highlight.get().reason === "keyboard") {
766
+ elementRef.current?.scrollIntoView?.({ block: "nearest" });
767
+ }
768
+ }, [highlighted, highlight]);
769
+ return useRender({
770
+ render,
771
+ defaultTagName: "div",
772
+ props: {
773
+ id: index >= 0 ? optionId(index) : void 0,
774
+ role: "option",
775
+ "aria-selected": selected,
776
+ "aria-disabled": disabled || void 0,
777
+ ...comboboxItemDataAttributes({ selected, highlighted, disabled }),
778
+ className,
779
+ children,
780
+ ref: elementRef,
781
+ onMouseDown(event) {
782
+ event.preventDefault();
783
+ },
784
+ onPointerMove() {
785
+ if (!disabled && !highlighted && index >= 0) setHighlight(index, "pointer");
786
+ },
787
+ onClick() {
788
+ if (!disabled) select(value);
789
+ }
790
+ },
791
+ consumerProps: rest
792
+ });
793
+ }
794
+
795
+ // src/combobox/ComboboxEmpty.tsx
796
+ function ComboboxEmpty({ className, children, render, ...rest }) {
797
+ const { filteredItems, loading } = useComboboxRootContext("Empty");
798
+ const element = useRender({
799
+ render,
800
+ defaultTagName: "div",
801
+ props: { className, children },
802
+ consumerProps: rest
803
+ });
804
+ return filteredItems.length === 0 && !loading ? element : null;
805
+ }
806
+
807
+ // src/combobox/ComboboxStatus.tsx
808
+ function ComboboxStatus({ className, children, render, ...rest }) {
809
+ useComboboxRootContext("Status");
810
+ return useRender({
811
+ render,
812
+ defaultTagName: "div",
813
+ props: { role: "status", "aria-live": "polite", "aria-atomic": true, className, children },
814
+ consumerProps: rest
815
+ });
816
+ }
817
+
818
+ // 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);
822
+ function useComboboxChipContext() {
823
+ const context = useContext2(ComboboxChipContext);
824
+ if (context === null) {
825
+ throw new Error("<Combobox.ChipRemove> must be rendered inside <Combobox.Chip>.");
826
+ }
827
+ return context;
828
+ }
829
+ function ComboboxChip({
830
+ value,
831
+ className,
832
+ children,
833
+ render,
834
+ ...rest
835
+ }) {
836
+ const { disabled, itemToKey, remove, registerChip, chips, inputRef } = useComboboxRootContext("Chip");
837
+ const key = itemToKey(value);
838
+ const elementRef = useRef7(null);
839
+ useLayoutEffect5(() => registerChip({ key, ref: elementRef }), [registerChip, key]);
840
+ const chipContext = useMemo2(() => ({ value }), [value]);
841
+ function focusAt(index) {
842
+ const chip = chips[index];
843
+ if (chip) chip.ref.current?.focus();
844
+ else inputRef.current?.focus();
845
+ }
846
+ const element = useRender({
847
+ render,
848
+ defaultTagName: "div",
849
+ props: {
850
+ tabIndex: -1,
851
+ "data-disabled": disabled ? "" : void 0,
852
+ className,
853
+ children,
854
+ ref: elementRef,
855
+ onKeyDown(event) {
856
+ const index = chips.findIndex((chip) => chip.ref.current === elementRef.current);
857
+ switch (event.key) {
858
+ case "ArrowLeft":
859
+ event.preventDefault();
860
+ if (index > 0) focusAt(index - 1);
861
+ return;
862
+ case "ArrowRight":
863
+ event.preventDefault();
864
+ focusAt(index + 1);
865
+ return;
866
+ case "Backspace":
867
+ case "Delete":
868
+ event.preventDefault();
869
+ if (disabled) return;
870
+ remove(value, "chip-remove");
871
+ focusAt(index + 1 < chips.length ? index + 1 : chips.length);
872
+ return;
873
+ case "Escape":
874
+ inputRef.current?.focus();
875
+ return;
876
+ }
877
+ }
878
+ },
879
+ consumerProps: rest
880
+ });
881
+ return /* @__PURE__ */ jsx3(ComboboxChipContext.Provider, { value: chipContext, children: element });
882
+ }
883
+
884
+ // src/combobox/ComboboxChipRemove.tsx
885
+ function ComboboxChipRemove({
886
+ className,
887
+ children,
888
+ render,
889
+ ...rest
890
+ }) {
891
+ const { disabled, remove, inputRef } = useComboboxRootContext("ChipRemove");
892
+ const { value } = useComboboxChipContext();
893
+ return useRender({
894
+ render,
895
+ defaultTagName: "button",
896
+ props: {
897
+ type: "button",
898
+ tabIndex: -1,
899
+ disabled: disabled || void 0,
900
+ className,
901
+ children,
902
+ onMouseDown(event) {
903
+ event.preventDefault();
904
+ },
905
+ onClick() {
906
+ if (disabled) return;
907
+ remove(value, "chip-remove");
908
+ inputRef.current?.focus();
909
+ }
910
+ },
911
+ consumerProps: rest
912
+ });
913
+ }
914
+
915
+ // src/combobox/ComboboxValue.tsx
916
+ import { Fragment as Fragment2, jsx as jsx4 } from "react/jsx-runtime";
917
+ function ComboboxValue({ children }) {
918
+ const { multiple, selectedItems, itemToString } = useComboboxRootContext("Value");
919
+ const value = multiple ? selectedItems : selectedItems[0] ?? null;
920
+ return /* @__PURE__ */ jsx4(Fragment2, { children: children(value, { itemToString }) });
921
+ }
922
+ export {
923
+ index_parts_exports as Combobox,
924
+ defaultFilter
925
+ };