@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.
- package/package.json +7 -5
- package/src/autocomplete/useAutocomplete.ts +656 -0
- package/src/collections/BaseCollection.ts +374 -0
- package/src/collections/CollectionBuilder.ts +343 -0
- package/src/collections/Document.ts +451 -0
- package/src/collections/Hidden.ts +84 -0
- package/src/collections/useCachedChildren.ts +103 -0
- package/src/components/Autocomplete.ts +124 -0
- package/src/components/Breadcrumbs.ts +170 -0
- package/src/components/Button.ts +209 -0
- package/src/components/Checkbox.ts +593 -0
- package/src/components/Collection.ts +301 -0
- package/src/components/ComboBox.ts +477 -0
- package/src/components/Dialog.ts +195 -0
- package/src/components/Disclosure.ts +322 -0
- package/src/components/DragAndDrop.ts +177 -0
- package/src/components/FieldError.ts +79 -0
- package/src/components/Form.ts +65 -0
- package/src/components/GridList.ts +1099 -0
- package/src/components/Group.ts +146 -0
- package/src/components/Header.ts +32 -0
- package/src/components/Heading.ts +45 -0
- package/src/components/Input.ts +139 -0
- package/src/components/Keyboard.ts +23 -0
- package/src/components/Label.ts +30 -0
- package/src/components/Link.ts +137 -0
- package/src/components/ListBox.ts +886 -0
- package/src/components/Menu.ts +685 -0
- package/src/components/Meter.ts +100 -0
- package/src/components/Modal.ts +352 -0
- package/src/components/NumberField.ts +221 -0
- package/src/components/OverlayArrow.ts +117 -0
- package/src/components/Popover.ts +385 -0
- package/src/components/ProgressBar.ts +112 -0
- package/src/components/RadioGroup.ts +542 -0
- package/src/components/SearchField.ts +208 -0
- package/src/components/Select.ts +505 -0
- package/src/components/SelectionIndicator.ts +52 -0
- package/src/components/Separator.ts +97 -0
- package/src/components/SharedElementTransition.ts +209 -0
- package/src/components/Slider.ts +472 -0
- package/src/components/Switch.ts +449 -0
- package/src/components/Tabs.ts +675 -0
- package/src/components/TagGroup.ts +443 -0
- package/src/components/Text.ts +27 -0
- package/src/components/TextArea.ts +92 -0
- package/src/components/TextField.ts +200 -0
- package/src/components/ToggleButton.ts +147 -0
- package/src/components/ToggleButtonGroup.ts +106 -0
- package/src/components/Toolbar.ts +74 -0
- package/src/components/Tooltip.ts +249 -0
- package/src/components/index.ts +302 -0
- package/src/components/useDragAndDrop.ts +164 -0
- package/src/components/utils.ts +521 -0
- package/src/intl/autocomplete/ar-AE.json +3 -0
- package/src/intl/autocomplete/bg-BG.json +3 -0
- package/src/intl/autocomplete/cs-CZ.json +3 -0
- package/src/intl/autocomplete/da-DK.json +3 -0
- package/src/intl/autocomplete/de-DE.json +3 -0
- package/src/intl/autocomplete/el-GR.json +3 -0
- package/src/intl/autocomplete/en-US.json +3 -0
- package/src/intl/autocomplete/es-ES.json +3 -0
- package/src/intl/autocomplete/et-EE.json +3 -0
- package/src/intl/autocomplete/fi-FI.json +3 -0
- package/src/intl/autocomplete/fr-FR.json +3 -0
- package/src/intl/autocomplete/he-IL.json +3 -0
- package/src/intl/autocomplete/hr-HR.json +3 -0
- package/src/intl/autocomplete/hu-HU.json +3 -0
- package/src/intl/autocomplete/index.ts +75 -0
- package/src/intl/autocomplete/it-IT.json +3 -0
- package/src/intl/autocomplete/ja-JP.json +3 -0
- package/src/intl/autocomplete/ko-KR.json +3 -0
- package/src/intl/autocomplete/lt-LT.json +3 -0
- package/src/intl/autocomplete/lv-LV.json +3 -0
- package/src/intl/autocomplete/nb-NO.json +3 -0
- package/src/intl/autocomplete/nl-NL.json +3 -0
- package/src/intl/autocomplete/pl-PL.json +3 -0
- package/src/intl/autocomplete/pt-BR.json +3 -0
- package/src/intl/autocomplete/pt-PT.json +3 -0
- package/src/intl/autocomplete/ro-RO.json +3 -0
- package/src/intl/autocomplete/ru-RU.json +3 -0
- package/src/intl/autocomplete/sk-SK.json +3 -0
- package/src/intl/autocomplete/sl-SI.json +3 -0
- package/src/intl/autocomplete/sr-SP.json +3 -0
- package/src/intl/autocomplete/sv-SE.json +3 -0
- package/src/intl/autocomplete/tr-TR.json +3 -0
- package/src/intl/autocomplete/uk-UA.json +3 -0
- package/src/intl/autocomplete/zh-CN.json +3 -0
- package/src/intl/autocomplete/zh-TW.json +3 -0
- package/src/stately/autocomplete/useAutocompleteState.ts +85 -0
- package/src/utils/animation.ts +149 -0
- package/src/utils/useLoadMoreSentinel.ts +78 -0
- 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
|
+
);
|