Skip to content
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

Open
wants to merge 3 commits into
base: virtualizer-docs
Choose a base branch
from

Conversation

devongovett
Copy link
Member

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 the Layout 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 to layoutOptions changing, meaning it doesn't need to be manually memoized by the user.

* is true, all rows will have equal heights.
* @default false
*/
preserveAspectRatio?: boolean,
Copy link
Member Author

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?

let item = this.virtualizer!.collection.getItem(key);
if (item?.index != null) {
persistedIndices.push(item.index);
if (node.type === 'skeleton') {
Copy link
Member Author

Choose a reason for hiding this comment

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

Skeleton is a bit Spectrum specific for now, but maybe useful in RAC eventually? Hard to pull it out here though without forking the whole layout. Maybe fine to just leave it here unused for now?

Copy link
Member

Choose a reason for hiding this comment

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

How much do you think non-spectrum people are likely to use these?

If someone doesn't want to use the skeleton code and it's too much in terms of size, they can also create their own layout, so I'm fine with it staying.

I feel like another common usecase, which would avoid this, is going to be using other virtualizer libraries, such as tanstack.

@rspbot
Copy link

rspbot commented Feb 6, 2025

@rspbot
Copy link

rspbot commented Feb 6, 2025

@rspbot
Copy link

rspbot commented Feb 6, 2025

## API Changes

react-aria-components

/react-aria-components:UNSTABLE_TableLayout

-UNSTABLE_TableLayout <T> {
-  constructor: (ListLayoutOptions) => void
-  getContentSize: () => void
-  getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget | null
-  getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
-  getItemRect: (Key) => Rect | null
-  getLayoutInfo: (Key) => void
-  getVisibleLayoutInfos: (Rect) => void
-  getVisibleRect: () => Rect
-  shouldInvalidate: (Rect, Rect) => boolean
-  update: (InvalidationContext<TableLayoutProps>) => void
-  updateItemSize: (Key, Size) => void
-  useLayoutOptions: () => void
-  virtualizer: Virtualizer<{}, any> | null
-}

/react-aria-components:UNSTABLE_Virtualizer

-UNSTABLE_Virtualizer <O> {
-  children: ReactNode
-  layout: ILayout<O>
-  layoutOptions?: O
-}

/react-aria-components:UNSTABLE_ListLayout

-UNSTABLE_ListLayout <O = any, T> {
-  constructor: (ListLayoutOptions) => void
-  getContentSize: () => void
-  getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget | null
-  getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
-  getItemRect: (Key) => Rect | null
-  getLayoutInfo: (Key) => void
-  getVisibleLayoutInfos: (Rect) => void
-  getVisibleRect: () => Rect
-  shouldInvalidate: (Rect, Rect) => boolean
-  update: (InvalidationContext<O>) => void
-  updateItemSize: (Key, Size) => void
-  virtualizer: Virtualizer<{}, any> | null
-}

/react-aria-components:UNSTABLE_GridLayout

-UNSTABLE_GridLayout <O = any, T> {
-  constructor: (GridLayoutOptions) => void
-  getContentSize: () => Size
-  getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget
-  getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
-  getItemRect: (Key) => Rect | null
-  getLayoutInfo: (Key) => LayoutInfo | null
-  getVisibleLayoutInfos: (Rect) => Array<LayoutInfo>
-  getVisibleRect: () => Rect
-  shouldInvalidate: (Rect, Rect) => boolean
-  update: () => void
-  updateItemSize: (Key, Size) => boolean
-  virtualizer: Virtualizer<{}, any> | null
-}

/react-aria-components:ListLayoutOptions

 ListLayoutOptions {
-  dropIndicatorThickness?: number
+  dropIndicatorThickness?: number = 2
   estimatedHeadingHeight?: number
   estimatedRowHeight?: number
-  headingHeight?: number
-  loaderHeight?: number
-  rowHeight?: number
+  gap?: number = 0
+  headingHeight?: number = 48
+  loaderHeight?: number = 48
+  padding?: number = 0
+  rowHeight?: number = 48
 }

/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:TableLayout

+TableLayout <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>
+  layoutOptions?: O
+}

/react-aria-components:ListLayout

+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
+  shouldInvalidateLayoutOptions: (ListLayoutOptions, ListLayoutOptions) => boolean
+  update: (InvalidationContext<ListLayoutOptions>) => void
+  updateItemSize: (Key, Size) => void
+  virtualizer: Virtualizer<{}, any> | null
+}

/react-aria-components:GridLayout

+GridLayout <O extends GridLayoutOptions = GridLayoutOptions, T> {
+  getContentSize: () => Size
+  getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget
+  getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
+  getLayoutInfo: (Key) => LayoutInfo
+  getVisibleLayoutInfos: (Rect) => Array<LayoutInfo>
+  shouldInvalidate: (Rect, Rect) => boolean
+  shouldInvalidateLayoutOptions: (GridLayoutOptions, GridLayoutOptions) => boolean
+  update: (InvalidationContext<GridLayoutOptions>) => void
+  updateItemSize: (Key, Size) => void
+  virtualizer: Virtualizer<{}, any> | null
+}

/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: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:LayoutInfo

+LayoutInfo {
+  allowOverflow: boolean = false
+  constructor: (string, Key, Rect) => void
+  content: any | null
+  copy: () => LayoutInfo
+  estimatedSize: boolean = false
+  isSticky: boolean = false
+  key: Key
+  opacity: number = 1
+  parentKey: Key | null
+  rect: Rect
+  transform: string | null
+  type: string
+  zIndex: number
+}

