@octanejs/aria 0.0.4 → 0.0.5

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.
Files changed (93) hide show
  1. package/package.json +7 -5
  2. package/src/autocomplete/useAutocomplete.ts +656 -0
  3. package/src/collections/BaseCollection.ts +374 -0
  4. package/src/collections/CollectionBuilder.ts +343 -0
  5. package/src/collections/Document.ts +451 -0
  6. package/src/collections/Hidden.ts +84 -0
  7. package/src/collections/useCachedChildren.ts +103 -0
  8. package/src/components/Autocomplete.ts +124 -0
  9. package/src/components/Breadcrumbs.ts +170 -0
  10. package/src/components/Button.ts +209 -0
  11. package/src/components/Checkbox.ts +593 -0
  12. package/src/components/Collection.ts +301 -0
  13. package/src/components/ComboBox.ts +477 -0
  14. package/src/components/Dialog.ts +195 -0
  15. package/src/components/Disclosure.ts +322 -0
  16. package/src/components/DragAndDrop.ts +177 -0
  17. package/src/components/FieldError.ts +79 -0
  18. package/src/components/Form.ts +65 -0
  19. package/src/components/GridList.ts +1099 -0
  20. package/src/components/Group.ts +146 -0
  21. package/src/components/Header.ts +32 -0
  22. package/src/components/Heading.ts +45 -0
  23. package/src/components/Input.ts +139 -0
  24. package/src/components/Keyboard.ts +23 -0
  25. package/src/components/Label.ts +30 -0
  26. package/src/components/Link.ts +137 -0
  27. package/src/components/ListBox.ts +886 -0
  28. package/src/components/Menu.ts +685 -0
  29. package/src/components/Meter.ts +100 -0
  30. package/src/components/Modal.ts +352 -0
  31. package/src/components/NumberField.ts +221 -0
  32. package/src/components/OverlayArrow.ts +117 -0
  33. package/src/components/Popover.ts +385 -0
  34. package/src/components/ProgressBar.ts +112 -0
  35. package/src/components/RadioGroup.ts +542 -0
  36. package/src/components/SearchField.ts +208 -0
  37. package/src/components/Select.ts +505 -0
  38. package/src/components/SelectionIndicator.ts +52 -0
  39. package/src/components/Separator.ts +97 -0
  40. package/src/components/SharedElementTransition.ts +209 -0
  41. package/src/components/Slider.ts +472 -0
  42. package/src/components/Switch.ts +449 -0
  43. package/src/components/Tabs.ts +675 -0
  44. package/src/components/TagGroup.ts +443 -0
  45. package/src/components/Text.ts +27 -0
  46. package/src/components/TextArea.ts +92 -0
  47. package/src/components/TextField.ts +200 -0
  48. package/src/components/ToggleButton.ts +147 -0
  49. package/src/components/ToggleButtonGroup.ts +106 -0
  50. package/src/components/Toolbar.ts +74 -0
  51. package/src/components/Tooltip.ts +249 -0
  52. package/src/components/index.ts +302 -0
  53. package/src/components/useDragAndDrop.ts +164 -0
  54. package/src/components/utils.ts +521 -0
  55. package/src/intl/autocomplete/ar-AE.json +3 -0
  56. package/src/intl/autocomplete/bg-BG.json +3 -0
  57. package/src/intl/autocomplete/cs-CZ.json +3 -0
  58. package/src/intl/autocomplete/da-DK.json +3 -0
  59. package/src/intl/autocomplete/de-DE.json +3 -0
  60. package/src/intl/autocomplete/el-GR.json +3 -0
  61. package/src/intl/autocomplete/en-US.json +3 -0
  62. package/src/intl/autocomplete/es-ES.json +3 -0
  63. package/src/intl/autocomplete/et-EE.json +3 -0
  64. package/src/intl/autocomplete/fi-FI.json +3 -0
  65. package/src/intl/autocomplete/fr-FR.json +3 -0
  66. package/src/intl/autocomplete/he-IL.json +3 -0
  67. package/src/intl/autocomplete/hr-HR.json +3 -0
  68. package/src/intl/autocomplete/hu-HU.json +3 -0
  69. package/src/intl/autocomplete/index.ts +75 -0
  70. package/src/intl/autocomplete/it-IT.json +3 -0
  71. package/src/intl/autocomplete/ja-JP.json +3 -0
  72. package/src/intl/autocomplete/ko-KR.json +3 -0
  73. package/src/intl/autocomplete/lt-LT.json +3 -0
  74. package/src/intl/autocomplete/lv-LV.json +3 -0
  75. package/src/intl/autocomplete/nb-NO.json +3 -0
  76. package/src/intl/autocomplete/nl-NL.json +3 -0
  77. package/src/intl/autocomplete/pl-PL.json +3 -0
  78. package/src/intl/autocomplete/pt-BR.json +3 -0
  79. package/src/intl/autocomplete/pt-PT.json +3 -0
  80. package/src/intl/autocomplete/ro-RO.json +3 -0
  81. package/src/intl/autocomplete/ru-RU.json +3 -0
  82. package/src/intl/autocomplete/sk-SK.json +3 -0
  83. package/src/intl/autocomplete/sl-SI.json +3 -0
  84. package/src/intl/autocomplete/sr-SP.json +3 -0
  85. package/src/intl/autocomplete/sv-SE.json +3 -0
  86. package/src/intl/autocomplete/tr-TR.json +3 -0
  87. package/src/intl/autocomplete/uk-UA.json +3 -0
  88. package/src/intl/autocomplete/zh-CN.json +3 -0
  89. package/src/intl/autocomplete/zh-TW.json +3 -0
  90. package/src/stately/autocomplete/useAutocompleteState.ts +85 -0
  91. package/src/utils/animation.ts +149 -0
  92. package/src/utils/useLoadMoreSentinel.ts +78 -0
  93. package/src/utils/useViewportSize.ts +123 -0
