@bento/listbox 0.2.1 → 0.2.2

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.mjs ADDED
@@ -0,0 +1,715 @@
1
+ import React, { createContext, useContext, useEffect, useMemo, useRef } from "react";
2
+ import { Collection as AriaCollection, CollectionBuilder, createBranchComponent, createLeafComponent } from "@react-aria/collections";
3
+ import { useProps } from "@bento/use-props";
4
+ import { withSlots } from "@bento/slots";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { FocusScope, ListKeyboardDelegate, mergeProps, useCollator, useFocusRing, useHover, useListBox, useListBoxSection, useLocale, useOption } from "react-aria";
7
+ import { useDataAttributes } from "@bento/use-data-attributes";
8
+ import { useListState } from "react-stately";
9
+ import { CollectionRendererContext } from "react-aria-components";
10
+ //#region src/header.tsx
11
+ /**
12
+ * React context for providing header-related attributes and refs to Header components.
13
+ * Used internally by ListBoxSection to pass heading props to Header elements.
14
+ * @public
15
+ */
16
+ const HeaderContext = createContext({});
17
+ /**
18
+ * Internal implementation of the BentoHeader component with slots support.
19
+ * This component handles prop processing and context integration.
20
+ * It merges props from useProps and HeaderContext while preserving styling props.
21
+ *
22
+ * @internal
23
+ */
24
+ const BentoHeaderImpl = withSlots("BentoHeader", function BentoHeader(...args) {
25
+ const [props, ref] = args;
26
+ const { props: processedProps, apply } = useProps(props);
27
+ const contextProps = useContext(HeaderContext);
28
+ const appliedUserProps = apply(processedProps);
29
+ return /* @__PURE__ */ jsx("header", {
30
+ ...contextProps,
31
+ ...appliedUserProps,
32
+ ref: contextProps.ref || ref,
33
+ children: processedProps.children
34
+ });
35
+ });
36
+ /**
37
+ * Wrapper component that connects the BentoHeaderImpl to React Aria's collection system.
38
+ * This function serves as an adapter between the createLeafComponent system and
39
+ * the internal BentoHeaderImpl component, ensuring proper prop forwarding and ref handling.
40
+ *
41
+ * @param {HeaderProps} props - Header component props
42
+ * @param {React.ReactNode} [props.children] - React children to render inside the header
43
+ * @param {React.ForwardedRef<HTMLElement>} ref - Forwarded ref to the header element
44
+ * @returns {React.ReactElement} The BentoHeaderImpl component with forwarded props and ref
45
+ * @internal
46
+ */
47
+ function HeaderWrapper(props, ref) {
48
+ return /* @__PURE__ */ jsx(BentoHeaderImpl, {
49
+ ...props,
50
+ ref
51
+ });
52
+ }
53
+ /**
54
+ * A Header component for section headings within a ListBox.
55
+ * Provides semantic header structure with proper accessibility attributes
56
+ * and integrates with React Aria's collection system for automatic handling.
57
+ *
58
+ * This is the main public interface for creating headers in ListBox sections.
59
+ * It automatically receives heading props from the parent ListBoxSection via HeaderContext.
60
+ *
61
+ * @component
62
+ * @example
63
+ * ```tsx
64
+ * <ListBoxSection>
65
+ * <Header>Fruits</Header>
66
+ * <ListBoxItem>Apple</ListBoxItem>
67
+ * <ListBoxItem>Banana</ListBoxItem>
68
+ * </ListBoxSection>
69
+ * ```
70
+ * @public
71
+ */
72
+ const Header = createLeafComponent("header", HeaderWrapper);
73
+ //#endregion
74
+ //#region src/listbox-section.tsx
75
+ /**
76
+ * Internal implementation of the BentoListBoxSection component with slots support.
77
+ * This component handles the core logic for rendering a section within a ListBox,
78
+ * including title rendering, accessibility attributes, and child content management.
79
+ * It integrates with React Aria's useListBoxSection hook for proper ARIA compliance.
80
+ *
81
+ * @internal
82
+ */
83
+ const BentoListBoxSectionImpl = withSlots("BentoListBoxSection", function BentoListBoxSectionImpl(...restArgs) {
84
+ const [{ __node, children, title: titleProp, ...rest }, ref] = restArgs;
85
+ const { props, apply } = useProps(rest);
86
+ const data = useDataAttributes({ level: __node?.level });
87
+ const headingRef = useRef(null);
88
+ const title = titleProp ?? props.title ?? __node?.rendered;
89
+ const { groupProps, headingProps } = useListBoxSection({
90
+ heading: title,
91
+ "aria-label": props["aria-label"]
92
+ });
93
+ const composed = mergeProps(apply({
94
+ ...data,
95
+ ...props
96
+ }, [
97
+ "children",
98
+ "title",
99
+ "slot"
100
+ ]), groupProps);
101
+ const sectionContent = children || props.children;
102
+ return /* @__PURE__ */ jsx("section", {
103
+ ...composed,
104
+ ref,
105
+ children: /* @__PURE__ */ jsxs(HeaderContext.Provider, {
106
+ value: {
107
+ ...headingProps,
108
+ ref: headingRef
109
+ },
110
+ children: [title && /* @__PURE__ */ jsx("div", {
111
+ ...headingProps,
112
+ children: title
113
+ }), sectionContent]
114
+ })
115
+ });
116
+ });
117
+ /**
118
+ * Wrapper component that connects BentoListBoxSectionImpl to React Aria's collection system.
119
+ * This function serves as an adapter between createBranchComponent and the internal
120
+ * BentoListBoxSectionImpl, ensuring proper prop forwarding and node injection for sections.
121
+ *
122
+ * @template T - The type of the section node data
123
+ * @param {ListBoxSectionProps} props - ListBoxSection component props
124
+ * @param {string} [props.slot] - Slot name for Bento's slot system
125
+ * @param {React.ReactNode} [props.title] - Title for the section
126
+ * @param {React.ReactNode} [props.children] - Children to render in the section
127
+ * @param {string} [props.aria-label] - ARIA label for accessibility
128
+ * @param {React.ForwardedRef<HTMLElement>} ref - Ref forwarded from the collection system
129
+ * @param {Node<T>} section - React Aria node containing section metadata and collection info
130
+ * @returns {React.ReactElement} The BentoListBoxSectionImpl component with proper node and ref wiring
131
+ * @internal
132
+ */
133
+ /* v8 ignore start */
134
+ function ListBoxSectionWrapper(props, ref, section) {
135
+ return /* @__PURE__ */ jsx(BentoListBoxSectionImpl, {
136
+ ...props,
137
+ __node: section,
138
+ ref
139
+ });
140
+ }
141
+ /* v8 ignore stop */
142
+ /**
143
+ * Base ListBoxSection component created through React Aria's collection system.
144
+ * This handles the connection to the parent ListBox's collection state and
145
+ * manages the branch structure for nested items.
146
+ * @internal
147
+ */
148
+ const ListBoxSectionBase = createBranchComponent("section", ListBoxSectionWrapper);
149
+ /**
150
+ * Internal component for rendering dynamic collection sections.
151
+ * This component is used specifically for sections that are part of a dynamic collection,
152
+ * connecting to the ListStateContext and CollectionRendererContext to properly render
153
+ * nested items through React Aria's collection system.
154
+ *
155
+ * @component
156
+ * @param {object} props - The component props containing the section node
157
+ * @param {Node<unknown>} props.section - The React Aria node representing this section in the collection
158
+ * @throws {BentoError} Throws an error if used outside of a ListBox context
159
+ * @returns {React.ReactElement} JSX element representing a dynamically rendered listbox section
160
+ * @internal
161
+ */
162
+ const ListBoxSectionInner = function ListBoxSectionInner({ section }) {
163
+ const state = useContext(ListStateContext);
164
+ const { CollectionBranch } = useContext(CollectionRendererContext);
165
+ return /* @__PURE__ */ jsx(BentoListBoxSectionImpl, {
166
+ ...section.props,
167
+ __node: section,
168
+ children: CollectionBranch && state?.collection ? /* @__PURE__ */ jsx(CollectionBranch, {
169
+ collection: state.collection,
170
+ parent: section
171
+ }) : null
172
+ });
173
+ };
174
+ /**
175
+ * A section component for organizing related items within a ListBox.
176
+ *
177
+ * @component
178
+ * @example
179
+ * ```tsx
180
+ * <ListBoxSection title="Fruits">
181
+ * <ListBoxItem>Apple</ListBoxItem>
182
+ * <ListBoxItem>Banana</ListBoxItem>
183
+ * </ListBoxSection>
184
+ * ```
185
+ * @public
186
+ */
187
+ const ListBoxSection = ListBoxSectionBase;
188
+ //#endregion
189
+ //#region src/utils.ts
190
+ /* v8 ignore next */
191
+ /**
192
+ * NOTE: This utility will be moved to the new use-collection package.
193
+ * Safe wrapper for React Aria's useObjectRef that handles test environments where refs are not extensible.
194
+ *
195
+ * **Critical for Vitest Browser Mode Testing**: When running tests in Vitest's browser mode with Playwright,
196
+ * the test environment can freeze or make objects non-extensible. React Aria's `useObjectRef` attempts to
197
+ * dynamically add properties to ref objects, which fails with "Cannot add property current, object is not extensible"
198
+ * in these constrained test environments.
199
+ *
200
+ * **Technical Details:**
201
+ * - Vitest browser mode uses Playwright's Chrome DevTools Protocol for test execution
202
+ * - The V8 engine's security model can freeze objects during test isolation
203
+ * - React Aria's useObjectRef uses `Object.defineProperty()` to add reactive properties to refs
204
+ * - This conflicts with frozen objects in browser testing scenarios
205
+ *
206
+ * **Why This Solution Works:**
207
+ * - Creates an internal ref that's always mutable (created in our controlled environment)
208
+ * - Safely forwards values to the external ref using try/catch for frozen object scenarios
209
+ * - Maintains the same ref forwarding behavior as React Aria's useObjectRef in normal environments
210
+ * - Gracefully degrades in test environments without breaking functionality
211
+ *
212
+ * **Production Impact**: Zero. Object freezing only occurs in specific test configurations.
213
+ * In production and development, this behaves identically to React Aria's useObjectRef.
214
+ *
215
+ * @template T - The type of the ref element
216
+ * @param {React.ForwardedRef<T>} ref - The forwarded ref to handle safely
217
+ * @returns {React.RefObject<T>} A safe ref object that works in all environments including frozen test contexts
218
+ * @public
219
+ */
220
+ function useSafeObjectRef(ref) {
221
+ const internalRef = useRef(null);
222
+ useEffect(function updateForwardedRef() {
223
+ const current = internalRef.current;
224
+ if (typeof ref === "function") ref(current);
225
+ else if (ref && "current" in ref) try {
226
+ ref.current = current;
227
+ } catch {}
228
+ });
229
+ return internalRef;
230
+ }
231
+ //#endregion
232
+ //#region src/listbox.tsx
233
+ /**
234
+ * React context for sharing ListBox state across components.
235
+ * This context provides the ListBox state to child components like ListBoxItem and ListBoxSection,
236
+ * enabling them to access selection state, collection data, and other shared functionality.
237
+ *
238
+ * @context
239
+ * @internal
240
+ */
241
+ const ListStateContext = createContext(null);
242
+ /**
243
+ * Custom hook to manage ListBox state creation and context handling.
244
+ * This hook either uses an existing state from context or creates a new one.
245
+ * It's designed to work both as a standalone component and within a parent component
246
+ * that provides ListBox state through context.
247
+ *
248
+ * @param {Record<string, unknown>} props - Configuration object for the ListBox state
249
+ * @returns {object} An object containing the state instance and context state flag
250
+ * @returns {ListState<unknown>} returns.state - The ListBox state instance
251
+ * @returns {ListState<unknown> | null} returns.contextState - Existing context state, if any
252
+ * @internal
253
+ */
254
+ function useListBoxState(props) {
255
+ const contextState = useContext(ListStateContext);
256
+ const stateProps = {
257
+ ...props,
258
+ children: void 0,
259
+ items: void 0
260
+ };
261
+ return {
262
+ state: contextState ?? useListState(stateProps),
263
+ contextState
264
+ };
265
+ }
266
+ /**
267
+ * Renders content with optional context provider wrapper.
268
+ * If no context state exists, wraps the content in a ListStateContext.Provider.
269
+ * This allows the ListBox to work both standalone and as part of a larger component tree.
270
+ *
271
+ * @param {React.ReactNode} content - The React content to render
272
+ * @param {ListState<unknown>} state - The ListBox state to provide via context
273
+ * @param {ListState<unknown> | null} contextState - Existing context state, if any
274
+ * @returns {React.ReactNode} The content, optionally wrapped in a context provider
275
+ * @internal
276
+ */
277
+ function renderWithOptionalContext(content, state, contextState) {
278
+ /* v8 ignore next */
279
+ return contextState ? content : /* @__PURE__ */ jsx(ListStateContext.Provider, {
280
+ value: state,
281
+ children: content
282
+ });
283
+ }
284
+ /**
285
+ * Creates and memoizes a keyboard delegate for the ListBox.
286
+ * The keyboard delegate handles keyboard navigation logic, including
287
+ * arrow key navigation, home/end keys, and type-ahead functionality.
288
+ *
289
+ * @param {object} config - Configuration object for the keyboard delegate
290
+ * @param {ListState<unknown>['collection']} config.collection - The collection of items in the ListBox
291
+ * @param {Intl.Collator} config.collator - Intl collator for string comparison in type-ahead
292
+ * @param {React.RefObject<HTMLDivElement>} config.listBoxRef - Reference to the ListBox DOM element
293
+ * @param {ListState<unknown>['selectionManager']} config.selectionManager - Selection manager from the state
294
+ * @param {'stack' | 'grid'} [config.layout] - Layout mode (stack or grid)
295
+ * @param {Orientation} [config.orientation] - Primary orientation of the items
296
+ * @param {'ltr' | 'rtl'} config.direction - Text direction (ltr or rtl)
297
+ * @param {ListKeyboardDelegate<unknown>} [config.keyboardDelegate] - Custom keyboard delegate to use instead of default
298
+ * @returns {ListKeyboardDelegate<unknown>} A keyboard delegate instance for handling keyboard interactions
299
+ * @internal
300
+ */
301
+ function useKeyboardDelegate({ collection, collator, listBoxRef, selectionManager, layout, orientation, direction, keyboardDelegate: providedDelegate }) {
302
+ const { disabledBehavior, disabledKeys } = selectionManager;
303
+ return useMemo(function createKeyboardDelegate() {
304
+ return providedDelegate || new ListKeyboardDelegate({
305
+ collection,
306
+ collator,
307
+ ref: listBoxRef,
308
+ disabledKeys,
309
+ disabledBehavior,
310
+ layout,
311
+ orientation,
312
+ direction
313
+ });
314
+ }, [
315
+ collection,
316
+ collator,
317
+ listBoxRef,
318
+ selectionManager,
319
+ orientation,
320
+ direction,
321
+ layout,
322
+ providedDelegate
323
+ ]);
324
+ }
325
+ /**
326
+ * Generates data attributes for the ListBox element based on its current state.
327
+ * These attributes are used for styling selectors and accessibility indicators.
328
+ *
329
+ * @param {object} config - Configuration object containing ListBox state flags
330
+ * @param {boolean} config.isEmpty - Whether the listbox has no items
331
+ * @param {boolean} config.isFocused - Whether the listbox is currently focused
332
+ * @param {boolean} config.isFocusVisible - Whether focus should be visually indicated
333
+ * @param {'stack' | 'grid'} [config.layout] - Layout mode (stack or grid)
334
+ * @param {Orientation} [config.orientation] - Primary orientation of the items
335
+ * @param {ListState<unknown>['selectionManager']} config.selectionManager - Selection manager containing selection state
336
+ * @param {boolean} [config.allowsTabNavigation] - Whether tab navigation is enabled
337
+ * @param {boolean} [config.shouldFocusWrap] - Whether focus wraps at boundaries
338
+ * @param {SelectionBehavior} [config.originalSelectionBehavior] - Original selection behavior setting
339
+ * @returns {Record<string, unknown>} Object with data attributes for the ListBox element
340
+ * @internal
341
+ */
342
+ function useListBoxDataAttributes({ isEmpty, isFocused, isFocusVisible, layout, orientation, selectionManager, allowsTabNavigation, shouldFocusWrap, originalSelectionBehavior }) {
343
+ return useDataAttributes({
344
+ empty: isEmpty,
345
+ focused: isFocused,
346
+ "focus-visible": isFocusVisible,
347
+ layout,
348
+ orientation,
349
+ "selection-mode": selectionManager.selectionMode !== "none" ? selectionManager.selectionMode : void 0,
350
+ "selection-behavior": originalSelectionBehavior !== void 0 ? selectionManager.selectionBehavior : void 0,
351
+ "allows-tab-navigation": allowsTabNavigation,
352
+ "focus-wrap": shouldFocusWrap
353
+ });
354
+ }
355
+ /**
356
+ * Composes all props for the ListBox element including DOM props, ARIA props,
357
+ * focus props, and data attributes. Handles prop application through useProps
358
+ * and manages ref assignment to avoid proxy extensibility issues.
359
+ *
360
+ * @param {object} config - Configuration object containing all props to compose
361
+ * @param {Record<string, unknown>} config.otherProps - Additional props from the component
362
+ * @param {ListBoxRenderProps} config.renderValues - Values available to render functions
363
+ * @param {Record<string, unknown>} config.listBoxProps - Props from useListBox hook
364
+ * @param {Record<string, unknown>} config.focusProps - Props from useFocusRing hook
365
+ * @param {Record<string, unknown>} config.dataAttributes - Data attributes for styling/selectors
366
+ * @param {ListState<unknown>['selectionManager']} config.selectionManager - Selection manager for ARIA attributes
367
+ * @param {React.RefObject<HTMLDivElement>} config.listBoxRef - Reference to attach to the final element
368
+ * @returns {Record<string, unknown>} Composed props object ready for the ListBox element
369
+ * @internal
370
+ */
371
+ function useComposedProps({ otherProps, renderValues, listBoxProps, focusProps, dataAttributes, selectionManager, listBoxRef }) {
372
+ const { apply } = useProps(otherProps, renderValues);
373
+ const appliedUserProps = apply(otherProps, [
374
+ "renderEmptyState",
375
+ "selectionMode",
376
+ "defaultSelectedKeys",
377
+ "disabledKeys",
378
+ "disallowEmptySelection",
379
+ "shouldFocusWrap",
380
+ "items",
381
+ "children",
382
+ "selectionBehavior",
383
+ "keyboardDelegate"
384
+ ]);
385
+ return {
386
+ ...mergeProps(listBoxProps, focusProps),
387
+ ...dataAttributes,
388
+ ...selectionManager.selectionMode !== "none" && { "aria-multiselectable": selectionManager.selectionMode === "multiple" },
389
+ ...appliedUserProps,
390
+ ref: listBoxRef
391
+ };
392
+ }
393
+ /**
394
+ * Renders the empty state content for the ListBox when no items are present.
395
+ * Handles both function-based render props and direct JSX elements.
396
+ * If a function is provided, calls it with render values; otherwise returns as-is.
397
+ *
398
+ * @param {(props: ListBoxRenderProps) => React.ReactNode} renderEmptyStateFn - Function or JSX element to render for empty state
399
+ * @param {ListBoxRenderProps} renderValues - Current render values to pass to render function
400
+ * @returns {React.ReactNode} Rendered empty state content
401
+ * @internal
402
+ */
403
+ function renderEmptyState(renderEmptyStateFn, renderValues) {
404
+ if (typeof renderEmptyStateFn === "function") return renderEmptyStateFn(renderValues);
405
+ return renderEmptyStateFn;
406
+ }
407
+ /**
408
+ * Renders all items in the collection as React elements.
409
+ * Handles both regular items and section items, using the appropriate
410
+ * components (ListBoxItemImpl for items, ListBoxSectionInner for sections).
411
+ *
412
+ * @param {ListState<unknown>['collection']} collection - The collection of items to render
413
+ * @returns {React.ReactElement[]} Array of rendered React elements for all collection items
414
+ * @internal
415
+ */
416
+ function renderCollectionItems(collection) {
417
+ return [...collection].map(function renderCollectionItem(item) {
418
+ return item.type === "section" ? /* @__PURE__ */ jsx(ListBoxSectionInner, { section: item }, item.key) : /* @__PURE__ */ jsx(ListBoxItemImpl, {
419
+ __node: item,
420
+ children: item.rendered
421
+ }, item.key);
422
+ });
423
+ }
424
+ /**
425
+ * Determines what content to render inside the ListBox based on its configuration.
426
+ * Handles three cases:
427
+ * 1. Function children without items (Bento render prop pattern with full render props)
428
+ * 2. Empty state when no items and renderEmptyState is provided
429
+ * 3. Normal collection rendering (including items with children functions for React Aria compatibility)
430
+ *
431
+ * @param {object} config - Configuration object for rendering
432
+ * @param {React.ReactNode | ((item: unknown) => React.ReactNode) | ((props: ListBoxRenderProps) => React.ReactNode)} [config.children] - Children prop (static, item render function, or ListBox render prop)
433
+ * @param {Iterable<unknown>} [config.items] - Items array for dynamic collections
434
+ * @param {boolean} config.isEmpty - Whether the collection is empty
435
+ * @param {(props: ListBoxRenderProps) => React.ReactNode} [config.renderEmptyStateProp] - Function to render empty state
436
+ * @param {ListBoxRenderProps} config.renderValues - Current render values for render functions
437
+ * @param {ListState<unknown>['collection']} config.collection - The collection state to render
438
+ * @returns {React.ReactNode} The appropriate content to render inside the ListBox
439
+ * @internal
440
+ */
441
+ function renderListBoxContent({ children, items, isEmpty, renderEmptyStateProp, renderValues, collection }) {
442
+ /* v8 ignore next 3 */
443
+ if (typeof children === "function" && !items) return children(renderValues);
444
+ if (isEmpty && renderEmptyStateProp) return renderEmptyState(renderEmptyStateProp, renderValues);
445
+ return renderCollectionItems(collection);
446
+ }
447
+ /**
448
+ * Internal ListBox component that handles the core rendering logic.
449
+ * This component manages all the hooks, state, and prop composition needed
450
+ * for a fully functional ListBox. It's wrapped by the main ListBox component
451
+ * which handles collection building.
452
+ *
453
+ * @param {object} props - Component props
454
+ * @param {ListState<unknown>} props.state - The ListBox state instance
455
+ * @param {(props: ListBoxRenderProps) => React.ReactNode} [props.renderEmptyState] - Function to render when no items are present
456
+ * @param {React.ReactNode | ((item: unknown) => React.ReactNode) | ((props: ListBoxRenderProps) => React.ReactNode)} [props.children] - Static children, item render function, or ListBox render function
457
+ * @param {Iterable<unknown>} [props.items] - Items array for dynamic collections
458
+ * @param {React.RefObject<HTMLDivElement>} props.listBoxRef - Reference to the ListBox DOM element
459
+ * @param {'stack'} [props.layout] - Layout mode (stack or grid)
460
+ * @param {Orientation} [props.orientation] - Primary orientation of the items
461
+ * @param {boolean} [props.shouldSelectOnPressUp] - Whether selection occurs on pointer up
462
+ * @param {ListKeyboardDelegate<unknown>} [props.keyboardDelegate] - Custom keyboard navigation delegate
463
+ * @param {boolean} [props.allowsTabNavigation] - Whether tab key navigates between items
464
+ * @param {boolean} [props.shouldFocusWrap] - Whether focus wraps at boundaries
465
+ * @param {'none' | 'single' | 'multiple'} [props.selectionMode] - Selection mode (none, single, multiple)
466
+ * @param {SelectionBehavior} [props.selectionBehavior] - Selection behavior (toggle, replace)
467
+ * @returns {React.ReactElement} A fully functional ListBox element with focus scope
468
+ * @internal
469
+ */
470
+ const ListBoxInner = function ListBoxInner({ state, renderEmptyState: renderEmptyStateProp, children, items, listBoxRef, ...otherProps }) {
471
+ const { layout = "stack", orientation = "vertical", shouldSelectOnPressUp, selectionBehavior } = otherProps;
472
+ const { collection, selectionManager } = state;
473
+ const { direction } = useLocale();
474
+ const keyboardDelegate = useKeyboardDelegate({
475
+ collection,
476
+ collator: useCollator({
477
+ usage: "search",
478
+ sensitivity: "base"
479
+ }),
480
+ listBoxRef,
481
+ selectionManager,
482
+ layout,
483
+ orientation,
484
+ direction,
485
+ keyboardDelegate: otherProps.keyboardDelegate
486
+ });
487
+ const { listBoxProps } = useListBox({
488
+ ...otherProps,
489
+ shouldSelectOnPressUp,
490
+ keyboardDelegate
491
+ }, state, listBoxRef);
492
+ const { focusProps, isFocused, isFocusVisible } = useFocusRing();
493
+ const isEmpty = state.collection.size === 0;
494
+ const renderValues = {
495
+ isEmpty,
496
+ isFocused,
497
+ isFocusVisible,
498
+ isDropTarget: false,
499
+ layout,
500
+ state,
501
+ items
502
+ };
503
+ return /* @__PURE__ */ jsx(FocusScope, { children: /* @__PURE__ */ jsx("div", {
504
+ ...useComposedProps({
505
+ otherProps,
506
+ renderValues,
507
+ listBoxProps,
508
+ focusProps,
509
+ dataAttributes: useListBoxDataAttributes({
510
+ isEmpty,
511
+ isFocused,
512
+ isFocusVisible,
513
+ layout,
514
+ orientation,
515
+ selectionManager,
516
+ allowsTabNavigation: otherProps.allowsTabNavigation,
517
+ shouldFocusWrap: otherProps.shouldFocusWrap,
518
+ originalSelectionBehavior: selectionBehavior
519
+ }),
520
+ selectionManager,
521
+ listBoxRef
522
+ }),
523
+ children: renderListBoxContent({
524
+ children,
525
+ items,
526
+ isEmpty,
527
+ renderEmptyStateProp,
528
+ renderValues,
529
+ collection
530
+ })
531
+ }) });
532
+ };
533
+ /**
534
+ * A complete ListBox component providing accessible selection lists with keyboard navigation.
535
+ * Supports both static children and dynamic collections, with single/multiple selection modes.
536
+ * Built on React Aria with full ARIA compliance and keyboard accessibility.
537
+ *
538
+ * @component
539
+ * @template T The type of items in the collection
540
+ * @param {ListBoxProps<T>} args - The properties passed to the ListBox component
541
+ * @param {React.ForwardedRef<HTMLDivElement>} ref - The ref to the listbox container
542
+ * @returns {React.ReactElement} A ListBox component
543
+ *
544
+ * @example
545
+ * ```tsx
546
+ * <ListBox aria-label="Fruits" selectionMode="single">
547
+ * <ListBoxItem id="apple" textValue="Apple">Apple</ListBoxItem>
548
+ * <ListBoxItem id="banana" textValue="Banana">Banana</ListBoxItem>
549
+ * </ListBox>
550
+ * ```
551
+ * @public
552
+ */
553
+ function ListBoxComponent(...args) {
554
+ const [_args, ref] = args;
555
+ return /* @__PURE__ */ jsx(CollectionBuilder, {
556
+ content: /* @__PURE__ */ jsx(AriaCollection, { ..._args }),
557
+ children: function buildCollection(collection) {
558
+ return /* @__PURE__ */ jsx(StandaloneListBox, {
559
+ props: _args,
560
+ listBoxRef: ref,
561
+ collection
562
+ });
563
+ }
564
+ });
565
+ }
566
+ /**
567
+ * Standalone ListBox component that manages its own state and collection.
568
+ * This component is used internally by the main ListBox component after
569
+ * collection building is complete. It handles prop processing, state creation,
570
+ * and context management.
571
+ *
572
+ * @param {object} props - Component props
573
+ * @param {ListBoxProps<unknown>} props.props - The original ListBox props
574
+ * @param {React.ForwardedRef<HTMLDivElement>} props.listBoxRef - Reference to forward to the ListBox element
575
+ * @param {unknown} props.collection - Built collection from CollectionBuilder
576
+ * @returns {React.ReactElement} A complete ListBox with state management and optional context wrapping
577
+ * @internal
578
+ */
579
+ const StandaloneListBox = function StandaloneListBox({ props, listBoxRef, collection }) {
580
+ const originalRenderEmptyState = props.renderEmptyState;
581
+ const { props: processedProps } = useProps(props);
582
+ const processedRef = useSafeObjectRef(listBoxRef);
583
+ const { state, contextState } = useListBoxState({
584
+ ...processedProps,
585
+ collection
586
+ });
587
+ const { renderEmptyState: _, ...cleanProcessedProps } = processedProps;
588
+ return renderWithOptionalContext(/* @__PURE__ */ jsx(ListBoxInner, {
589
+ state,
590
+ listBoxRef: processedRef,
591
+ renderEmptyState: originalRenderEmptyState,
592
+ ...cleanProcessedProps
593
+ }), state, contextState);
594
+ };
595
+ /**
596
+ * A complete ListBox component providing accessible selection lists with keyboard navigation.
597
+ * Supports both static children and dynamic collections, with single/multiple selection modes.
598
+ * Built on React Aria with full ARIA compliance and keyboard accessibility.
599
+ *
600
+ * @component
601
+ * @example
602
+ * ```tsx
603
+ * <ListBox aria-label="Fruits" selectionMode="single">
604
+ * <ListBoxItem id="apple" textValue="Apple">Apple</ListBoxItem>
605
+ * <ListBoxItem id="banana" textValue="Banana">Banana</ListBoxItem>
606
+ * </ListBox>
607
+ * ```
608
+ * @public
609
+ */
610
+ const ListBox = withSlots("BentoListBox", ListBoxComponent);
611
+ //#endregion
612
+ //#region src/listbox-item.tsx
613
+ /**
614
+ * Internal context for providing text-related slot attributes to child components.
615
+ * This context allows ListBoxItem to pass label and description attributes
616
+ * to nested components that need them for accessibility.
617
+ * @internal
618
+ */
619
+ const TextContext = createContext({ slots: {} });
620
+ /**
621
+ * Enhanced ListBoxItem implementation with slots support.
622
+ * This wraps the core ListBoxItemImplComponent with Bento's slot system
623
+ * for advanced composition and styling capabilities.
624
+ * @internal
625
+ */
626
+ const ListBoxItemImpl = withSlots("BentoListBoxItem", function ListBoxItemImplComponent(...restArgs) {
627
+ const [{ __node, ...props }, ref] = restArgs;
628
+ const state = useContext(ListStateContext);
629
+ const safeRef = useSafeObjectRef(ref);
630
+ const { optionProps, labelProps, descriptionProps, ...states } = useOption({
631
+ key: __node.key,
632
+ "aria-label": props["aria-label"],
633
+ isDisabled: props.isDisabled
634
+ }, state, safeRef);
635
+ const { hoverProps, isHovered } = useHover({
636
+ isDisabled: states.isDisabled,
637
+ onHoverStart: props.onHoverStart,
638
+ onHoverChange: props.onHoverChange,
639
+ onHoverEnd: props.onHoverEnd
640
+ });
641
+ const renderValues = {
642
+ ...states,
643
+ isHovered,
644
+ selectionMode: state.selectionManager.selectionMode,
645
+ selectionBehavior: state.selectionManager.selectionBehavior
646
+ };
647
+ const content = typeof props.children === "function" ? props.children(renderValues) : props.children;
648
+ const { apply } = useProps(props, renderValues);
649
+ const dataAttributes = useDataAttributes({
650
+ selected: states.isSelected,
651
+ disabled: states.isDisabled,
652
+ hovered: isHovered,
653
+ focused: states.isFocused,
654
+ "focus-visible": states.isFocusVisible,
655
+ pressed: states.isPressed,
656
+ level: __node.level,
657
+ "selection-mode": state.selectionManager.selectionMode,
658
+ "selection-behavior": state.selectionManager.selectionBehavior
659
+ });
660
+ const textContext = useMemo(function createTextContext() {
661
+ return { slots: {
662
+ label: labelProps,
663
+ description: descriptionProps
664
+ } };
665
+ }, [labelProps, descriptionProps]);
666
+ const ElementType = __node.props.href ? "a" : "div";
667
+ const appliedUserProps = apply(__node.props, ["ref"]);
668
+ const finalAttributes = {
669
+ ...mergeProps(optionProps, hoverProps),
670
+ ...dataAttributes,
671
+ ...appliedUserProps,
672
+ ref: safeRef,
673
+ "data-text-value": __node.textValue
674
+ };
675
+ return /* @__PURE__ */ jsx(TextContext.Provider, {
676
+ value: textContext,
677
+ children: React.createElement(ElementType, finalAttributes, content)
678
+ });
679
+ });
680
+ /**
681
+ * Adapter component that connects ListBoxItemImpl to React Aria's collection system.
682
+ * This function serves as a bridge between React Aria's createLeafComponent and
683
+ * the internal ListBoxItemImpl, ensuring proper prop forwarding and node injection.
684
+ *
685
+ * @template T - The type of the item value
686
+ * @param {ListBoxItemProps<T>} props - ListBoxItem component props
687
+ * @param {React.ForwardedRef<HTMLDivElement>} forwardedRef - Ref forwarded from the collection system
688
+ * @param {Node<T>} item - React Aria node containing item metadata and collection info
689
+ * @returns {React.ReactElement} The ListBoxItemImpl component with proper node and ref wiring
690
+ * @internal
691
+ */
692
+ function ListBoxItemComponent(props, forwardedRef, item) {
693
+ return /* @__PURE__ */ jsx(ListBoxItemImpl, {
694
+ ...props,
695
+ ref: forwardedRef,
696
+ __node: item
697
+ });
698
+ }
699
+ /**
700
+ * A single item within a ListBox component.
701
+ * Handles user interactions, accessibility, and state management for individual options.
702
+ *
703
+ * @component
704
+ * @template T The type of the item value
705
+ * @example
706
+ * ```tsx
707
+ * <ListBoxItem>Simple option</ListBoxItem>
708
+ * ```
709
+ * @public
710
+ */
711
+ const ListBoxItem = createLeafComponent("item", ListBoxItemComponent);
712
+ //#endregion
713
+ export { AriaCollection as Collection, Header, HeaderContext, ListBox, ListBoxItem, ListBoxSection };
714
+
715
+ //# sourceMappingURL=index.mjs.map