From 9ec0b15bf0fcc4a4f000ee0f12bf5d16bb30f467 Mon Sep 17 00:00:00 2001 From: Valerii Sidorenko Date: Mon, 30 Sep 2024 15:12:33 +0200 Subject: [PATCH 1/8] feat(Virtializer): add fixed size rows virtualizer with hierarchy --- package-lock.json | 26 ++ package.json | 1 + .../lab/Virtualizer/Virtualizer.tsx | 231 ++++++++++++++++++ 3 files changed, 258 insertions(+) create mode 100644 src/components/lab/Virtualizer/Virtualizer.tsx diff --git a/package-lock.json b/package-lock.json index c384c6844d..c11370b6ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@floating-ui/react": "^0.26.28", "@gravity-ui/i18n": "^1.7.0", "@gravity-ui/icons": "^2.11.0", + "@tanstack/react-virtual": "^3.10.8", "blueimp-md5": "^2.19.0", "focus-trap": "^7.6.2", "lodash": "^4.17.21", @@ -6241,6 +6242,31 @@ "@swc/counter": "^0.1.3" } }, + "node_modules/@tanstack/react-virtual": { + "version": "3.10.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.10.8.tgz", + "integrity": "sha512-VbzbVGSsZlQktyLrP5nxE+vE1ZR+U0NFAWPbJLoG2+DKPwd2D7dVICTVIIaYlJqX1ZCEnYDbaOpmMwbsyhBoIA==", + "dependencies": { + "@tanstack/virtual-core": "3.10.8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.10.8", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.10.8.tgz", + "integrity": "sha512-PBu00mtt95jbKFi6Llk9aik8bnR3tR/oQP1o3TSi+iG//+Q2RTIzCEgKkHG8BB86kxMNW6O8wku+Lmi+QFR6jA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", diff --git a/package.json b/package.json index 62b8cb8617..3cbe219647 100644 --- a/package.json +++ b/package.json @@ -138,6 +138,7 @@ "@floating-ui/react": "^0.26.28", "@gravity-ui/i18n": "^1.7.0", "@gravity-ui/icons": "^2.11.0", + "@tanstack/react-virtual": "^3.10.8", "blueimp-md5": "^2.19.0", "focus-trap": "^7.6.2", "lodash": "^4.17.21", diff --git a/src/components/lab/Virtualizer/Virtualizer.tsx b/src/components/lab/Virtualizer/Virtualizer.tsx new file mode 100644 index 0000000000..d3e0ef8ba5 --- /dev/null +++ b/src/components/lab/Virtualizer/Virtualizer.tsx @@ -0,0 +1,231 @@ +import React from 'react'; + +import type {Rect, VirtualItem} from '@tanstack/react-virtual'; +import {useVirtualizer} from '@tanstack/react-virtual'; + +import {useForkRef} from '../../../hooks'; +import type {Key} from '../../types'; + +type Item = {index: number; key: Key}; + +export type ScrollAlignment = 'start' | 'center' | 'end' | 'auto'; + +export interface VirtualizerRef { + scrollToOffset: (offset: number, align?: ScrollAlignment) => void; + scrollOffset: number | null; + scrollRect: Rect | null; +} + +export function Virtualizer({ + listRef, + containerRef, + size, + getItemSize, + getItemKey, + disableVirtualization, + renderRow, +}: { + listRef?: React.Ref; + containerRef?: React.Ref; + size: number; + getItemSize: (index: number, key?: Key) => number; + getItemKey: (index: number, key?: Key) => Key; + disableVirtualization?: boolean; + renderRow: ( + item: Item, + parentKey: Key | undefined, + renderChildren: ({size, height}: {size: number; height: number}) => React.ReactNode, + ) => React.ReactNode; +}) { + const scrollContainerRef = React.useRef(null); + const ref = useForkRef(containerRef, scrollContainerRef); + + const virtualizer = useVirtualizer({ + count: size, + getScrollElement: () => scrollContainerRef.current, + getItemKey, + estimateSize: getItemSize, + overscan: disableVirtualization ? size : 0, + }); + + React.useImperativeHandle( + listRef, + () => ({ + scrollToOffset: (offset: number, align: ScrollAlignment = 'auto') => { + virtualizer.scrollToOffset(virtualizer.getOffsetForAlignment(offset, align)); + }, + get scrollOffset() { + return virtualizer.scrollOffset; + }, + get scrollRect() { + return virtualizer.scrollRect; + }, + }), + [virtualizer], + ); + + const visibleItems = virtualizer.getVirtualItems(); + + return ( +
+ {renderChildren({ + height: virtualizer.getTotalSize(), + start: 0, + items: visibleItems, + scrollContainer: virtualizer.scrollElement, + parentKey: undefined, + renderRow, + getItemSize, + getItemKey, + disableVirtualization, + })} +
+ ); +} + +function renderChildren({ + height, + start, + parentKey, + getItemSize, + getItemKey, + renderRow, + items, + scrollContainer, + disableVirtualization, +}: { + height: number; + start: number; + parentKey?: Key; + getItemSize: (index: number, key?: Key) => number; + getItemKey: (index: number, key?: Key) => Key; + renderRow: ( + item: Item, + parentKey: Key | undefined, + renderChildren: ({size, height}: {size: number; height: number}) => React.ReactNode, + ) => React.ReactNode; + items: VirtualItem[]; + scrollContainer: HTMLElement | null; + disableVirtualization?: boolean; +}) { + return ( +
+ {items.map((virtualRow) => ( +
+ {renderRow(virtualRow as Item, parentKey, ({height, size}) => ( + + ))} +
+ ))} +
+ ); +} + +function ChildrenVirtualizer(props: { + start: number; + scrollContainer: HTMLElement | null; + size: number; + getItemSize: (index: number, key?: Key) => number; + getItemKey: (index: number, key?: Key) => Key; + parentKey: Key; + renderRow: ( + item: Item, + parentKey: Key | undefined, + renderChildren: ({size, height}: {size: number; height: number}) => React.ReactNode, + ) => React.ReactNode; + disableVirtualization?: boolean; +}) { + const { + start, + scrollContainer, + size, + getItemSize, + getItemKey, + renderRow, + parentKey, + disableVirtualization, + } = props; + const virtualizer = useVirtualizer({ + count: size, + getScrollElement: () => scrollContainer, + estimateSize: (index) => getItemSize(index, parentKey), + getItemKey: (index) => getItemKey(index, parentKey), + scrollToFn: () => {}, // parent element controls scroll, so disable it here + paddingStart: start, + overscan: 0, + enabled: !disableVirtualization, + }); + + let items = virtualizer.getVirtualItems(); + let height = virtualizer.getTotalSize() - start; + if (disableVirtualization) { + height = 0; + items = new Array(size).fill(0).map((_, index) => { + height += getItemSize(index, parentKey); + return { + index, + key: getItemKey(index), + start: 0, + end: 0, + size: 0, + lane: 0, + }; + }); + } + + return renderChildren({ + getItemKey, + getItemSize, + height, + start, + items, + scrollContainer, + parentKey, + renderRow, + disableVirtualization, + }); +} From 53436fd33a9e576509a1402f4cf0663866034a3c Mon Sep 17 00:00:00 2001 From: Valerii Sidorenko Date: Tue, 1 Oct 2024 16:55:35 +0200 Subject: [PATCH 2/8] feat(Virtualizer): add load more functionality --- .../lab/Virtualizer/Virtualizer.tsx | 29 +++++--- .../lab/Virtualizer/useLoadMore.tsx | 68 +++++++++++++++++++ 2 files changed, 87 insertions(+), 10 deletions(-) create mode 100644 src/components/lab/Virtualizer/useLoadMore.tsx diff --git a/src/components/lab/Virtualizer/Virtualizer.tsx b/src/components/lab/Virtualizer/Virtualizer.tsx index d3e0ef8ba5..44c3d69118 100644 --- a/src/components/lab/Virtualizer/Virtualizer.tsx +++ b/src/components/lab/Virtualizer/Virtualizer.tsx @@ -5,6 +5,9 @@ import {useVirtualizer} from '@tanstack/react-virtual'; import {useForkRef} from '../../../hooks'; import type {Key} from '../../types'; +import type {Loadable} from '../Collection/Collection'; + +import {useLoadMore} from './useLoadMore'; type Item = {index: number; key: Key}; @@ -16,15 +19,7 @@ export interface VirtualizerRef { scrollRect: Rect | null; } -export function Virtualizer({ - listRef, - containerRef, - size, - getItemSize, - getItemKey, - disableVirtualization, - renderRow, -}: { +interface VirtualizerProps extends Loadable { listRef?: React.Ref; containerRef?: React.Ref; size: number; @@ -36,7 +31,19 @@ export function Virtualizer({ parentKey: Key | undefined, renderChildren: ({size, height}: {size: number; height: number}) => React.ReactNode, ) => React.ReactNode; -}) { +} + +export function Virtualizer({ + listRef, + containerRef, + size, + getItemSize, + getItemKey, + disableVirtualization, + renderRow, + loading, + onLoadMore, +}: VirtualizerProps) { const scrollContainerRef = React.useRef(null); const ref = useForkRef(containerRef, scrollContainerRef); @@ -66,6 +73,8 @@ export function Virtualizer({ const visibleItems = virtualizer.getVirtualItems(); + useLoadMore(scrollContainerRef, {onLoadMore, loading}); + return (
void; + /** + * The amount of offset from bottom that should trigger load more. + * The value is multiplied to the size of the visible area. + * + * @default 1 + */ + scrollOffset?: number; +} + +export function useLoadMore( + scrollContainerRef: React.RefObject, + options: LoadMoreOptions, +) { + const {onLoadMore, loading, scrollOffset = 1} = options; + + const isLoadingRef = React.useRef(loading); + React.useEffect(() => { + const element = scrollContainerRef.current; + if (!element || typeof onLoadMore !== 'function') { + return undefined; + } + + const onScroll = () => { + if (isLoadingRef.current) { + return; + } + + const shouldLoadMore = + element.scrollHeight - element.scrollTop - element.clientHeight < + element.clientHeight * scrollOffset; + if (shouldLoadMore) { + isLoadingRef.current = true; + onLoadMore(); + } + }; + element.addEventListener('scroll', onScroll); + return () => { + element.removeEventListener('scroll', onScroll); + }; + }, [scrollContainerRef, onLoadMore, scrollOffset]); + + const loadingRef = React.useRef(loading); + React.useLayoutEffect(() => { + if (loading !== loadingRef.current) { + isLoadingRef.current = loading; + loadingRef.current = loading; + } + + const element = scrollContainerRef.current; + if (!element || typeof onLoadMore !== 'function') { + return; + } + + const shouldLoadMore = + !isLoadingRef.current && element.scrollHeight === element.clientHeight; + if (shouldLoadMore) { + isLoadingRef.current = true; + onLoadMore(); + } + }, [loading, onLoadMore, scrollContainerRef]); +} From b712520fdb07a2d43fe03c6e91aed8de2c74e739 Mon Sep 17 00:00:00 2001 From: Valerii Sidorenko Date: Fri, 4 Oct 2024 10:38:25 +0200 Subject: [PATCH 3/8] feat(Virtualizer): add persisted rows --- .../lab/Virtualizer/Virtualizer.tsx | 50 ++++++++++++++++++- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/src/components/lab/Virtualizer/Virtualizer.tsx b/src/components/lab/Virtualizer/Virtualizer.tsx index 44c3d69118..a07346c3d0 100644 --- a/src/components/lab/Virtualizer/Virtualizer.tsx +++ b/src/components/lab/Virtualizer/Virtualizer.tsx @@ -1,7 +1,9 @@ +'use client'; + import React from 'react'; -import type {Rect, VirtualItem} from '@tanstack/react-virtual'; -import {useVirtualizer} from '@tanstack/react-virtual'; +import type {Range, Rect, VirtualItem} from '@tanstack/react-virtual'; +import {defaultRangeExtractor, useVirtualizer} from '@tanstack/react-virtual'; import {useForkRef} from '../../../hooks'; import type {Key} from '../../types'; @@ -31,6 +33,7 @@ interface VirtualizerProps extends Loadable { parentKey: Key | undefined, renderChildren: ({size, height}: {size: number; height: number}) => React.ReactNode, ) => React.ReactNode; + persistedIndexes?: Array; } export function Virtualizer({ @@ -43,15 +46,19 @@ export function Virtualizer({ renderRow, loading, onLoadMore, + persistedIndexes, }: VirtualizerProps) { const scrollContainerRef = React.useRef(null); const ref = useForkRef(containerRef, scrollContainerRef); + const {rangeExtractor, persistedChildren} = + getRangeExtractorAndChildrenIndexes(persistedIndexes); const virtualizer = useVirtualizer({ count: size, getScrollElement: () => scrollContainerRef.current, getItemKey, estimateSize: getItemSize, + rangeExtractor, overscan: disableVirtualization ? size : 0, }); @@ -95,6 +102,7 @@ export function Virtualizer({ getItemSize, getItemKey, disableVirtualization, + persistedChildren, })}
); @@ -110,6 +118,7 @@ function renderChildren({ items, scrollContainer, disableVirtualization, + persistedChildren, }: { height: number; start: number; @@ -124,6 +133,7 @@ function renderChildren({ items: VirtualItem[]; scrollContainer: HTMLElement | null; disableVirtualization?: boolean; + persistedChildren?: Map>; }) { return (
))}
@@ -187,6 +198,7 @@ function ChildrenVirtualizer(props: { renderChildren: ({size, height}: {size: number; height: number}) => React.ReactNode, ) => React.ReactNode; disableVirtualization?: boolean; + persistedIndexes?: Array; }) { const { start, @@ -197,7 +209,10 @@ function ChildrenVirtualizer(props: { renderRow, parentKey, disableVirtualization, + persistedIndexes, } = props; + const {rangeExtractor, persistedChildren} = + getRangeExtractorAndChildrenIndexes(persistedIndexes); const virtualizer = useVirtualizer({ count: size, getScrollElement: () => scrollContainer, @@ -205,6 +220,7 @@ function ChildrenVirtualizer(props: { getItemKey: (index) => getItemKey(index, parentKey), scrollToFn: () => {}, // parent element controls scroll, so disable it here paddingStart: start, + rangeExtractor, overscan: 0, enabled: !disableVirtualization, }); @@ -236,5 +252,35 @@ function ChildrenVirtualizer(props: { parentKey, renderRow, disableVirtualization, + persistedChildren, }); } + +function getRangeExtractorAndChildrenIndexes(persistedIndexes?: Array) { + if (!persistedIndexes) { + return {}; + } + const persistedChildren = new Map>(); + const persist: number[] = []; + for (const [index, ...childrenIndexes] of persistedIndexes) { + if (index >= 0) { + persist.push(index); + const children = persistedChildren.get(index) ?? []; + children.push(childrenIndexes); + persistedChildren.set(index, children); + } + } + + if (persist.length === 0) { + return {}; + } + + const rangeExtractor = (range: Range) => { + const next = new Set( + persist.filter((i) => i < range.count).concat(defaultRangeExtractor(range)), + ); + return Array.from(next).sort((a, b) => a - b); + }; + + return {rangeExtractor, persistedChildren}; +} From 5a48b6557f552c7047e3fa30cc35e5014f38995e Mon Sep 17 00:00:00 2001 From: Valerii Sidorenko Date: Sun, 6 Oct 2024 02:04:58 +0200 Subject: [PATCH 4/8] feat(Virtualizer) adjust and comment API --- .../lab/Virtualizer/Virtualizer.tsx | 55 ++++++++++++------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/src/components/lab/Virtualizer/Virtualizer.tsx b/src/components/lab/Virtualizer/Virtualizer.tsx index a07346c3d0..77450acf71 100644 --- a/src/components/lab/Virtualizer/Virtualizer.tsx +++ b/src/components/lab/Virtualizer/Virtualizer.tsx @@ -15,31 +15,48 @@ type Item = {index: number; key: Key}; export type ScrollAlignment = 'start' | 'center' | 'end' | 'auto'; -export interface VirtualizerRef { +export interface VirtualizerApi { scrollToOffset: (offset: number, align?: ScrollAlignment) => void; scrollOffset: number | null; scrollRect: Rect | null; } interface VirtualizerProps extends Loadable { - listRef?: React.Ref; + /** The ref of the virtualizer api. */ + apiRef?: React.Ref; + /** The ref of the scroll container element. */ containerRef?: React.Ref; - size: number; - getItemSize: (index: number, key?: Key) => number; - getItemKey: (index: number, key?: Key) => Key; + /** The number of first level items in the list. */ + count: number; + /** The size of the item in the list. Size should include all children. For children items parentKey is passed. */ + getItemSize: (index: number, parentKey?: Key) => number; + /** The key of the item in the list. For children items parentKey is passed. */ + getItemKey: (index: number, parentKey?: Key) => Key; + /** Disables virtualization of the list. This might be useful for small lists. */ disableVirtualization?: boolean; + /** Renders the row of the list. */ renderRow: ( + /** The item of the row. + * @param item.index The index of the item in current level. + * @param item.key The key of the item in the list. + */ item: Item, + /** The key of the parent item in the list. */ parentKey: Key | undefined, - renderChildren: ({size, height}: {size: number; height: number}) => React.ReactNode, + /** Renders the children of the row. + * @param options.count The number of children items. + * @param options.height The self height of the row. + */ + renderChildren: (options: {count: number; height: number}) => React.ReactNode, ) => React.ReactNode; + /** The indexes of the persisted items. Each item is an array of indexes in the hierarchy. */ persistedIndexes?: Array; } export function Virtualizer({ - listRef, + apiRef, containerRef, - size, + count, getItemSize, getItemKey, disableVirtualization, @@ -54,16 +71,16 @@ export function Virtualizer({ const {rangeExtractor, persistedChildren} = getRangeExtractorAndChildrenIndexes(persistedIndexes); const virtualizer = useVirtualizer({ - count: size, + count, getScrollElement: () => scrollContainerRef.current, getItemKey, estimateSize: getItemSize, rangeExtractor, - overscan: disableVirtualization ? size : 0, + overscan: disableVirtualization ? count : 0, }); React.useImperativeHandle( - listRef, + apiRef, () => ({ scrollToOffset: (offset: number, align: ScrollAlignment = 'auto') => { virtualizer.scrollToOffset(virtualizer.getOffsetForAlignment(offset, align)); @@ -128,7 +145,7 @@ function renderChildren({ renderRow: ( item: Item, parentKey: Key | undefined, - renderChildren: ({size, height}: {size: number; height: number}) => React.ReactNode, + renderChildren: (options: {count: number; height: number}) => React.ReactNode, ) => React.ReactNode; items: VirtualItem[]; scrollContainer: HTMLElement | null; @@ -165,10 +182,10 @@ function renderChildren({ } } > - {renderRow(virtualRow as Item, parentKey, ({height, size}) => ( + {renderRow(virtualRow as Item, parentKey, ({height, count}) => ( number; getItemKey: (index: number, key?: Key) => Key; parentKey: Key; renderRow: ( item: Item, parentKey: Key | undefined, - renderChildren: ({size, height}: {size: number; height: number}) => React.ReactNode, + renderChildren: (options: {count: number; height: number}) => React.ReactNode, ) => React.ReactNode; disableVirtualization?: boolean; persistedIndexes?: Array; @@ -203,7 +220,7 @@ function ChildrenVirtualizer(props: { const { start, scrollContainer, - size, + count, getItemSize, getItemKey, renderRow, @@ -214,7 +231,7 @@ function ChildrenVirtualizer(props: { const {rangeExtractor, persistedChildren} = getRangeExtractorAndChildrenIndexes(persistedIndexes); const virtualizer = useVirtualizer({ - count: size, + count, getScrollElement: () => scrollContainer, estimateSize: (index) => getItemSize(index, parentKey), getItemKey: (index) => getItemKey(index, parentKey), @@ -229,7 +246,7 @@ function ChildrenVirtualizer(props: { let height = virtualizer.getTotalSize() - start; if (disableVirtualization) { height = 0; - items = new Array(size).fill(0).map((_, index) => { + items = new Array(count).fill(0).map((_, index) => { height += getItemSize(index, parentKey); return { index, From 1bad3db73b40ea7f5ab8553cb70216e1097fad2d Mon Sep 17 00:00:00 2001 From: Valerii Sidorenko Date: Tue, 22 Oct 2024 10:00:36 +0200 Subject: [PATCH 5/8] feat(Virtualizer): support dinamic size of items for flat list --- .../lab/Virtualizer/Virtualizer.tsx | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/components/lab/Virtualizer/Virtualizer.tsx b/src/components/lab/Virtualizer/Virtualizer.tsx index 77450acf71..2619b1c37b 100644 --- a/src/components/lab/Virtualizer/Virtualizer.tsx +++ b/src/components/lab/Virtualizer/Virtualizer.tsx @@ -2,7 +2,12 @@ import React from 'react'; -import type {Range, Rect, VirtualItem} from '@tanstack/react-virtual'; +import type { + Range, + Rect, + VirtualItem, + Virtualizer as VirtualizerInstance, +} from '@tanstack/react-virtual'; import {defaultRangeExtractor, useVirtualizer} from '@tanstack/react-virtual'; import {useForkRef} from '../../../hooks'; @@ -17,6 +22,7 @@ export type ScrollAlignment = 'start' | 'center' | 'end' | 'auto'; export interface VirtualizerApi { scrollToOffset: (offset: number, align?: ScrollAlignment) => void; + scrollToIndex: (index: number, align?: ScrollAlignment) => void; scrollOffset: number | null; scrollRect: Rect | null; } @@ -85,6 +91,9 @@ export function Virtualizer({ scrollToOffset: (offset: number, align: ScrollAlignment = 'auto') => { virtualizer.scrollToOffset(virtualizer.getOffsetForAlignment(offset, align)); }, + scrollToIndex: (index: number, align: ScrollAlignment = 'auto') => { + virtualizer.scrollToIndex(index, {align}); + }, get scrollOffset() { return virtualizer.scrollOffset; }, @@ -109,8 +118,8 @@ export function Virtualizer({ height: '100%', }} > - {renderChildren({ - height: virtualizer.getTotalSize(), + {renderRows({ + totalHeight: virtualizer.getTotalSize(), start: 0, items: visibleItems, scrollContainer: virtualizer.scrollElement, @@ -120,13 +129,14 @@ export function Virtualizer({ getItemKey, disableVirtualization, persistedChildren, + measureElement: virtualizer.measureElement, })} ); } -function renderChildren({ - height, +function renderRows({ + totalHeight, start, parentKey, getItemSize, @@ -136,8 +146,9 @@ function renderChildren({ scrollContainer, disableVirtualization, persistedChildren, + measureElement, }: { - height: number; + totalHeight: number; start: number; parentKey?: Key; getItemSize: (index: number, key?: Key) => number; @@ -151,15 +162,16 @@ function renderChildren({ scrollContainer: HTMLElement | null; disableVirtualization?: boolean; persistedChildren?: Map>; + measureElement?: VirtualizerInstance['measureElement']; }) { return (
{items.map((virtualRow) => (
Date: Tue, 5 Nov 2024 09:24:35 +0100 Subject: [PATCH 6/8] feat(Virtualizer): allow set any html attributes to container --- src/components/lab/Virtualizer/Virtualizer.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/lab/Virtualizer/Virtualizer.tsx b/src/components/lab/Virtualizer/Virtualizer.tsx index 2619b1c37b..886a3925dd 100644 --- a/src/components/lab/Virtualizer/Virtualizer.tsx +++ b/src/components/lab/Virtualizer/Virtualizer.tsx @@ -27,7 +27,7 @@ export interface VirtualizerApi { scrollRect: Rect | null; } -interface VirtualizerProps extends Loadable { +interface VirtualizerProps extends Loadable, React.HTMLAttributes { /** The ref of the virtualizer api. */ apiRef?: React.Ref; /** The ref of the scroll container element. */ @@ -70,6 +70,7 @@ export function Virtualizer({ loading, onLoadMore, persistedIndexes, + ...props }: VirtualizerProps) { const scrollContainerRef = React.useRef(null); const ref = useForkRef(containerRef, scrollContainerRef); @@ -110,12 +111,12 @@ export function Virtualizer({ return (
{renderRows({ From c1fd5a025de14e6fc44d41d74385c01a14b355a3 Mon Sep 17 00:00:00 2001 From: Valerii Sidorenko Date: Fri, 29 Nov 2024 10:41:44 +0100 Subject: [PATCH 7/8] fix(Virtualizer): add Loadable interface --- src/components/lab/Virtualizer/Virtualizer.tsx | 2 +- src/components/lab/Virtualizer/useLoadMore.tsx | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/lab/Virtualizer/Virtualizer.tsx b/src/components/lab/Virtualizer/Virtualizer.tsx index 886a3925dd..1dfeb2b42d 100644 --- a/src/components/lab/Virtualizer/Virtualizer.tsx +++ b/src/components/lab/Virtualizer/Virtualizer.tsx @@ -12,9 +12,9 @@ import {defaultRangeExtractor, useVirtualizer} from '@tanstack/react-virtual'; import {useForkRef} from '../../../hooks'; import type {Key} from '../../types'; -import type {Loadable} from '../Collection/Collection'; import {useLoadMore} from './useLoadMore'; +import type {Loadable} from './useLoadMore'; type Item = {index: number; key: Key}; diff --git a/src/components/lab/Virtualizer/useLoadMore.tsx b/src/components/lab/Virtualizer/useLoadMore.tsx index 4903496e39..949fc1da97 100644 --- a/src/components/lab/Virtualizer/useLoadMore.tsx +++ b/src/components/lab/Virtualizer/useLoadMore.tsx @@ -1,10 +1,13 @@ import React from 'react'; -interface LoadMoreOptions { - /** Whether the data is currently loading. */ +export interface Loadable { + /** Whether the items are currently loading. */ loading?: boolean; /** Handler that is called when more items should be loaded, e.g. while scrolling near the bottom. */ onLoadMore?: () => void; +} + +export interface LoadMoreOptions extends Loadable { /** * The amount of offset from bottom that should trigger load more. * The value is multiplied to the size of the visible area. From 87b755d8a6d3c56187abd3d4733111f3733818d8 Mon Sep 17 00:00:00 2001 From: Valerii Sidorenko Date: Wed, 11 Dec 2024 11:19:33 +0100 Subject: [PATCH 8/8] fix: rename --- src/components/lab/Virtualizer/useLoadMore.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/lab/Virtualizer/useLoadMore.tsx b/src/components/lab/Virtualizer/useLoadMore.tsx index 949fc1da97..d029f2a8bc 100644 --- a/src/components/lab/Virtualizer/useLoadMore.tsx +++ b/src/components/lab/Virtualizer/useLoadMore.tsx @@ -49,11 +49,11 @@ export function useLoadMore( }; }, [scrollContainerRef, onLoadMore, scrollOffset]); - const loadingRef = React.useRef(loading); + const prevLoadingPropRef = React.useRef(loading); React.useLayoutEffect(() => { - if (loading !== loadingRef.current) { + if (loading !== prevLoadingPropRef.current) { isLoadingRef.current = loading; - loadingRef.current = loading; + prevLoadingPropRef.current = loading; } const element = scrollContainerRef.current;