/react-aria-components:Size

+Size {
+  area: any
+  constructor: (any, any) => void
+  copy: () => Size
+  equals: (Size) => boolean
+  height: number
+  width: number
+}

/react-aria-components:Rect

+Rect {
+  area: number
+  bottomLeft: Point
+  bottomRight: Point
+  constructor: (any, any, any, any) => void
+  containsPoint: (Point) => boolean
+  containsRect: (Rect) => boolean
+  copy: () => Rect
+  equals: (Rect) => void
+  getCornerInRect: (Rect) => RectCorner | null
+  height: number
+  intersection: (Rect) => Rect
+  intersects: (Rect) => boolean
+  maxX: number
+  maxY: number
+  pointEquals: (Point | Rect) => void
+  sizeEquals: (Size | Rect) => void
+  topLeft: Point
+  topRight: Point
+  union: (Rect) => void
+  width: number
+  x: number
+  y: number
+}

/react-aria-components:Point

+Point {
+  constructor: (any, any) => void
+  copy: () => Point
+  equals: (Point) => boolean
+  isOrigin: () => boolean
+  x: number
+  y: number
+}

/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
-  getItemRect: (Key) => Rect | null
   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
-  getVisibleRect: () => Rect
   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
-  getItemRect: (Key) => Rect | null
   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
-  getVisibleRect: () => Rect
   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
-  getItemRect: (Key) => Rect | null
   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
-  getVisibleRect: () => Rect
   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-spectrum/s2

/@react-spectrum/s2:ImageProps

 ImageProps {
   UNSAFE_className?: string
   UNSAFE_style?: CSSProperties
   alt?: string
   crossOrigin?: 'anonymous' | 'use-credentials'
   decoding?: 'async' | 'auto' | 'sync'
-  fetchPriority?: 'high' | 'low' | 'auto'
   group?: ImageGroup
   loading?: 'eager' | 'lazy'
   referrerPolicy?: HTMLAttributeReferrerPolicy
   renderError?: () => ReactNode
   src?: string
   styles?: StyleString
 }

@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:ListLayoutOptions

 ListLayoutOptions {
-  dropIndicatorThickness?: number
+  dropIndicatorThickness?: number = 2
   estimatedHeadingHeight?: number
   estimatedRowHeight?: number
-  headingHeight?: number
-  loaderHeight?: number
-  rowHeight?: number
+  gap?: number = 0
+  headingHeight?: number = 48
+  loaderHeight?: number = 48
+  padding?: number = 0
+  rowHeight?: number = 48
 }

/@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
-  getItemRect: (Key) => Rect | null
-  getLayoutInfo: (Key) => LayoutInfo | null
+  getLayoutInfo: (Key) => LayoutInfo
   getVisibleLayoutInfos: (Rect) => Array<LayoutInfo>
-  getVisibleRect: () => Rect
   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
-  getItemRect: (Key) => Rect | null
   getLayoutInfo: (Key) => void
   getVisibleLayoutInfos: (Rect) => void
-  getVisibleRect: () => Rect
   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
-  getItemRect: (Key) => Rect | null
   getLayoutInfo: (Key) => void
   getVisibleLayoutInfos: (Rect) => void
-  getVisibleRect: () => Rect
   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 {}> {
+Layout <O = any, T extends {} = Node<any>> {
   getContentSize: () => Size
   getDropTargetLayoutInfo: (ItemDropTarget) => LayoutInfo
-  getItemRect: (Key) => Rect | null
   getLayoutInfo: (Key) => LayoutInfo | null
   getVisibleLayoutInfos: (Rect) => Array<LayoutInfo>
-  getVisibleRect: () => Rect
   shouldInvalidate: (Rect, Rect) => boolean
+  shouldInvalidateLayoutOptions: (O, O) => boolean
   update: (InvalidationContext<O>) => void
   updateItemSize: (Key, Size) => boolean
   virtualizer: Virtualizer<{}, any> | null
 }

/@react-stately/virtualizer:LayoutInfo

 LayoutInfo {
   allowOverflow: boolean = false
   constructor: (string, Key, Rect) => void
   content: any | null
   copy: () => LayoutInfo
-  estimatedSize: boolean
-  isSticky: boolean
+  estimatedSize: boolean = false
+  isSticky: boolean = false
   key: Key
-  opacity: number
+  opacity: number = 1
   parentKey: Key | null
   rect: Rect
   transform: string | null
   type: string
 }

items.push({id: i, name});
}

let layout = useMemo(() => new WaterfallLayout(), []);
Copy link
Member

Choose a reason for hiding this comment

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

is there any legitimate case for invalidating this and creating a new instance? if not, then should people just pass the layout class into the layout prop?
could use layoutOptions for any initial/setup values they wanted

I assume we'd want to discourage people from sending values to the constructor, instead making use of the layoutOptions

let item = this.virtualizer!.collection.getItem(key);
if (item?.index != null) {
persistedIndices.push(item.index);
if (node.type === 'skeleton') {
Copy link
Member

Choose a reason for hiding this comment

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

How much do you think non-spectrum people are likely to use these?

If someone doesn't want to use the skeleton code and it's too much in terms of size, they can also create their own layout, so I'm fine with it staying.

I feel like another common usecase, which would avoid this, is going to be using other virtualizer libraries, such as tanstack.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants