Skip to content

Commit b46d23b

Browse files
authored
Render drop indicators with collection renderers and add GridLayout (#6631)
1 parent 4f553ad commit b46d23b

29 files changed

+726
-222
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ export function useScrollView(props: ScrollViewProps, ref: RefObject<HTMLElement
243243
style
244244
},
245245
contentProps: {
246+
role: 'presentation',
246247
style: innerStyle
247248
}
248249
};

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

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,20 @@
1313
import {Collection, Key} from '@react-types/shared';
1414
import {Layout, Rect, ReusableView, useVirtualizerState, VirtualizerState} from '@react-stately/virtualizer';
1515
import {mergeProps, useLayoutEffect} from '@react-aria/utils';
16-
import React, {createContext, HTMLAttributes, ReactElement, ReactNode, RefObject, useCallback, useMemo, useRef} from 'react';
16+
import React, {HTMLAttributes, ReactElement, ReactNode, RefObject, useCallback, useMemo, useRef} from 'react';
1717
import {ScrollView} from './ScrollView';
1818
import {VirtualizerItem} from './VirtualizerItem';
1919

20+
type RenderWrapper<T extends object, V> = (
21+
parent: ReusableView<T, V> | null,
22+
reusableView: ReusableView<T, V>,
23+
children: ReusableView<T, V>[],
24+
renderChildren: (views: ReusableView<T, V>[]) => ReactElement[]
25+
) => ReactElement;
26+
2027
interface VirtualizerProps<T extends object, V, O> extends Omit<HTMLAttributes<HTMLElement>, 'children'> {
2128
children: (type: string, content: T) => V,
22-
renderWrapper?: (
23-
parent: ReusableView<T, V> | null,
24-
reusableView: ReusableView<T, V>,
25-
children: ReusableView<T, V>[],
26-
renderChildren: (views: ReusableView<T, V>[]) => ReactElement[]
27-
) => ReactElement,
29+
renderWrapper?: RenderWrapper<T, V>,
2830
layout: Layout<T, O>,
2931
collection: Collection<T>,
3032
focusedKey?: Key,
@@ -35,8 +37,6 @@ interface VirtualizerProps<T extends object, V, O> extends Omit<HTMLAttributes<H
3537
layoutOptions?: O
3638
}
3739

