Skip to content

Commit 2bda4c9

Browse files
authored
Add support for custom collection renderers (e.g. virtualization) (#5912)
1 parent 174c8af commit 2bda4c9

File tree

31 files changed

+771
-987
lines changed

31 files changed

+771
-987
lines changed

packages/@react-aria/menu/src/useMenuItem.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ export interface AriaMenuItemProps extends DOMProps, PressEvents, HoverEvents, K
100100
*/
101101
export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, ref: RefObject<FocusableElement>): MenuItemAria {
102102
let {
103+
id,
103104
key,
104105
closeOnSelect,
105106
isVirtualized,
@@ -160,6 +161,7 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
160161
let keyboardId = useSlotId();
161162

162163
let ariaProps = {
164+
id,
163165
'aria-disabled': isDisabled || undefined,
164166
role,
165167
'aria-label': props['aria-label'],

packages/@react-aria/menu/src/useSubmenuTrigger.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,19 @@
1313
import {AriaMenuItemProps} from './useMenuItem';
1414
import {AriaMenuOptions} from './useMenu';
1515
import type {AriaPopoverProps, OverlayProps} from '@react-aria/overlays';
16-
import {FocusableElement, FocusStrategy, KeyboardEvent, PressEvent, Node as RSNode} from '@react-types/shared';
16+
import {FocusableElement, FocusStrategy, KeyboardEvent, Node, PressEvent} from '@react-types/shared';
1717
import {RefObject, useCallback, useRef} from 'react';
1818
import type {SubmenuTriggerState} from '@react-stately/menu';
1919
import {useEffectEvent, useId, useLayoutEffect} from '@react-aria/utils';
2020
import {useLocale} from '@react-aria/i18n';
2121
import {useSafelyMouseToSubmenu} from './useSafelyMouseToSubmenu';
2222

2323
export interface AriaSubmenuTriggerProps {
24-
/** An object representing the submenu trigger menu item. Contains all the relevant information that makes up the menu item. */
25-
node: RSNode<unknown>,
24+
/**
25+
* An object representing the submenu trigger menu item. Contains all the relevant information that makes up the menu item.
26+
* @deprecated
27+
*/
28+
node?: Node<unknown>,
2629
/** Whether the submenu trigger is disabled. */
2730
isDisabled?: boolean,
2831
/** The type of the contents that the submenu trigger opens. */
@@ -64,7 +67,7 @@ export interface SubmenuTriggerAria<T> {
6467
* @param ref - Ref to the submenu trigger element.
6568
*/
6669
export function useSubmenuTrigger<T>(props: AriaSubmenuTriggerProps, state: SubmenuTriggerState, ref: RefObject<FocusableElement>): SubmenuTriggerAria<T> {
67-
let {parentMenuRef, submenuRef, type = 'menu', isDisabled, node, delay = 200} = props;
70+
let {parentMenuRef, submenuRef, type = 'menu', isDisabled, delay = 200} = props;
6871
let submenuTriggerId = useId();
6972
let overlayId = useId();
7073
let {direction} = useLocale();
@@ -117,7 +120,7 @@ export function useSubmenuTrigger<T>(props: AriaSubmenuTriggerProps, state: Subm
117120

118121
let submenuProps = {
119122
id: overlayId,
120-
'aria-label': node.textValue,
123+
'aria-labelledby': submenuTriggerId,
121124
submenuLevel: state.submenuLevel,
122125
...(type === 'menu' && {
123126
onClose: state.closeAll,

packages/@react-aria/virtualizer/src/ScrollView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ function ScrollView(props: ScrollViewProps, ref: RefObject<HTMLDivElement>) {
202202
};
203203

204204
return (
205-
<div {...otherProps} style={style} ref={ref} onScroll={onScroll}>
205+
<div role="presentation" {...otherProps} style={style} ref={ref} onScroll={onScroll}>
206206
<div role="presentation" style={innerStyle}>
207207
{children}
208208
</div>

packages/@react-aria/virtualizer/src/Virtualizer.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {Collection, Key} from '@react-types/shared';
1414
import {getInteractionModality} from '@react-aria/interactions';
1515
import {Layout, Rect, ReusableView, useVirtualizerState, VirtualizerState} from '@react-stately/virtualizer';
1616
import {mergeProps, useLayoutEffect} from '@react-aria/utils';
17-
import React, {FocusEvent, HTMLAttributes, ReactElement, ReactNode, RefObject, useCallback, useEffect, useMemo, useRef} from 'react';
17+
import React, {createContext, FocusEvent, HTMLAttributes, ReactElement, ReactNode, RefObject, useCallback, useEffect, useMemo, useRef} from 'react';
1818
import {ScrollView} from './ScrollView';
1919
import {VirtualizerItem} from './VirtualizerItem';
2020

@@ -39,6 +39,8 @@ interface VirtualizerProps<T extends object, V> extends Omit<HTMLAttributes<HTML
3939
autoFocus?: boolean
4040
}
4141

42+
export const VirtualizerContext = createContext<VirtualizerState<any, any, any> | null>(null);
43+
4244
function Virtualizer<T extends object, V extends ReactNode>(props: VirtualizerProps<T, V>, ref: RefObject<HTMLDivElement>) {
4345
let {
4446
children: renderView,
@@ -90,7 +92,9 @@ function Virtualizer<T extends object, V extends ReactNode>(props: VirtualizerPr
9092
onScrollEnd={state.endScrolling}
9193
sizeToFit={sizeToFit}
9294
scrollDirection={scrollDirection}>
93-
{state.visibleViews}
95+
<VirtualizerContext.Provider value={state}>
96+
{state.visibleViews}
97+
</VirtualizerContext.Provider>
9498
</ScrollView>
9599
);
96100
}

packages/@react-aria/virtualizer/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
export type {RTLOffsetType} from './utils';
1414
export type {VirtualizerItemOptions} from './useVirtualizerItem';
15-
export {useVirtualizer, Virtualizer} from './Virtualizer';
15+
export {useVirtualizer, Virtualizer, VirtualizerContext} from './Virtualizer';
1616
export {useVirtualizerItem} from './useVirtualizerItem';
1717
export {VirtualizerItem, layoutInfoToStyle} from './VirtualizerItem';
1818
export {ScrollView} from './ScrollView';

packages/@react-aria/virtualizer/src/useVirtualizerItem.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ export function useVirtualizerItem(options: VirtualizerItemOptions) {
2929
let {layoutInfo, virtualizer, ref} = options;
3030

3131
let updateSize = useCallback(() => {
32-
let size = getSize(ref.current);
33-
virtualizer.updateItemSize(layoutInfo.key, size);
34-
}, [virtualizer, layoutInfo.key, ref]);
32+
if (layoutInfo) {
33+
let size = getSize(ref.current);
34+
virtualizer.updateItemSize(layoutInfo.key, size);
35+
}
36+
}, [virtualizer, layoutInfo?.key, ref]);
3537

3638
useLayoutEffect(() => {
37-
if (layoutInfo.estimatedSize) {
39+
if (layoutInfo?.estimatedSize) {
3840
updateSize();
3941
}
4042
});

packages/@react-spectrum/listbox/src/ListBoxBase.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ function ListBoxBase<T>(props: ListBoxBaseProps<T>, ref: RefObject<HTMLDivElemen
9797
item={reusableView.content}
9898
layoutInfo={reusableView.layoutInfo}
9999
virtualizer={reusableView.virtualizer}
100-
headerLayoutInfo={children.find(c => c.viewType === 'header').layoutInfo}>
100+
headerLayoutInfo={children.find(c => c.viewType === 'header')?.layoutInfo}>
101101
{renderChildren(children.filter(c => c.viewType === 'item'))}
102102
</ListBoxSection>
103103
);

packages/@react-spectrum/listbox/src/ListBoxSection.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export function ListBoxSection<T>(props: ListBoxSectionProps<T>) {
4646

4747
return (
4848
<Fragment>
49-
<div role="presentation" ref={headerRef} style={layoutInfoToStyle(headerLayoutInfo, direction)}>
49+
{headerLayoutInfo && <div role="presentation" ref={headerRef} style={layoutInfoToStyle(headerLayoutInfo, direction)}>
5050
{item.key !== state.collection.getFirstKey() &&
5151
<div
5252
role="presentation"
@@ -67,7 +67,7 @@ export function ListBoxSection<T>(props: ListBoxSectionProps<T>) {
6767
{item.rendered}
6868
</div>
6969
}
70-
</div>
70+
</div>}
7171
<div
7272
{...groupProps}
7373
style={layoutInfoToStyle(layoutInfo, direction)}

packages/@react-spectrum/menu/src/ContextualHelpTrigger.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,9 @@ function ContextualHelpTrigger(props: InternalMenuDialogTriggerProps): ReactElem
4343
let triggerRef = useRef<HTMLLIElement>(null);
4444
let popoverRef = useRef(null);
4545
let {popoverContainer, trayContainerRef, rootMenuTriggerState, menu: parentMenuRef, state} = useMenuStateContext();
46-
let triggerNode = state.collection.getItem(targetKey);
4746
let submenuTriggerState = useSubmenuTriggerState({triggerKey: targetKey}, {...rootMenuTriggerState, ...state});
4847
let submenuRef = unwrapDOMRef(popoverRef);
4948
let {submenuTriggerProps, popoverProps} = useSubmenuTrigger({
50-
node: triggerNode,
5149
parentMenuRef,
5250
submenuRef,
5351
type: 'dialog',

packages/@react-spectrum/menu/src/SubmenuTrigger.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,9 @@ function SubmenuTrigger(props: SubmenuTriggerProps) {
4040
} = props;
4141

4242
let [menuTrigger, menu] = React.Children.toArray(children);
43-
let {popoverContainer, trayContainerRef, menu: parentMenuRef, submenu: menuRef, rootMenuTriggerState, state} = useMenuStateContext();
44-
let triggerNode = state.collection.getItem(targetKey);
43+
let {popoverContainer, trayContainerRef, menu: parentMenuRef, submenu: menuRef, rootMenuTriggerState} = useMenuStateContext();
4544
let submenuTriggerState = useSubmenuTriggerState({triggerKey: targetKey}, rootMenuTriggerState);
4645
let {submenuTriggerProps, submenuProps, popoverProps} = useSubmenuTrigger({
47-
node: triggerNode,
4846
parentMenuRef,
4947
submenuRef: menuRef
5048
}, submenuTriggerState, triggerRef);

0 commit comments

Comments
 (0)