Skip to content

Commit

Permalink
Render drop indicators with collection renderers and add GridLayout (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
devongovett authored Jun 28, 2024
1 parent 4f553ad commit b46d23b
Show file tree
Hide file tree
Showing 29 changed files with 726 additions and 222 deletions.
1 change: 1 addition & 0 deletions packages/@react-aria/virtualizer/src/ScrollView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ export function useScrollView(props: ScrollViewProps, ref: RefObject<HTMLElement
style
},
contentProps: {
role: 'presentation',
style: innerStyle
}
};
Expand Down
36 changes: 22 additions & 14 deletions packages/@react-aria/virtualizer/src/Virtualizer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,20 @@
import {Collection, Key} from '@react-types/shared';
import {Layout, Rect, ReusableView, useVirtualizerState, VirtualizerState} from '@react-stately/virtualizer';
import {mergeProps, useLayoutEffect} from '@react-aria/utils';
import React, {createContext, HTMLAttributes, ReactElement, ReactNode, RefObject, useCallback, useMemo, useRef} from 'react';
import React, {HTMLAttributes, ReactElement, ReactNode, RefObject, useCallback, useMemo, useRef} from 'react';
import {ScrollView} from './ScrollView';
import {VirtualizerItem} from './VirtualizerItem';

type RenderWrapper<T extends object, V> = (
parent: ReusableView<T, V> | null,
reusableView: ReusableView<T, V>,
children: ReusableView<T, V>[],
renderChildren: (views: ReusableView<T, V>[]) => ReactElement[]
) => ReactElement;

interface VirtualizerProps<T extends object, V, O> extends Omit<HTMLAttributes<HTMLElement>, 'children'> {
children: (type: string, content: T) => V,
renderWrapper?: (
parent: ReusableView<T, V> | null,
reusableView: ReusableView<T, V>,
children: ReusableView<T, V>[],
renderChildren: (views: ReusableView<T, V>[]) => ReactElement[]
) => ReactElement,
renderWrapper?: RenderWrapper<T, V>,
layout: Layout<T, O>,
collection: Collection<T>,
focusedKey?: Key,
Expand All @@ -35,8 +37,6 @@ interface VirtualizerProps<T extends object, V, O> extends Omit<HTMLAttributes<H
layoutOptions?: O
}

export const VirtualizerContext = createContext<VirtualizerState<any, any, any> | null>(null);

