-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Add WaterfallLayout to RAC, and update GridLayout #7729
Conversation
* is true, all rows will have equal heights. | ||
* @default false | ||
*/ | ||
preserveAspectRatio?: boolean, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we think variable row heights or fixed row heights are a better default?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New API looks good
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good in general.
The one comment I have is that the scrolling experience on https://reactspectrum.blob.core.windows.net/reactspectrum/2e42453df169d922e48d7c5cf18000f2a8dc2a0a/docs/react-aria/Virtualizer.html#listlayout GridList with variable height rows due to text wrapping.
is really pretty bad. Even after scrolling the entire thing so that the layoutInfos have been calculated. Oddly enough, waterfall only has this issue initially, it's fine once all the layoutinfos are calculated.
I haven't looked into why that would be, but happy to help out. I don't know if it's bad enough to block release on.
Trying to click and drag the scrollbar is pretty much a no go. When scrolling fast with the scrollwheel, the contents sometimes entirely disappear, making it hard to scan as you scroll past. Maybe the second one could be solved with a larger overscroll area?
@snowystinger I'm not seeing the behavior you describe. It scrolls really smoothly for me. Did you have the dev tools open when testing this? That definitely causes a slowdown. |
No, no dev tools open. Here's a screen recording of what I'm seeing. Keep in mind, in this video I already scrolled the entire list once, so all the layoutInfos should exist already. It's way worse on first scroll. |
what browser? here's how it looks in chrome for me Screen.Recording.2025-02-18.at.11.56.22.AM.mov |
Also chrome |
The base branch was changed.
2e42453
to
cf908e8
Compare
last commit should fix verdaccio build on main |
## API Changes
react-aria-components/react-aria-components:TableLayout-TableLayout <T> {
+TableLayout <O extends TableLayoutProps = TableLayoutProps, T> {
constructor: (ListLayoutOptions) => void
getContentSize: () => void
getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget | null
getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
getLayoutInfo: (Key) => void
getVisibleLayoutInfos: (Rect) => void
shouldInvalidate: (Rect, Rect) => boolean
+ shouldInvalidateLayoutOptions: (TableLayoutProps, TableLayoutProps) => boolean
update: (InvalidationContext<TableLayoutProps>) => void
updateItemSize: (Key, Size) => void
useLayoutOptions: () => void
virtualizer: Virtualizer<{}, any> | null /react-aria-components:Virtualizer Virtualizer <O> {
children: ReactNode
- layout: ILayout<O>
+ layout: LayoutClass<O> | ILayout<O>
layoutOptions?: O
} /react-aria-components:ListLayout-ListLayout <O = any, T> {
+ListLayout <O extends ListLayoutOptions = ListLayoutOptions, T> {
constructor: (ListLayoutOptions) => void
getContentSize: () => void
getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget | null
getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
getLayoutInfo: (Key) => void
getVisibleLayoutInfos: (Rect) => void
shouldInvalidate: (Rect, Rect) => boolean
- update: (InvalidationContext<O>) => void
+ shouldInvalidateLayoutOptions: (ListLayoutOptions, ListLayoutOptions) => boolean
+ update: (InvalidationContext<ListLayoutOptions>) => void
updateItemSize: (Key, Size) => void
virtualizer: Virtualizer<{}, any> | null
} /react-aria-components:GridLayout-GridLayout <O = any, T> {
+GridLayout <O extends GridLayoutOptions = GridLayoutOptions, T> {
- constructor: (GridLayoutOptions) => void
getContentSize: () => Size
getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget
getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
- getLayoutInfo: (Key) => LayoutInfo | null
+ getLayoutInfo: (Key) => LayoutInfo
getVisibleLayoutInfos: (Rect) => Array<LayoutInfo>
shouldInvalidate: (Rect, Rect) => boolean
- update: () => void
- updateItemSize: (Key, Size) => boolean
+ shouldInvalidateLayoutOptions: (GridLayoutOptions, GridLayoutOptions) => boolean
+ update: (InvalidationContext<GridLayoutOptions>) => void
+ updateItemSize: (Key, Size) => void
virtualizer: Virtualizer<{}, any> | null
} /react-aria-components:Layout Layout <O = any, T extends {} = Node<any>> {
getContentSize: () => Size
getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
getLayoutInfo: (Key) => LayoutInfo | null
getVisibleLayoutInfos: (Rect) => Array<LayoutInfo>
shouldInvalidate: (Rect, Rect) => boolean
+ shouldInvalidateLayoutOptions: (O, O) => boolean
update: (InvalidationContext<O>) => void
updateItemSize: (Key, Size) => boolean
virtualizer: Virtualizer<{}, any> | null
} /react-aria-components:VirtualizerProps VirtualizerProps <O> {
children: ReactNode
- layout: ILayout<O>
+ layout: LayoutClass<O> | ILayout<O>
layoutOptions?: O
} /react-aria-components:GridLayoutOptions GridLayoutOptions {
dropIndicatorThickness?: number = 2
maxColumns?: number = Infinity
maxItemSize?: Size = Infinity
minItemSize?: Size = 200 x 200
minSpace?: Size = 18 x 18
+ preserveAspectRatio?: boolean = false
} /react-aria-components:WaterfallLayout+WaterfallLayout <O extends WaterfallLayoutOptions = WaterfallLayoutOptions, T extends {}> {
+ getContentSize: () => Size
+ getDropTargetFromPoint: (number, number) => DropTarget
+ getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
+ getKeyLeftOf: (Key) => Key | null
+ getKeyRange: (Key, Key) => Array<Key>
+ getKeyRightOf: (Key) => Key | null
+ getLayoutInfo: (Key) => LayoutInfo
+ getVisibleLayoutInfos: (Rect) => Array<LayoutInfo>
+ shouldInvalidate: (Rect, Rect) => boolean
+ shouldInvalidateLayoutOptions: (WaterfallLayoutOptions, WaterfallLayoutOptions) => boolean
+ update: (InvalidationContext<WaterfallLayoutOptions>) => void
+ updateItemSize: (Key, Size) => void
+ virtualizer: Virtualizer<{}, any> | null
+} /react-aria-components:WaterfallLayoutOptions+WaterfallLayoutOptions {
+ dropIndicatorThickness?: number = 2
+ maxColumns?: number = Infinity
+ maxItemSize?: Size = Infinity
+ minItemSize?: Size = 200 x 200
+ minSpace?: Size = 18 x 18
+} @react-spectrum/card/@react-spectrum/card:GalleryLayout GalleryLayout <T> {
_distributeWidths: (any) => void
_findClosest: (Rect, Rect) => void
_findClosestLayoutInfo: (Rect, Rect) => void
buildCollection: () => void
collection: GridCollection<T>
constructor: (GalleryLayoutOptions) => void
direction: Direction
disabledKeys: Set<Key>
getContentSize: () => void
getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
getFirstKey: () => void
getKeyAbove: (Key) => void
getKeyBelow: (Key) => void
getKeyForSearch: (string, Key) => void
getKeyLeftOf: (Key) => void
getKeyPageAbove: (Key) => void
getKeyPageBelow: (Key) => void
getKeyRightOf: (Key) => void
getLastKey: () => void
getLayoutInfo: (Key) => void
getVisibleLayoutInfos: (Rect, any) => void
isLoading: boolean
isVisible: (LayoutInfo, Rect, boolean) => void
itemPadding: number
layoutType: any
margin: number
scale: Scale
shouldInvalidate: (Rect, Rect) => boolean
+ shouldInvalidateLayoutOptions: (O, O) => boolean
update: (InvalidationContext<CardViewLayoutOptions>) => void
updateItemSize: (Key, Size) => boolean
virtualizer: Virtualizer<{}, any> | null
} /@react-spectrum/card:GridLayout GridLayout <T> {
_findClosest: (Rect, Rect) => void
_findClosestLayoutInfo: (Rect, Rect) => void
buildChild: (Node<T>, number, number) => LayoutInfo
buildCollection: () => void
cardOrientation: Orientation
collection: GridCollection<T>
constructor: (GridLayoutOptions) => void
direction: Direction
disabledKeys: Set<Key>
getContentSize: () => void
getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
getFirstKey: () => void
getIndexAtPoint: (any, any, any) => void
getKeyAbove: (Key) => void
getKeyBelow: (Key) => void
getKeyForSearch: (string, Key) => void
getKeyLeftOf: (Key) => void
getKeyPageAbove: (Key) => void
getKeyPageBelow: (Key) => void
getKeyRightOf: (Key) => void
getLastKey: () => void
getLayoutInfo: (Key) => void
getVisibleLayoutInfos: (Rect, any) => void
isLoading: boolean
isVisible: (LayoutInfo, Rect, boolean) => void
itemPadding: number
layoutType: any
margin: number
scale: Scale
shouldInvalidate: (Rect, Rect) => boolean
+ shouldInvalidateLayoutOptions: (O, O) => boolean
update: (InvalidationContext<CardViewLayoutOptions>) => void
updateItemSize: (Key, Size) => boolean
virtualizer: Virtualizer<{}, any> | null
} /@react-spectrum/card:WaterfallLayout WaterfallLayout <T> {
_findClosest: (Rect, Rect) => void
_findClosestLayoutInfo: (Rect, Rect) => void
buildCollection: (InvalidationContext) => void
collection: GridCollection<T>
constructor: (WaterfallLayoutOptions) => void
direction: Direction
disabledKeys: Set<Key>
getClosestLeft: (Key) => void
getClosestRight: (Key) => void
getContentSize: () => void
getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
getFirstKey: () => void
getKeyAbove: (Key) => void
getKeyBelow: (Key) => void
getKeyForSearch: (string, Key) => void
getKeyLeftOf: (Key) => void
getKeyPageAbove: (Key) => void
getKeyPageBelow: (Key) => void
getKeyRightOf: (Key) => void
getLastKey: () => void
getLayoutInfo: (Key) => void
getNextColumnIndex: (any) => void
getVisibleLayoutInfos: (Rect, any) => void
isLoading: boolean
isVisible: (LayoutInfo, Rect, boolean) => void
layoutType: any
margin: number
scale: Scale
shouldInvalidate: (Rect, Rect) => boolean
+ shouldInvalidateLayoutOptions: (O, O) => boolean
update: (InvalidationContext<CardViewLayoutOptions>) => void
updateItemSize: (Key, Size) => void
virtualizer: Virtualizer<{}, any> | null
} @react-stately/layout/@react-stately/layout:GridLayoutOptions GridLayoutOptions {
dropIndicatorThickness?: number = 2
maxColumns?: number = Infinity
maxItemSize?: Size = Infinity
minItemSize?: Size = 200 x 200
minSpace?: Size = 18 x 18
+ preserveAspectRatio?: boolean = false
} /@react-stately/layout:TableLayoutProps TableLayoutProps {
columnWidths?: Map<Key, number>
+ dropIndicatorThickness?: number = 2
+ estimatedHeadingHeight?: number
+ estimatedRowHeight?: number
+ gap?: number = 0
+ headingHeight?: number = 48
+ loaderHeight?: number = 48
+ padding?: number = 0
+ rowHeight?: number = 48
} /@react-stately/layout:GridLayout-GridLayout <O = any, T> {
+GridLayout <O extends GridLayoutOptions = GridLayoutOptions, T> {
- constructor: (GridLayoutOptions) => void
getContentSize: () => Size
getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget
getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
- getLayoutInfo: (Key) => LayoutInfo | null
+ getLayoutInfo: (Key) => LayoutInfo
getVisibleLayoutInfos: (Rect) => Array<LayoutInfo>
shouldInvalidate: (Rect, Rect) => boolean
- update: () => void
- updateItemSize: (Key, Size) => boolean
+ shouldInvalidateLayoutOptions: (GridLayoutOptions, GridLayoutOptions) => boolean
+ update: (InvalidationContext<GridLayoutOptions>) => void
+ updateItemSize: (Key, Size) => void
virtualizer: Virtualizer<{}, any> | null
} /@react-stately/layout:ListLayout-ListLayout <O = any, T> {
+ListLayout <O extends ListLayoutOptions = ListLayoutOptions, T> {
constructor: (ListLayoutOptions) => void
getContentSize: () => void
getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget | null
getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
getLayoutInfo: (Key) => void
getVisibleLayoutInfos: (Rect) => void
shouldInvalidate: (Rect, Rect) => boolean
- update: (InvalidationContext<O>) => void
+ shouldInvalidateLayoutOptions: (ListLayoutOptions, ListLayoutOptions) => boolean
+ update: (InvalidationContext<ListLayoutOptions>) => void
updateItemSize: (Key, Size) => void
virtualizer: Virtualizer<{}, any> | null
} /@react-stately/layout:TableLayout TableLayout <O extends TableLayoutProps = TableLayoutProps, T> {
constructor: (ListLayoutOptions) => void
getContentSize: () => void
getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget | null
getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
getLayoutInfo: (Key) => void
getVisibleLayoutInfos: (Rect) => void
shouldInvalidate: (Rect, Rect) => boolean
+ shouldInvalidateLayoutOptions: (TableLayoutProps, TableLayoutProps) => boolean
update: (InvalidationContext<TableLayoutProps>) => void
updateItemSize: (Key, Size) => void
virtualizer: Virtualizer<{}, any> | null
} /@react-stately/layout:WaterfallLayoutOptions+WaterfallLayoutOptions {
+ dropIndicatorThickness?: number = 2
+ maxColumns?: number = Infinity
+ maxItemSize?: Size = Infinity
+ minItemSize?: Size = 200 x 200
+ minSpace?: Size = 18 x 18
+} /@react-stately/layout:WaterfallLayout+WaterfallLayout <O extends WaterfallLayoutOptions = WaterfallLayoutOptions, T extends {}> {
+ getContentSize: () => Size
+ getDropTargetFromPoint: (number, number) => DropTarget
+ getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
+ getKeyLeftOf: (Key) => Key | null
+ getKeyRange: (Key, Key) => Array<Key>
+ getKeyRightOf: (Key) => Key | null
+ getLayoutInfo: (Key) => LayoutInfo
+ getVisibleLayoutInfos: (Rect) => Array<LayoutInfo>
+ shouldInvalidate: (Rect, Rect) => boolean
+ shouldInvalidateLayoutOptions: (WaterfallLayoutOptions, WaterfallLayoutOptions) => boolean
+ update: (InvalidationContext<WaterfallLayoutOptions>) => void
+ updateItemSize: (Key, Size) => void
+ virtualizer: Virtualizer<{}, any> | null
+} @react-stately/virtualizer/@react-stately/virtualizer:InvalidationContext InvalidationContext <O = any> {
contentChanged?: boolean
itemSizeChanged?: boolean
layoutOptions?: O
+ layoutOptionsChanged?: boolean
offsetChanged?: boolean
sizeChanged?: boolean
} /@react-stately/virtualizer:Layout Layout <O = any, T extends {} = Node<any>> {
getContentSize: () => Size
getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
getLayoutInfo: (Key) => LayoutInfo | null
getVisibleLayoutInfos: (Rect) => Array<LayoutInfo>
shouldInvalidate: (Rect, Rect) => boolean
+ shouldInvalidateLayoutOptions: (O, O) => boolean
update: (InvalidationContext<O>) => void
updateItemSize: (Key, Size) => boolean
virtualizer: Virtualizer<{}, any> | null
} |
Depends on #7700
This moves the WaterfallLayout from S2 into RAC, and adds it to the documentation. It also updates GridLayout to support variable row heights like in S2 instead of only equal sized items.
Note that the GridLayout changes are breaking, especially in cases where it is subclassed. This was exported as
UNSTABLE
from RAC, but not directly from the@react-stately/layout
package so we need to decide how to release this.Also updated the docs to use the
layoutOptions
prop instead of passing options to theLayout
constructor. This allows options to be changed at runtime without invalidating the entire layout. Virtualizer will ask the layout if it needs to invalidate in response tolayoutOptions
changing, meaning it doesn't need to be manually memoized by the user.