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