38-
export const VirtualizerContext = createContext<VirtualizerState<any, any, any> | null>(null);
39-
4040
function Virtualizer<T extends object, V extends ReactNode, O>(props: VirtualizerProps<T, V, O>, ref: RefObject<HTMLDivElement | null>) {
4141
let {
4242
children: renderView,
@@ -61,7 +61,6 @@ function Virtualizer<T extends object, V extends ReactNode, O>(props: Virtualize
6161
layout,
6262
collection,
6363
renderView,
64-
renderWrapper: renderWrapper || defaultRenderWrapper,
6564
onVisibleRectChange(rect) {
6665
ref.current.scrollLeft = rect.x;
6766
ref.current.scrollTop = rect.y;
@@ -81,9 +80,7 @@ function Virtualizer<T extends object, V extends ReactNode, O>(props: Virtualize
8180
onScrollEnd={state.endScrolling}
8281
sizeToFit={sizeToFit}
8382
scrollDirection={scrollDirection}>
84-
<VirtualizerContext.Provider value={state}>
85-
{state.visibleViews}
86-
</VirtualizerContext.Provider>
83+
{renderChildren(null, state.visibleViews, renderWrapper || defaultRenderWrapper)}
8784
</ScrollView>
8885
);
8986
}
@@ -96,7 +93,7 @@ interface VirtualizerOptions {
9693
}
9794

9895
// eslint-disable-next-line @typescript-eslint/no-unused-vars
99-
export function useVirtualizer<T extends object, V extends ReactNode, W>(props: VirtualizerOptions, state: VirtualizerState<T, V, W>, ref: RefObject<HTMLElement | null>) {
96+
export function useVirtualizer<T extends object, V extends ReactNode, W>(props: VirtualizerOptions, state: VirtualizerState<T, V>, ref: RefObject<HTMLElement | null>) {
10097
let {isLoading, onLoadMore} = props;
10198
let {setVisibleRect, virtualizer} = state;
10299

@@ -153,6 +150,17 @@ export function useVirtualizer<T extends object, V extends ReactNode, W>(props:
153150
const _Virtualizer = React.forwardRef(Virtualizer) as <T extends object, V, O>(props: VirtualizerProps<T, V, O> & {ref?: RefObject<HTMLDivElement | null>}) => ReactElement;
154151
export {_Virtualizer as Virtualizer};
155152

153+
function renderChildren<T extends object, V>(parent: ReusableView<T, V> | null, views: ReusableView<T, V>[], renderWrapper: RenderWrapper<T, V>) {
154+
return views.map(view => {
155+
return renderWrapper(
156+
parent,
157+
view,
158+
view.children ? Array.from(view.children) : [],
159+
childViews => renderChildren(view, childViews, renderWrapper)
160+
);
161+
});
162+
}
163+
156164
function defaultRenderWrapper<T extends object, V extends ReactNode>(
157165
parent: ReusableView<T, V> | null,
158166
reusableView: ReusableView<T, V>

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, VirtualizerContext} from './Virtualizer';
15+
export {useVirtualizer, Virtualizer} from './Virtualizer';
1616
export {useVirtualizerItem} from './useVirtualizerItem';
1717
export {VirtualizerItem, layoutInfoToStyle} from './VirtualizerItem';
1818
export {ScrollView, useScrollView} from './ScrollView';

packages/@react-spectrum/list/src/ListViewLayout.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
/*
2+
* Copyright 2024 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
112
import {InvalidationContext, LayoutInfo, Rect} from '@react-stately/virtualizer';
213
import {LayoutNode, ListLayout} from '@react-stately/layout';
314
import {Node} from '@react-types/shared';

packages/@react-spectrum/table/src/TableViewBase.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -550,11 +550,10 @@ function TableVirtualizer<T>(props: TableVirtualizerProps<T>) {
550550
getDefaultMinWidth
551551
}, tableState);
552552

553-
let state = useVirtualizerState<object, ReactNode, ReactNode>({
553+
let state = useVirtualizerState<GridNode<unknown>, ReactNode>({
554554
layout,
555555
collection,
556556
renderView,
557-
renderWrapper,
558557
onVisibleRectChange(rect) {
559558
bodyRef.current.scrollTop = rect.y;
560559
setScrollLeft(bodyRef.current, direction, rect.x);
@@ -623,6 +622,8 @@ function TableVirtualizer<T>(props: TableVirtualizerProps<T>) {
623622
scrollPadding = columnResizeState.getColumnWidth(firstColumn.key);
624623
}
625624

625+
let visibleViews = renderChildren(null, state.visibleViews, renderWrapper);
626+
626627
return (
627628
<VirtualizerContext.Provider value={resizingColumn}>
628629
<FocusScope>
@@ -641,7 +642,7 @@ function TableVirtualizer<T>(props: TableVirtualizerProps<T>) {
641642
}}
642643
ref={headerRef}>
643644
<ResizeStateContext.Provider value={columnResizeState}>
644-
{state.visibleViews[0]}
645+
{visibleViews[0]}
645646
</ResizeStateContext.Provider>
646647
</div>
647648
<ScrollView
@@ -679,7 +680,7 @@ function TableVirtualizer<T>(props: TableVirtualizerProps<T>) {
679680
onScrollStart={state.startScrolling}
680681
onScrollEnd={state.endScrolling}
681682
onScroll={onScroll}>
682-
{state.visibleViews[1]}
683+
{visibleViews[1]}
683684
<div
684685
className={classNames(styles, 'spectrum-Table-bodyResizeIndicator')}
685686
style={{[direction === 'ltr' ? 'left' : 'right']: `${resizerPosition}px`, height: `${Math.max(state.virtualizer.contentSize.height, state.virtualizer.visibleRect.height)}px`, display: columnResizeState.resizingColumn ? 'block' : 'none'}} />
@@ -690,6 +691,17 @@ function TableVirtualizer<T>(props: TableVirtualizerProps<T>) {
690691
);
691692
}
692693

694+
function renderChildren<T extends object>(parent: View | null, views: View[], renderWrapper: TableVirtualizerProps<T>['renderWrapper']) {
695+
return views.map(view => {
696+
return renderWrapper(
697+
parent,
698+
view,
699+
view.children ? Array.from(view.children) : [],
700+
childViews => renderChildren(view, childViews, renderWrapper)
701+
);
702+
});
703+
}
704+
693705
function useStyle(layoutInfo: LayoutInfo, parent: LayoutInfo | null) {
694706
let {direction} = useLocale();
695707
let style = layoutInfoToStyle(layoutInfo, direction, parent);

packages/@react-spectrum/table/src/TableViewLayout.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,15 @@
1+
/*
2+
* Copyright 2024 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
import {DropTarget} from '@react-types/shared';
113
import {GridNode} from '@react-types/grid';
214
import {LayoutInfo, Rect} from '@react-stately/virtualizer';
315
import {LayoutNode, TableLayout} from '@react-stately/layout';
@@ -66,4 +78,10 @@ export class TableViewLayout<T> extends TableLayout<T> {
6678
protected isStickyColumn(node: GridNode<T>) {
6779
return node.props?.isDragButtonCell || node.props?.isSelectionCell;
6880
}
81+
82+
getDropTargetFromPoint(x: number, y: number, isValidDropTarget: (target: DropTarget) => boolean): DropTarget {
83+
// Offset for height of header row
84+
y -= this.virtualizer.layout.getVisibleLayoutInfos(new Rect(x, y, 1, 1)).find(info => info.type === 'headerrow')?.rect.height;
85+
return super.getDropTargetFromPoint(x, y, isValidDropTarget);
86+
}
6987
}

packages/@react-stately/data/src/useListData.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ export function useListData<T>(options: ListOptions<T>): ListData<T> {
140140
let {
141141
initialItems = [],
142142
initialSelectedKeys,
143-
getKey = (item: any) => item.id || item.key,
143+
getKey = (item: any) => item.id ?? item.key,
144144
filter,
145145
initialFilterText = ''
146146
} = options;

0 commit comments

Comments
 (0)