@kenos-ui/react-combobox 0.1.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/index.js ADDED
@@ -0,0 +1,753 @@
1
+ import { createContext, useSyncExternalStore, useCallback, useContext, useId, useMemo, useRef, useEffect, useLayoutEffect, useState } from 'react';
2
+ import { restoreFocus, useListNavigation, useSelectCollection, useFloating, usePresence, useClickOutside, useEscapeKey, useFocusTrap } from '@kenos-ui/utils';
3
+ import { jsx } from 'react/jsx-runtime';
4
+
5
+ var __defProp = Object.defineProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/index.parts.ts
12
+ var index_parts_exports = {};
13
+ __export(index_parts_exports, {
14
+ Clear: () => Clear,
15
+ Content: () => Content,
16
+ Empty: () => Empty,
17
+ Input: () => Input,
18
+ Item: () => Item,
19
+ ItemText: () => ItemText,
20
+ Label: () => Label,
21
+ List: () => List,
22
+ Root: () => Root,
23
+ Trigger: () => Trigger
24
+ });
25
+ var ComboboxStore = class {
26
+ state;
27
+ listeners = /* @__PURE__ */ new Set();
28
+ constructor(initial = {}) {
29
+ this.state = {
30
+ open: false,
31
+ openSource: null,
32
+ value: null,
33
+ inputValue: "",
34
+ highlightedValue: null,
35
+ items: /* @__PURE__ */ new Map(),
36
+ ...initial
37
+ };
38
+ }
39
+ getState() {
40
+ return this.state;
41
+ }
42
+ subscribe(listener) {
43
+ this.listeners.add(listener);
44
+ return () => this.listeners.delete(listener);
45
+ }
46
+ notify() {
47
+ for (const listener of this.listeners) {
48
+ listener();
49
+ }
50
+ }
51
+ // ── Mutations ────────────────────────────────────────────────────────────
52
+ setOpen(open, source = null) {
53
+ if (this.state.open === open) return;
54
+ this.state = {
55
+ ...this.state,
56
+ open,
57
+ openSource: open ? source : null,
58
+ highlightedValue: open ? this.state.highlightedValue : null
59
+ };
60
+ this.notify();
61
+ }
62
+ setValue(value) {
63
+ if (this.state.value === value) return;
64
+ this.state = { ...this.state, value };
65
+ this.notify();
66
+ }
67
+ setInputValue(inputValue) {
68
+ if (this.state.inputValue === inputValue) return;
69
+ this.state = { ...this.state, inputValue };
70
+ this.notify();
71
+ }
72
+ clearValue() {
73
+ if (this.state.value === null && this.state.inputValue === "") return;
74
+ this.state = { ...this.state, value: null, inputValue: "" };
75
+ this.notify();
76
+ }
77
+ isSelected(value, comparator) {
78
+ const current = this.state.value;
79
+ return typeof current === "string" && comparator(current, value);
80
+ }
81
+ setHighlightedValue(value) {
82
+ if (this.state.highlightedValue === value) return;
83
+ this.state = { ...this.state, highlightedValue: value };
84
+ this.notify();
85
+ }
86
+ registerItem(record) {
87
+ const next = new Map(this.state.items);
88
+ next.set(record.value, record);
89
+ this.state = { ...this.state, items: next };
90
+ this.notify();
91
+ }
92
+ unregisterItem(value) {
93
+ if (!this.state.items.has(value)) return;
94
+ const next = new Map(this.state.items);
95
+ next.delete(value);
96
+ this.state = { ...this.state, items: next };
97
+ this.notify();
98
+ }
99
+ updateItemRef(value, ref) {
100
+ const item = this.state.items.get(value);
101
+ if (!item || item.ref === ref) return;
102
+ const next = new Map(this.state.items);
103
+ next.set(value, { ...item, ref });
104
+ this.state = { ...this.state, items: next };
105
+ }
106
+ };
107
+ function useComboboxStore(store, selector) {
108
+ return useSyncExternalStore(
109
+ useCallback((cb) => store.subscribe(cb), [store]),
110
+ () => selector(store.getState()),
111
+ () => selector(store.getState())
112
+ );
113
+ }
114
+ var ComboboxContext = createContext(null);
115
+ function useComboboxContext() {
116
+ const ctx = useContext(ComboboxContext);
117
+ if (!ctx) {
118
+ throw new Error("Combobox compound components must be rendered inside <Combobox.Root>.");
119
+ }
120
+ return ctx;
121
+ }
122
+ var defaultIsItemEqualToValue = (a, b) => a === b;
123
+ function resolveLabel(value, items, staticItems) {
124
+ if (value == null) return "";
125
+ return items.get(value)?.label ?? staticItems[value] ?? value;
126
+ }
127
+ function Root(props) {
128
+ const {
129
+ children,
130
+ value,
131
+ defaultValue,
132
+ onValueChange,
133
+ inputValue,
134
+ defaultInputValue,
135
+ onInputValueChange,
136
+ open,
137
+ defaultOpen,
138
+ onOpenChange,
139
+ onOpenChangeComplete,
140
+ disabled = false,
141
+ required = false,
142
+ readOnly = false,
143
+ modal = false,
144
+ id,
145
+ items = {},
146
+ isItemEqualToValue = defaultIsItemEqualToValue,
147
+ filter
148
+ } = props;
149
+ const uid = useId();
150
+ const prefix = id ?? `cbx-${uid.replace(/:/g, "")}`;
151
+ const ids = useMemo(
152
+ () => ({
153
+ root: prefix,
154
+ label: `${prefix}-label`,
155
+ input: `${prefix}-input`,
156
+ trigger: `${prefix}-trigger`,
157
+ content: `${prefix}-content`
158
+ }),
159
+ [prefix]
160
+ );
161
+ const inputRef = useRef(null);
162
+ const triggerRef = useRef(null);
163
+ const contentRef = useRef(null);
164
+ const listRef = useRef(null);
165
+ const isControlledValue = value !== void 0;
166
+ const isControlledOpen = open !== void 0;
167
+ const isControlledInputValue = inputValue !== void 0;
168
+ const initialValue = isControlledValue ? value ?? null : defaultValue ?? null;
169
+ const initialInputValue = isControlledInputValue ? inputValue ?? "" : defaultInputValue ?? resolveLabel(initialValue, /* @__PURE__ */ new Map(), items);
170
+ const storeRef = useRef(null);
171
+ if (!storeRef.current) {
172
+ storeRef.current = new ComboboxStore({
173
+ value: initialValue,
174
+ inputValue: initialInputValue,
175
+ open: isControlledOpen ? open ?? false : defaultOpen ?? false
176
+ });
177
+ }
178
+ const store = storeRef.current;
179
+ const prevControlledValue = useRef(value);
180
+ useEffect(() => {
181
+ if (!isControlledValue) return;
182
+ if (value === prevControlledValue.current) return;
183
+ prevControlledValue.current = value;
184
+ store.setValue(typeof value === "string" ? value : null);
185
+ }, [isControlledValue, value, store]);
186
+ const prevControlledInputValue = useRef(inputValue);
187
+ useEffect(() => {
188
+ if (!isControlledInputValue) return;
189
+ if (inputValue === prevControlledInputValue.current) return;
190
+ prevControlledInputValue.current = inputValue;
191
+ store.setInputValue(inputValue ?? "");
192
+ }, [isControlledInputValue, inputValue, store]);
193
+ const prevControlledOpen = useRef(open);
194
+ useEffect(() => {
195
+ if (!isControlledOpen) return;
196
+ if (open === prevControlledOpen.current) return;
197
+ prevControlledOpen.current = open;
198
+ store.setOpen(open ?? false);
199
+ }, [isControlledOpen, open, store]);
200
+ const onValueChangeRef = useRef(onValueChange);
201
+ onValueChangeRef.current = onValueChange;
202
+ const prevStoreValue = useRef(store.getState().value);
203
+ useEffect(() => {
204
+ return store.subscribe(() => {
205
+ const state = store.getState();
206
+ if (state.value !== prevStoreValue.current) {
207
+ prevStoreValue.current = state.value;
208
+ onValueChangeRef.current?.(
209
+ typeof state.value === "string" ? state.value : null
210
+ );
211
+ }
212
+ });
213
+ }, [store]);
214
+ const onInputValueChangeRef = useRef(onInputValueChange);
215
+ onInputValueChangeRef.current = onInputValueChange;
216
+ const prevStoreInputValue = useRef(store.getState().inputValue);
217
+ useEffect(() => {
218
+ return store.subscribe(() => {
219
+ const state = store.getState();
220
+ if (state.inputValue !== prevStoreInputValue.current) {
221
+ prevStoreInputValue.current = state.inputValue;
222
+ onInputValueChangeRef.current?.(state.inputValue);
223
+ }
224
+ });
225
+ }, [store]);
226
+ const onOpenChangeRef = useRef(onOpenChange);
227
+ onOpenChangeRef.current = onOpenChange;
228
+ const prevStoreOpen = useRef(store.getState().open);
229
+ useEffect(() => {
230
+ return store.subscribe(() => {
231
+ const state = store.getState();
232
+ if (state.open !== prevStoreOpen.current) {
233
+ prevStoreOpen.current = state.open;
234
+ onOpenChangeRef.current?.(state.open);
235
+ }
236
+ });
237
+ }, [store]);
238
+ const close = useCallback(() => {
239
+ const state = store.getState();
240
+ if (!state.open) return;
241
+ store.setOpen(false);
242
+ restoreFocus({
243
+ openSource: state.openSource === "trigger" ? "trigger" : "input",
244
+ trigger: inputRef.current ?? triggerRef.current
245
+ });
246
+ }, [store]);
247
+ const selectValue = useCallback(
248
+ (itemValue) => {
249
+ const item = store.getState().items.get(itemValue);
250
+ const label = item?.label ?? items[itemValue] ?? itemValue;
251
+ store.setValue(itemValue);
252
+ store.setInputValue(label);
253
+ close();
254
+ },
255
+ [store, close, items]
256
+ );
257
+ const clearValue = useCallback(() => {
258
+ store.clearValue();
259
+ }, [store]);
260
+ const config = useMemo(
261
+ () => ({
262
+ disabled,
263
+ required,
264
+ readOnly,
265
+ modal,
266
+ items,
267
+ isItemEqualToValue,
268
+ filter: filter ?? ((item, query) => {
269
+ const normalized = query.trim().toLowerCase();
270
+ if (!normalized) return true;
271
+ return item.textValue.toLowerCase().includes(normalized) || item.label.toLowerCase().includes(normalized);
272
+ })
273
+ }),
274
+ [disabled, required, readOnly, modal, items, isItemEqualToValue, filter]
275
+ );
276
+ const ctx = useMemo(
277
+ () => ({
278
+ store,
279
+ ids,
280
+ refs: { inputRef, triggerRef, contentRef, listRef },
281
+ config,
282
+ isControlledValue,
283
+ isControlledOpen,
284
+ isControlledInputValue,
285
+ onOpenChangeComplete,
286
+ close,
287
+ selectValue,
288
+ clearValue
289
+ }),
290
+ [
291
+ store,
292
+ ids,
293
+ config,
294
+ isControlledValue,
295
+ isControlledOpen,
296
+ isControlledInputValue,
297
+ onOpenChangeComplete,
298
+ close,
299
+ selectValue,
300
+ clearValue
301
+ ]
302
+ );
303
+ return /* @__PURE__ */ jsx(ComboboxContext.Provider, { value: ctx, children });
304
+ }
305
+ function Label({ children, ...props }) {
306
+ const { ids } = useComboboxContext();
307
+ return /* @__PURE__ */ jsx("label", { id: ids.label, htmlFor: ids.input, ...props, children });
308
+ }
309
+ function Input({
310
+ onChange,
311
+ onFocus,
312
+ onKeyDown,
313
+ disabled,
314
+ readOnly,
315
+ ...props
316
+ }) {
317
+ const { store, ids, refs, config, close, selectValue } = useComboboxContext();
318
+ const open = useComboboxStore(store, (s) => s.open);
319
+ const inputValue = useComboboxStore(store, (s) => s.inputValue);
320
+ const highlightedValue = useComboboxStore(store, (s) => s.highlightedValue);
321
+ const items = useComboboxStore(store, (s) => s.items);
322
+ const isDisabled = disabled ?? config.disabled;
323
+ const isReadOnly = readOnly ?? config.readOnly;
324
+ const filteredItems = Array.from(items.values()).filter((item) => config.filter(item, inputValue)).map((item) => ({
325
+ value: item.value,
326
+ disabled: item.disabled
327
+ }));
328
+ const { onKeyDown: onNavKeyDown } = useListNavigation({
329
+ enabled: open && !isDisabled && !isReadOnly,
330
+ items: filteredItems,
331
+ highlightedValue,
332
+ onHighlight: (v) => store.setHighlightedValue(v),
333
+ loop: true
334
+ });
335
+ const activeDescendantId = open && highlightedValue != null ? `${ids.content}-opt-${highlightedValue}` : void 0;
336
+ const handleKeyDown = useCallback(
337
+ (e) => {
338
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
339
+ if (!open) {
340
+ store.setOpen(true, "input");
341
+ }
342
+ onNavKeyDown(e);
343
+ onKeyDown?.(e);
344
+ return;
345
+ }
346
+ if (e.key === "Enter" && open && highlightedValue != null) {
347
+ const item = items.get(highlightedValue);
348
+ if (item && !item.disabled) {
349
+ e.preventDefault();
350
+ selectValue(highlightedValue);
351
+ }
352
+ onKeyDown?.(e);
353
+ return;
354
+ }
355
+ if (e.key === "Escape" && open) {
356
+ e.preventDefault();
357
+ e.stopPropagation();
358
+ close();
359
+ onKeyDown?.(e);
360
+ return;
361
+ }
362
+ onKeyDown?.(e);
363
+ },
364
+ [
365
+ open,
366
+ highlightedValue,
367
+ items,
368
+ store,
369
+ onNavKeyDown,
370
+ selectValue,
371
+ close,
372
+ onKeyDown
373
+ ]
374
+ );
375
+ return /* @__PURE__ */ jsx(
376
+ "input",
377
+ {
378
+ ref: refs.inputRef,
379
+ type: "text",
380
+ id: ids.input,
381
+ role: "combobox",
382
+ "aria-haspopup": "listbox",
383
+ "aria-expanded": open,
384
+ "aria-controls": open ? ids.content : void 0,
385
+ "aria-autocomplete": "list",
386
+ "aria-labelledby": ids.label ? `${ids.label} ${ids.input}` : void 0,
387
+ "aria-activedescendant": activeDescendantId,
388
+ "aria-disabled": isDisabled || isReadOnly || void 0,
389
+ autoComplete: "off",
390
+ "data-disabled": isDisabled || isReadOnly ? "true" : void 0,
391
+ "data-open": open ? "true" : void 0,
392
+ "data-state": open ? "open" : "closed",
393
+ disabled: isDisabled,
394
+ readOnly: isReadOnly,
395
+ value: inputValue,
396
+ onChange: (e) => {
397
+ if (isDisabled || isReadOnly) return;
398
+ store.setInputValue(e.target.value);
399
+ store.setOpen(true, "input");
400
+ onChange?.(e);
401
+ },
402
+ onFocus: (e) => {
403
+ if (!isDisabled && !isReadOnly) {
404
+ store.setOpen(true, "input");
405
+ }
406
+ onFocus?.(e);
407
+ },
408
+ onKeyDown: handleKeyDown,
409
+ ...props
410
+ }
411
+ );
412
+ }
413
+ function Trigger({ children, onClick, disabled, ...props }) {
414
+ const { store, ids, refs, config } = useComboboxContext();
415
+ const open = useComboboxStore(store, (s) => s.open);
416
+ const isDisabled = disabled ?? config.disabled;
417
+ const isReadOnly = config.readOnly;
418
+ return /* @__PURE__ */ jsx(
419
+ "button",
420
+ {
421
+ ref: refs.triggerRef,
422
+ type: "button",
423
+ id: ids.trigger,
424
+ "aria-haspopup": "listbox",
425
+ "aria-expanded": open,
426
+ "aria-controls": open ? ids.content : void 0,
427
+ "aria-label": "Toggle suggestions",
428
+ "aria-disabled": isDisabled || isReadOnly || void 0,
429
+ "data-disabled": isDisabled || isReadOnly ? "true" : void 0,
430
+ "data-open": open ? "true" : void 0,
431
+ "data-state": open ? "open" : "closed",
432
+ disabled: isDisabled || isReadOnly,
433
+ tabIndex: -1,
434
+ onClick: (e) => {
435
+ if (isDisabled || isReadOnly) return;
436
+ store.setOpen(!open, "trigger");
437
+ refs.inputRef.current?.focus();
438
+ onClick?.(e);
439
+ },
440
+ ...props,
441
+ children
442
+ }
443
+ );
444
+ }
445
+ var ComboboxCollectionContext = createContext(
446
+ null
447
+ );
448
+ function useComboboxCollection() {
449
+ const ctx = useContext(ComboboxCollectionContext);
450
+ if (!ctx) {
451
+ throw new Error(
452
+ "Combobox collection context is missing. Render inside <Combobox.Content>."
453
+ );
454
+ }
455
+ return ctx;
456
+ }
457
+ function Content({
458
+ children,
459
+ forceMount,
460
+ side = "bottom",
461
+ align = "start",
462
+ sideOffset = 4,
463
+ alignOffset = 0,
464
+ avoidCollisions = true,
465
+ collisionPadding = 8,
466
+ sameWidth = false,
467
+ lazyMount = true,
468
+ unmountOnExit = false,
469
+ onOpenChangeComplete: onOpenChangeCompleteProp,
470
+ style,
471
+ onKeyDown,
472
+ ...props
473
+ }) {
474
+ const {
475
+ store,
476
+ ids,
477
+ refs,
478
+ config,
479
+ close,
480
+ selectValue,
481
+ onOpenChangeComplete: onOpenChangeCompleteRoot
482
+ } = useComboboxContext();
483
+ const open = useComboboxStore(store, (s) => s.open);
484
+ const highlightedValue = useComboboxStore(store, (s) => s.highlightedValue);
485
+ const allItems = useComboboxStore(store, (s) => s.items);
486
+ const inputValue = useComboboxStore(store, (s) => s.inputValue);
487
+ const onOpenChangeComplete = onOpenChangeCompleteProp ?? onOpenChangeCompleteRoot;
488
+ const collection = useSelectCollection({
489
+ items: allItems,
490
+ inputValue,
491
+ filter: config.filter
492
+ });
493
+ const collectionValue = useMemo(
494
+ () => ({
495
+ filteredItems: collection.items,
496
+ enabledItems: collection.enabledItems,
497
+ isEmpty: collection.isEmpty
498
+ }),
499
+ [collection.items, collection.enabledItems, collection.isEmpty]
500
+ );
501
+ const navItems = collection.enabledItems.map((item) => ({
502
+ value: item.value,
503
+ disabled: item.disabled
504
+ }));
505
+ const { setReference, setFloating, floatingStyles, isPositioned } = useFloating({
506
+ open,
507
+ side,
508
+ align,
509
+ sideOffset,
510
+ alignOffset,
511
+ avoidCollisions,
512
+ collisionPadding,
513
+ sameWidth
514
+ });
515
+ useLayoutEffect(() => {
516
+ if (!open) return;
517
+ setReference(refs.inputRef.current);
518
+ }, [open, refs.inputRef, setReference]);
519
+ const mergedRef = useCallback(
520
+ (node) => {
521
+ refs.contentRef.current = node;
522
+ setFloating(node);
523
+ },
524
+ [refs.contentRef, setFloating]
525
+ );
526
+ const { present } = usePresence({
527
+ open,
528
+ lazyMount,
529
+ unmountOnExit,
530
+ onOpenChangeComplete
531
+ });
532
+ useClickOutside([refs.contentRef, refs.inputRef, refs.triggerRef], close, open);
533
+ useEscapeKey({
534
+ enabled: open,
535
+ stopPropagation: true,
536
+ onEscape: close
537
+ });
538
+ useFocusTrap(refs.contentRef, open && config.modal);
539
+ const { onKeyDown: onNavKeyDown } = useListNavigation({
540
+ enabled: open && !config.disabled && !config.readOnly,
541
+ items: navItems,
542
+ highlightedValue,
543
+ onHighlight: (v) => store.setHighlightedValue(v),
544
+ loop: true
545
+ });
546
+ const handleKeyDown = useCallback(
547
+ (e) => {
548
+ if (e.key === "Enter" || e.key === " ") {
549
+ if (highlightedValue != null) {
550
+ e.preventDefault();
551
+ const item = allItems.get(highlightedValue);
552
+ if (item && !item.disabled) {
553
+ selectValue(highlightedValue);
554
+ }
555
+ }
556
+ return;
557
+ }
558
+ onNavKeyDown(e);
559
+ onKeyDown?.(e);
560
+ },
561
+ [highlightedValue, allItems, selectValue, onNavKeyDown, onKeyDown]
562
+ );
563
+ const [transitionsReady, setTransitionsReady] = useState(false);
564
+ useEffect(() => {
565
+ if (!open || !isPositioned) {
566
+ setTransitionsReady(false);
567
+ return;
568
+ }
569
+ const raf = requestAnimationFrame(() => setTransitionsReady(true));
570
+ return () => cancelAnimationFrame(raf);
571
+ }, [open, isPositioned]);
572
+ useEffect(() => {
573
+ if (!open) return;
574
+ const state = store.getState();
575
+ if (state.highlightedValue == null) {
576
+ const first = navItems.find((i) => !i.disabled);
577
+ if (first) store.setHighlightedValue(first.value);
578
+ }
579
+ }, [open, inputValue, navItems.length]);
580
+ useEffect(() => {
581
+ if (!open || !highlightedValue) return;
582
+ const item = allItems.get(highlightedValue);
583
+ if (typeof item?.ref?.scrollIntoView === "function") {
584
+ item.ref.scrollIntoView({ block: "nearest" });
585
+ }
586
+ }, [highlightedValue, allItems, open]);
587
+ if (!present && !forceMount) return null;
588
+ return /* @__PURE__ */ jsx(ComboboxCollectionContext.Provider, { value: collectionValue, children: /* @__PURE__ */ jsx(
589
+ "div",
590
+ {
591
+ ref: mergedRef,
592
+ id: ids.content,
593
+ "data-state": open ? "open" : "closed",
594
+ "data-open": open ? "true" : void 0,
595
+ "data-empty": collection.isEmpty ? "true" : void 0,
596
+ "aria-modal": config.modal ? "true" : void 0,
597
+ tabIndex: -1,
598
+ style: {
599
+ ...floatingStyles,
600
+ ...!open ? { display: "none" } : void 0,
601
+ ...open && !isPositioned ? { opacity: 0, pointerEvents: "none" } : void 0,
602
+ ...open && !transitionsReady ? { transition: "none" } : void 0,
603
+ ...style
604
+ },
605
+ onKeyDown: handleKeyDown,
606
+ ...props,
607
+ children
608
+ }
609
+ ) });
610
+ }
611
+ function List({ children, ...props }) {
612
+ const { ids, refs } = useComboboxContext();
613
+ return /* @__PURE__ */ jsx(
614
+ "ul",
615
+ {
616
+ ref: refs.listRef,
617
+ role: "listbox",
618
+ id: `${ids.content}-list`,
619
+ "aria-labelledby": ids.label,
620
+ ...props,
621
+ children
622
+ }
623
+ );
624
+ }
625
+ Item.displayName = "Combobox.Item";
626
+ function Item({
627
+ value,
628
+ disabled = false,
629
+ textValue,
630
+ children,
631
+ onClick,
632
+ onPointerMove,
633
+ style,
634
+ ...props
635
+ }) {
636
+ const { store, ids, config, selectValue } = useComboboxContext();
637
+ const selectedValue = useComboboxStore(store, (s) => s.value);
638
+ const highlightedValue = useComboboxStore(store, (s) => s.highlightedValue);
639
+ const { filteredItems } = useComboboxCollection();
640
+ const liRef = useRef(null);
641
+ const isVisible = filteredItems.some((item) => item.value === value);
642
+ const isSelected = typeof selectedValue === "string" && config.isItemEqualToValue(selectedValue, value);
643
+ const isHighlighted = highlightedValue === value;
644
+ const isDisabled = disabled || config.disabled || config.readOnly;
645
+ useLayoutEffect(() => {
646
+ const el = liRef.current;
647
+ const label = textValue ?? (el?.textContent ?? value);
648
+ store.registerItem({
649
+ value,
650
+ label,
651
+ textValue: textValue ?? label,
652
+ disabled: isDisabled,
653
+ ref: el
654
+ });
655
+ return () => store.unregisterItem(value);
656
+ }, [value, isDisabled, store, textValue, children]);
657
+ useLayoutEffect(() => {
658
+ store.updateItemRef(value, liRef.current);
659
+ });
660
+ const handleClick = useCallback(
661
+ (e) => {
662
+ if (isDisabled) return;
663
+ selectValue(value);
664
+ onClick?.(e);
665
+ },
666
+ [isDisabled, selectValue, value, onClick]
667
+ );
668
+ const handlePointerMove = useCallback(
669
+ (e) => {
670
+ if (!isDisabled) store.setHighlightedValue(value);
671
+ onPointerMove?.(e);
672
+ },
673
+ [isDisabled, store, value, onPointerMove]
674
+ );
675
+ return /* @__PURE__ */ jsx(
676
+ "li",
677
+ {
678
+ ref: liRef,
679
+ id: `${ids.content}-opt-${value}`,
680
+ role: "option",
681
+ "aria-selected": isSelected,
682
+ "aria-disabled": isDisabled || void 0,
683
+ "aria-hidden": !isVisible ? true : void 0,
684
+ "data-highlighted": isHighlighted ? "true" : void 0,
685
+ "data-selected": isSelected ? "true" : void 0,
686
+ "data-disabled": isDisabled ? "true" : void 0,
687
+ "data-hidden": !isVisible ? "true" : void 0,
688
+ tabIndex: -1,
689
+ style: {
690
+ ...style,
691
+ display: isVisible ? style?.display : "none"
692
+ },
693
+ onClick: handleClick,
694
+ onPointerMove: handlePointerMove,
695
+ ...props,
696
+ children
697
+ }
698
+ );
699
+ }
700
+ function ItemText({ children, ...props }) {
701
+ return /* @__PURE__ */ jsx("span", { ...props, children });
702
+ }
703
+ function Empty({ children = "No results found", ...props }) {
704
+ const { store } = useComboboxContext();
705
+ const open = useComboboxStore(store, (s) => s.open);
706
+ const { isEmpty } = useComboboxCollection();
707
+ if (!open || !isEmpty) {
708
+ return null;
709
+ }
710
+ return /* @__PURE__ */ jsx("div", { role: "status", "aria-live": "polite", "data-empty": "true", ...props, children });
711
+ }
712
+ function Clear({ onClick, ...props }) {
713
+ const { store, config, clearValue, refs } = useComboboxContext();
714
+ const value = useComboboxStore(store, (s) => s.value);
715
+ const inputValue = useComboboxStore(store, (s) => s.inputValue);
716
+ const hasValue = value != null || inputValue.length > 0;
717
+ const handleActivate = useCallback(
718
+ (e) => {
719
+ e.preventDefault();
720
+ e.stopPropagation();
721
+ clearValue();
722
+ refs.inputRef.current?.focus();
723
+ onClick?.(e);
724
+ },
725
+ [clearValue, onClick, refs.inputRef]
726
+ );
727
+ const handleKeyDown = useCallback(
728
+ (e) => {
729
+ if (e.key === "Enter" || e.key === " ") {
730
+ handleActivate(e);
731
+ }
732
+ },
733
+ [handleActivate]
734
+ );
735
+ if (!hasValue || config.disabled || config.readOnly) {
736
+ return null;
737
+ }
738
+ return /* @__PURE__ */ jsx(
739
+ "span",
740
+ {
741
+ role: "button",
742
+ tabIndex: 0,
743
+ "aria-label": "Clear",
744
+ onClick: handleActivate,
745
+ onKeyDown: handleKeyDown,
746
+ ...props
747
+ }
748
+ );
749
+ }
750
+
751
+ export { index_parts_exports as Combobox, ComboboxStore, useComboboxContext, useComboboxStore };
752
+ //# sourceMappingURL=index.js.map
753
+ //# sourceMappingURL=index.js.map