function Virtualizer<T extends object, V extends ReactNode, O>(props: VirtualizerProps<T, V, O>, ref: RefObject<HTMLDivElement | null>) {
let {
children: renderView,
Expand All @@ -61,7 +61,6 @@ function Virtualizer<T extends object, V extends ReactNode, O>(props: Virtualize
layout,
collection,
renderView,
renderWrapper: renderWrapper || defaultRenderWrapper,
onVisibleRectChange(rect) {
ref.current.scrollLeft = rect.x;
ref.current.scrollTop = rect.y;
Expand All @@ -81,9 +80,7 @@ function Virtualizer<T extends object, V extends ReactNode, O>(props: Virtualize
onScrollEnd={state.endScrolling}
sizeToFit={sizeToFit}
scrollDirection={scrollDirection}>
<VirtualizerContext.Provider value={state}>
{state.visibleViews}
</VirtualizerContext.Provider>
{renderChildren(null, state.visibleViews, renderWrapper || defaultRenderWrapper)}
</ScrollView>
);
}
Expand All @@ -96,7 +93,7 @@ interface VirtualizerOptions {
}

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

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

function renderChildren<T extends object, V>(parent: ReusableView<T, V> | null, views: ReusableView<T, V>[], renderWrapper: RenderWrapper<T, V>) {
return views.map(view => {
return renderWrapper(
parent,
view,
view.children ? Array.from(view.children) : [],
childViews => renderChildren(view, childViews, renderWrapper)
);
});
}

function defaultRenderWrapper<T extends object, V extends ReactNode>(
parent: ReusableView<T, V> | null,
reusableView: ReusableView<T, V>
Expand Down
2 changes: 1 addition & 1 deletion packages/@react-aria/virtualizer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

export type {RTLOffsetType} from './utils';
export type {VirtualizerItemOptions} from './useVirtualizerItem';
export {useVirtualizer, Virtualizer, VirtualizerContext} from './Virtualizer';
export {useVirtualizer, Virtualizer} from './Virtualizer';
export {useVirtualizerItem} from './useVirtualizerItem';
export {VirtualizerItem, layoutInfoToStyle} from './VirtualizerItem';
export {ScrollView, useScrollView} from './ScrollView';
Expand Down
11 changes: 11 additions & 0 deletions packages/@react-spectrum/list/src/ListViewLayout.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
/*
* Copyright 2024 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import {InvalidationContext, LayoutInfo, Rect} from '@react-stately/virtualizer';
import {LayoutNode, ListLayout} from '@react-stately/layout';
import {Node} from '@react-types/shared';
Expand Down
20 changes: 16 additions & 4 deletions packages/@react-spectrum/table/src/TableViewBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -550,11 +550,10 @@ function TableVirtualizer<T>(props: TableVirtualizerProps<T>) {
getDefaultMinWidth
}, tableState);

let state = useVirtualizerState<object, ReactNode, ReactNode>({
let state = useVirtualizerState<GridNode<unknown>, ReactNode>({
layout,
collection,
renderView,
renderWrapper,
onVisibleRectChange(rect) {
bodyRef.current.scrollTop = rect.y;
setScrollLeft(bodyRef.current, direction, rect.x);
Expand Down Expand Up @@ -623,6 +622,8 @@ function TableVirtualizer<T>(props: TableVirtualizerProps<T>) {
scrollPadding = columnResizeState.getColumnWidth(firstColumn.key);
}

let visibleViews = renderChildren(null, state.visibleViews, renderWrapper);

return (
<VirtualizerContext.Provider value={resizingColumn}>
<FocusScope>
Expand All @@ -641,7 +642,7 @@ function TableVirtualizer<T>(props: TableVirtualizerProps<T>) {
}}
ref={headerRef}>
<ResizeStateContext.Provider value={columnResizeState}>
{state.visibleViews[0]}
{visibleViews[0]}
</ResizeStateContext.Provider>
</div>
<ScrollView
Expand Down Expand Up @@ -679,7 +680,7 @@ function TableVirtualizer<T>(props: TableVirtualizerProps<T>) {
onScrollStart={state.startScrolling}
onScrollEnd={state.endScrolling}
onScroll={onScroll}>
{state.visibleViews[1]}
{visibleViews[1]}
<div
className={classNames(styles, 'spectrum-Table-bodyResizeIndicator')}
style={{[direction === 'ltr' ? 'left' : 'right']: `${resizerPosition}px`, height: `${Math.max(state.virtualizer.contentSize.height, state.virtualizer.visibleRect.height)}px`, display: columnResizeState.resizingColumn ? 'block' : 'none'}} />
Expand All @@ -690,6 +691,17 @@ function TableVirtualizer<T>(props: TableVirtualizerProps<T>) {
);
}

function renderChildren<T extends object>(parent: View | null, views: View[], renderWrapper: TableVirtualizerProps<T>['renderWrapper']) {
return views.map(view => {
return renderWrapper(
parent,
view,
view.children ? Array.from(view.children) : [],
childViews => renderChildren(view, childViews, renderWrapper)
);
});
}

function useStyle(layoutInfo: LayoutInfo, parent: LayoutInfo | null) {
let {direction} = useLocale();
let style = layoutInfoToStyle(layoutInfo, direction, parent);
Expand Down
18 changes: 18 additions & 0 deletions packages/@react-spectrum/table/src/TableViewLayout.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
/*
* Copyright 2024 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
import {DropTarget} from '@react-types/shared';
import {GridNode} from '@react-types/grid';
import {LayoutInfo, Rect} from '@react-stately/virtualizer';
import {LayoutNode, TableLayout} from '@react-stately/layout';
Expand Down Expand Up @@ -66,4 +78,10 @@ export class TableViewLayout<T> extends TableLayout<T> {
protected isStickyColumn(node: GridNode<T>) {
return node.props?.isDragButtonCell || node.props?.isSelectionCell;
}

getDropTargetFromPoint(x: number, y: number, isValidDropTarget: (target: DropTarget) => boolean): DropTarget {
// Offset for height of header row
y -= this.virtualizer.layout.getVisibleLayoutInfos(new Rect(x, y, 1, 1)).find(info => info.type === 'headerrow')?.rect.height;
return super.getDropTargetFromPoint(x, y, isValidDropTarget);
}
}
2 changes: 1 addition & 1 deletion packages/@react-stately/data/src/useListData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export function useListData<T>(options: ListOptions<T>): ListData<T> {
let {
initialItems = [],
initialSelectedKeys,
getKey = (item: any) => item.id || item.key,
getKey = (item: any) => item.id ?? item.key,
filter,
initialFilterText = ''
} = options;
Expand Down
Loading

1 comment on commit b46d23b

@rspbot
Copy link

@rspbot rspbot commented on b46d23b Jun 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.