@@ -0,0 +1,886 @@
1
+ // Ported from react-aria-components (source: .react-spectrum/packages/react-aria-components/src/ListBox.tsx).
2
+ // octane adaptations: `.tsx` → `.ts`, JSX → `createElement`; NO forwardRef — the forwarded
3
+ // ref is `props.ref` (ListBox passes it into `useContextProps` explicitly; ListBoxItem and
4
+ // the drop-indicator wrapper adapt theirs with `useObjectRef` exactly like upstream's
5
+ // forwarded refs); the plain-`.ts` components use the S()/subSlot component-slot convention.
6
+ // The collection composes the Phase-4 engine: `CollectionBuilder`/`createLeafComponent`/
7
+ // `createBranchComponent` from `../collections/CollectionBuilder` and the renderer's
8
+ // `CollectionRoot`/`CollectionBranch` via `CollectionRendererContext`. Upstream's RAC-local
9
+ // `CollectionProps` import is our `ItemCollectionProps` (see ./Collection.ts). Upstream's
10
+ // `inertValue` (React <19 string compat) collapses to the plain boolean — octane follows
11
+ // React 19 `inert` semantics. The dnd branches use the PHASE-7 structural aliases from
12
+ // ./useDragAndDrop and stay inert until a consumer can construct `dragAndDropHooks`.
13
+ // react-aria's private `useLoadMoreSentinel` lives in ../utils/useLoadMoreSentinel
14
+ // (shared with GridList). Explicit dep arrays are preserved verbatim.
15
+ import type {
16
+ HoverEvents,
17
+ Collection as ICollection,
18
+ Key,
19
+ LinkDOMProps,
20
+ Node,
21
+ Orientation,
22
+ PressEvents,
23
+ SelectionBehavior,
24
+ } from '@react-types/shared';
25
+ import {
26
+ Fragment,
27
+ createContext,
28
+ createElement,
29
+ useContext,
30
+ useEffect,
31
+ useMemo,
32
+ useRef,
33
+ } from 'octane';
34
+
35
+ import { ItemNode, LoaderNode, SectionNode } from '../collections/BaseCollection';
36
+ import {
37
+ CollectionBuilder,
38
+ createBranchComponent,
39
+ createLeafComponent,
40
+ } from '../collections/CollectionBuilder';
41
+ import { FocusScope } from '../focus/FocusScope';
42
+ import { useFocusRing } from '../focus/useFocusRing';
43
+ import { useCollator } from '../i18n/useCollator';
44
+ import { useLocale } from '../i18n/I18nProvider';
45
+ // octane adaptation: the ported NATIVE-event handler bags (upstream: FocusEvents/
46
+ // KeyboardEvents from '@react-types/shared', typed over React synthetic events).
47
+ import { type FocusEvents, useFocus } from '../interactions/useFocus';
48
+ import { useHover } from '../interactions/useHover';
49
+ import { type KeyboardEvents, useKeyboard } from '../interactions/useKeyboard';
50
+ import { S, subSlot } from '../internal';
51
+ import { type AriaListBoxOptions, type AriaListBoxProps, useListBox } from '../listbox/useListBox';
52
+ import { useListBoxSection } from '../listbox/useListBoxSection';
53
+ import { useOption } from '../listbox/useOption';
54
+ import { ListKeyboardDelegate } from '../selection/ListKeyboardDelegate';
55
+ import {
56
+ type ListState,
57
+ UNSTABLE_useFilteredListState,
58
+ useListState,
59
+ } from '../stately/list/useListState';
60
+ import { filterDOMProps } from '../utils/filterDOMProps';
61
+ import { mergeProps } from '../utils/mergeProps';
62
+ import { type LoadMoreSentinelProps, useLoadMoreSentinel } from '../utils/useLoadMoreSentinel';
63
+ import { useObjectRef } from '../utils/useObjectRef';
64
+ import { SelectableCollectionContext, type SelectableCollectionContextValue } from './Autocomplete';
65
+ import {
66
+ Collection,
67
+ CollectionRendererContext,
68
+ type ItemCollectionProps,
69
+ type ItemRenderProps,
70
+ SectionContext,
71
+ type SectionProps,
72
+ } from './Collection';
73
+ import {
74
+ DragAndDropContext,
75
+ DropIndicatorContext,
76
+ type DropIndicatorProps,
77
+ useDndPersistedKeys,
78
+ useRenderDropIndicator,
79
+ } from './DragAndDrop';
80
+ import type {
81
+ DragAndDropHooks,
82
+ DraggableCollectionState,
83
+ DraggableItemResult,
84
+ DroppableCollectionResult,
85
+ DroppableCollectionState,
86
+ DroppableItemResult,
87
+ } from './useDragAndDrop';
88
+ import { HeaderContext } from './Header';
89
+ import { SelectionIndicatorContext } from './SelectionIndicator';
90
+ import { SeparatorContext } from './Separator';
91
+ import { SharedElementTransition } from './SharedElementTransition';
92
+ import { TextContext } from './Text';
93
+ import {
94
+ type ClassNameOrFunction,
95
+ type ContextValue,
96
+ DEFAULT_SLOT,
97
+ dom,
98
+ type DOMRenderProps,
99
+ type PossibleLinkDOMRenderProps,
100
+ Provider,
101
+ type RenderProps,
102
+ type SlotProps,
103
+ type StyleProps,
104
+ type StyleRenderProps,
105
+ useContextProps,
106
+ useRenderProps,
107
+ useSlot,
108
+ } from './utils';
109
+
110
+ // octane adaptation: structural bag (upstream's `GlobalDOMAttributes` drags React handler types).
111
+ type GlobalDOMAttributes = Record<string, any>;
112
+ type RefObject<T> = { current: T };
113
+ type ReactNode = any;
114
+
115
+ export interface ListBoxRenderProps {
116
+ /**
117
+ * Whether the listbox has no items and should display its empty state.
118
+ *
119
+ * @selector [data-empty]
120
+ */
121
+ isEmpty: boolean;
122
+ /**
123
+ * Whether the listbox is currently focused.
124
+ *
125
+ * @selector [data-focused]
126
+ */
127
+ isFocused: boolean;
128
+ /**
129
+ * Whether the listbox is currently keyboard focused.
130
+ *
131
+ * @selector [data-focus-visible]
132
+ */
133
+ isFocusVisible: boolean;
134
+ /**
135
+ * Whether the listbox is currently the active drop target.
136
+ *
137
+ * @selector [data-drop-target]
138
+ */
139
+ isDropTarget: boolean;
140
+ /**
141
+ * Whether the items are arranged in a stack or grid.
142
+ *
143
+ * @selector [data-layout="stack | grid"]
144
+ */
145
+ layout: 'stack' | 'grid';
146
+ /**
147
+ * The primary orientation of the items.
148
+ *
149
+ * @selector [data-orientation="vertical | horizontal"]
150
+ */
151
+ orientation: Orientation;
152
+ /**
153
+ * State of the listbox.
154
+ */
155
+ state: ListState<unknown>;
156
+ }
157
+
158
+ export interface ListBoxProps<T>
159
+ extends
160
+ Omit<AriaListBoxProps<T>, 'children' | 'label'>,
161
+ ItemCollectionProps<T>,
162
+ StyleRenderProps<ListBoxRenderProps>,
163
+ SlotProps,
164
+ GlobalDOMAttributes {
165
+ /**
166
+ * The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
167
+ * element. A function may be provided to compute the class based on component state.
168
+ *
169
+ * @default 'react-aria-ListBox'
170
+ */
171
+ className?: ClassNameOrFunction<ListBoxRenderProps>;
172
+ /**
173
+ * How multiple selection should behave in the collection.
174
+ *
175
+ * @default 'toggle'
176
+ */
177
+ selectionBehavior?: SelectionBehavior;
178
+ /**
179
+ * The drag and drop hooks returned by `useDragAndDrop` used to enable drag and drop behavior for
180
+ * the ListBox.
181
+ */
182
+ dragAndDropHooks?: DragAndDropHooks<NoInfer<T>>;
183
+ /** Provides content to display when there are no items in the list. */
184
+ renderEmptyState?: (props: ListBoxRenderProps) => ReactNode;
185
+ /**
186
+ * Whether the items are arranged in a stack or grid.
187
+ *
188
+ * @default 'stack'
189
+ */
190
+ layout?: 'stack' | 'grid';
191
+ /**
192
+ * The primary orientation of the items. Usually this is the
193
+ * direction that the collection scrolls.
194
+ *
195
+ * @default 'vertical'
196
+ */
197
+ orientation?: Orientation;
198
+ }
199
+
200
+ export const ListBoxContext = createContext<ContextValue<ListBoxProps<any>, HTMLDivElement>>(null);
201
+ export const ListStateContext = createContext<ListState<any> | null>(null);
202
+
203
+ /**
204
+ * A listbox displays a list of options and allows a user to select one or more of them.
205
+ */
206
+ export function ListBox<T extends object>(props: ListBoxProps<T>): any {
207
+ const slot = S('ListBox');
208
+ let ref: any;
209
+ [props, ref] = useContextProps(props, (props as any).ref, ListBoxContext, subSlot(slot, 'ctx'));
210
+ let state = useContext(ListStateContext);
211
+
212
+ // The structure of ListBox is a bit strange because it needs to work inside other components like ComboBox and Select.
213
+ // Those components render two copies of their children so that the collection can be built even when the popover is closed.
214
+ // The first copy sends a collection document via context which we render the collection portal into.
215
+ // The second copy sends a ListState object via context which we use to render the ListBox without rebuilding the state.
216
+ // Otherwise, we have a standalone ListBox, so we need to create a collection and state ourselves.
217
+ if (state) {
218
+ return createElement(ListBoxInner, { state, props, listBoxRef: ref });
219
+ }
220
+
221
+ return createElement(CollectionBuilder, {
222
+ content: createElement(Collection, props as any),
223
+ children: (collection: ICollection<Node<any>>) =>
224
+ createElement(StandaloneListBox, { props, listBoxRef: ref, collection }),
225
+ });
226
+ }
227
+
228
+ interface StandaloneListBoxProps<T> {
229
+ props: ListBoxProps<T>;
230
+ listBoxRef: RefObject<HTMLDivElement | null>;
231
+ collection: ICollection<Node<T>>;
232
+ }
233
+
234
+ function StandaloneListBox<T extends object>({
235
+ props,
236
+ listBoxRef,
237
+ collection,
238
+ }: StandaloneListBoxProps<T>): any {
239
+ const slot = S('StandaloneListBox');
240
+ props = { ...props, collection, children: null, items: null } as any;
241
+ let { layoutDelegate } = useContext(CollectionRendererContext);
242
+ let state = useListState({ ...props, layoutDelegate } as any, subSlot(slot, 'state'));
243
+ return createElement(ListBoxInner, { state, props, listBoxRef });
244
+ }
245
+
246
+ interface ListBoxInnerProps<T> {
247
+ state: ListState<T>;
248
+ props: ListBoxProps<T> &
249
+ AriaListBoxOptions<T> & { filter?: SelectableCollectionContextValue<T>['filter'] };
250
+ listBoxRef: RefObject<HTMLElement | null>;
251
+ }
252
+
253
+ function ListBoxInner<T>({ state: inputState, props, listBoxRef }: ListBoxInnerProps<T>): any {
254
+ const slot = S('ListBoxInner');
255
+ [props, listBoxRef] = useContextProps(
256
+ props,
257
+ listBoxRef as any,
258
+ SelectableCollectionContext,
259
+ subSlot(slot, 'ctx'),
260
+ ) as any;
261
+ let { dragAndDropHooks, layout = 'stack', orientation = 'vertical', filter } = props;
262
+ let state = UNSTABLE_useFilteredListState(inputState, filter, subSlot(slot, 'filtered'));
263
+ let { collection, selectionManager } = state;
264
+ let isListDraggable = !!dragAndDropHooks?.useDraggableCollectionState;
265
+ let isListDroppable = !!dragAndDropHooks?.useDroppableCollectionState;
266
+ let { direction } = useLocale(subSlot(slot, 'locale'));
267
+ let { disabledBehavior, disabledKeys } = selectionManager;
268
+ let collator = useCollator({ usage: 'search', sensitivity: 'base' }, subSlot(slot, 'collator'));
269
+ let {
270
+ isVirtualized,
271
+ layoutDelegate,
272
+ dropTargetDelegate: ctxDropTargetDelegate,
273
+ CollectionRoot,
274
+ } = useContext(CollectionRendererContext);
275
+ let keyboardDelegate = useMemo(
276
+ () =>
277
+ props.keyboardDelegate ||
278
+ new ListKeyboardDelegate({
279
+ collection,
280
+ collator,
281
+ ref: listBoxRef,
282
+ disabledKeys,
283
+ disabledBehavior,
284
+ layout,
285
+ orientation,
286
+ direction,
287
+ layoutDelegate,
288
+ }),
289
+ [
290
+ collection,
291
+ collator,
292
+ listBoxRef,
293
+ disabledBehavior,
294
+ disabledKeys,
295
+ orientation,
296
+ direction,
297
+ props.keyboardDelegate,
298
+ layout,
299
+ layoutDelegate,
300
+ ],
301
+ subSlot(slot, 'keyboardDelegate'),
302
+ );
303
+
304
+ let { listBoxProps } = useListBox(
305
+ {
306
+ ...props,
307
+ shouldSelectOnPressUp: isListDraggable || props.shouldSelectOnPressUp,
308
+ keyboardDelegate,
309
+ isVirtualized,
310
+ },
311
+ state,
312
+ listBoxRef,
313
+ subSlot(slot, 'listBox'),
314
+ );
315
+
316
+ let dragHooksProvided = useRef(isListDraggable, subSlot(slot, 'dragProvided'));
317
+ let dropHooksProvided = useRef(isListDroppable, subSlot(slot, 'dropProvided'));
318
+ useEffect(
319
+ () => {
320
+ if (process.env.NODE_ENV === 'production') {
321
+ return;
322
+ }
323
+ if (dragHooksProvided.current !== isListDraggable) {
324
+ console.warn(
325
+ 'Drag hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.',
326
+ );
327
+ }
328
+ if (dropHooksProvided.current !== isListDroppable) {
329
+ console.warn(
330
+ 'Drop hooks were provided during one render, but not another. This should be avoided as it may produce unexpected behavior.',
331
+ );
332
+ }
333
+ },
334
+ [isListDraggable, isListDroppable],
335
+ subSlot(slot, 'dndWarn'),
336
+ );
337
+
338
+ let dragState: DraggableCollectionState | undefined = undefined;
339
+ let dropState: DroppableCollectionState | undefined = undefined;
340
+ let droppableCollection: DroppableCollectionResult | undefined = undefined;
341
+ let isRootDropTarget = false;
342
+ let dragPreview: any = null;
343
+ let preview = useRef<any>(null, subSlot(slot, 'preview'));
344
+
345
+ // PHASE-7: these branches call consumer-provided dnd hooks (upstream contract); they are
346
+ // unreachable until `useDragAndDrop` is ported and a consumer can construct hooks.
347
+ if (isListDraggable && dragAndDropHooks) {
348
+ dragState = dragAndDropHooks.useDraggableCollectionState!({
349
+ collection,
350
+ selectionManager,
351
+ preview: dragAndDropHooks.renderDragPreview ? preview : undefined,
352
+ });
353
+ dragAndDropHooks.useDraggableCollection!({}, dragState, listBoxRef);
354
+
355
+ let DragPreview = dragAndDropHooks.DragPreview!;
356
+ dragPreview = dragAndDropHooks.renderDragPreview
357
+ ? createElement(DragPreview, { ref: preview, children: dragAndDropHooks.renderDragPreview })
358
+ : null;
359
+ }
360
+
361
+ if (isListDroppable && dragAndDropHooks) {
362
+ dropState = dragAndDropHooks.useDroppableCollectionState!({
363
+ collection,
364
+ selectionManager,
365
+ });
366
+
367
+ let dropTargetDelegate =
368
+ dragAndDropHooks.dropTargetDelegate ||
369
+ ctxDropTargetDelegate ||
370
+ new dragAndDropHooks.ListDropTargetDelegate(collection, listBoxRef, {
371
+ orientation,
372
+ layout,
373
+ direction,
374
+ });
375
+ droppableCollection = dragAndDropHooks.useDroppableCollection!(
376
+ {
377
+ keyboardDelegate,
378
+ dropTargetDelegate,
379
+ },
380
+ dropState,
381
+ listBoxRef,
382
+ );
383
+
384
+ isRootDropTarget = dropState.isDropTarget({ type: 'root' });
385
+ }
386
+
387
+ let { focusProps, isFocused, isFocusVisible } = useFocusRing(
388
+ undefined,
389
+ subSlot(slot, 'focusRing'),
390
+ );
391
+ let isEmpty = state.collection.size === 0;
392
+ let renderValues = {
393
+ isDropTarget: isRootDropTarget,
394
+ isEmpty,
395
+ isFocused,
396
+ isFocusVisible,
397
+ layout: props.layout || 'stack',
398
+ orientation,
399
+ state,
400
+ };
401
+ let renderProps = useRenderProps(
402
+ {
403
+ ...props,
404
+ children: undefined,
405
+ defaultClassName: 'react-aria-ListBox',
406
+ values: renderValues,
407
+ } as any,
408
+ subSlot(slot, 'render'),
409
+ );
410
+
411
+ let emptyState: any = null;
412
+ if (isEmpty && props.renderEmptyState) {
413
+ emptyState = createElement('div', {
414
+ role: 'option',
415
+ style: { display: 'contents' },
416
+ children: props.renderEmptyState(renderValues as any),
417
+ });
418
+ }
419
+
420
+ let DOMProps = filterDOMProps(props, { global: true });
421
+
422
+ let persistedKeys = useDndPersistedKeys(
423
+ selectionManager,
424
+ dragAndDropHooks,
425
+ dropState,
426
+ subSlot(slot, 'persisted'),
427
+ );
428
+ let renderDropIndicator = useRenderDropIndicator(
429
+ dragAndDropHooks,
430
+ dropState,
431
+ subSlot(slot, 'dropIndicator'),
432
+ );
433
+
434
+ return createElement(FocusScope, {
435
+ children: createElement(dom.div, {
436
+ ...mergeProps(
437
+ DOMProps,
438
+ renderProps,
439
+ listBoxProps,
440
+ focusProps,
441
+ droppableCollection?.collectionProps,
442
+ ),
443
+ ref: listBoxRef,
444
+ slot: props.slot || undefined,
445
+ onScroll: props.onScroll,
446
+ 'data-drop-target': isRootDropTarget || undefined,
447
+ 'data-empty': isEmpty || undefined,
448
+ 'data-focused': isFocused || undefined,
449
+ 'data-focus-visible': isFocusVisible || undefined,
450
+ 'data-layout': props.layout || 'stack',
451
+ 'data-orientation': orientation,
452
+ // Positional (non-array) children: the trailing empty/drag-preview slots are
453
+ // unkeyed siblings of the collection Provider, exactly like upstream's JSX.
454
+ children: createElement(
455
+ Fragment,
456
+ null,
457
+ createElement(Provider, {
458
+ values: [
459
+ [ListBoxContext, props],
460
+ [ListStateContext, state],
461
+ [DragAndDropContext, { dragAndDropHooks, dragState, dropState }],
462
+ [SeparatorContext, { elementType: 'div' }],
463
+ [DropIndicatorContext, { render: ListBoxDropIndicatorWrapper }],
464
+ [SectionContext, { name: 'ListBoxSection', render: ListBoxSectionInner }],
465
+ ] as any,
466
+ children: createElement(SharedElementTransition, {
467
+ children: createElement(CollectionRoot, {
468
+ collection,
469
+ scrollRef: listBoxRef,
470
+ persistedKeys,
471
+ renderDropIndicator,
472
+ }),
473
+ }),
474
+ }),
475
+ emptyState,
476
+ dragPreview,
477
+ ),
478
+ }),
479
+ });
480
+ }
481
+
482
+ export interface ListBoxSectionProps<T>
483
+ extends SectionProps<T>, DOMRenderProps<'section', undefined> {
484
+ /**
485
+ * The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
486
+ * element.
487
+ *
488
+ * @default 'react-aria-ListBoxSection'
489
+ */
490
+ className?: string;
491
+ }
492
+
493
+ function ListBoxSectionInner<T>(
494
+ props: ListBoxSectionProps<T>,
495
+ ref: any,
496
+ section: Node<T>,
497
+ className = 'react-aria-ListBoxSection',
498
+ ): any {
499
+ const slot = S('ListBoxSection');
500
+ let state = useContext(ListStateContext)!;
501
+ let { dragAndDropHooks, dropState } = useContext(DragAndDropContext)!;
502
+ let { CollectionBranch } = useContext(CollectionRendererContext);
503
+ let [headingRef, heading] = useSlot(undefined, subSlot(slot, 'heading'));
504
+ let { headingProps, groupProps } = useListBoxSection(
505
+ {
506
+ heading,
507
+ 'aria-label': props['aria-label'] ?? undefined,
508
+ },
509
+ subSlot(slot, 'section'),
510
+ );
511
+ let renderProps = useRenderProps(
512
+ {
513
+ ...props,
514
+ id: undefined,
515
+ children: undefined,
516
+ defaultClassName: className,
517
+ values: undefined,
518
+ } as any,
519
+ subSlot(slot, 'render'),
520
+ );
521
+ let renderDropIndicator = useRenderDropIndicator(
522
+ dragAndDropHooks,
523
+ dropState,
524
+ subSlot(slot, 'dropIndicator'),
525
+ );
526
+
527
+ let DOMProps = filterDOMProps(props as any, { global: true });
528
+ delete DOMProps.id;
529
+
530
+ return createElement(dom.section, {
531
+ ...mergeProps(DOMProps, renderProps, groupProps),
532
+ ref,
533
+ children: createElement(HeaderContext.Provider, {
534
+ value: { ...headingProps, ref: headingRef },
535
+ children: createElement(CollectionBranch, {
536
+ collection: state.collection,
537
+ parent: section,
538
+ renderDropIndicator,
539
+ }),
540
+ }),
541
+ });
542
+ }
543
+
544
+ /**
545
+ * A ListBoxSection represents a section within a ListBox.
546
+ */
547
+ export const ListBoxSection: <T extends object = object>(
548
+ props: ListBoxSectionProps<T> & { ref?: any },
549
+ ) => any = /*#__PURE__*/ createBranchComponent(SectionNode, ListBoxSectionInner);
550
+
551
+ export interface ListBoxItemRenderProps extends ItemRenderProps {}
552
+
553
+ export interface ListBoxItemProps<T = object>
554
+ extends
555
+ Omit<RenderProps<ListBoxItemRenderProps>, 'render'>,
556
+ PossibleLinkDOMRenderProps<'div', ListBoxItemRenderProps>,
557
+ LinkDOMProps,
558
+ HoverEvents,
559
+ PressEvents,
560
+ KeyboardEvents,
561
+ FocusEvents<HTMLDivElement>,
562
+ Omit<GlobalDOMAttributes, 'onClick'> {
563
+ /**
564
+ * The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
565
+ * element. A function may be provided to compute the class based on component state.
566
+ *
567
+ * @default 'react-aria-ListBoxItem'
568
+ */
569
+ className?: ClassNameOrFunction<ListBoxItemRenderProps>;
570
+ /** The unique id of the item. */
571
+ id?: Key;
572
+ /**
573
+ * The object value that this item represents. When using dynamic collections, this is set
574
+ * automatically.
575
+ */
576
+ value?: T;
577
+ /** A string representation of the item's contents, used for features like typeahead. */
578
+ textValue?: string;
579
+ /** An accessibility label for this item. */
580
+ 'aria-label'?: string;
581
+ /** Whether the item is disabled. */
582
+ isDisabled?: boolean;
583
+ /**
584
+ * Handler that is called when a user performs an action on the item. The exact user event depends
585
+ * on the collection's `selectionBehavior` prop and the interaction modality.
586
+ */
587
+ onAction?: () => void;
588
+ }
589
+
590
+ /**
591
+ * A ListBoxItem represents an individual option in a ListBox.
592
+ */
593
+ export const ListBoxItem: <T extends object = object>(
594
+ props: ListBoxItemProps<T> & { ref?: any },
595
+ ) => any = /*#__PURE__*/ createLeafComponent(
596
+ ItemNode,
597
+ // The third (item) parameter is declared so `render.length === 3` keeps the
598
+ // engine's "cannot be rendered outside a collection" guard; it is always
599
+ // provided when rendered from a collection node.
600
+ function ListBoxItem<T>(props: ListBoxItemProps<T>, forwardedRef: any, item?: Node<T>): any {
601
+ const slot = S('ListBoxItem');
602
+ let ref = useObjectRef<any>(forwardedRef, subSlot(slot, 'objectRef'));
603
+ let state = useContext(ListStateContext)!;
604
+ let { dragAndDropHooks, dragState, dropState } = useContext(DragAndDropContext)!;
605
+ let isDraggable =
606
+ dragState && !(dragState.isDisabled || dragState.selectionManager.isDisabled(item!.key));
607
+ let { optionProps, labelProps, descriptionProps, ...states } = useOption(
608
+ { key: item!.key, 'aria-label': props?.['aria-label'] },
609
+ state,
610
+ ref,
611
+ subSlot(slot, 'option'),
612
+ );
613
+
614
+ let { hoverProps, isHovered } = useHover(
615
+ {
616
+ isDisabled: !states.allowsSelection && !states.hasAction && !isDraggable,
617
+ onHoverStart: item!.props.onHoverStart,
618
+ onHoverChange: item!.props.onHoverChange,
619
+ onHoverEnd: item!.props.onHoverEnd,
620
+ },
621
+ subSlot(slot, 'hover'),
622
+ );
623
+
624
+ let { keyboardProps } = useKeyboard(props, subSlot(slot, 'keyboard'));
625
+ let { focusProps } = useFocus(props, subSlot(slot, 'focus'));
626
+
627
+ // PHASE-7: consumer-provided dnd item hooks (unreachable until useDragAndDrop is ported).
628
+ let draggableItem: DraggableItemResult | null = null;
629
+ if (dragState && dragAndDropHooks) {
630
+ draggableItem = dragAndDropHooks.useDraggableItem!(
631
+ { key: item!.key, hasAction: states.hasAction },
632
+ dragState,
633
+ );
634
+ }
635
+
636
+ let droppableItem: DroppableItemResult | null = null;
637
+ if (dropState && dragAndDropHooks) {
638
+ droppableItem = dragAndDropHooks.useDroppableItem!(
639
+ {
640
+ target: { type: 'item', key: item!.key, dropPosition: 'on' },
641
+ },
642
+ dropState,
643
+ ref,
644
+ );
645
+ }
646
+
647
+ let isDragging = dragState && dragState.isDragging(item!.key);
648
+ let renderProps = useRenderProps<ListBoxItemRenderProps, any>(
649
+ {
650
+ ...props,
651
+ id: undefined,
652
+ children: props.children,
653
+ defaultClassName: 'react-aria-ListBoxItem',
654
+ values: {
655
+ ...states,
656
+ isHovered,
657
+ selectionMode: state.selectionManager.selectionMode,
658
+ selectionBehavior: state.selectionManager.selectionBehavior,
659
+ allowsDragging: !!dragState,
660
+ isDragging,
661
+ isDropTarget: droppableItem?.isDropTarget,
662
+ },
663
+ } as any,
664
+ subSlot(slot, 'render'),
665
+ );
666
+
667
+ useEffect(
668
+ () => {
669
+ if (!item!.textValue && process.env.NODE_ENV !== 'production') {
670
+ console.warn(
671
+ 'A `textValue` prop is required for <ListBoxItem> elements with non-plain text children in order to support accessibility features such as type to select.',
672
+ );
673
+ }
674
+ },
675
+ [item!.textValue],
676
+ subSlot(slot, 'textValueWarn'),
677
+ );
678
+
679
+ let ElementType = props.href ? dom.a : dom.div;
680
+ let DOMProps = filterDOMProps(props as any, { global: true });
681
+ delete DOMProps.id;
682
+ delete DOMProps.onClick;
683
+
684
+ if (props.href && optionProps.tabIndex == null) {
685
+ optionProps.tabIndex = -1;
686
+ }
687
+
688
+ return createElement(ElementType, {
689
+ ...mergeProps(
690
+ DOMProps,
691
+ renderProps,
692
+ optionProps,
693
+ hoverProps,
694
+ keyboardProps,
695
+ focusProps,
696
+ draggableItem?.dragProps,
697
+ droppableItem?.dropProps,
698
+ ),
699
+ ref,
700
+ 'data-allows-dragging': !!dragState || undefined,
701
+ 'data-selected': states.isSelected || undefined,
702
+ 'data-disabled': states.isDisabled || undefined,
703
+ 'data-hovered': isHovered || undefined,
704
+ 'data-focused': states.isFocused || undefined,
705
+ 'data-focus-visible': states.isFocusVisible || undefined,
706
+ 'data-pressed': states.isPressed || undefined,
707
+ 'data-dragging': isDragging || undefined,
708
+ 'data-drop-target': droppableItem?.isDropTarget || undefined,
709
+ 'data-selection-mode':
710
+ state.selectionManager.selectionMode === 'none'
711
+ ? undefined
712
+ : state.selectionManager.selectionMode,
713
+ children: createElement(Provider, {
714
+ values: [
715
+ [
716
+ TextContext,
717
+ {
718
+ slots: {
719
+ [DEFAULT_SLOT]: labelProps,
720
+ label: labelProps,
721
+ description: descriptionProps,
722
+ },
723
+ },
724
+ ],
725
+ [SelectionIndicatorContext, { isSelected: states.isSelected }],
726
+ ] as any,
727
+ children: renderProps.children,
728
+ }),
729
+ });
730
+ },
731
+ );
732
+
733
+ function ListBoxDropIndicatorWrapper(props: DropIndicatorProps, forwardedRef: any): any {
734
+ const slot = S('ListBoxDropIndicatorWrapper');
735
+ let ref = useObjectRef<HTMLElement>(forwardedRef, subSlot(slot, 'objectRef'));
736
+ let { dragAndDropHooks, dropState } = useContext(DragAndDropContext)!;
737
+ let { dropIndicatorProps, isHidden, isDropTarget } = dragAndDropHooks!.useDropIndicator!(
738
+ props,
739
+ dropState!,
740
+ ref,
741
+ );
742
+
743
+ if (isHidden) {
744
+ return null;
745
+ }
746
+
747
+ return createElement(ListBoxDropIndicator, {
748
+ ...props,
749
+ dropIndicatorProps,
750
+ isDropTarget,
751
+ ref,
752
+ });
753
+ }
754
+
755
+ interface ListBoxDropIndicatorProps extends DropIndicatorProps {
756
+ dropIndicatorProps: Record<string, any>;
757
+ isDropTarget: boolean;
758
+ ref?: any;
759
+ }
760
+
761
+ // octane adaptation: no forwardRef — the forwarded ref arrives as `props.ref` (upstream wraps
762
+ // this in `forwardRef` as ListBoxDropIndicatorForwardRef).
763
+ function ListBoxDropIndicator(props: ListBoxDropIndicatorProps): any {
764
+ const slot = S('ListBoxDropIndicator');
765
+ let { dropIndicatorProps, isDropTarget, ref, ...otherProps } = props;
766
+
767
+ let renderProps = useRenderProps(
768
+ {
769
+ ...otherProps,
770
+ defaultClassName: 'react-aria-DropIndicator',
771
+ values: {
772
+ isDropTarget,
773
+ },
774
+ } as any,
775
+ subSlot(slot, 'render'),
776
+ );
777
+
778
+ return createElement(dom.div, {
779
+ ...dropIndicatorProps,
780
+ ...renderProps,
781
+ role: 'option',
782
+ ref,
783
+ 'data-drop-target': isDropTarget || undefined,
784
+ });
785
+ }
786
+
787
+ export interface ListBoxLoadMoreItemProps
788
+ extends
789
+ Omit<LoadMoreSentinelProps, 'collection'>,
790
+ StyleProps,
791
+ DOMRenderProps<'div', undefined>,
792
+ GlobalDOMAttributes {
793
+ /**
794
+ * The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the
795
+ * element.
796
+ *
797
+ * @default 'react-aria-ListBoxLoadMoreItem'
798
+ */
799
+ className?: string;
800
+ /**
801
+ * The load more spinner to render when loading additional items.
802
+ */
803
+ children?: ReactNode;
804
+ /**
805
+ * Whether or not the loading spinner should be rendered or not.
806
+ */
807
+ isLoading?: boolean;
808
+ }
809
+
810
+ export const ListBoxLoadMoreItem: (props: ListBoxLoadMoreItemProps & { ref?: any }) => any =
811
+ createLeafComponent(
812
+ LoaderNode,
813
+ // Third (item) parameter declared for the engine's `render.length === 3` guard.
814
+ function ListBoxLoadingIndicator(
815
+ props: ListBoxLoadMoreItemProps,
816
+ ref: any,
817
+ item?: Node<object>,
818
+ ): any {
819
+ const slot = S('ListBoxLoadMoreItem');
820
+ let state = useContext(ListStateContext)!;
821
+ let { isLoading, onLoadMore, scrollOffset, ...otherProps } = props;
822
+
823
+ let sentinelRef = useRef<HTMLDivElement | null>(null, subSlot(slot, 'sentinel'));
824
+ let memoedLoadMoreProps = useMemo(
825
+ () => ({
826
+ onLoadMore,
827
+ collection: state?.collection,
828
+ sentinelRef,
829
+ scrollOffset,
830
+ }),
831
+ [onLoadMore, scrollOffset, state?.collection],
832
+ subSlot(slot, 'loadMoreProps'),
833
+ );
834
+ useLoadMoreSentinel(
835
+ memoedLoadMoreProps as any,
836
+ sentinelRef as any,
837
+ subSlot(slot, 'loadMore'),
838
+ );
839
+ let renderProps = useRenderProps(
840
+ {
841
+ ...otherProps,
842
+ id: undefined,
843
+ children: item!.rendered,
844
+ defaultClassName: 'react-aria-ListBoxLoadingIndicator',
845
+ values: undefined,
846
+ } as any,
847
+ subSlot(slot, 'render'),
848
+ );
849
+
850
+ let optionProps = {
851
+ // For Android talkback
852
+ tabIndex: -1,
853
+ // For now don't include aria-posinset and aria-setsize on loader since they aren't keyboard focusable
854
+ // Arguably shouldn't include them ever since it might be confusing to the user to include the loaders as part of the
855
+ // item count
856
+ };
857
+
858
+ return createElement(
859
+ Fragment,
860
+ null,
861
+ // Alway render the sentinel. For now onus is on the user for styling when using flex + gap
862
+ // (this would introduce a gap even though it doesn't take room). octane adaptation:
863
+ // upstream `inertValue` is React <19 string compat; octane follows React 19 boolean
864
+ // `inert` semantics.
865
+ createElement('div', {
866
+ style: { position: 'relative', width: 0, height: 0 },
867
+ inert: true,
868
+ children: createElement('div', {
869
+ 'data-testid': 'loadMoreSentinel',
870
+ ref: sentinelRef,
871
+ style: { position: 'absolute', height: 1, width: 1 },
872
+ }),
873
+ }),
874
+ isLoading && renderProps.children
875
+ ? createElement(dom.div, {
876
+ ...mergeProps(filterDOMProps(props, { global: true }), optionProps),
877
+ ...renderProps,
878
+ // aria-selected isn't needed here since this option is not selectable.
879
+ role: 'option',
880
+ ref,
881
+ children: renderProps.children,
882
+ })
883
+ : null,
884
+ );
885
+ },
886
+ );