From 3f0c7e3301c673c437a1500e92596658bb553eba Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Fri, 23 Aug 2024 12:45:35 -0700 Subject: [PATCH 01/42] Export helper function for useMeasureList --- .../react-virtualizer/library/src/hooks/index.ts | 1 + .../react-virtualizer/library/src/index.ts | 8 +++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/react-components/react-virtualizer/library/src/hooks/index.ts b/packages/react-components/react-virtualizer/library/src/hooks/index.ts index 4b9defe52372d..1b64be17abdbb 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/index.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/index.ts @@ -3,3 +3,4 @@ export * from './useVirtualizerMeasure'; export * from './useDynamicVirtualizerMeasure'; export * from './useResizeObserverRef'; export * from './hooks.types'; +export * from './useMeasureList'; diff --git a/packages/react-components/react-virtualizer/library/src/index.ts b/packages/react-components/react-virtualizer/library/src/index.ts index bad0518470b8f..e677de0bec9d7 100644 --- a/packages/react-components/react-virtualizer/library/src/index.ts +++ b/packages/react-components/react-virtualizer/library/src/index.ts @@ -19,9 +19,15 @@ export { useStaticVirtualizerMeasure, useDynamicVirtualizerMeasure, useResizeObserverRef_unstable, + useMeasureList, } from './Hooks'; -export type { ResizeCallbackWithRef, VirtualizerMeasureDynamicProps, VirtualizerMeasureProps } from './Hooks'; +export type { + ResizeCallbackWithRef, + VirtualizerMeasureDynamicProps, + VirtualizerMeasureProps, + IndexedResizeCallbackElement, +} from './Hooks'; export type { ScrollToItemDynamicParams, ScrollToItemStaticParams, ScrollToInterface } from './Utilities'; From ab70756567f0b61ec4a9ba54ba762c0e8d51b12e Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Fri, 23 Aug 2024 12:52:45 -0700 Subject: [PATCH 02/42] Add change file --- ...t-virtualizer-bab73d05-7372-4052-8d22-ca75b5fadfe2.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 change/@fluentui-react-virtualizer-bab73d05-7372-4052-8d22-ca75b5fadfe2.json diff --git a/change/@fluentui-react-virtualizer-bab73d05-7372-4052-8d22-ca75b5fadfe2.json b/change/@fluentui-react-virtualizer-bab73d05-7372-4052-8d22-ca75b5fadfe2.json new file mode 100644 index 0000000000000..a3eaf6e136311 --- /dev/null +++ b/change/@fluentui-react-virtualizer-bab73d05-7372-4052-8d22-ca75b5fadfe2.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "fix: Export useMeasureList and type for consumption", + "packageName": "@fluentui/react-virtualizer", + "email": "mifraser@microsoft.com", + "dependentChangeType": "patch" +} From b6fbcd2c980e2f1d50f73eb0a4cbb7bd450f3836 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Fri, 23 Aug 2024 12:54:27 -0700 Subject: [PATCH 03/42] Update API --- .../library/etc/react-virtualizer.api.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/react-components/react-virtualizer/library/etc/react-virtualizer.api.md b/packages/react-components/react-virtualizer/library/etc/react-virtualizer.api.md index 6b47e848983c2..499ae441ed8d7 100644 --- a/packages/react-components/react-virtualizer/library/etc/react-virtualizer.api.md +++ b/packages/react-components/react-virtualizer/library/etc/react-virtualizer.api.md @@ -15,6 +15,12 @@ import type { SetStateAction } from 'react'; import type { Slot } from '@fluentui/react-utilities'; import type { SlotClassNames } from '@fluentui/react-utilities'; +// @public (undocumented) +export interface IndexedResizeCallbackElement { + // (undocumented) + handleResize: () => void; +} + // @public (undocumented) export const renderVirtualizer_unstable: (state: VirtualizerState) => JSX.Element; @@ -80,6 +86,15 @@ export const useIntersectionObserver: (callback: IntersectionObserverCallback, o observer: MutableRefObject; }; +// @public +export function useMeasureList(currentIndex: number, refLength: number, totalLength: number, defaultItemSize: number): { + widthArray: React_2.MutableRefObject; + heightArray: React_2.MutableRefObject; + createIndexedRef: (index: number) => (el: TElement) => void; + refArray: React_2.MutableRefObject<(TElement | null | undefined)[]>; + sizeUpdateCount: number; +}; + // @public export const useResizeObserverRef_unstable: (resizeCallback: ResizeCallbackWithRef) => (instance: HTMLElement | HTMLDivElement | null) => void; From c0765a6883b99491d825b00b6d2e9ed0fdea27b1 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Fri, 23 Aug 2024 16:20:53 -0700 Subject: [PATCH 04/42] Optimize virtualizer --- .../components/Virtualizer/useVirtualizer.ts | 76 ++++++++----------- .../src/hooks/useDynamicVirtualizerMeasure.ts | 11 ++- .../library/src/hooks/useMeasureList.ts | 10 ++- .../stories/.storybook/preview-body.html | 2 +- .../src/Virtualizer/Dynamic.stories.tsx | 5 +- 5 files changed, 48 insertions(+), 56 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index 27957cbfc4c6a..0429fe0bf4402 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -58,8 +58,6 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // We want to be methodical about updating the render with child reference array const forceUpdate = useReducer(() => ({}), {})[1]; - const horizontal = axis === 'horizontal'; - const populateSizeArrays = () => { if (!getItemSize) { // Static sizes, never mind! @@ -140,10 +138,6 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta return; } - /* IO initiates this function when needed (bookend entering view) */ - let measurementPos = 0; - let bufferCount = bufferItems; - // Grab latest entry that is intersecting const latestEntry = entries.length === 1 @@ -159,46 +153,36 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta return; } - if (latestEntry.target === afterElementRef.current) { - // We need to inverse the buffer count - bufferCount = virtualizerLength - bufferItems; - measurementPos = reversed ? calculateAfter() : calculateTotalSize() - calculateAfter(); - if (!horizontal) { - if (reversed) { - // Scrolling 'up' and hit the after element below - measurementPos -= Math.abs(latestEntry.boundingClientRect.bottom); - } else if (latestEntry.boundingClientRect.top < 0) { - // Scrolling 'down' and hit the after element above top: 0 - measurementPos -= latestEntry.boundingClientRect.top; - } - } else { - if (reversed) { - // Scrolling 'left' and hit the after element - measurementPos -= Math.abs(latestEntry.boundingClientRect.right); - } else if (latestEntry.boundingClientRect.left < 0) { - // Scrolling 'right' and hit the after element - measurementPos -= latestEntry.boundingClientRect.left; - } - } - } else if (latestEntry.target === beforeElementRef.current) { - measurementPos = reversed ? calculateTotalSize() - calculateBefore() : calculateBefore(); - if (!horizontal) { - if (!reversed) { - measurementPos -= Math.abs(latestEntry.boundingClientRect.bottom); - } else if (latestEntry.boundingClientRect.top < 0) { - // Scrolling 'down' in reverse order and hit the before element above top: 0 - measurementPos -= latestEntry.boundingClientRect.top; + const calculateOverBuffer = (latestEntry: IntersectionObserverEntry): number => { + let measurementPos = 0; + if (latestEntry.target === afterElementRef.current) { + measurementPos = reversed ? calculateAfter() : calculateTotalSize() - calculateAfter(); + const afterAmount = + axis === 'vertical' ? latestEntry.boundingClientRect.top : latestEntry.boundingClientRect.left; + const reversedAfterAmount = + axis === 'vertical' ? latestEntry.boundingClientRect.bottom : latestEntry.boundingClientRect.right; + if (!reversed && afterAmount < 0) { + measurementPos -= afterAmount; + } else if (reversed && reversedAfterAmount > 0) { + measurementPos -= reversedAfterAmount; } - } else { - if (!reversed) { - measurementPos -= Math.abs(latestEntry.boundingClientRect.right); - } else if (latestEntry.boundingClientRect.left < 0) { - // Scrolling 'left' and hit before element - measurementPos -= latestEntry.boundingClientRect.left; + } else if (latestEntry.target === beforeElementRef.current) { + measurementPos = reversed ? calculateTotalSize() - calculateBefore() : calculateBefore(); + const afterAmount = + axis === 'vertical' ? latestEntry.boundingClientRect.bottom : latestEntry.boundingClientRect.right; + const reversedAfterAmount = + axis === 'vertical' ? latestEntry.boundingClientRect.top : latestEntry.boundingClientRect.left; + if (!reversed && afterAmount > 0) { + measurementPos -= afterAmount; + } else if (reversed && reversedAfterAmount < 0) { + measurementPos -= reversedAfterAmount; } } - } + return measurementPos; + }; + /* IO initiates this function when needed (bookend entering view) */ + let measurementPos = calculateOverBuffer(latestEntry); if (reversed) { // We're reversed, up is down, left is right, invert the scroll measure. measurementPos = Math.max(calculateTotalSize() - Math.abs(measurementPos), 0); @@ -206,12 +190,12 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // For now lets use hardcoded size to assess current element to paginate on const startIndex = getIndexFromScrollPosition(measurementPos); - const bufferedIndex = Math.max(startIndex - bufferCount, 0); + const dirMod = + latestEntry.target === beforeElementRef.current ? bufferItems : virtualizerLength - 1 - bufferItems * 2; // Safety limits const maxIndex = Math.max(numItems - virtualizerLength, 0); - const newStartIndex = Math.min(Math.max(bufferedIndex, 0), maxIndex); - + const newStartIndex = Math.min(Math.max(startIndex - dirMod, 0), maxIndex); if (actualIndex !== newStartIndex) { // We flush sync this and perform an immediate state update flushSync(() => { @@ -445,7 +429,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta forceUpdate(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [renderChild, updateChildRows]); + }, [renderChild]); useEffect(() => { // Ensure we repopulate if getItemSize callback changes diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts index 9eda6d577c275..6afa36c615a41 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts @@ -58,14 +58,13 @@ export const useDynamicVirtualizerMeasure = ( /* * Number of items to append at each end, i.e. 'preload' each side before entering view. */ - const bufferItems = Math.max(Math.floor(length / 4), 4); + const bufferItems = Math.max(Math.floor(length / 3), 1); /* * This is how far we deviate into the bufferItems to detect a redraw. */ - const bufferSize = Math.max(Math.floor((length / 8) * defaultItemSize), 1); - - const totalLength = length + bufferItems * 2 + 1; + const bufferSize = Math.max(defaultItemSize / 2.0, 1); + const totalLength = length + bufferItems * 2; setState({ virtualizerLength: totalLength, virtualizerBufferSize: bufferSize, @@ -99,8 +98,8 @@ export const useDynamicVirtualizerMeasure = ( const containerSize = direction === 'vertical' - ? container.current?.getBoundingClientRect().height * 1.5 - : container.current?.getBoundingClientRect().width * 1.5; + ? container.current?.getBoundingClientRect().height + : container.current?.getBoundingClientRect().width; let couldBeSmaller = false; let recheckTotal = 0; diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useMeasureList.ts b/packages/react-components/react-virtualizer/library/src/hooks/useMeasureList.ts index ee03e649091d2..a7524e3616348 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useMeasureList.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useMeasureList.ts @@ -58,8 +58,14 @@ export function useMeasureList< }; React.useEffect(() => { - widthArray.current = new Array(totalLength).fill(defaultItemSize); - heightArray.current = new Array(totalLength).fill(defaultItemSize); + const newHeightLength = totalLength - heightArray.current.length; + const newWidthLength = totalLength - widthArray.current.length; + if (newWidthLength > 0) { + widthArray.current = widthArray.current.concat(new Array(newWidthLength).fill(defaultItemSize)); + } + if (newHeightLength > 0) { + heightArray.current = heightArray.current.concat(new Array(newHeightLength).fill(defaultItemSize)); + } }, [defaultItemSize, totalLength]); // Keep the reference of ResizeObserver as a ref, as it should live through renders diff --git a/packages/react-components/react-virtualizer/stories/.storybook/preview-body.html b/packages/react-components/react-virtualizer/stories/.storybook/preview-body.html index 22c02a93fea16..d37ea59491b1f 100644 --- a/packages/react-components/react-virtualizer/stories/.storybook/preview-body.html +++ b/packages/react-components/react-virtualizer/stories/.storybook/preview-body.html @@ -1,5 +1,5 @@ diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx index 1a4e4ad3774a1..cef1e22b02b78 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx @@ -4,7 +4,7 @@ import { useDynamicVirtualizerMeasure, VirtualizerContextProvider, } from '@fluentui/react-components/unstable'; -import { makeStyles } from '@fluentui/react-components'; +import { makeStyles, useMergedRefs } from '@fluentui/react-components'; import { useCallback, useRef } from 'react'; const smallSize = 100; @@ -70,6 +70,8 @@ export const Dynamic = () => { currentIndex, }); + const combineRefs = useMergedRefs(scrollRef); + return (
@@ -80,6 +82,7 @@ export const Dynamic = () => { bufferItems={bufferItems} virtualizerLength={virtualizerLength} itemSize={100} + scrollViewRef={combineRefs} > {useCallback( (index: number) => { From 1aff22393716cbeab65e9c2f2604eb296855ff92 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Fri, 23 Aug 2024 16:24:00 -0700 Subject: [PATCH 05/42] Truncate width/height arrays --- .../react-virtualizer/library/src/hooks/useMeasureList.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useMeasureList.ts b/packages/react-components/react-virtualizer/library/src/hooks/useMeasureList.ts index a7524e3616348..72f2c62be9f33 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useMeasureList.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useMeasureList.ts @@ -60,11 +60,16 @@ export function useMeasureList< React.useEffect(() => { const newHeightLength = totalLength - heightArray.current.length; const newWidthLength = totalLength - widthArray.current.length; + // Ensure we grow or truncate arrays with prior properties if (newWidthLength > 0) { widthArray.current = widthArray.current.concat(new Array(newWidthLength).fill(defaultItemSize)); + } else if (newWidthLength < 0) { + widthArray.current = widthArray.current.slice(0, totalLength); } if (newHeightLength > 0) { heightArray.current = heightArray.current.concat(new Array(newHeightLength).fill(defaultItemSize)); + } else if (newHeightLength < 0) { + heightArray.current = heightArray.current.slice(0, totalLength); } }, [defaultItemSize, totalLength]); From 6ec8d78b3a3f6b96612255fe832ce1c1a3c6e6e1 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Fri, 23 Aug 2024 16:36:28 -0700 Subject: [PATCH 06/42] Lint --- .../library/src/components/Virtualizer/useVirtualizer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index 0429fe0bf4402..58afa87adc325 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -153,7 +153,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta return; } - const calculateOverBuffer = (latestEntry: IntersectionObserverEntry): number => { + const calculateOverBuffer = (): number => { let measurementPos = 0; if (latestEntry.target === afterElementRef.current) { measurementPos = reversed ? calculateAfter() : calculateTotalSize() - calculateAfter(); @@ -182,7 +182,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta return measurementPos; }; /* IO initiates this function when needed (bookend entering view) */ - let measurementPos = calculateOverBuffer(latestEntry); + let measurementPos = calculateOverBuffer(); if (reversed) { // We're reversed, up is down, left is right, invert the scroll measure. measurementPos = Math.max(calculateTotalSize() - Math.abs(measurementPos), 0); From d000103332177e61c2d97effbfe04992ebe42728 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Fri, 23 Aug 2024 17:39:15 -0700 Subject: [PATCH 07/42] Update algo --- .../components/Virtualizer/useVirtualizer.ts | 22 +++++++++++-------- .../src/hooks/useDynamicVirtualizerMeasure.ts | 2 +- .../VirtualizerScrollView/Default.stories.tsx | 2 +- .../ScrollTo.stories.tsx | 2 +- .../AutoMeasure.stories.tsx | 8 +++---- .../Default.stories.tsx | 2 +- .../ScrollLoading.stories.tsx | 2 +- .../ScrollTo.stories.tsx | 2 +- 8 files changed, 23 insertions(+), 19 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index 58afa87adc325..e8a71f4c654fe 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -161,9 +161,9 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta axis === 'vertical' ? latestEntry.boundingClientRect.top : latestEntry.boundingClientRect.left; const reversedAfterAmount = axis === 'vertical' ? latestEntry.boundingClientRect.bottom : latestEntry.boundingClientRect.right; - if (!reversed && afterAmount < 0) { + if (!reversed) { measurementPos -= afterAmount; - } else if (reversed && reversedAfterAmount > 0) { + } else if (reversed) { measurementPos -= reversedAfterAmount; } } else if (latestEntry.target === beforeElementRef.current) { @@ -172,9 +172,9 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta axis === 'vertical' ? latestEntry.boundingClientRect.bottom : latestEntry.boundingClientRect.right; const reversedAfterAmount = axis === 'vertical' ? latestEntry.boundingClientRect.top : latestEntry.boundingClientRect.left; - if (!reversed && afterAmount > 0) { + if (!reversed) { measurementPos -= afterAmount; - } else if (reversed && reversedAfterAmount < 0) { + } else if (reversed) { measurementPos -= reversedAfterAmount; } } @@ -185,17 +185,21 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta let measurementPos = calculateOverBuffer(); if (reversed) { // We're reversed, up is down, left is right, invert the scroll measure. - measurementPos = Math.max(calculateTotalSize() - Math.abs(measurementPos), 0); + measurementPos = Math.max(calculateTotalSize() - measurementPos, 0); } + const dirMod = latestEntry.target === beforeElementRef.current ? -1 : 1; // For now lets use hardcoded size to assess current element to paginate on - const startIndex = getIndexFromScrollPosition(measurementPos); - const dirMod = - latestEntry.target === beforeElementRef.current ? bufferItems : virtualizerLength - 1 - bufferItems * 2; + let startIndex = getIndexFromScrollPosition(measurementPos); + + if (startIndex === actualIndex) { + startIndex += dirMod; + } // Safety limits const maxIndex = Math.max(numItems - virtualizerLength, 0); - const newStartIndex = Math.min(Math.max(startIndex - dirMod, 0), maxIndex); + const newStartIndex = Math.min(Math.max(startIndex, 0), maxIndex); + if (actualIndex !== newStartIndex) { // We flush sync this and perform an immediate state update flushSync(() => { diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts index 6afa36c615a41..c5d62bcbb6ee2 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts @@ -58,7 +58,7 @@ export const useDynamicVirtualizerMeasure = ( /* * Number of items to append at each end, i.e. 'preload' each side before entering view. */ - const bufferItems = Math.max(Math.floor(length / 3), 1); + const bufferItems = Math.max(Math.ceil(length / 3), 2); /* * This is how far we deviate into the bufferItems to detect a redraw. diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/Default.stories.tsx index e9f90c5b82320..dd3c8f2a199dc 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/Default.stories.tsx @@ -18,7 +18,7 @@ export const Default = () => { {(index: number) => { return ( diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/ScrollTo.stories.tsx b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/ScrollTo.stories.tsx index e8d7f93ae9ed8..b5e85a9c61121 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/ScrollTo.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/ScrollTo.stories.tsx @@ -42,7 +42,7 @@ export const ScrollTo = () => { {(index: number) => { diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx index 9df460624fbcc..23bc2be4025f3 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx @@ -14,8 +14,8 @@ const useStyles = makeStyles({ export const AutoMeasure = () => { const styles = useStyles(); const childLength = 1000; - const minHeight = 42; - const maxHeightIncrease = 150; + const minHeight = 300; + const maxHeightIncrease = 200; // Array size ref stores a list of random num for div sizing and callbacks const arraySize = React.useRef(new Array(childLength).fill(minHeight)); @@ -30,8 +30,8 @@ export const AutoMeasure = () => { {(index: number) => { const backgroundColor = index % 2 ? '#FFFFFF' : '#ABABAB'; diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/Default.stories.tsx index 63fff1f9724f8..1617268db3358 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/Default.stories.tsx @@ -42,7 +42,7 @@ export const Default = () => { numItems={childLength} itemSize={minHeight} getItemSize={getItemSizeCallback} - container={{ role: 'list', style: { maxHeight: '100vh' } }} + container={{ role: 'list', style: { maxHeight: '80vh' } }} > {(index: number) => { const backgroundColor = index % 2 ? '#FFFFFF' : '#ABABAB'; diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/ScrollLoading.stories.tsx b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/ScrollLoading.stories.tsx index c3e30de72025b..3f546f9d5e069 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/ScrollLoading.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/ScrollLoading.stories.tsx @@ -42,7 +42,7 @@ export const ScrollLoading = () => { numItems={childLength} itemSize={minHeight} getItemSize={getItemSizeCallback} - container={{ role: 'list', style: { maxHeight: '100vh' } }} + container={{ role: 'list', style: { maxHeight: '80vh' } }} > {(index: number, isScrolling = false) => { const backgroundColor = index % 2 ? '#FFFFFF' : '#ABABAB'; diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/ScrollTo.stories.tsx b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/ScrollTo.stories.tsx index 2400818360479..8fdb53122a9ee 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/ScrollTo.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/ScrollTo.stories.tsx @@ -70,7 +70,7 @@ export const ScrollTo = () => { getItemSize={getItemSizeCallback} imperativeRef={scrollRef} imperativeVirtualizerRef={sizeRef} - container={{ role: 'list', style: { maxHeight: '100vh' } }} + container={{ role: 'list', style: { maxHeight: '80vh' } }} > {(index: number) => { const backgroundColor = index % 2 ? '#FFFFFF' : '#ABABAB'; From 5efd619d490b23936285159576f5c49c1455b906 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Fri, 23 Aug 2024 19:36:34 -0700 Subject: [PATCH 08/42] Seperate logic to simplify --- .../components/Virtualizer/useVirtualizer.ts | 51 ++++++++++--------- .../src/hooks/useDynamicVirtualizerMeasure.ts | 2 +- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index e8a71f4c654fe..58c113e185d8f 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -155,38 +155,43 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta const calculateOverBuffer = (): number => { let measurementPos = 0; - if (latestEntry.target === afterElementRef.current) { - measurementPos = reversed ? calculateAfter() : calculateTotalSize() - calculateAfter(); - const afterAmount = - axis === 'vertical' ? latestEntry.boundingClientRect.top : latestEntry.boundingClientRect.left; - const reversedAfterAmount = - axis === 'vertical' ? latestEntry.boundingClientRect.bottom : latestEntry.boundingClientRect.right; - if (!reversed) { + if (!reversed) { + if (latestEntry.target === afterElementRef.current) { + console.log('AFTER ELE'); + measurementPos = calculateTotalSize() - calculateAfter(); + const afterAmount = + axis === 'vertical' ? latestEntry.boundingClientRect.top : latestEntry.boundingClientRect.left; measurementPos -= afterAmount; - } else if (reversed) { - measurementPos -= reversedAfterAmount; - } - } else if (latestEntry.target === beforeElementRef.current) { - measurementPos = reversed ? calculateTotalSize() - calculateBefore() : calculateBefore(); - const afterAmount = - axis === 'vertical' ? latestEntry.boundingClientRect.bottom : latestEntry.boundingClientRect.right; - const reversedAfterAmount = - axis === 'vertical' ? latestEntry.boundingClientRect.top : latestEntry.boundingClientRect.left; - if (!reversed) { + } else if (latestEntry.target === beforeElementRef.current) { + console.log('BEFORE ELE'); + measurementPos = calculateBefore(); + const afterAmount = + axis === 'vertical' ? latestEntry.boundingClientRect.bottom : latestEntry.boundingClientRect.right; measurementPos -= afterAmount; - } else if (reversed) { - measurementPos -= reversedAfterAmount; } + } else { + if (latestEntry.target === afterElementRef.current) { + console.log('REVERSED AFTER ELE'); + measurementPos = calculateAfter(); + // const reversedAfterAmount = + // axis === 'vertical' ? latestEntry.boundingClientRect.bottom : latestEntry.boundingClientRect.right; + + // measurementPos += reversedAfterAmount; + } else if (latestEntry.target === beforeElementRef.current) { + console.log('REVERSED BEFORE ELE'); + measurementPos = calculateTotalSize() - calculateBefore(); + // const reversedAfterAmount = + // axis === 'vertical' ? latestEntry.boundingClientRect.top : latestEntry.boundingClientRect.left; + + // measurementPos += reversedAfterAmount; + } + measurementPos = Math.max(calculateTotalSize() - measurementPos, 0); } return measurementPos; }; /* IO initiates this function when needed (bookend entering view) */ let measurementPos = calculateOverBuffer(); - if (reversed) { - // We're reversed, up is down, left is right, invert the scroll measure. - measurementPos = Math.max(calculateTotalSize() - measurementPos, 0); - } const dirMod = latestEntry.target === beforeElementRef.current ? -1 : 1; // For now lets use hardcoded size to assess current element to paginate on diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts index c5d62bcbb6ee2..12c4b67fb44fb 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts @@ -63,7 +63,7 @@ export const useDynamicVirtualizerMeasure = ( /* * This is how far we deviate into the bufferItems to detect a redraw. */ - const bufferSize = Math.max(defaultItemSize / 2.0, 1); + const bufferSize = Math.min(defaultItemSize / 2.0, 100); const totalLength = length + bufferItems * 2; setState({ virtualizerLength: totalLength, From 1d9c7837f7b2c824183b2dc29689e59cd32a4ff2 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Mon, 26 Aug 2024 12:46:57 -0700 Subject: [PATCH 09/42] Update after element detection --- .../Virtualizer/Virtualizer.types.ts | 5 ++ .../components/Virtualizer/useVirtualizer.ts | 65 +++++++++++-------- .../VirtualizerScrollView.types.ts | 2 +- .../useVirtualizerScrollView.ts | 3 +- .../VirtualizerScrollViewDynamic.types.ts | 2 +- .../useVirtualizerScrollViewDynamic.tsx | 3 +- .../src/hooks/useDynamicVirtualizerMeasure.ts | 5 ++ .../src/hooks/useVirtualizerMeasure.ts | 12 +++- .../src/testing/useVirtualizer.test.ts | 3 + .../src/Virtualizer/Default.stories.tsx | 18 +++-- .../Virtualizer/DefaultUnbounded.stories.tsx | 3 +- .../src/Virtualizer/Horizontal.stories.tsx | 3 +- .../Virtualizer/MultiUnbounded.stories.tsx | 3 +- .../stories/src/Virtualizer/RTL.stories.tsx | 3 +- .../src/Virtualizer/Reversed.stories.tsx | 3 +- .../ReversedHorizontal.stories.tsx | 3 +- 16 files changed, 91 insertions(+), 45 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts index f2664f6d6d9f6..d7d5ec2986e3f 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts @@ -171,6 +171,11 @@ export type VirtualizerConfigProps = { * Imperative ref contains our scrollTo index functionality for user control. */ imperativeVirtualizerRef?: RefObject; + + /** + * A ref that provides the size of container (vertical - height, horizontal - width), set by a resize observer. + */ + containerSizeRef: MutableRefObject; }; export type VirtualizerProps = ComponentProps> & VirtualizerConfigProps; diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index 58c113e185d8f..e0497c8b665ba 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -24,6 +24,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta virtualizerContext, onRenderedFlaggedIndex, imperativeVirtualizerRef, + containerSizeRef, } = props; /* The context is optional, it's useful for injecting additional index logic, or performing uniform state updates*/ @@ -31,13 +32,17 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // We use this ref as a constant source to access the virtualizer's state imperatively const actualIndexRef = useRef(_virtualizerContext.contextIndex); - if (actualIndexRef.current !== _virtualizerContext.contextIndex) { - actualIndexRef.current = _virtualizerContext.contextIndex; - } const flaggedIndex = useRef(null); const actualIndex = _virtualizerContext.contextIndex; - const setActualIndex = _virtualizerContext.setContextIndex; + // Just in case our ref gets out of date vs the context during a re-render + if (_virtualizerContext.contextIndex != actualIndexRef.current) { + actualIndexRef.current = _virtualizerContext.contextIndex; + } + const setActualIndex = (index: number) => { + _virtualizerContext.setContextIndex(index); + actualIndexRef.current = index; + }; // Store ref to before padding element const beforeElementRef = useRef(null); @@ -116,7 +121,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta updateChildRows(index); updateCurrentItemSizes(index); - // Set before 'setActualIndex' call + // Set before 'setActualIndex' call & re-render // If it changes before render, or injected via context, re-render will update ref. actualIndexRef.current = index; @@ -158,16 +163,28 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta if (!reversed) { if (latestEntry.target === afterElementRef.current) { console.log('AFTER ELE'); + // Get after buffers position measurementPos = calculateTotalSize() - calculateAfter(); - const afterAmount = - axis === 'vertical' ? latestEntry.boundingClientRect.top : latestEntry.boundingClientRect.left; - measurementPos -= afterAmount; + // Get exact intersection position based on overflow size (how far into IO did we scroll?) + let overflowAmount = + axis === 'vertical' ? latestEntry.intersectionRect.height : latestEntry.intersectionRect.width; + // Add to original after position + measurementPos += overflowAmount; + // Ignore buffer size (IO offset) + measurementPos -= bufferSize; + // we hit the after buffer and detected the end of view, we need to find the start index. + measurementPos -= containerSizeRef.current; } else if (latestEntry.target === beforeElementRef.current) { - console.log('BEFORE ELE'); - measurementPos = calculateBefore(); - const afterAmount = - axis === 'vertical' ? latestEntry.boundingClientRect.bottom : latestEntry.boundingClientRect.right; - measurementPos -= afterAmount; + // Get before buffers position + return scrollViewRef?.current?.scrollTop ?? 0; + // measurementPos = calculateBefore(); + // // console.log('Before pos:', measurementPos); + // // Get exact intersection position based on overflow size (how far into IO did we scroll?) + // const overflowAmount = + // axis === 'vertical' ? latestEntry.boundingClientRect.height : latestEntry.boundingClientRect.width; + // measurementPos -= overflowAmount; + // // Ignore buffer size (actual endpoint) + // measurementPos += bufferSize; } } else { if (latestEntry.target === afterElementRef.current) { @@ -190,16 +207,12 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta return measurementPos; }; - /* IO initiates this function when needed (bookend entering view) */ + + // Get exact 'scrollTop' via IO let measurementPos = calculateOverBuffer(); - const dirMod = latestEntry.target === beforeElementRef.current ? -1 : 1; - // For now lets use hardcoded size to assess current element to paginate on - let startIndex = getIndexFromScrollPosition(measurementPos); - - if (startIndex === actualIndex) { - startIndex += dirMod; - } + // getIndexFromScrollPosition, then start one item earlier + let startIndex = getIndexFromScrollPosition(measurementPos) - 1; // Safety limits const maxIndex = Math.max(numItems - virtualizerLength, 0); @@ -280,7 +293,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta }, [getItemSize, itemSize, numItems]); const calculateBefore = useCallback(() => { - const currentIndex = Math.min(actualIndex, numItems - 1); + const currentIndex = Math.min(actualIndexRef.current, numItems - 1); if (!getItemSize) { // The missing items from before virtualization starts height @@ -293,14 +306,14 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // Time for custom size calcs return childProgressiveSizes.current[currentIndex - 1]; - }, [actualIndex, getItemSize, itemSize, numItems]); + }, [getItemSize, itemSize, numItems]); const calculateAfter = useCallback(() => { - if (numItems === 0 || actualIndex + virtualizerLength >= numItems) { + if (numItems === 0 || actualIndexRef.current + virtualizerLength >= numItems) { return 0; } - const lastItemIndex = Math.min(actualIndex + virtualizerLength, numItems); + const lastItemIndex = Math.min(actualIndexRef.current + virtualizerLength, numItems); if (!getItemSize) { // The missing items from after virtualization ends height const remainingItems = numItems - lastItemIndex; @@ -309,7 +322,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // Time for custom size calcs return childProgressiveSizes.current[numItems - 1] - childProgressiveSizes.current[lastItemIndex - 1]; - }, [actualIndex, getItemSize, itemSize, numItems, virtualizerLength]); + }, [getItemSize, itemSize, numItems, virtualizerLength]); const updateChildRows = useCallback( (newIndex: number) => { diff --git a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts index a6299ee3a99f2..a80c0c0554b2e 100644 --- a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts +++ b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts @@ -6,7 +6,7 @@ import type { VirtualizerChildRenderFunction, } from '../Virtualizer/Virtualizer.types'; import type { ScrollToInterface } from '../../Utilities'; -import type { RefObject } from 'react'; +import type { MutableRefObject, RefObject } from 'react'; export type VirtualizerScrollViewSlots = VirtualizerSlots & { /** diff --git a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts index a1aebaca2b782..8dbe6dad46c09 100644 --- a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts +++ b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts @@ -10,7 +10,7 @@ import { useStaticVirtualizerPagination } from '../../hooks/useStaticPagination' export function useVirtualizerScrollView_unstable(props: VirtualizerScrollViewProps): VirtualizerScrollViewState { const { imperativeRef, itemSize, numItems, axis = 'vertical', reversed, enablePagination = false } = props; - const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useStaticVirtualizerMeasure({ + const { virtualizerLength, bufferItems, bufferSize, scrollRef, containerSizeRef } = useStaticVirtualizerMeasure({ defaultItemSize: props.itemSize, direction: props.axis ?? 'vertical', }); @@ -64,6 +64,7 @@ export function useVirtualizerScrollView_unstable(props: VirtualizerScrollViewPr scrollViewRef, onRenderedFlaggedIndex: handleRenderedIndex, imperativeVirtualizerRef, + containerSizeRef, }); return { diff --git a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts index 8ff695b652275..8e2dd615e2b73 100644 --- a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts +++ b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts @@ -6,7 +6,7 @@ import type { } from '../Virtualizer/Virtualizer.types'; import type { VirtualizerScrollViewSlots } from '../VirtualizerScrollView/VirtualizerScrollView.types'; -import type { RefObject } from 'react'; +import type { MutableRefObject, RefObject } from 'react'; import type { ScrollToInterface } from '../../Utilities'; export type VirtualizerScrollViewDynamicSlots = VirtualizerScrollViewSlots; diff --git a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.tsx b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.tsx index 7957635acbdf4..515fd2ebe36af 100644 --- a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.tsx +++ b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.tsx @@ -41,7 +41,7 @@ export function useVirtualizerScrollViewDynamic_unstable( [sizeTrackingArray, props.itemSize, sizeUpdateCount], ); - const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useDynamicVirtualizerMeasure({ + const { virtualizerLength, bufferItems, bufferSize, scrollRef, containerSizeRef } = useDynamicVirtualizerMeasure({ defaultItemSize: props.itemSize, direction: props.axis ?? 'vertical', getItemSize: props.getItemSize ?? getChildSizeAuto, @@ -117,6 +117,7 @@ export function useVirtualizerScrollViewDynamic_unstable( virtualizerContext: contextState, imperativeVirtualizerRef: _imperativeVirtualizerRef, onRenderedFlaggedIndex: handleRenderedIndex, + containerSizeRef, }); const measureObject = useMeasureList( diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts index 12c4b67fb44fb..b7c220cd56691 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts @@ -14,6 +14,7 @@ export const useDynamicVirtualizerMeasure = ( bufferItems: number; bufferSize: number; scrollRef: (instance: TElement | null) => void; + containerSizeRef: React.MutableRefObject; } => { const { defaultItemSize, direction = 'vertical', numItems, getItemSize, currentIndex } = virtualizerProps; const indexRef = useRef(currentIndex); @@ -25,6 +26,7 @@ export const useDynamicVirtualizerMeasure = ( virtualizerBufferSize: 0, }); + const containerSizeRef = React.useRef(0); const { virtualizerLength, virtualizerBufferItems, virtualizerBufferSize } = state; const container = React.useRef(null); @@ -44,6 +46,8 @@ export const useDynamicVirtualizerMeasure = ( ? scrollRef.current.getBoundingClientRect().height : scrollRef.current.getBoundingClientRect().width; + containerSizeRef.current = containerSize; + let indexSizer = 0; let length = 0; @@ -130,5 +134,6 @@ export const useDynamicVirtualizerMeasure = ( bufferItems: virtualizerBufferItems, bufferSize: virtualizerBufferSize, scrollRef, + containerSizeRef, }; }; diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts index aeeec5bb5a6db..dcc671eb61ba3 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts @@ -12,6 +12,7 @@ export const useStaticVirtualizerMeasure = ( bufferItems: number; bufferSize: number; scrollRef: (instance: TElement | null) => void; + containerSizeRef: React.MutableRefObject; } => { const { defaultItemSize, direction = 'vertical' } = virtualizerProps; @@ -21,6 +22,8 @@ export const useStaticVirtualizerMeasure = ( bufferItems: 0, }); + const containerSizeRef = React.useRef(0); + const { virtualizerLength, bufferItems, bufferSize } = state; const resizeCallback = React.useCallback( @@ -40,6 +43,8 @@ export const useStaticVirtualizerMeasure = ( ? scrollRef?.current.getBoundingClientRect().height : scrollRef?.current.getBoundingClientRect().width; + containerSizeRef.current = containerSize; + /* * Number of items required to cover viewport. */ @@ -48,12 +53,14 @@ export const useStaticVirtualizerMeasure = ( /* * Number of items to append at each end, i.e. 'preload' each side before entering view. */ - const newBufferItems = Math.max(Math.floor(length / 4), 2); + // const newBufferItems = Math.max(Math.floor(length / 4), 2); + const newBufferItems = 1; /* * This is how far we deviate into the bufferItems to detect a redraw. */ - const newBufferSize = Math.max(Math.floor((length / 8) * defaultItemSize), 1); + // const newBufferSize = Math.max(Math.floor((length / 8) * defaultItemSize), 1); + const newBufferSize = 5; const totalLength = length + newBufferItems * 2 + 1; @@ -73,5 +80,6 @@ export const useStaticVirtualizerMeasure = ( bufferItems, bufferSize, scrollRef, + containerSizeRef, }; }; diff --git a/packages/react-components/react-virtualizer/library/src/testing/useVirtualizer.test.ts b/packages/react-components/react-virtualizer/library/src/testing/useVirtualizer.test.ts index 1205684eb4d34..d01411949959f 100644 --- a/packages/react-components/react-virtualizer/library/src/testing/useVirtualizer.test.ts +++ b/packages/react-components/react-virtualizer/library/src/testing/useVirtualizer.test.ts @@ -1,5 +1,6 @@ import { renderHook } from '@testing-library/react-hooks'; import { useVirtualizer_unstable } from '../components/Virtualizer/useVirtualizer'; +import { useRef } from 'react'; describe('useVirtualizer', () => { beforeEach(() => { @@ -17,6 +18,7 @@ describe('useVirtualizer', () => { const virtualizerLength = 50; const actualLength = 250; const divArr = new Array(actualLength).fill('Test-Node'); + const containerSizeRef = useRef(300); const rowFunc = (index: number) => { return divArr[index]; @@ -27,6 +29,7 @@ describe('useVirtualizer', () => { virtualizerLength, itemSize: 100, // 100 pixels children: rowFunc, + containerSizeRef, }), ); diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx index a1e8b98ff9145..520a18f32f623 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { Virtualizer, useStaticVirtualizerMeasure } from '@fluentui/react-components/unstable'; -import { makeStyles } from '@fluentui/react-components'; +import { makeStyles, useMergedRefs } from '@fluentui/react-components'; const useStyles = makeStyles({ container: { @@ -12,8 +12,8 @@ const useStyles = makeStyles({ maxHeight: '750px', }, child: { - height: '100px', - lineHeight: '100px', + height: '25px', + lineHeight: '25px', width: '100%', }, }); @@ -22,18 +22,22 @@ export const Default = () => { const styles = useStyles(); const childLength = 1000; - const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useStaticVirtualizerMeasure({ - defaultItemSize: 100, + const { virtualizerLength, bufferItems, bufferSize, scrollRef, containerSizeRef } = useStaticVirtualizerMeasure({ + defaultItemSize: 25, }); + const mergedRef = useMergedRefs(scrollRef); + return ( -
+
{index => { return ( diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/DefaultUnbounded.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/DefaultUnbounded.stories.tsx index a22d4758951ae..39f71463e16db 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/DefaultUnbounded.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/DefaultUnbounded.stories.tsx @@ -36,7 +36,7 @@ const useStyles = makeStyles({ export const DefaultUnbounded = () => { const styles = useStyles(); const childLength = 1000; - const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useStaticVirtualizerMeasure({ + const { virtualizerLength, bufferItems, bufferSize, scrollRef, containerSizeRef } = useStaticVirtualizerMeasure({ defaultItemSize: 100, }); @@ -54,6 +54,7 @@ export const DefaultUnbounded = () => { bufferItems={bufferItems} bufferSize={bufferSize} itemSize={100} + containerSizeRef={containerSizeRef} > {(index, isScrolling) => { return ( diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Horizontal.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Horizontal.stories.tsx index 16edbe43f39f4..8649668951f11 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Horizontal.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Horizontal.stories.tsx @@ -23,7 +23,7 @@ export const Horizontal = () => { const childLength = 1000; const itemWidth = 100; - const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useStaticVirtualizerMeasure({ + const { virtualizerLength, bufferItems, bufferSize, scrollRef, containerSizeRef } = useStaticVirtualizerMeasure({ defaultItemSize: itemWidth, direction: 'horizontal', }); @@ -37,6 +37,7 @@ export const Horizontal = () => { bufferItems={bufferItems} bufferSize={bufferSize} itemSize={itemWidth} + containerSizeRef={containerSizeRef} > {index => { return ( diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/MultiUnbounded.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/MultiUnbounded.stories.tsx index e6bf79745fd88..8b40f1e64913c 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/MultiUnbounded.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/MultiUnbounded.stories.tsx @@ -36,7 +36,7 @@ export const MultiUnbounded = () => { const childLength = 100; const repeatingVirtualizers = 5; - const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useStaticVirtualizerMeasure({ + const { virtualizerLength, bufferItems, bufferSize, scrollRef, containerSizeRef } = useStaticVirtualizerMeasure({ defaultItemSize: 100, }); @@ -58,6 +58,7 @@ export const MultiUnbounded = () => { bufferSize={bufferSize} itemSize={100} key={`virtualizer-container-${index}`} + containerSizeRef={containerSizeRef} > {rowIndex => { return ( diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/RTL.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/RTL.stories.tsx index dcb16f951cb12..aba7aa3f175c1 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/RTL.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/RTL.stories.tsx @@ -24,7 +24,7 @@ export const RTL = () => { const childLength = 1000; const itemWidth = 100; - const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useStaticVirtualizerMeasure({ + const { virtualizerLength, bufferItems, bufferSize, scrollRef, containerSizeRef } = useStaticVirtualizerMeasure({ defaultItemSize: itemWidth, direction: 'horizontal', }); @@ -39,6 +39,7 @@ export const RTL = () => { bufferItems={bufferItems} bufferSize={bufferSize} itemSize={100} + containerSizeRef={containerSizeRef} > {index => { return ( diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Reversed.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Reversed.stories.tsx index 109f2da1f82c9..f78c80a4346d2 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Reversed.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Reversed.stories.tsx @@ -23,7 +23,7 @@ export const Reversed = () => { const childLength = 1000; const itemSize = 100; - const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useStaticVirtualizerMeasure({ + const { virtualizerLength, bufferItems, bufferSize, scrollRef, containerSizeRef } = useStaticVirtualizerMeasure({ defaultItemSize: itemSize, }); @@ -36,6 +36,7 @@ export const Reversed = () => { bufferItems={bufferItems} bufferSize={bufferSize} itemSize={itemSize} + containerSizeRef={containerSizeRef} > {index => { return ( diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/ReversedHorizontal.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/ReversedHorizontal.stories.tsx index bdb10df418cf6..8daeb44c1951b 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/ReversedHorizontal.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/ReversedHorizontal.stories.tsx @@ -24,7 +24,7 @@ export const ReversedHorizontal = () => { const itemWidth = 100; - const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useStaticVirtualizerMeasure({ + const { virtualizerLength, bufferItems, bufferSize, scrollRef, containerSizeRef } = useStaticVirtualizerMeasure({ defaultItemSize: itemWidth, direction: 'horizontal', }); @@ -39,6 +39,7 @@ export const ReversedHorizontal = () => { bufferItems={bufferItems} bufferSize={bufferSize} itemSize={itemWidth} + containerSizeRef={containerSizeRef} > {index => { return ( From 7a4e49aaa459cea5aa475576dc970ecc2ab184b3 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Mon, 26 Aug 2024 14:39:36 -0700 Subject: [PATCH 10/42] Algo works, now polish --- .../components/Virtualizer/useVirtualizer.ts | 88 +++++++++---------- .../useVirtualizerScrollViewDynamic.tsx | 12 ++- .../library/src/hooks/hooks.types.ts | 20 +++++ .../src/hooks/useDynamicVirtualizerMeasure.ts | 22 +++-- .../src/hooks/useVirtualizerMeasure.ts | 26 +++--- .../src/Virtualizer/Default.stories.tsx | 4 +- .../src/Virtualizer/Dynamic.stories.tsx | 3 +- 7 files changed, 103 insertions(+), 72 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index e0497c8b665ba..3c740d831c298 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -160,62 +160,54 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta const calculateOverBuffer = (): number => { let measurementPos = 0; - if (!reversed) { - if (latestEntry.target === afterElementRef.current) { - console.log('AFTER ELE'); - // Get after buffers position - measurementPos = calculateTotalSize() - calculateAfter(); - // Get exact intersection position based on overflow size (how far into IO did we scroll?) - let overflowAmount = - axis === 'vertical' ? latestEntry.intersectionRect.height : latestEntry.intersectionRect.width; - // Add to original after position - measurementPos += overflowAmount; - // Ignore buffer size (IO offset) - measurementPos -= bufferSize; - // we hit the after buffer and detected the end of view, we need to find the start index. - measurementPos -= containerSizeRef.current; - } else if (latestEntry.target === beforeElementRef.current) { - // Get before buffers position - return scrollViewRef?.current?.scrollTop ?? 0; - // measurementPos = calculateBefore(); - // // console.log('Before pos:', measurementPos); - // // Get exact intersection position based on overflow size (how far into IO did we scroll?) - // const overflowAmount = - // axis === 'vertical' ? latestEntry.boundingClientRect.height : latestEntry.boundingClientRect.width; - // measurementPos -= overflowAmount; - // // Ignore buffer size (actual endpoint) - // measurementPos += bufferSize; - } - } else { - if (latestEntry.target === afterElementRef.current) { - console.log('REVERSED AFTER ELE'); - measurementPos = calculateAfter(); - // const reversedAfterAmount = - // axis === 'vertical' ? latestEntry.boundingClientRect.bottom : latestEntry.boundingClientRect.right; - - // measurementPos += reversedAfterAmount; - } else if (latestEntry.target === beforeElementRef.current) { - console.log('REVERSED BEFORE ELE'); - measurementPos = calculateTotalSize() - calculateBefore(); - // const reversedAfterAmount = - // axis === 'vertical' ? latestEntry.boundingClientRect.top : latestEntry.boundingClientRect.left; - - // measurementPos += reversedAfterAmount; - } - measurementPos = Math.max(calculateTotalSize() - measurementPos, 0); + if (latestEntry.target === afterElementRef.current) { + // Get after buffers position + measurementPos = calculateTotalSize() - calculateAfter(); + // Get exact intersection position based on overflow size (how far into IO did we scroll?) + const overflowAmount = + axis === 'vertical' ? latestEntry.intersectionRect.height : latestEntry.intersectionRect.width; + // Add to original after position + measurementPos += overflowAmount; + // Ignore buffer size (IO offset) + measurementPos -= bufferSize; + // we hit the after buffer and detected the end of view, we need to find the start index. + measurementPos -= containerSizeRef.current; + + console.log('AFTER - target:', latestEntry.target); + console.log('AFTER - Measure:', measurementPos); + console.log('AFTER - Scroll:', scrollViewRef?.current?.scrollTop); + } else if (latestEntry.target === beforeElementRef.current) { + // Get before buffers position + measurementPos = calculateBefore(); + // // Get exact intersection position based on overflow size (how far into IO did we scroll?) + const overflowAmount = + axis === 'vertical' ? latestEntry.intersectionRect.height : latestEntry.intersectionRect.width; + // Add to original after position + measurementPos -= overflowAmount; + // Ignore buffer size (IO offset) + measurementPos += bufferSize; + + console.log('BEFORE - target:', latestEntry.target); + console.log('BEFORE - Measure:', measurementPos); + console.log('BEFORE - Scroll:', scrollViewRef?.current?.scrollTop); } return measurementPos; }; - // Get exact 'scrollTop' via IO - let measurementPos = calculateOverBuffer(); + // Get exact 'scrollTop' via IO values + const measurementPos = calculateOverBuffer(); - // getIndexFromScrollPosition, then start one item earlier - let startIndex = getIndexFromScrollPosition(measurementPos) - 1; + /* dirMod: Set the index to before/after the current scroll top element (depending on direction) */ + const maxIndex = Math.max(numItems - virtualizerLength, 0); + const halfBuffer = Math.ceil(bufferItems / 2); + const dirMod = latestEntry.target === afterElementRef.current ? 1 : -1; + let startIndex = getIndexFromScrollPosition(measurementPos) - halfBuffer; + if (startIndex > 0 && startIndex < maxIndex && startIndex === actualIndex) { + startIndex += dirMod; + } // Safety limits - const maxIndex = Math.max(numItems - virtualizerLength, 0); const newStartIndex = Math.min(Math.max(startIndex, 0), maxIndex); if (actualIndex !== newStartIndex) { diff --git a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.tsx b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.tsx index 515fd2ebe36af..bdad5a623b620 100644 --- a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.tsx +++ b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.tsx @@ -19,7 +19,15 @@ export function useVirtualizerScrollViewDynamic_unstable( 'use no memo'; const contextState = useVirtualizerContextState_unstable(props.virtualizerContext); - const { imperativeRef, axis = 'vertical', reversed, imperativeVirtualizerRef, enablePagination = false } = props; + const { + imperativeRef, + axis = 'vertical', + reversed, + imperativeVirtualizerRef, + enablePagination = false, + bufferItems: _bufferItems, + bufferSize: _bufferSize, + } = props; let sizeTrackingArray = React.useRef(new Array(props.numItems).fill(props.itemSize)); @@ -47,6 +55,8 @@ export function useVirtualizerScrollViewDynamic_unstable( getItemSize: props.getItemSize ?? getChildSizeAuto, currentIndex: contextState?.contextIndex ?? 0, numItems: props.numItems, + bufferItems: _bufferItems, + bufferSize: _bufferSize, }); const _imperativeVirtualizerRef = useMergedRefs(React.useRef(null), imperativeVirtualizerRef); diff --git a/packages/react-components/react-virtualizer/library/src/hooks/hooks.types.ts b/packages/react-components/react-virtualizer/library/src/hooks/hooks.types.ts index 7d8d27dcb730c..1af9bc009c4c1 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/hooks.types.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/hooks.types.ts @@ -3,6 +3,16 @@ import { MutableRefObject, RefObject } from 'react'; export type VirtualizerMeasureProps = { defaultItemSize: number; direction?: 'vertical' | 'horizontal'; + + /** + * Override recommended number of buffer items + */ + bufferItems?: number; + + /** + * Override recommended buffer size (px) + */ + bufferSize?: number; }; export type VirtualizerMeasureDynamicProps = { @@ -11,6 +21,16 @@ export type VirtualizerMeasureDynamicProps = { numItems: number; getItemSize: (index: number) => number; direction?: 'vertical' | 'horizontal'; + + /** + * Override recommended number of buffer items + */ + bufferItems?: number; + + /** + * Override recommended buffer size (px) + */ + bufferSize?: number; }; export type VirtualizerStaticPaginationProps = { diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts index b7c220cd56691..e25d674094229 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts @@ -16,7 +16,15 @@ export const useDynamicVirtualizerMeasure = ( scrollRef: (instance: TElement | null) => void; containerSizeRef: React.MutableRefObject; } => { - const { defaultItemSize, direction = 'vertical', numItems, getItemSize, currentIndex } = virtualizerProps; + const { + defaultItemSize, + direction = 'vertical', + numItems, + getItemSize, + currentIndex, + bufferItems, + bufferSize, + } = virtualizerProps; const indexRef = useRef(currentIndex); indexRef.current = currentIndex; @@ -62,20 +70,20 @@ export const useDynamicVirtualizerMeasure = ( /* * Number of items to append at each end, i.e. 'preload' each side before entering view. */ - const bufferItems = Math.max(Math.ceil(length / 3), 2); + const newBufferItems = bufferItems ?? Math.max(Math.ceil(length / 3), 2); /* * This is how far we deviate into the bufferItems to detect a redraw. */ - const bufferSize = Math.min(defaultItemSize / 2.0, 100); - const totalLength = length + bufferItems * 2; + const newBufferSize = bufferSize ?? Math.min(defaultItemSize / 2.0, 100); + const totalLength = length + newBufferItems * 2; setState({ virtualizerLength: totalLength, - virtualizerBufferSize: bufferSize, - virtualizerBufferItems: bufferItems, + virtualizerBufferSize: newBufferSize, + virtualizerBufferItems: newBufferItems, }); }, - [defaultItemSize, direction, getItemSize, numItems], + [bufferItems, bufferSize, defaultItemSize, direction, getItemSize, numItems], ); const resizeCallback = React.useCallback( diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts index dcc671eb61ba3..bfa497ab1d472 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts @@ -14,17 +14,17 @@ export const useStaticVirtualizerMeasure = ( scrollRef: (instance: TElement | null) => void; containerSizeRef: React.MutableRefObject; } => { - const { defaultItemSize, direction = 'vertical' } = virtualizerProps; + const { defaultItemSize, direction = 'vertical', bufferItems, bufferSize } = virtualizerProps; const [state, setState] = React.useState({ virtualizerLength: 0, - bufferSize: 0, - bufferItems: 0, + _bufferSize: 0, + _bufferItems: 0, }); const containerSizeRef = React.useRef(0); - const { virtualizerLength, bufferItems, bufferSize } = state; + const { virtualizerLength, _bufferItems, _bufferSize } = state; const resizeCallback = React.useCallback( ( @@ -53,32 +53,30 @@ export const useStaticVirtualizerMeasure = ( /* * Number of items to append at each end, i.e. 'preload' each side before entering view. */ - // const newBufferItems = Math.max(Math.floor(length / 4), 2); - const newBufferItems = 1; + const newBufferItems = bufferItems ?? Math.max(Math.floor(length / 4), 1); /* * This is how far we deviate into the bufferItems to detect a redraw. */ - // const newBufferSize = Math.max(Math.floor((length / 8) * defaultItemSize), 1); - const newBufferSize = 5; + const newBufferSize = bufferSize ?? Math.max(Math.floor((length / 8) * defaultItemSize), 1); - const totalLength = length + newBufferItems * 2 + 1; + const totalLength = length + newBufferItems * 2 - 1; setState({ virtualizerLength: totalLength, - bufferItems: newBufferItems, - bufferSize: newBufferSize, + _bufferItems: newBufferItems, + _bufferSize: newBufferSize, }); }, - [defaultItemSize, direction], + [bufferItems, bufferSize, defaultItemSize, direction], ); const scrollRef = useResizeObserverRef_unstable(resizeCallback); return { virtualizerLength, - bufferItems, - bufferSize, + bufferItems: _bufferItems, + bufferSize: _bufferSize, scrollRef, containerSizeRef, }; diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx index 520a18f32f623..51c07beccedb4 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx @@ -9,7 +9,7 @@ const useStyles = makeStyles({ overflowY: 'auto', width: '100%', height: '100%', - maxHeight: '750px', + maxHeight: '60vh', }, child: { height: '25px', @@ -24,6 +24,8 @@ export const Default = () => { const { virtualizerLength, bufferItems, bufferSize, scrollRef, containerSizeRef } = useStaticVirtualizerMeasure({ defaultItemSize: 25, + bufferItems: 1, + bufferSize: 12, }); const mergedRef = useMergedRefs(scrollRef); diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx index cef1e22b02b78..fcbafe392ee75 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx @@ -63,7 +63,7 @@ export const Dynamic = () => { [flag], ); - const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useDynamicVirtualizerMeasure({ + const { virtualizerLength, bufferItems, bufferSize, scrollRef, containerSizeRef } = useDynamicVirtualizerMeasure({ defaultItemSize: 100, getItemSize: getSizeForIndex, numItems: childLength, @@ -83,6 +83,7 @@ export const Dynamic = () => { virtualizerLength={virtualizerLength} itemSize={100} scrollViewRef={combineRefs} + containerSizeRef={containerSizeRef} > {useCallback( (index: number) => { From 89a130be8ceb787509b8d8b9a2aad0c1c4429074 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Mon, 26 Aug 2024 15:16:15 -0700 Subject: [PATCH 11/42] Fix up IO to always detect actual distance if past window bounds --- .../Virtualizer/Virtualizer.types.ts | 2 +- .../components/Virtualizer/useVirtualizer.ts | 18 +++--- .../src/Virtualizer/Default.stories.tsx | 58 ++++++++++++------- 3 files changed, 49 insertions(+), 29 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts index d7d5ec2986e3f..8e74ecafce488 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts @@ -131,7 +131,7 @@ export type VirtualizerConfigProps = { /** * Enables users to override the intersectionObserverRoot. */ - scrollViewRef?: React.MutableRefObject; + scrollViewRef: React.MutableRefObject; /** * The scroll direction diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index 3c740d831c298..bb42d8981e2a9 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -173,13 +173,15 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // we hit the after buffer and detected the end of view, we need to find the start index. measurementPos -= containerSizeRef.current; - console.log('AFTER - target:', latestEntry.target); - console.log('AFTER - Measure:', measurementPos); - console.log('AFTER - Scroll:', scrollViewRef?.current?.scrollTop); + // Calculate how far past the window bounds we are (this will be zero if IO is within window) + const hOverflow = latestEntry.boundingClientRect.top - latestEntry.intersectionRect.top; + const wOverflow = latestEntry.boundingClientRect.left - latestEntry.intersectionRect.left; + const additionalOverflow = axis === 'vertical' ? hOverflow : wOverflow; + measurementPos -= additionalOverflow; } else if (latestEntry.target === beforeElementRef.current) { // Get before buffers position measurementPos = calculateBefore(); - // // Get exact intersection position based on overflow size (how far into IO did we scroll?) + // Get exact intersection position based on overflow size (how far into window did we scroll IO?) const overflowAmount = axis === 'vertical' ? latestEntry.intersectionRect.height : latestEntry.intersectionRect.width; // Add to original after position @@ -187,9 +189,11 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // Ignore buffer size (IO offset) measurementPos += bufferSize; - console.log('BEFORE - target:', latestEntry.target); - console.log('BEFORE - Measure:', measurementPos); - console.log('BEFORE - Scroll:', scrollViewRef?.current?.scrollTop); + // Calculate how far past the window bounds we are (this will be zero if IO is within window) + const hOverflow = latestEntry.boundingClientRect.bottom - latestEntry.intersectionRect.bottom; + const wOverflow = latestEntry.boundingClientRect.right - latestEntry.intersectionRect.right; + const additionalOverflow = axis === 'vertical' ? hOverflow : wOverflow; + measurementPos -= additionalOverflow; } return measurementPos; diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx index 51c07beccedb4..b64f8a0429254 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { Virtualizer, useStaticVirtualizerMeasure } from '@fluentui/react-components/unstable'; -import { makeStyles, useMergedRefs } from '@fluentui/react-components'; +import { makeStyles, useMergedRefs, Button } from '@fluentui/react-components'; const useStyles = makeStyles({ container: { @@ -31,28 +31,44 @@ export const Default = () => { const mergedRef = useMergedRefs(scrollRef); return ( -
- + + +
+ + {index => { + return ( + {`Node-${index}`} + ); + }} + +
); }; From 6c7168523a0aa4e091b6f23d1007de59dc1e3145 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Mon, 26 Aug 2024 15:30:36 -0700 Subject: [PATCH 12/42] Reduce unneeded length --- .../components/Virtualizer/Virtualizer.types.ts | 4 ++-- .../src/components/Virtualizer/useVirtualizer.ts | 6 ++++++ .../src/hooks/useDynamicVirtualizerMeasure.ts | 13 ++++++++++--- .../library/src/hooks/useVirtualizerMeasure.ts | 2 +- .../stories/src/Virtualizer/Default.stories.tsx | 16 +--------------- 5 files changed, 20 insertions(+), 21 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts index 8e74ecafce488..fef7a0dbbd021 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts @@ -118,7 +118,7 @@ export type VirtualizerConfigProps = { bufferItems?: number; /** - * Defaults to half of bufferItems size (in pixels). + * Defaults to half of bufferItems * itemSize size (in pixels). * The length (in pixels) before the end/start DOM index where the virtualizer recalculation will be triggered. * Increasing this reduces whitespace on ultra-fast scroll, as additional elements * are buffered to appear while virtualization recalculates. @@ -131,7 +131,7 @@ export type VirtualizerConfigProps = { /** * Enables users to override the intersectionObserverRoot. */ - scrollViewRef: React.MutableRefObject; + scrollViewRef?: React.MutableRefObject; /** * The scroll direction diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index bb42d8981e2a9..b184124e7bfa7 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -159,6 +159,12 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta } const calculateOverBuffer = (): number => { + /** + * We avoid using the scroll ref scrollTop, it may be incorrect + * as virtualization may exist within a scroll view with other elements + * The benefit of using IO is that we can detect relative scrolls, + * so any items can be placed around the virtualizer in the scroll view + */ let measurementPos = 0; if (latestEntry.target === afterElementRef.current) { // Get after buffers position diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts index e25d674094229..a9fad6334eb70 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts @@ -121,8 +121,7 @@ export const useDynamicVirtualizerMeasure = ( const newLength = i - currentIndex; - const bufferItems = Math.max(Math.floor(newLength / 4), 2); - const totalNewLength = newLength + bufferItems * 2 + 4; + const totalNewLength = newLength + virtualizerBufferItems * 2; const compareLengths = totalNewLength < virtualizerLength; if (recheckTotal > containerSize && compareLengths) { @@ -135,7 +134,15 @@ export const useDynamicVirtualizerMeasure = ( if (recheckTotal < containerSize || couldBeSmaller) { handleScrollResize(container); } - }, [getItemSize, currentIndex, direction, virtualizerLength, resizeCallback, handleScrollResize]); + }, [ + getItemSize, + currentIndex, + direction, + virtualizerLength, + resizeCallback, + handleScrollResize, + virtualizerBufferItems, + ]); return { virtualizerLength, diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts index bfa497ab1d472..3d25bceb206ac 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts @@ -60,7 +60,7 @@ export const useStaticVirtualizerMeasure = ( */ const newBufferSize = bufferSize ?? Math.max(Math.floor((length / 8) * defaultItemSize), 1); - const totalLength = length + newBufferItems * 2 - 1; + const totalLength = length + newBufferItems * 2; setState({ virtualizerLength: totalLength, diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx index b64f8a0429254..76d94657d40db 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { Virtualizer, useStaticVirtualizerMeasure } from '@fluentui/react-components/unstable'; -import { makeStyles, useMergedRefs, Button } from '@fluentui/react-components'; +import { makeStyles, useMergedRefs } from '@fluentui/react-components'; const useStyles = makeStyles({ container: { @@ -32,20 +32,6 @@ export const Default = () => { return (
- -
Date: Mon, 26 Aug 2024 16:58:35 -0700 Subject: [PATCH 13/42] Fix up the window reference height --- .../src/hooks/useVirtualizerMeasure.ts | 22 +++++++++++++------ .../stories/.storybook/preview-body.html | 5 ----- 2 files changed, 15 insertions(+), 12 deletions(-) delete mode 100644 packages/react-components/react-virtualizer/stories/.storybook/preview-body.html diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts index 3d25bceb206ac..2ab313cc72b41 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts @@ -1,6 +1,7 @@ import * as React from 'react'; import { VirtualizerMeasureProps } from './hooks.types'; import { useResizeObserverRef_unstable } from './useResizeObserverRef'; +import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; /** * React hook that measures virtualized space based on a static size to ensure optimized virtualization length. @@ -23,6 +24,7 @@ export const useStaticVirtualizerMeasure = ( }); const containerSizeRef = React.useRef(0); + const { targetDocument } = useFluent(); const { virtualizerLength, _bufferItems, _bufferSize } = state; @@ -38,17 +40,23 @@ export const useStaticVirtualizerMeasure = ( return; } - const containerSize = - direction === 'vertical' - ? scrollRef?.current.getBoundingClientRect().height - : scrollRef?.current.getBoundingClientRect().width; - - containerSizeRef.current = containerSize; + if (scrollRef.current !== targetDocument?.body) { + // We have a local scroll container + const containerSize = + direction === 'vertical' + ? scrollRef?.current.getBoundingClientRect().height + : scrollRef?.current.getBoundingClientRect().width; + containerSizeRef.current = containerSize; + } else if (targetDocument?.defaultView) { + // If our scroll ref is the document body, we should check window height + containerSizeRef.current = + direction === 'vertical' ? targetDocument?.defaultView?.innerHeight : targetDocument?.defaultView?.innerWidth; + } /* * Number of items required to cover viewport. */ - const length = Math.ceil(containerSize / defaultItemSize + 1); + const length = Math.ceil(containerSizeRef.current / defaultItemSize + 1); /* * Number of items to append at each end, i.e. 'preload' each side before entering view. diff --git a/packages/react-components/react-virtualizer/stories/.storybook/preview-body.html b/packages/react-components/react-virtualizer/stories/.storybook/preview-body.html deleted file mode 100644 index d37ea59491b1f..0000000000000 --- a/packages/react-components/react-virtualizer/stories/.storybook/preview-body.html +++ /dev/null @@ -1,5 +0,0 @@ - From 7b8b0c16561d54f78c5ab754ef06d4b21400f14b Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Mon, 26 Aug 2024 17:15:18 -0700 Subject: [PATCH 14/42] Tidy up and ensure smoothness --- .../components/Virtualizer/useVirtualizer.ts | 10 ++-- .../src/hooks/useDynamicVirtualizerMeasure.ts | 49 ++++++++++--------- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index b184124e7bfa7..0d3dbab56b03a 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -167,6 +167,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta */ let measurementPos = 0; if (latestEntry.target === afterElementRef.current) { + console.log('After'); // Get after buffers position measurementPos = calculateTotalSize() - calculateAfter(); // Get exact intersection position based on overflow size (how far into IO did we scroll?) @@ -185,6 +186,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta const additionalOverflow = axis === 'vertical' ? hOverflow : wOverflow; measurementPos -= additionalOverflow; } else if (latestEntry.target === beforeElementRef.current) { + console.log('Before'); // Get before buffers position measurementPos = calculateBefore(); // Get exact intersection position based on overflow size (how far into window did we scroll IO?) @@ -193,7 +195,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // Add to original after position measurementPos -= overflowAmount; // Ignore buffer size (IO offset) - measurementPos += bufferSize; + measurementPos -= bufferSize; // Calculate how far past the window bounds we are (this will be zero if IO is within window) const hOverflow = latestEntry.boundingClientRect.bottom - latestEntry.intersectionRect.bottom; @@ -211,11 +213,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta /* dirMod: Set the index to before/after the current scroll top element (depending on direction) */ const maxIndex = Math.max(numItems - virtualizerLength, 0); const halfBuffer = Math.ceil(bufferItems / 2); - const dirMod = latestEntry.target === afterElementRef.current ? 1 : -1; - let startIndex = getIndexFromScrollPosition(measurementPos) - halfBuffer; - if (startIndex > 0 && startIndex < maxIndex && startIndex === actualIndex) { - startIndex += dirMod; - } + const startIndex = getIndexFromScrollPosition(measurementPos) - halfBuffer; // Safety limits const newStartIndex = Math.min(Math.max(startIndex, 0), maxIndex); diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts index a9fad6334eb70..b730e8f64924f 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts @@ -3,6 +3,7 @@ import * as React from 'react'; import { VirtualizerMeasureDynamicProps } from './hooks.types'; import { useResizeObserverRef_unstable } from './useResizeObserverRef'; import { useRef } from 'react'; +import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; /** * React hook that measures virtualized space dynamically to ensure optimized virtualization length. @@ -37,6 +38,7 @@ export const useDynamicVirtualizerMeasure = ( const containerSizeRef = React.useRef(0); const { virtualizerLength, virtualizerBufferItems, virtualizerBufferSize } = state; + const { targetDocument } = useFluent(); const container = React.useRef(null); const handleScrollResize = React.useCallback( (scrollRef: React.MutableRefObject) => { @@ -45,21 +47,24 @@ export const useDynamicVirtualizerMeasure = ( return; } - if (scrollRef.current !== container.current) { - container.current = scrollRef.current; + if (scrollRef.current !== targetDocument?.body) { + // We have a local scroll container + const containerSize = + direction === 'vertical' + ? scrollRef?.current.getBoundingClientRect().height + : scrollRef?.current.getBoundingClientRect().width; + + containerSizeRef.current = containerSize; + } else if (targetDocument?.defaultView) { + // If our scroll ref is the document body, we should check window height + containerSizeRef.current = + direction === 'vertical' ? targetDocument?.defaultView?.innerHeight : targetDocument?.defaultView?.innerWidth; } - const containerSize = - direction === 'vertical' - ? scrollRef.current.getBoundingClientRect().height - : scrollRef.current.getBoundingClientRect().width; - - containerSizeRef.current = containerSize; - let indexSizer = 0; let length = 0; - while (indexSizer <= containerSize && length < numItems) { + while (indexSizer <= containerSizeRef.current && length < numItems) { const iItemSize = getItemSize(indexRef.current + length); // Increment @@ -83,7 +88,16 @@ export const useDynamicVirtualizerMeasure = ( virtualizerBufferItems: newBufferItems, }); }, - [bufferItems, bufferSize, defaultItemSize, direction, getItemSize, numItems], + [ + bufferItems, + bufferSize, + defaultItemSize, + direction, + getItemSize, + numItems, + targetDocument?.body, + targetDocument?.defaultView, + ], ); const resizeCallback = React.useCallback( @@ -104,15 +118,6 @@ export const useDynamicVirtualizerMeasure = ( const scrollRef = useResizeObserverRef_unstable(resizeCallback); useIsomorphicLayoutEffect(() => { - if (!container.current) { - return; - } - - const containerSize = - direction === 'vertical' - ? container.current?.getBoundingClientRect().height - : container.current?.getBoundingClientRect().width; - let couldBeSmaller = false; let recheckTotal = 0; for (let i = currentIndex; i < currentIndex + virtualizerLength; i++) { @@ -124,14 +129,14 @@ export const useDynamicVirtualizerMeasure = ( const totalNewLength = newLength + virtualizerBufferItems * 2; const compareLengths = totalNewLength < virtualizerLength; - if (recheckTotal > containerSize && compareLengths) { + if (recheckTotal > containerSizeRef.current && compareLengths) { couldBeSmaller = true; break; } } // Check if the render has caused us to need a re-calc of virtualizer length - if (recheckTotal < containerSize || couldBeSmaller) { + if (recheckTotal < containerSizeRef.current || couldBeSmaller) { handleScrollResize(container); } }, [ From 5d59358ebd68877070960739c4448a89fe695b9b Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Tue, 27 Aug 2024 09:38:27 -0700 Subject: [PATCH 15/42] Fix buffer sizes --- .../src/hooks/useDynamicVirtualizerMeasure.ts | 4 ++-- .../library/src/hooks/useVirtualizerMeasure.ts | 6 +++--- .../stories/src/Virtualizer/Default.stories.tsx | 16 ++++++---------- .../src/Virtualizer/DefaultUnbounded.stories.tsx | 2 +- .../stories/src/Virtualizer/Dynamic.stories.tsx | 3 --- 5 files changed, 12 insertions(+), 19 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts index b730e8f64924f..ea5f46149f002 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts @@ -75,12 +75,12 @@ export const useDynamicVirtualizerMeasure = ( /* * Number of items to append at each end, i.e. 'preload' each side before entering view. */ - const newBufferItems = bufferItems ?? Math.max(Math.ceil(length / 3), 2); + const newBufferItems = bufferItems ?? Math.max(Math.ceil(length / 4), 1); /* * This is how far we deviate into the bufferItems to detect a redraw. */ - const newBufferSize = bufferSize ?? Math.min(defaultItemSize / 2.0, 100); + const newBufferSize = bufferSize ?? Math.max(defaultItemSize / 2.0, 5); const totalLength = length + newBufferItems * 2; setState({ virtualizerLength: totalLength, diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts index 2ab313cc72b41..d358f6cddf782 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts @@ -61,12 +61,12 @@ export const useStaticVirtualizerMeasure = ( /* * Number of items to append at each end, i.e. 'preload' each side before entering view. */ - const newBufferItems = bufferItems ?? Math.max(Math.floor(length / 4), 1); + const newBufferItems = bufferItems ?? Math.max(Math.ceil(length / 4), 1); /* * This is how far we deviate into the bufferItems to detect a redraw. */ - const newBufferSize = bufferSize ?? Math.max(Math.floor((length / 8) * defaultItemSize), 1); + const newBufferSize = bufferSize ?? Math.max(defaultItemSize / 2.0, 5); const totalLength = length + newBufferItems * 2; @@ -76,7 +76,7 @@ export const useStaticVirtualizerMeasure = ( _bufferSize: newBufferSize, }); }, - [bufferItems, bufferSize, defaultItemSize, direction], + [bufferItems, bufferSize, defaultItemSize, direction, targetDocument?.body, targetDocument?.defaultView], ); const scrollRef = useResizeObserverRef_unstable(resizeCallback); diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx index 76d94657d40db..4dd40c349a9dc 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { Virtualizer, useStaticVirtualizerMeasure } from '@fluentui/react-components/unstable'; -import { makeStyles, useMergedRefs } from '@fluentui/react-components'; +import { makeStyles } from '@fluentui/react-components'; const useStyles = makeStyles({ container: { @@ -13,8 +13,9 @@ const useStyles = makeStyles({ }, child: { height: '25px', - lineHeight: '25px', + lineHeight: '100px', width: '100%', + minHeight: '100px', }, }); @@ -23,24 +24,19 @@ export const Default = () => { const childLength = 1000; const { virtualizerLength, bufferItems, bufferSize, scrollRef, containerSizeRef } = useStaticVirtualizerMeasure({ - defaultItemSize: 25, - bufferItems: 1, - bufferSize: 12, + defaultItemSize: 100, }); - const mergedRef = useMergedRefs(scrollRef); - return (
-
+
{index => { return ( diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/DefaultUnbounded.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/DefaultUnbounded.stories.tsx index 39f71463e16db..231812d7754f9 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/DefaultUnbounded.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/DefaultUnbounded.stories.tsx @@ -20,7 +20,6 @@ const useStyles = makeStyles({ }, child: { display: 'flex', - height: '100px', lineHeight: '100px', width: '100%', }, @@ -30,6 +29,7 @@ const useStyles = makeStyles({ paddingBottom: '100px', fontSize: '36px', textAlign: 'center', + minHeight: '100px', }, }); diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx index fcbafe392ee75..79d36bbbea8c0 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx @@ -70,8 +70,6 @@ export const Dynamic = () => { currentIndex, }); - const combineRefs = useMergedRefs(scrollRef); - return (
@@ -82,7 +80,6 @@ export const Dynamic = () => { bufferItems={bufferItems} virtualizerLength={virtualizerLength} itemSize={100} - scrollViewRef={combineRefs} containerSizeRef={containerSizeRef} > {useCallback( From c5b853ebe17217e47cb8cfc749d6940373a67c50 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Tue, 27 Aug 2024 12:27:22 -0700 Subject: [PATCH 16/42] Update algo to account for dynamic size changes --- .../components/Virtualizer/useVirtualizer.ts | 475 ++++++++++-------- .../src/hooks/useDynamicVirtualizerMeasure.ts | 7 +- .../src/hooks/useVirtualizerMeasure.ts | 3 +- .../src/Virtualizer/Default.stories.tsx | 7 +- .../AutoMeasure.stories.tsx | 8 +- 5 files changed, 287 insertions(+), 213 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index 0d3dbab56b03a..cb6b6ac25fa77 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -36,13 +36,16 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta const actualIndex = _virtualizerContext.contextIndex; // Just in case our ref gets out of date vs the context during a re-render - if (_virtualizerContext.contextIndex != actualIndexRef.current) { + if (_virtualizerContext.contextIndex !== actualIndexRef.current) { actualIndexRef.current = _virtualizerContext.contextIndex; } - const setActualIndex = (index: number) => { - _virtualizerContext.setContextIndex(index); - actualIndexRef.current = index; - }; + const setActualIndex = useCallback( + (index: number) => { + actualIndexRef.current = index; + _virtualizerContext.setContextIndex(index); + }, + [_virtualizerContext], + ); // Store ref to before padding element const beforeElementRef = useRef(null); @@ -50,6 +53,12 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // Store ref to before padding element const afterElementRef = useRef(null); + // Store ref to before padding element + const beforeElementContainerRef = useRef(null); + + // Store ref to before padding element + const afterElementContainerRef = useRef(null); + // We need to store an array to track dynamic sizes, we can use this to incrementally update changes const childSizes = useRef(new Array(getItemSize ? numItems : 0)); @@ -116,172 +125,128 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta initializeScrollingTimer(); }, [actualIndex, initializeScrollingTimer]); - const batchUpdateNewIndex = (index: number) => { - // Local updates - updateChildRows(index); - updateCurrentItemSizes(index); - - // Set before 'setActualIndex' call & re-render - // If it changes before render, or injected via context, re-render will update ref. - actualIndexRef.current = index; - - // State setters - setActualIndex(index); - }; - - // Observe intersections of virtualized components - const { setObserverList } = useIntersectionObserver( - // TODO: exclude types from this lint rule: https://github.com/microsoft/fluentui/issues/31286 - // eslint-disable-next-line no-restricted-globals - (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => { - /* Sanity check - do we even need virtualization? */ - if (virtualizerLength > numItems) { - if (actualIndex !== 0) { - batchUpdateNewIndex(0); - } - // No-op + const updateChildRows = useCallback( + (newIndex: number) => { + if (numItems === 0) { + /* Nothing to virtualize */ return; } - // Grab latest entry that is intersecting - const latestEntry = - entries.length === 1 - ? entries[0] - : entries - .sort((entry1, entry2) => entry2.time - entry1.time) - .find(entry => { - return entry.intersectionRatio > 0; - }); - - if (!latestEntry) { - // If we don't find an intersecting area, ignore for now. - return; + /* + We reset the array every time to ensure children are re-rendered + This function should only be called when update is nessecary + */ + childArray.current = new Array(virtualizerLength); + const _actualIndex = Math.max(newIndex, 0); + const end = Math.min(_actualIndex + virtualizerLength, numItems); + for (let i = _actualIndex; i < end; i++) { + childArray.current[i - _actualIndex] = renderChild(i, isScrolling); } + }, + [isScrolling, numItems, renderChild, virtualizerLength], + ); - const calculateOverBuffer = (): number => { - /** - * We avoid using the scroll ref scrollTop, it may be incorrect - * as virtualization may exist within a scroll view with other elements - * The benefit of using IO is that we can detect relative scrolls, - * so any items can be placed around the virtualizer in the scroll view - */ - let measurementPos = 0; - if (latestEntry.target === afterElementRef.current) { - console.log('After'); - // Get after buffers position - measurementPos = calculateTotalSize() - calculateAfter(); - // Get exact intersection position based on overflow size (how far into IO did we scroll?) - const overflowAmount = - axis === 'vertical' ? latestEntry.intersectionRect.height : latestEntry.intersectionRect.width; - // Add to original after position - measurementPos += overflowAmount; - // Ignore buffer size (IO offset) - measurementPos -= bufferSize; - // we hit the after buffer and detected the end of view, we need to find the start index. - measurementPos -= containerSizeRef.current; - - // Calculate how far past the window bounds we are (this will be zero if IO is within window) - const hOverflow = latestEntry.boundingClientRect.top - latestEntry.intersectionRect.top; - const wOverflow = latestEntry.boundingClientRect.left - latestEntry.intersectionRect.left; - const additionalOverflow = axis === 'vertical' ? hOverflow : wOverflow; - measurementPos -= additionalOverflow; - } else if (latestEntry.target === beforeElementRef.current) { - console.log('Before'); - // Get before buffers position - measurementPos = calculateBefore(); - // Get exact intersection position based on overflow size (how far into window did we scroll IO?) - const overflowAmount = - axis === 'vertical' ? latestEntry.intersectionRect.height : latestEntry.intersectionRect.width; - // Add to original after position - measurementPos -= overflowAmount; - // Ignore buffer size (IO offset) - measurementPos -= bufferSize; - - // Calculate how far past the window bounds we are (this will be zero if IO is within window) - const hOverflow = latestEntry.boundingClientRect.bottom - latestEntry.intersectionRect.bottom; - const wOverflow = latestEntry.boundingClientRect.right - latestEntry.intersectionRect.right; - const additionalOverflow = axis === 'vertical' ? hOverflow : wOverflow; - measurementPos -= additionalOverflow; + const updateCurrentItemSizes = useCallback( + (newIndex: number) => { + if (!getItemSize) { + // Static sizes, not required. + return; + } + // We should always call our size function on index change (only for the items that will be rendered) + // This ensures we request the latest data for incoming items in case sizing has changed. + const endIndex = Math.min(newIndex + virtualizerLength, numItems); + const startIndex = Math.max(newIndex, 0); + + let didUpdate = false; + for (let i = startIndex; i < endIndex; i++) { + const newSize = getItemSize(i); + if (newSize !== childSizes.current[i]) { + childSizes.current[i] = newSize; + didUpdate = true; } + } - return measurementPos; - }; - - // Get exact 'scrollTop' via IO values - const measurementPos = calculateOverBuffer(); - - /* dirMod: Set the index to before/after the current scroll top element (depending on direction) */ - const maxIndex = Math.max(numItems - virtualizerLength, 0); - const halfBuffer = Math.ceil(bufferItems / 2); - const startIndex = getIndexFromScrollPosition(measurementPos) - halfBuffer; - - // Safety limits - const newStartIndex = Math.min(Math.max(startIndex, 0), maxIndex); - - if (actualIndex !== newStartIndex) { - // We flush sync this and perform an immediate state update - flushSync(() => { - batchUpdateNewIndex(newStartIndex); - }); + if (didUpdate) { + // Update our progressive size array + for (let i = startIndex; i < numItems; i++) { + const prevSize = i > 0 ? childProgressiveSizes.current[i - 1] : 0; + childProgressiveSizes.current[i] = prevSize + childSizes.current[i]; + } } }, - { - root: scrollViewRef ? scrollViewRef?.current : null, - rootMargin: '0px', - threshold: 0, + [getItemSize, numItems, virtualizerLength], + ); + + const batchUpdateNewIndex = useCallback( + (index: number) => { + // Local updates + updateChildRows(index); + updateCurrentItemSizes(index); + + // State setters + setActualIndex(index); }, + [setActualIndex, updateChildRows, updateCurrentItemSizes], ); - const findIndexRecursive = (scrollPos: number, lowIndex: number, highIndex: number): number => { - if (lowIndex > highIndex) { - // We shouldn't get here - but no-op the index if we do. - return actualIndex; - } - const midpoint = Math.floor((lowIndex + highIndex) / 2); - const iBefore = Math.max(midpoint - 1, 0); - const iAfter = Math.min(midpoint + 1, childProgressiveSizes.current.length - 1); - const indexValue = childProgressiveSizes.current[midpoint]; - const afterIndexValue = childProgressiveSizes.current[iAfter]; - const beforeIndexValue = childProgressiveSizes.current[iBefore]; - if (scrollPos <= afterIndexValue && scrollPos >= beforeIndexValue) { - /* We've found our index - if we are exactly matching before/after index that's ok, + const findIndexRecursive = useCallback( + (scrollPos: number, lowIndex: number, highIndex: number): number => { + if (lowIndex > highIndex) { + // We shouldn't get here - but no-op the index if we do. + return actualIndex; + } + const midpoint = Math.floor((lowIndex + highIndex) / 2); + const iBefore = Math.max(midpoint - 1, 0); + const iAfter = Math.min(midpoint + 1, childProgressiveSizes.current.length - 1); + const indexValue = childProgressiveSizes.current[midpoint]; + const afterIndexValue = childProgressiveSizes.current[iAfter]; + const beforeIndexValue = childProgressiveSizes.current[iBefore]; + if (scrollPos <= afterIndexValue && scrollPos >= beforeIndexValue) { + /* We've found our index - if we are exactly matching before/after index that's ok, better to reduce checks if it's right on the boundary. */ - return midpoint; - } - - if (indexValue > scrollPos) { - return findIndexRecursive(scrollPos, lowIndex, midpoint - 1); - } else { - return findIndexRecursive(scrollPos, midpoint + 1, highIndex); - } - }; + return midpoint; + } - const getIndexFromSizeArray = (scrollPos: number): number => { - /* Quick searches our progressive height array */ - if ( - scrollPos === 0 || - childProgressiveSizes.current.length === 0 || - scrollPos <= childProgressiveSizes.current[0] - ) { - // Check start - return 0; - } + if (indexValue > scrollPos) { + return findIndexRecursive(scrollPos, lowIndex, midpoint - 1); + } else { + return findIndexRecursive(scrollPos, midpoint + 1, highIndex); + } + }, + [actualIndex], + ); - if (scrollPos >= childProgressiveSizes.current[childProgressiveSizes.current.length - 1]) { - // Check end - return childProgressiveSizes.current.length - 1; - } + const getIndexFromSizeArray = useCallback( + (scrollPos: number): number => { + /* Quick searches our progressive height array */ + if ( + scrollPos === 0 || + childProgressiveSizes.current.length === 0 || + scrollPos <= childProgressiveSizes.current[0] + ) { + // Check start + return 0; + } - return findIndexRecursive(scrollPos, 0, childProgressiveSizes.current.length - 1); - }; + if (scrollPos >= childProgressiveSizes.current[childProgressiveSizes.current.length - 1]) { + // Check end + return childProgressiveSizes.current.length - 1; + } - const getIndexFromScrollPosition = (scrollPos: number) => { - if (!getItemSize) { - return Math.round(scrollPos / itemSize); - } + return findIndexRecursive(scrollPos, 0, childProgressiveSizes.current.length - 1); + }, + [findIndexRecursive], + ); + const getIndexFromScrollPosition = useCallback( + (scrollPos: number) => { + if (!getItemSize) { + return Math.round(scrollPos / itemSize); + } - return getIndexFromSizeArray(scrollPos); - }; + return getIndexFromSizeArray(scrollPos); + }, + [getIndexFromSizeArray, getItemSize, itemSize], + ); const calculateTotalSize = useCallback(() => { if (!getItemSize) { @@ -293,7 +258,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta }, [getItemSize, itemSize, numItems]); const calculateBefore = useCallback(() => { - const currentIndex = Math.min(actualIndexRef.current, numItems - 1); + const currentIndex = Math.min(actualIndex, numItems - 1); if (!getItemSize) { // The missing items from before virtualization starts height @@ -306,14 +271,14 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // Time for custom size calcs return childProgressiveSizes.current[currentIndex - 1]; - }, [getItemSize, itemSize, numItems]); + }, [actualIndex, getItemSize, itemSize, numItems]); const calculateAfter = useCallback(() => { - if (numItems === 0 || actualIndexRef.current + virtualizerLength >= numItems) { + if (numItems === 0 || actualIndex + virtualizerLength >= numItems) { return 0; } - const lastItemIndex = Math.min(actualIndexRef.current + virtualizerLength, numItems); + const lastItemIndex = Math.min(actualIndex + virtualizerLength, numItems); if (!getItemSize) { // The missing items from after virtualization ends height const remainingItems = numItems - lastItemIndex; @@ -322,27 +287,157 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // Time for custom size calcs return childProgressiveSizes.current[numItems - 1] - childProgressiveSizes.current[lastItemIndex - 1]; - }, [getItemSize, itemSize, numItems, virtualizerLength]); + }, [actualIndex, getItemSize, itemSize, numItems, virtualizerLength]); - const updateChildRows = useCallback( - (newIndex: number) => { - if (numItems === 0) { - /* Nothing to virtualize */ - return; - } + // Observe intersections of virtualized components + const { setObserverList } = useIntersectionObserver( + useCallback( + // TODO: exclude types from this lint rule: https://github.com/microsoft/fluentui/issues/31286 + // eslint-disable-next-line no-restricted-globals + (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => { + /* Sanity check - do we even need virtualization? */ + if (virtualizerLength > numItems) { + if (actualIndex !== 0) { + batchUpdateNewIndex(0); + } + // No-op + return; + } - /* - We reset the array every time to ensure children are re-rendered - This function should only be called when update is nessecary - */ - childArray.current = new Array(virtualizerLength); - const _actualIndex = Math.max(newIndex, 0); - const end = Math.min(_actualIndex + virtualizerLength, numItems); - for (let i = _actualIndex; i < end; i++) { - childArray.current[i - _actualIndex] = renderChild(i, isScrolling); - } + // Grab latest entry that is intersecting + const latestEntry = + entries.length === 1 + ? entries[0] + : entries + .sort((entry1, entry2) => entry2.time - entry1.time) + .find(entry => { + return entry.intersectionRatio > 0; + }); + + if (!latestEntry || !latestEntry.isIntersecting) { + // If we don't find an intersecting area, ignore for now. + return; + } + + // We have to be sure our item sizes are up to date with current indexed ref before calculation + // Check if we still need + updateCurrentItemSizes(actualIndexRef.current); + + const calculateOverBuffer = (): number => { + /** + * We avoid using the scroll ref scrollTop, it may be incorrect + * as virtualization may exist within a scroll view with other elements + * The benefit of using IO is that we can detect relative scrolls, + * so any items can be placed around the virtualizer in the scroll view + */ + let measurementPos = 0; + if (latestEntry.target === afterElementRef.current) { + console.log('AFTER'); + // Get after buffers position - static + measurementPos = calculateTotalSize() - calculateAfter(); + + // Get exact intersection position based on overflow size (how far into IO did we scroll?) + const overflowAmount = + axis === 'vertical' ? latestEntry.intersectionRect.height : latestEntry.intersectionRect.width; + // Add to original after position + measurementPos += overflowAmount; + // Ignore buffer size (IO offset) + measurementPos -= bufferSize; + // we hit the after buffer and detected the end of view, we need to find the start index. + measurementPos -= containerSizeRef.current; + + // Calculate how far past the window bounds we are (this will be zero if IO is within window) + const hOverflow = latestEntry.boundingClientRect.top - latestEntry.intersectionRect.top; + const hOverflowReversed = latestEntry.boundingClientRect.bottom - latestEntry.intersectionRect.bottom; + const wOverflow = latestEntry.boundingClientRect.left - latestEntry.intersectionRect.left; + const wOverflowReversed = latestEntry.boundingClientRect.right - latestEntry.intersectionRect.right; + const widthOverflow = reversed ? wOverflowReversed : wOverflow; + const heightOverflow = reversed ? hOverflowReversed : hOverflow; + const additionalOverflow = axis === 'vertical' ? heightOverflow : widthOverflow; + measurementPos -= additionalOverflow; + } else if (latestEntry.target === beforeElementRef.current) { + console.log('BEFORE'); + // Get before buffers position + measurementPos = calculateBefore(); + // Get exact intersection position based on overflow size (how far into window did we scroll IO?) + const overflowAmount = + axis === 'vertical' ? latestEntry.intersectionRect.height : latestEntry.intersectionRect.width; + // Add to original after position + measurementPos -= overflowAmount; + // Ignore buffer size (IO offset) + measurementPos += bufferSize; + + // Calculate how far past the window bounds we are (this will be zero if IO is within window) + const hOverflow = latestEntry.boundingClientRect.bottom - latestEntry.intersectionRect.bottom; + const hOverflowReversed = latestEntry.boundingClientRect.top - latestEntry.intersectionRect.top; + const wOverflow = latestEntry.boundingClientRect.right - latestEntry.intersectionRect.right; + const wOverflowReversed = latestEntry.boundingClientRect.left - latestEntry.intersectionRect.left; + const widthOverflow = reversed ? wOverflowReversed : wOverflow; + const heightOverflow = reversed ? hOverflowReversed : hOverflow; + const additionalOverflow = axis === 'vertical' ? heightOverflow : widthOverflow; + measurementPos -= additionalOverflow; + } + + return measurementPos; + }; + + // Get exact relative 'scrollTop' via IO values + const measurementPos = calculateOverBuffer(); + + const maxIndex = Math.max(numItems - (virtualizerLength - 1), 0); + + let startIndex = getIndexFromScrollPosition(measurementPos); + if (latestEntry.target === afterElementRef.current) { + // Render one less than scroll position if after element to be sure we don't trigger the before buffer + startIndex--; + } + + const dirMod = latestEntry.target === beforeElementRef.current ? -1 : 1; + if (actualIndex === startIndex) { + /* We can't actually hit the same index twice, as IO only fires once per intersection + * Usually this is caused by dynamically changing sizes causing us to recalc the same index + * Instead, we buffer it -1:1 so that we can recalc the index with latest values + */ + startIndex += dirMod; + } + + // Safety limits + const newStartIndex = Math.min(Math.max(startIndex, 0), maxIndex); + + /* + * Sometimes the dynamic virtualizer length changes on the final index, + * if this occurs we technically have a 'new' start index + * however, it is not needed as we already have a valid index + length to reach the end. + */ + const endAlreadyReached = + actualIndex < newStartIndex && + actualIndex + virtualizerLength >= numItems && + newStartIndex + virtualizerLength >= numItems; + if (actualIndex !== newStartIndex && !endAlreadyReached) { + batchUpdateNewIndex(newStartIndex); + } + }, + [ + actualIndex, + axis, + batchUpdateNewIndex, + bufferSize, + calculateAfter, + calculateBefore, + calculateTotalSize, + containerSizeRef, + getIndexFromScrollPosition, + numItems, + reversed, + updateCurrentItemSizes, + virtualizerLength, + ], + ), + { + root: scrollViewRef ? scrollViewRef?.current : null, + rootMargin: '0px', + threshold: 0, }, - [isScrolling, numItems, renderChild, virtualizerLength], ); const setBeforeRef = useCallback( @@ -385,34 +480,6 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta [setObserverList], ); - const updateCurrentItemSizes = (newIndex: number) => { - if (!getItemSize) { - // Static sizes, not required. - return; - } - // We should always call our size function on index change (only for the items that will be rendered) - // This ensures we request the latest data for incoming items in case sizing has changed. - const endIndex = Math.min(newIndex + virtualizerLength, numItems); - const startIndex = Math.max(newIndex, 0); - - let didUpdate = false; - for (let i = startIndex; i < endIndex; i++) { - const newSize = getItemSize(i); - if (newSize !== childSizes.current[i]) { - childSizes.current[i] = newSize; - didUpdate = true; - } - } - - if (didUpdate) { - // Update our progressive size array - for (let i = startIndex; i < numItems; i++) { - const prevSize = i > 0 ? childProgressiveSizes.current[i - 1] : 0; - childProgressiveSizes.current[i] = prevSize + childSizes.current[i]; - } - } - }; - // Initialize the size array before first render. const hasInitialized = useRef(false); const initializeSizeArray = () => { @@ -511,12 +578,14 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta }), beforeContainer: slot.always(props.beforeContainer, { defaultProps: { + ref: beforeElementContainerRef, role: 'none', }, elementType: 'div', }), afterContainer: slot.always(props.afterContainer, { defaultProps: { + ref: afterElementContainerRef, role: 'none', }, elementType: 'div', diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts index ea5f46149f002..df9ce7cab0329 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts @@ -62,7 +62,7 @@ export const useDynamicVirtualizerMeasure = ( } let indexSizer = 0; - let length = 0; + let length = 1; while (indexSizer <= containerSizeRef.current && length < numItems) { const iItemSize = getItemSize(indexRef.current + length); @@ -74,13 +74,14 @@ export const useDynamicVirtualizerMeasure = ( /* * Number of items to append at each end, i.e. 'preload' each side before entering view. + * Minimum: 2 - we give slightly more buffer for dynamic version. */ - const newBufferItems = bufferItems ?? Math.max(Math.ceil(length / 4), 1); + const newBufferItems = bufferItems ?? Math.max(Math.ceil(length / 3), 1); /* * This is how far we deviate into the bufferItems to detect a redraw. */ - const newBufferSize = bufferSize ?? Math.max(defaultItemSize / 2.0, 5); + const newBufferSize = bufferSize ?? Math.max(defaultItemSize / 2.0, 1); const totalLength = length + newBufferItems * 2; setState({ virtualizerLength: totalLength, diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts index d358f6cddf782..58cfc64729ab4 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts @@ -60,13 +60,14 @@ export const useStaticVirtualizerMeasure = ( /* * Number of items to append at each end, i.e. 'preload' each side before entering view. + * Minimum: 1 */ const newBufferItems = bufferItems ?? Math.max(Math.ceil(length / 4), 1); /* * This is how far we deviate into the bufferItems to detect a redraw. */ - const newBufferSize = bufferSize ?? Math.max(defaultItemSize / 2.0, 5); + const newBufferSize = bufferSize ?? Math.max(defaultItemSize / 2.0, 1); const totalLength = length + newBufferItems * 2; diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx index 4dd40c349a9dc..77567ee66e686 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { Virtualizer, useStaticVirtualizerMeasure } from '@fluentui/react-components/unstable'; -import { makeStyles } from '@fluentui/react-components'; +import { makeStyles, useMergedRefs } from '@fluentui/react-components'; const useStyles = makeStyles({ container: { @@ -27,9 +27,11 @@ export const Default = () => { defaultItemSize: 100, }); + const mergedref = useMergedRefs(scrollRef); + return (
-
+
{ bufferSize={bufferSize} itemSize={100} containerSizeRef={containerSizeRef} + scrollViewRef={mergedref} > {index => { return ( diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx index 23bc2be4025f3..0dd26b242fa99 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx @@ -14,8 +14,8 @@ const useStyles = makeStyles({ export const AutoMeasure = () => { const styles = useStyles(); const childLength = 1000; - const minHeight = 300; - const maxHeightIncrease = 200; + const minHeight = 50; + const maxHeightIncrease = 500; // Array size ref stores a list of random num for div sizing and callbacks const arraySize = React.useRef(new Array(childLength).fill(minHeight)); @@ -29,8 +29,8 @@ export const AutoMeasure = () => { return ( {(index: number) => { From 29f88de505e6a79fbc660a398c28053326b82480 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Tue, 27 Aug 2024 12:38:55 -0700 Subject: [PATCH 17/42] Fix up reversed order lists --- .../src/components/Virtualizer/useVirtualizer.ts | 16 ++++++++++++---- .../stories/src/Virtualizer/Reversed.stories.tsx | 7 +++++-- .../Virtualizer/ReversedHorizontal.stories.tsx | 7 +++++-- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index cb6b6ac25fa77..c289d1ecccc3c 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -332,7 +332,6 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta */ let measurementPos = 0; if (latestEntry.target === afterElementRef.current) { - console.log('AFTER'); // Get after buffers position - static measurementPos = calculateTotalSize() - calculateAfter(); @@ -354,9 +353,13 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta const widthOverflow = reversed ? wOverflowReversed : wOverflow; const heightOverflow = reversed ? hOverflowReversed : hOverflow; const additionalOverflow = axis === 'vertical' ? heightOverflow : widthOverflow; - measurementPos -= additionalOverflow; + + if (reversed) { + measurementPos += additionalOverflow; + } else { + measurementPos -= additionalOverflow; + } } else if (latestEntry.target === beforeElementRef.current) { - console.log('BEFORE'); // Get before buffers position measurementPos = calculateBefore(); // Get exact intersection position based on overflow size (how far into window did we scroll IO?) @@ -375,7 +378,12 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta const widthOverflow = reversed ? wOverflowReversed : wOverflow; const heightOverflow = reversed ? hOverflowReversed : hOverflow; const additionalOverflow = axis === 'vertical' ? heightOverflow : widthOverflow; - measurementPos -= additionalOverflow; + + if (reversed) { + measurementPos += additionalOverflow; + } else { + measurementPos -= additionalOverflow; + } } return measurementPos; diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Reversed.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Reversed.stories.tsx index f78c80a4346d2..542e5b51123ab 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Reversed.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Reversed.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { useStaticVirtualizerMeasure, Virtualizer } from '@fluentui/react-components/unstable'; -import { makeStyles } from '@fluentui/react-components'; +import { makeStyles, useMergedRefs } from '@fluentui/react-components'; const useStyles = makeStyles({ container: { @@ -27,8 +27,10 @@ export const Reversed = () => { defaultItemSize: itemSize, }); + const mergedRef = useMergedRefs(scrollRef); + return ( -
+
{ bufferSize={bufferSize} itemSize={itemSize} containerSizeRef={containerSizeRef} + scrollViewRef={mergedRef} > {index => { return ( diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/ReversedHorizontal.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/ReversedHorizontal.stories.tsx index 8daeb44c1951b..ca2217ef1a07c 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/ReversedHorizontal.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/ReversedHorizontal.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { useStaticVirtualizerMeasure, Virtualizer } from '@fluentui/react-components/unstable'; -import { makeStyles } from '@fluentui/react-components'; +import { makeStyles, useMergedRefs } from '@fluentui/react-components'; const useStyles = makeStyles({ container: { @@ -29,8 +29,10 @@ export const ReversedHorizontal = () => { direction: 'horizontal', }); + const mergedRef = useMergedRefs(scrollRef); + return ( -
+
{ bufferSize={bufferSize} itemSize={itemWidth} containerSizeRef={containerSizeRef} + scrollViewRef={mergedRef} > {index => { return ( From c6a3cada4a639257a3524f806690dc7d9bfaef70 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Tue, 27 Aug 2024 12:47:26 -0700 Subject: [PATCH 18/42] Update build and test --- .../react-virtualizer/library/etc/react-virtualizer.api.md | 6 ++++++ .../library/src/components/Virtualizer/useVirtualizer.ts | 1 - .../VirtualizerScrollView/VirtualizerScrollView.types.ts | 2 +- .../VirtualizerScrollViewDynamic.types.ts | 2 +- .../library/src/testing/useVirtualizer.test.ts | 4 +++- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/etc/react-virtualizer.api.md b/packages/react-components/react-virtualizer/library/etc/react-virtualizer.api.md index 499ae441ed8d7..8837416b5e9db 100644 --- a/packages/react-components/react-virtualizer/library/etc/react-virtualizer.api.md +++ b/packages/react-components/react-virtualizer/library/etc/react-virtualizer.api.md @@ -77,6 +77,7 @@ export const useDynamicVirtualizerMeasure: (virtua bufferItems: number; bufferSize: number; scrollRef: (instance: TElement | null) => void; + containerSizeRef: React_2.MutableRefObject; }; // @public @@ -104,6 +105,7 @@ export const useStaticVirtualizerMeasure: (virtual bufferItems: number; bufferSize: number; scrollRef: (instance: TElement | null) => void; + containerSizeRef: React_2.MutableRefObject; }; // @public (undocumented) @@ -160,12 +162,16 @@ export type VirtualizerMeasureDynamicProps = { numItems: number; getItemSize: (index: number) => number; direction?: 'vertical' | 'horizontal'; + bufferItems?: number; + bufferSize?: number; }; // @public (undocumented) export type VirtualizerMeasureProps = { defaultItemSize: number; direction?: 'vertical' | 'horizontal'; + bufferItems?: number; + bufferSize?: number; }; // @public (undocumented) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index c289d1ecccc3c..fcdbf39241fa6 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -3,7 +3,6 @@ import type { VirtualizerProps, VirtualizerState } from './Virtualizer.types'; import { useEffect, useRef, useCallback, useReducer, useImperativeHandle, useState } from 'react'; import { useIntersectionObserver } from '../../hooks/useIntersectionObserver'; -import { flushSync } from 'react-dom'; import { useVirtualizerContextState_unstable } from '../../Utilities'; import { slot, useTimeout } from '@fluentui/react-utilities'; diff --git a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts index a80c0c0554b2e..a6299ee3a99f2 100644 --- a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts +++ b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts @@ -6,7 +6,7 @@ import type { VirtualizerChildRenderFunction, } from '../Virtualizer/Virtualizer.types'; import type { ScrollToInterface } from '../../Utilities'; -import type { MutableRefObject, RefObject } from 'react'; +import type { RefObject } from 'react'; export type VirtualizerScrollViewSlots = VirtualizerSlots & { /** diff --git a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts index 8e2dd615e2b73..8ff695b652275 100644 --- a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts +++ b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts @@ -6,7 +6,7 @@ import type { } from '../Virtualizer/Virtualizer.types'; import type { VirtualizerScrollViewSlots } from '../VirtualizerScrollView/VirtualizerScrollView.types'; -import type { MutableRefObject, RefObject } from 'react'; +import type { RefObject } from 'react'; import type { ScrollToInterface } from '../../Utilities'; export type VirtualizerScrollViewDynamicSlots = VirtualizerScrollViewSlots; diff --git a/packages/react-components/react-virtualizer/library/src/testing/useVirtualizer.test.ts b/packages/react-components/react-virtualizer/library/src/testing/useVirtualizer.test.ts index d01411949959f..beafd560992f1 100644 --- a/packages/react-components/react-virtualizer/library/src/testing/useVirtualizer.test.ts +++ b/packages/react-components/react-virtualizer/library/src/testing/useVirtualizer.test.ts @@ -18,7 +18,9 @@ describe('useVirtualizer', () => { const virtualizerLength = 50; const actualLength = 250; const divArr = new Array(actualLength).fill('Test-Node'); - const containerSizeRef = useRef(300); + const containerSizeRef = { + current: 300, + }; const rowFunc = (index: number) => { return divArr[index]; From 4eb4177d2050b4b199476767474d4e310529c0b5 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Tue, 27 Aug 2024 14:42:12 -0700 Subject: [PATCH 19/42] Update --- .../Virtualizer/Virtualizer.types.ts | 9 ++++++-- .../components/Virtualizer/useVirtualizer.ts | 23 ++++++------------- .../useVirtualizerScrollView.ts | 1 - .../useVirtualizerScrollViewDynamic.tsx | 1 - .../src/testing/useVirtualizer.test.ts | 1 - .../AutoMeasure.stories.tsx | 2 ++ 6 files changed, 16 insertions(+), 21 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts index fef7a0dbbd021..e6c99c5db383d 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts @@ -109,16 +109,19 @@ export type VirtualizerConfigProps = { virtualizerLength: number; /** - * Defaults to 1/4th of virtualizerLength. + * Defaults to 1/4th (or 1/3rd for dynamic items) of virtualizerLength. + * RECOMMEND: Override this with a consistent value if using a dynamic virtualizer. + * * Controls the number of elements rendered before the current index entering the virtualized viewport. * Constraints: * - Large enough to cover bufferSize (prevents buffers intersecting into the viewport during rest state). - * - Small enough that the end buffer and end index (start index + virtualizerLength) is not within viewport at rest. + * - Small enough that the virtualizer only renders a few items outside of view. */ bufferItems?: number; /** * Defaults to half of bufferItems * itemSize size (in pixels). + * RECOMMEND: Override this with a consistent minimum item size value if using a dynamic virtualizer. * The length (in pixels) before the end/start DOM index where the virtualizer recalculation will be triggered. * Increasing this reduces whitespace on ultra-fast scroll, as additional elements * are buffered to appear while virtualization recalculates. @@ -130,6 +133,8 @@ export type VirtualizerConfigProps = { /** * Enables users to override the intersectionObserverRoot. + * RECOMMEND: DO NOT PASS THIS IN, as it can cause side effects + * when overlapping with other scroll views */ scrollViewRef?: React.MutableRefObject; diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index fcdbf39241fa6..4d699abe28108 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -17,13 +17,13 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta getItemSize, bufferItems = Math.round(virtualizerLength / 4.0), bufferSize = Math.floor(bufferItems / 2.0) * itemSize, - scrollViewRef, axis = 'vertical', reversed = false, virtualizerContext, onRenderedFlaggedIndex, imperativeVirtualizerRef, containerSizeRef, + scrollViewRef, } = props; /* The context is optional, it's useful for injecting additional index logic, or performing uniform state updates*/ @@ -52,12 +52,6 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // Store ref to before padding element const afterElementRef = useRef(null); - // Store ref to before padding element - const beforeElementContainerRef = useRef(null); - - // Store ref to before padding element - const afterElementContainerRef = useRef(null); - // We need to store an array to track dynamic sizes, we can use this to incrementally update changes const childSizes = useRef(new Array(getItemSize ? numItems : 0)); @@ -331,7 +325,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta */ let measurementPos = 0; if (latestEntry.target === afterElementRef.current) { - // Get after buffers position - static + // Get after buffers position measurementPos = calculateTotalSize() - calculateAfter(); // Get exact intersection position based on overflow size (how far into IO did we scroll?) @@ -393,11 +387,9 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta const maxIndex = Math.max(numItems - (virtualizerLength - 1), 0); - let startIndex = getIndexFromScrollPosition(measurementPos); - if (latestEntry.target === afterElementRef.current) { - // Render one less than scroll position if after element to be sure we don't trigger the before buffer - startIndex--; - } + // const offset = latestEntry.target === beforeElementRef.current ? bufferItems : Math.max(bufferItems - 1, 1); + // const offset = Math.max(bufferItems - 1, 1); + let startIndex = getIndexFromScrollPosition(measurementPos) - bufferItems; const dirMod = latestEntry.target === beforeElementRef.current ? -1 : 1; if (actualIndex === startIndex) { @@ -428,6 +420,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta actualIndex, axis, batchUpdateNewIndex, + bufferItems, bufferSize, calculateAfter, calculateBefore, @@ -525,7 +518,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta forceUpdate(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [renderChild]); + }, [renderChild, isScrolling]); useEffect(() => { // Ensure we repopulate if getItemSize callback changes @@ -585,14 +578,12 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta }), beforeContainer: slot.always(props.beforeContainer, { defaultProps: { - ref: beforeElementContainerRef, role: 'none', }, elementType: 'div', }), afterContainer: slot.always(props.afterContainer, { defaultProps: { - ref: afterElementContainerRef, role: 'none', }, elementType: 'div', diff --git a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts index 8dbe6dad46c09..93d7e640b0f34 100644 --- a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts +++ b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts @@ -61,7 +61,6 @@ export function useVirtualizerScrollView_unstable(props: VirtualizerScrollViewPr virtualizerLength, bufferItems, bufferSize, - scrollViewRef, onRenderedFlaggedIndex: handleRenderedIndex, imperativeVirtualizerRef, containerSizeRef, diff --git a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.tsx b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.tsx index bdad5a623b620..3698521073656 100644 --- a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.tsx +++ b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.tsx @@ -123,7 +123,6 @@ export function useVirtualizerScrollViewDynamic_unstable( virtualizerLength, bufferItems, bufferSize, - scrollViewRef, virtualizerContext: contextState, imperativeVirtualizerRef: _imperativeVirtualizerRef, onRenderedFlaggedIndex: handleRenderedIndex, diff --git a/packages/react-components/react-virtualizer/library/src/testing/useVirtualizer.test.ts b/packages/react-components/react-virtualizer/library/src/testing/useVirtualizer.test.ts index beafd560992f1..0290dd4cf94a0 100644 --- a/packages/react-components/react-virtualizer/library/src/testing/useVirtualizer.test.ts +++ b/packages/react-components/react-virtualizer/library/src/testing/useVirtualizer.test.ts @@ -1,6 +1,5 @@ import { renderHook } from '@testing-library/react-hooks'; import { useVirtualizer_unstable } from '../components/Virtualizer/useVirtualizer'; -import { useRef } from 'react'; describe('useVirtualizer', () => { beforeEach(() => { diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx index 0dd26b242fa99..0b51f930e8f0e 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx @@ -32,6 +32,8 @@ export const AutoMeasure = () => { // We can use itemSize to set average height itemSize={(minHeight + maxHeightIncrease) / 2.0} container={{ role: 'list', style: { maxHeight: '80vh' } }} + bufferItems={1} + bufferSize={25} > {(index: number) => { const backgroundColor = index % 2 ? '#FFFFFF' : '#ABABAB'; From f2a24d100ee1de77795b707402ee43e5e3f70210 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Tue, 27 Aug 2024 16:12:00 -0700 Subject: [PATCH 20/42] Type check and update average automeasure size --- .../stories/src/Virtualizer/Dynamic.stories.tsx | 2 +- .../VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx index 79d36bbbea8c0..6f0c0f61f6e05 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx @@ -4,7 +4,7 @@ import { useDynamicVirtualizerMeasure, VirtualizerContextProvider, } from '@fluentui/react-components/unstable'; -import { makeStyles, useMergedRefs } from '@fluentui/react-components'; +import { makeStyles } from '@fluentui/react-components'; import { useCallback, useRef } from 'react'; const smallSize = 100; diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx index 0b51f930e8f0e..11655b320bc55 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx @@ -29,11 +29,11 @@ export const AutoMeasure = () => { return ( {(index: number) => { const backgroundColor = index % 2 ? '#FFFFFF' : '#ABABAB'; From b4a9a332d352707afe3feb8ea8299f4ae165103a Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Tue, 27 Aug 2024 16:16:27 -0700 Subject: [PATCH 21/42] Type check stories fixup --- .../src/VirtualizerScrollViewDynamic/Default.stories.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/Default.stories.tsx index 1617268db3358..d3fe2084ad780 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/Default.stories.tsx @@ -15,6 +15,7 @@ export const Default = () => { const styles = useStyles(); const childLength = 1000; const minHeight = 42; + const maxHeightMod = 150; // Array size ref stores a list of random num for div sizing and callbacks const arraySize = React.useRef(new Array(childLength).fill(minHeight)); // totalSize flag drives our callback update @@ -23,7 +24,7 @@ export const Default = () => { useEffect(() => { let _totalSize = 0; for (let i = 0; i < childLength; i++) { - arraySize.current[i] = Math.floor(Math.random() * 150 + minHeight); + arraySize.current[i] = Math.floor(Math.random() * maxHeightMod + minHeight); _totalSize += arraySize.current[i]; } setTotalSize(_totalSize); @@ -40,7 +41,7 @@ export const Default = () => { return ( From 7c9d01570013efbbe1911c0a1fd71a8cf6151548 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Tue, 27 Aug 2024 16:16:50 -0700 Subject: [PATCH 22/42] Update dynamic scrollview props --- .../src/VirtualizerScrollViewDynamic/Default.stories.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/Default.stories.tsx index d3fe2084ad780..90c0c00b16a04 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/Default.stories.tsx @@ -44,6 +44,8 @@ export const Default = () => { itemSize={minHeight + maxHeightMod / 2.0} getItemSize={getItemSizeCallback} container={{ role: 'list', style: { maxHeight: '80vh' } }} + bufferItems={1} + bufferSize={minHeight / 2.0} > {(index: number) => { const backgroundColor = index % 2 ? '#FFFFFF' : '#ABABAB'; From dabb0490c3a10a65766cfe573db4cf2c9006c87b Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Wed, 28 Aug 2024 17:14:19 -0700 Subject: [PATCH 23/42] Fix up max index and remove scrollref from virtualizer hook in stories --- .../library/src/components/Virtualizer/useVirtualizer.ts | 4 +--- .../stories/src/Virtualizer/Default.stories.tsx | 7 ++----- .../stories/src/Virtualizer/Reversed.stories.tsx | 7 ++----- .../stories/src/Virtualizer/ReversedHorizontal.stories.tsx | 7 ++----- .../stories/src/VirtualizerScrollView/Default.stories.tsx | 2 +- .../src/VirtualizerScrollViewDynamic/Default.stories.tsx | 2 +- 6 files changed, 9 insertions(+), 20 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index 4d699abe28108..69562d0ab5eed 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -385,10 +385,8 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // Get exact relative 'scrollTop' via IO values const measurementPos = calculateOverBuffer(); - const maxIndex = Math.max(numItems - (virtualizerLength - 1), 0); + const maxIndex = Math.max(numItems - virtualizerLength, 0); - // const offset = latestEntry.target === beforeElementRef.current ? bufferItems : Math.max(bufferItems - 1, 1); - // const offset = Math.max(bufferItems - 1, 1); let startIndex = getIndexFromScrollPosition(measurementPos) - bufferItems; const dirMod = latestEntry.target === beforeElementRef.current ? -1 : 1; diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx index 77567ee66e686..4dd40c349a9dc 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { Virtualizer, useStaticVirtualizerMeasure } from '@fluentui/react-components/unstable'; -import { makeStyles, useMergedRefs } from '@fluentui/react-components'; +import { makeStyles } from '@fluentui/react-components'; const useStyles = makeStyles({ container: { @@ -27,11 +27,9 @@ export const Default = () => { defaultItemSize: 100, }); - const mergedref = useMergedRefs(scrollRef); - return (
-
+
{ bufferSize={bufferSize} itemSize={100} containerSizeRef={containerSizeRef} - scrollViewRef={mergedref} > {index => { return ( diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Reversed.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Reversed.stories.tsx index 542e5b51123ab..f78c80a4346d2 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Reversed.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Reversed.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { useStaticVirtualizerMeasure, Virtualizer } from '@fluentui/react-components/unstable'; -import { makeStyles, useMergedRefs } from '@fluentui/react-components'; +import { makeStyles } from '@fluentui/react-components'; const useStyles = makeStyles({ container: { @@ -27,10 +27,8 @@ export const Reversed = () => { defaultItemSize: itemSize, }); - const mergedRef = useMergedRefs(scrollRef); - return ( -
+
{ bufferSize={bufferSize} itemSize={itemSize} containerSizeRef={containerSizeRef} - scrollViewRef={mergedRef} > {index => { return ( diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/ReversedHorizontal.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/ReversedHorizontal.stories.tsx index ca2217ef1a07c..8daeb44c1951b 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/ReversedHorizontal.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/ReversedHorizontal.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { useStaticVirtualizerMeasure, Virtualizer } from '@fluentui/react-components/unstable'; -import { makeStyles, useMergedRefs } from '@fluentui/react-components'; +import { makeStyles } from '@fluentui/react-components'; const useStyles = makeStyles({ container: { @@ -29,10 +29,8 @@ export const ReversedHorizontal = () => { direction: 'horizontal', }); - const mergedRef = useMergedRefs(scrollRef); - return ( -
+
{ bufferSize={bufferSize} itemSize={itemWidth} containerSizeRef={containerSizeRef} - scrollViewRef={mergedRef} > {index => { return ( diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/Default.stories.tsx index dd3c8f2a199dc..1c2b9490c7784 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/Default.stories.tsx @@ -12,7 +12,7 @@ const useStyles = makeStyles({ export const Default = () => { const styles = useStyles(); - const childLength = 100; + const childLength = 10000; return ( { const styles = useStyles(); - const childLength = 1000; + const childLength = 10000; const minHeight = 42; const maxHeightMod = 150; // Array size ref stores a list of random num for div sizing and callbacks From 13bec551cab72a7facc8e344aef9437716f25cf0 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Wed, 28 Aug 2024 17:24:14 -0700 Subject: [PATCH 24/42] Update virtualizer buffer removal, should be inverse --- .../library/src/components/Virtualizer/useVirtualizer.ts | 6 +++--- .../VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index 69562d0ab5eed..ddadb9832bf97 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -334,7 +334,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // Add to original after position measurementPos += overflowAmount; // Ignore buffer size (IO offset) - measurementPos -= bufferSize; + measurementPos += bufferSize; // we hit the after buffer and detected the end of view, we need to find the start index. measurementPos -= containerSizeRef.current; @@ -358,10 +358,10 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // Get exact intersection position based on overflow size (how far into window did we scroll IO?) const overflowAmount = axis === 'vertical' ? latestEntry.intersectionRect.height : latestEntry.intersectionRect.width; - // Add to original after position + // Minus from original before position measurementPos -= overflowAmount; // Ignore buffer size (IO offset) - measurementPos += bufferSize; + measurementPos -= bufferSize; // Calculate how far past the window bounds we are (this will be zero if IO is within window) const hOverflow = latestEntry.boundingClientRect.bottom - latestEntry.intersectionRect.bottom; diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx index 11655b320bc55..6b37fcc8f289f 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx @@ -13,7 +13,7 @@ const useStyles = makeStyles({ export const AutoMeasure = () => { const styles = useStyles(); - const childLength = 1000; + const childLength = 100; const minHeight = 50; const maxHeightIncrease = 500; // Array size ref stores a list of random num for div sizing and callbacks From 518894b16897d21c10f1cb79e10f448945c2fe98 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Thu, 29 Aug 2024 13:22:33 -0700 Subject: [PATCH 25/42] Correctly handle partially visible elements --- .../src/Carousel/CarouselDefault.stories.tsx | 4 +- .../components/Virtualizer/useVirtualizer.ts | 10 +- .../useVirtualizerScrollViewDynamic.tsx | 2 +- .../library/src/hooks/hooks.types.ts | 3 +- .../src/hooks/useDynamicVirtualizerMeasure.ts | 109 +++++++++++------- .../library/src/hooks/useResizeObserverRef.ts | 12 +- .../VirtualizerContext/VirtualizerContext.ts | 33 +++++- .../src/utilities/VirtualizerContext/types.ts | 4 + .../src/Virtualizer/Dynamic.stories.tsx | 12 +- .../AutoMeasure.stories.tsx | 15 ++- 10 files changed, 145 insertions(+), 59 deletions(-) diff --git a/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselDefault.stories.tsx b/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselDefault.stories.tsx index d734ec6efa2c7..51c95a302b55d 100644 --- a/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselDefault.stories.tsx +++ b/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselDefault.stories.tsx @@ -14,7 +14,7 @@ const useClasses = makeStyles({ ...typographyStyles.largeTitle, alignContent: 'center', borderRadius: tokens.borderRadiusLarge, - height: '450px', + minHeight: '450px', textAlign: 'center', }, }); @@ -31,7 +31,7 @@ const TestComponent: React.FC<{ accentColor: string; children: React.ReactNode } }; export const Default = () => ( - + diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index ddadb9832bf97..ae095defe8dd5 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -58,6 +58,9 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta /* We keep track of the progressive sizing/placement down the list, this helps us skip re-calculations unless children/size changes */ const childProgressiveSizes = useRef(new Array(getItemSize ? numItems : 0)); + if (virtualizerContext) { + virtualizerContext.childProgressiveSizes.current = childProgressiveSizes.current; + } // The internal tracking REF for child array (updates often). const childArray = useRef(new Array(virtualizerLength)); @@ -77,6 +80,9 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta if (numItems !== childProgressiveSizes.current.length) { childProgressiveSizes.current = new Array(numItems); + if (virtualizerContext) { + virtualizerContext.childProgressiveSizes.current = childProgressiveSizes.current; + } } for (let index = 0; index < numItems; index++) { @@ -410,11 +416,13 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta actualIndex < newStartIndex && actualIndex + virtualizerLength >= numItems && newStartIndex + virtualizerLength >= numItems; + _virtualizerContext.setContextPosition(measurementPos); if (actualIndex !== newStartIndex && !endAlreadyReached) { batchUpdateNewIndex(newStartIndex); } }, [ + _virtualizerContext, actualIndex, axis, batchUpdateNewIndex, @@ -504,7 +512,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // Only fire on mount (no deps). useEffect(() => { if (actualIndex < 0) { - batchUpdateNewIndex(0); + batchUpdateNewIndex(0, 0); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.tsx b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.tsx index 3698521073656..64dff6c4a8601 100644 --- a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.tsx +++ b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.tsx @@ -53,7 +53,7 @@ export function useVirtualizerScrollViewDynamic_unstable( defaultItemSize: props.itemSize, direction: props.axis ?? 'vertical', getItemSize: props.getItemSize ?? getChildSizeAuto, - currentIndex: contextState?.contextIndex ?? 0, + virtualizerContext: contextState, numItems: props.numItems, bufferItems: _bufferItems, bufferSize: _bufferSize, diff --git a/packages/react-components/react-virtualizer/library/src/hooks/hooks.types.ts b/packages/react-components/react-virtualizer/library/src/hooks/hooks.types.ts index 1af9bc009c4c1..325d0f83c3d28 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/hooks.types.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/hooks.types.ts @@ -1,4 +1,5 @@ import { MutableRefObject, RefObject } from 'react'; +import { VirtualizerContextProps } from '../Utilities'; export type VirtualizerMeasureProps = { defaultItemSize: number; @@ -17,7 +18,7 @@ export type VirtualizerMeasureProps = { export type VirtualizerMeasureDynamicProps = { defaultItemSize: number; - currentIndex: number; + virtualizerContext: VirtualizerContextProps; numItems: number; getItemSize: (index: number) => number; direction?: 'vertical' | 'horizontal'; diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts index df9ce7cab0329..a347a7eb3b564 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts @@ -1,4 +1,4 @@ -import { useIsomorphicLayoutEffect } from '@fluentui/react-utilities'; +import { useIsomorphicLayoutEffect, useMergedRefs } from '@fluentui/react-utilities'; import * as React from 'react'; import { VirtualizerMeasureDynamicProps } from './hooks.types'; import { useResizeObserverRef_unstable } from './useResizeObserverRef'; @@ -22,12 +22,12 @@ export const useDynamicVirtualizerMeasure = ( direction = 'vertical', numItems, getItemSize, - currentIndex, bufferItems, bufferSize, + virtualizerContext, } = virtualizerProps; - const indexRef = useRef(currentIndex); - indexRef.current = currentIndex; + const indexRef = useRef(virtualizerContext.contextIndex); + indexRef.current = virtualizerContext.contextIndex; const [state, setState] = React.useState({ virtualizerLength: 0, @@ -46,15 +46,14 @@ export const useDynamicVirtualizerMeasure = ( // Error? ignore? return; } + console.log('Scroll ref!'); if (scrollRef.current !== targetDocument?.body) { // We have a local scroll container - const containerSize = + containerSizeRef.current = direction === 'vertical' ? scrollRef?.current.getBoundingClientRect().height : scrollRef?.current.getBoundingClientRect().width; - - containerSizeRef.current = containerSize; } else if (targetDocument?.defaultView) { // If our scroll ref is the document body, we should check window height containerSizeRef.current = @@ -62,16 +61,66 @@ export const useDynamicVirtualizerMeasure = ( } let indexSizer = 0; + let i = 0; let length = 1; - while (indexSizer <= containerSizeRef.current && length < numItems) { - const iItemSize = getItemSize(indexRef.current + length); - + console.log('indexRef:', indexRef.current); + console.log('virtualizerContext.contextPosition:', virtualizerContext.contextPosition); + console.log('array positions:', virtualizerContext.childProgressiveSizes); + console.log('containerSizeRef.current:', containerSizeRef.current); + + console.log('indexSizer <= containerSizeRef.current:', indexSizer <= containerSizeRef.current); + console.log( + 'length + virtualizerContext.contextPosition < numItems:', + length + virtualizerContext.contextPosition < numItems, + ); + + const sizeToBeat = containerSizeRef.current + virtualizerBufferSize * 2; + console.log('numItems:', numItems); + while (indexSizer <= containerSizeRef.current && i + virtualizerContext.contextIndex < numItems) { + console.log('index sizer 1:', indexSizer); + const iItemSize = getItemSize(indexRef.current + i); + if ( + !virtualizerContext.childProgressiveSizes?.current || + virtualizerContext.childProgressiveSizes?.current.length < numItems + ) { + console.log('QUICK EXIT'); + return virtualizerLength - virtualizerBufferSize * 2; + } + + const currentScrollPos = virtualizerContext.contextPosition; + const currentItemPos = virtualizerContext.childProgressiveSizes?.current[indexRef.current + i] - iItemSize; + + let variance = 0; + console.log('CHECK INDEX:', indexRef.current + i); + console.log('currentScrollPos:', currentScrollPos); + console.log('item end pos:', currentItemPos + iItemSize); + console.log('CURRENT ITEM POS:', currentItemPos); + if (currentScrollPos > currentItemPos + iItemSize) { + console.log('This item is FULLY hidden - 1: ', indexRef.current + i); + // The item isn't in view, ignore for now. + i++; + continue; + } else if (currentScrollPos > currentItemPos) { + console.log('This item is PARTIALLY hidden: ', indexRef.current + i); + // The item is partially out of view, ignore the out of bounds + variance = currentItemPos + iItemSize - currentScrollPos; + console.log('VARIANCE:', variance); + indexSizer += variance; + } else { + indexSizer += iItemSize; + } // Increment - indexSizer += iItemSize; + console.log('iItemSize:', iItemSize); + console.log('variance:', variance); + i++; length++; } + console.log('Check: ', i + virtualizerContext.contextPosition < numItems); + console.log('index sizer 2:', indexSizer); + console.log('Got new length:', length); + /* * Number of items to append at each end, i.e. 'preload' each side before entering view. * Minimum: 2 - we give slightly more buffer for dynamic version. @@ -98,6 +147,10 @@ export const useDynamicVirtualizerMeasure = ( numItems, targetDocument?.body, targetDocument?.defaultView, + virtualizerBufferSize, + virtualizerContext.childProgressiveSizes, + virtualizerContext.contextPosition, + virtualizerLength, ], ); @@ -116,39 +169,11 @@ export const useDynamicVirtualizerMeasure = ( [handleScrollResize], ); - const scrollRef = useResizeObserverRef_unstable(resizeCallback); + const scrollRef = useMergedRefs(container, useResizeObserverRef_unstable(resizeCallback)); useIsomorphicLayoutEffect(() => { - let couldBeSmaller = false; - let recheckTotal = 0; - for (let i = currentIndex; i < currentIndex + virtualizerLength; i++) { - const newItemSize = getItemSize(i); - recheckTotal += newItemSize; - - const newLength = i - currentIndex; - - const totalNewLength = newLength + virtualizerBufferItems * 2; - const compareLengths = totalNewLength < virtualizerLength; - - if (recheckTotal > containerSizeRef.current && compareLengths) { - couldBeSmaller = true; - break; - } - } - - // Check if the render has caused us to need a re-calc of virtualizer length - if (recheckTotal < containerSizeRef.current || couldBeSmaller) { - handleScrollResize(container); - } - }, [ - getItemSize, - currentIndex, - direction, - virtualizerLength, - resizeCallback, - handleScrollResize, - virtualizerBufferItems, - ]); + handleScrollResize(container); + }, [virtualizerContext.contextIndex]); return { virtualizerLength, diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useResizeObserverRef.ts b/packages/react-components/react-virtualizer/library/src/hooks/useResizeObserverRef.ts index aabb9113db19e..302e4b2ab9d2d 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useResizeObserverRef.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useResizeObserverRef.ts @@ -12,11 +12,21 @@ export const useResizeObserverRef_unstable = (resizeCallback: ResizeCallbackWith const { targetDocument } = useFluent(); const container = React.useRef(null); + const containerHeightRef = React.useRef(0); + const containerWidthRef = React.useRef(0); // the handler for resize observer // TODO: exclude types from this lint rule: https://github.com/microsoft/fluentui/issues/31286 // eslint-disable-next-line no-restricted-globals const handleResize = debounce((entries: ResizeObserverEntry[], observer: ResizeObserver) => { - resizeCallback(entries, observer, container); + const containerHeight = container.current?.clientHeight; + const containerWidth = container.current?.clientWidth; + console.log('Resize observer - 1'); + if (containerHeightRef.current !== containerHeight || containerWidth !== containerWidthRef.current) { + console.log('Resize observer - 2'); + containerWidthRef.current = containerWidth ?? 0; + containerHeightRef.current = containerHeight ?? 0; + resizeCallback(entries, observer, container); + } }); // Keep the reference of ResizeObserver in the state, as it should live through renders diff --git a/packages/react-components/react-virtualizer/library/src/utilities/VirtualizerContext/VirtualizerContext.ts b/packages/react-components/react-virtualizer/library/src/utilities/VirtualizerContext/VirtualizerContext.ts index 5d0cc41d69ead..4bf5c4d5ec2ed 100644 --- a/packages/react-components/react-virtualizer/library/src/utilities/VirtualizerContext/VirtualizerContext.ts +++ b/packages/react-components/react-virtualizer/library/src/utilities/VirtualizerContext/VirtualizerContext.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import type { VirtualizerContextProps } from './types'; -import { useMemo, useState } from 'react'; +import { useMemo, useState, useRef } from 'react'; const VirtualizerContext = React.createContext( undefined, @@ -17,17 +17,40 @@ export const useVirtualizerContextState_unstable = ( ): VirtualizerContextProps => { const virtualizerContext = useVirtualizerContext_unstable(); const [_contextIndex, _setContextIndex] = useState(-1); + const [_contextPosition, _setContextPosition] = useState(0); + const childProgressiveSizes = useRef([]); /* We respect any wrapped providers while also ensuring defaults or passed through * Order of usage -> Passed Prop -> Provider Context -> Internal State default */ const _context = useMemo( - () => passedContext ?? virtualizerContext ?? { contextIndex: _contextIndex, setContextIndex: _setContextIndex }, - [_contextIndex, passedContext, virtualizerContext], + () => + passedContext ?? + virtualizerContext ?? { + contextIndex: _contextIndex, + setContextIndex: _setContextIndex, + contextPosition: _contextPosition, + setContextPosition: _setContextPosition, + childProgressiveSizes, + }, + [_contextIndex, _contextPosition, passedContext, virtualizerContext], ); + const context = useMemo(() => { - return { contextIndex: _context.contextIndex, setContextIndex: _context.setContextIndex }; - }, [_context]); + return { + contextIndex: _context.contextIndex, + setContextIndex: _context.setContextIndex, + contextPosition: _context.contextPosition, + setContextPosition: _context.setContextPosition, + childProgressiveSizes, + }; + }, [ + _context.contextIndex, + _context.contextPosition, + _context.setContextIndex, + _context.setContextPosition, + childProgressiveSizes, + ]); return context; }; diff --git a/packages/react-components/react-virtualizer/library/src/utilities/VirtualizerContext/types.ts b/packages/react-components/react-virtualizer/library/src/utilities/VirtualizerContext/types.ts index 408d3773f4228..783301fd4d8c5 100644 --- a/packages/react-components/react-virtualizer/library/src/utilities/VirtualizerContext/types.ts +++ b/packages/react-components/react-virtualizer/library/src/utilities/VirtualizerContext/types.ts @@ -1,7 +1,11 @@ +import * as React from 'react'; /** * {@docCategory Virtualizer} */ export type VirtualizerContextProps = { contextIndex: number; setContextIndex: (index: number) => void; + contextPosition: number; + setContextPosition: (index: number) => void; + childProgressiveSizes: React.MutableRefObject; }; diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx index 6f0c0f61f6e05..e415bd8d2e447 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx @@ -34,6 +34,7 @@ const useStyles = makeStyles({ export const Dynamic = () => { const [currentIndex, setCurrentIndex] = React.useState(-1); + const [currentPosition, setCurrentPosition] = React.useState(0); const [flag, toggleFlag] = React.useState(false); const styles = useStyles(); const childLength = 1000; @@ -63,15 +64,22 @@ export const Dynamic = () => { [flag], ); + const contextState = { + contextIndex: currentIndex, + setContextIndex: setCurrentIndex, + contextPosition: currentPosition, + setContextPosition: setCurrentPosition, + }; + const { virtualizerLength, bufferItems, bufferSize, scrollRef, containerSizeRef } = useDynamicVirtualizerMeasure({ defaultItemSize: 100, getItemSize: getSizeForIndex, numItems: childLength, - currentIndex, + virtualizerContext: contextState, }); return ( - +
{ const styles = useStyles(); const childLength = 100; - const minHeight = 50; - const maxHeightIncrease = 500; + const minHeight = 25; + const maxHeightIncrease = 55; // Array size ref stores a list of random num for div sizing and callbacks const arraySize = React.useRef(new Array(childLength).fill(minHeight)); useEffect(() => { // Set random heights on init (to be measured) for (let i = 0; i < childLength; i++) { - arraySize.current[i] = Math.floor(Math.random() * maxHeightIncrease + minHeight); + // if (i % 10 == 0) { + if (i < 10) { + arraySize.current[i] = 1000; + } else { + arraySize.current[i] = Math.floor(Math.random() * maxHeightIncrease + minHeight); + } + + // arraySize.current[i] = Math.floor(Math.random() * maxHeightIncrease + minHeight); } }, []); @@ -30,7 +37,7 @@ export const AutoMeasure = () => { Date: Thu, 29 Aug 2024 13:44:04 -0700 Subject: [PATCH 26/42] Update --- .../components/Virtualizer/useVirtualizer.ts | 2 + .../library/src/hooks/hooks.types.ts | 4 +- .../src/hooks/useDynamicVirtualizerMeasure.ts | 40 ++++--------------- .../library/src/hooks/useResizeObserverRef.ts | 3 +- .../react-virtualizer/library/src/index.ts | 2 +- .../src/utilities/VirtualizerContext/types.ts | 11 +++-- .../src/Virtualizer/Dynamic.stories.tsx | 5 ++- .../AutoMeasure.stories.tsx | 4 +- 8 files changed, 28 insertions(+), 43 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index ae095defe8dd5..84ae960b98d27 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -331,6 +331,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta */ let measurementPos = 0; if (latestEntry.target === afterElementRef.current) { + console.log('AFTER'); // Get after buffers position measurementPos = calculateTotalSize() - calculateAfter(); @@ -359,6 +360,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta measurementPos -= additionalOverflow; } } else if (latestEntry.target === beforeElementRef.current) { + console.log('BEFORE'); // Get before buffers position measurementPos = calculateBefore(); // Get exact intersection position based on overflow size (how far into window did we scroll IO?) diff --git a/packages/react-components/react-virtualizer/library/src/hooks/hooks.types.ts b/packages/react-components/react-virtualizer/library/src/hooks/hooks.types.ts index 325d0f83c3d28..72323c793048e 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/hooks.types.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/hooks.types.ts @@ -1,5 +1,5 @@ import { MutableRefObject, RefObject } from 'react'; -import { VirtualizerContextProps } from '../Utilities'; +import { DynamicVirtualizerContextProps } from '../Utilities'; export type VirtualizerMeasureProps = { defaultItemSize: number; @@ -18,7 +18,7 @@ export type VirtualizerMeasureProps = { export type VirtualizerMeasureDynamicProps = { defaultItemSize: number; - virtualizerContext: VirtualizerContextProps; + virtualizerContext: DynamicVirtualizerContextProps; numItems: number; getItemSize: (index: number) => number; direction?: 'vertical' | 'horizontal'; diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts index a347a7eb3b564..20b6b70155c11 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts @@ -46,7 +46,6 @@ export const useDynamicVirtualizerMeasure = ( // Error? ignore? return; } - console.log('Scroll ref!'); if (scrollRef.current !== targetDocument?.body) { // We have a local scroll container @@ -62,29 +61,15 @@ export const useDynamicVirtualizerMeasure = ( let indexSizer = 0; let i = 0; - let length = 1; - - console.log('indexRef:', indexRef.current); - console.log('virtualizerContext.contextPosition:', virtualizerContext.contextPosition); - console.log('array positions:', virtualizerContext.childProgressiveSizes); - console.log('containerSizeRef.current:', containerSizeRef.current); - - console.log('indexSizer <= containerSizeRef.current:', indexSizer <= containerSizeRef.current); - console.log( - 'length + virtualizerContext.contextPosition < numItems:', - length + virtualizerContext.contextPosition < numItems, - ); + let length = 0; const sizeToBeat = containerSizeRef.current + virtualizerBufferSize * 2; - console.log('numItems:', numItems); - while (indexSizer <= containerSizeRef.current && i + virtualizerContext.contextIndex < numItems) { - console.log('index sizer 1:', indexSizer); + while (indexSizer <= sizeToBeat && i + virtualizerContext.contextIndex < numItems) { const iItemSize = getItemSize(indexRef.current + i); if ( !virtualizerContext.childProgressiveSizes?.current || virtualizerContext.childProgressiveSizes?.current.length < numItems ) { - console.log('QUICK EXIT'); return virtualizerLength - virtualizerBufferSize * 2; } @@ -92,35 +77,22 @@ export const useDynamicVirtualizerMeasure = ( const currentItemPos = virtualizerContext.childProgressiveSizes?.current[indexRef.current + i] - iItemSize; let variance = 0; - console.log('CHECK INDEX:', indexRef.current + i); - console.log('currentScrollPos:', currentScrollPos); - console.log('item end pos:', currentItemPos + iItemSize); - console.log('CURRENT ITEM POS:', currentItemPos); if (currentScrollPos > currentItemPos + iItemSize) { - console.log('This item is FULLY hidden - 1: ', indexRef.current + i); // The item isn't in view, ignore for now. i++; continue; } else if (currentScrollPos > currentItemPos) { - console.log('This item is PARTIALLY hidden: ', indexRef.current + i); // The item is partially out of view, ignore the out of bounds variance = currentItemPos + iItemSize - currentScrollPos; - console.log('VARIANCE:', variance); indexSizer += variance; } else { indexSizer += iItemSize; } // Increment - console.log('iItemSize:', iItemSize); - console.log('variance:', variance); i++; length++; } - console.log('Check: ', i + virtualizerContext.contextPosition < numItems); - console.log('index sizer 2:', indexSizer); - console.log('Got new length:', length); - /* * Number of items to append at each end, i.e. 'preload' each side before entering view. * Minimum: 2 - we give slightly more buffer for dynamic version. @@ -149,6 +121,7 @@ export const useDynamicVirtualizerMeasure = ( targetDocument?.defaultView, virtualizerBufferSize, virtualizerContext.childProgressiveSizes, + virtualizerContext.contextIndex, virtualizerContext.contextPosition, virtualizerLength, ], @@ -172,8 +145,11 @@ export const useDynamicVirtualizerMeasure = ( const scrollRef = useMergedRefs(container, useResizeObserverRef_unstable(resizeCallback)); useIsomorphicLayoutEffect(() => { - handleScrollResize(container); - }, [virtualizerContext.contextIndex]); + if (virtualizerContext.contextIndex + virtualizerLength < numItems) { + // Avoid re-rendering/re-calculating when the end index has already been reached + handleScrollResize(container); + } + }, [handleScrollResize, numItems, virtualizerContext.contextIndex, virtualizerLength]); return { virtualizerLength, diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useResizeObserverRef.ts b/packages/react-components/react-virtualizer/library/src/hooks/useResizeObserverRef.ts index 302e4b2ab9d2d..fa44e72e928d5 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useResizeObserverRef.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useResizeObserverRef.ts @@ -20,9 +20,8 @@ export const useResizeObserverRef_unstable = (resizeCallback: ResizeCallbackWith const handleResize = debounce((entries: ResizeObserverEntry[], observer: ResizeObserver) => { const containerHeight = container.current?.clientHeight; const containerWidth = container.current?.clientWidth; - console.log('Resize observer - 1'); + // Our resize observer will fire on scroll resize, let index change handle that instead. if (containerHeightRef.current !== containerHeight || containerWidth !== containerWidthRef.current) { - console.log('Resize observer - 2'); containerWidthRef.current = containerWidth ?? 0; containerHeightRef.current = containerHeight ?? 0; resizeCallback(entries, observer, container); diff --git a/packages/react-components/react-virtualizer/library/src/index.ts b/packages/react-components/react-virtualizer/library/src/index.ts index e677de0bec9d7..6912c0057e4a7 100644 --- a/packages/react-components/react-virtualizer/library/src/index.ts +++ b/packages/react-components/react-virtualizer/library/src/index.ts @@ -38,7 +38,7 @@ export { scrollToItemDynamic, } from './Utilities'; -export type { VirtualizerContextProps } from './Utilities'; +export type { VirtualizerContextProps, DynamicVirtualizerContextProps } from './Utilities'; export { VirtualizerScrollView, diff --git a/packages/react-components/react-virtualizer/library/src/utilities/VirtualizerContext/types.ts b/packages/react-components/react-virtualizer/library/src/utilities/VirtualizerContext/types.ts index 783301fd4d8c5..02589559ce8db 100644 --- a/packages/react-components/react-virtualizer/library/src/utilities/VirtualizerContext/types.ts +++ b/packages/react-components/react-virtualizer/library/src/utilities/VirtualizerContext/types.ts @@ -5,7 +5,12 @@ import * as React from 'react'; export type VirtualizerContextProps = { contextIndex: number; setContextIndex: (index: number) => void; - contextPosition: number; - setContextPosition: (index: number) => void; - childProgressiveSizes: React.MutableRefObject; + /* + * These option props are used in dynamic virtualizer + */ + contextPosition?: number; + setContextPosition?: (index: number) => void; + childProgressiveSizes?: React.MutableRefObject; }; + +export type DynamicVirtualizerContextProps = Required; diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx index e415bd8d2e447..2473822ddaa7d 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx @@ -4,6 +4,7 @@ import { useDynamicVirtualizerMeasure, VirtualizerContextProvider, } from '@fluentui/react-components/unstable'; +import type { DynamicVirtualizerContextProps } from '@fluentui/react-components/unstable'; import { makeStyles } from '@fluentui/react-components'; import { useCallback, useRef } from 'react'; @@ -35,6 +36,7 @@ const useStyles = makeStyles({ export const Dynamic = () => { const [currentIndex, setCurrentIndex] = React.useState(-1); const [currentPosition, setCurrentPosition] = React.useState(0); + const childProgressiveSizes = React.useRef([]); const [flag, toggleFlag] = React.useState(false); const styles = useStyles(); const childLength = 1000; @@ -64,11 +66,12 @@ export const Dynamic = () => { [flag], ); - const contextState = { + const contextState: DynamicVirtualizerContextProps = { contextIndex: currentIndex, setContextIndex: setCurrentIndex, contextPosition: currentPosition, setContextPosition: setCurrentPosition, + childProgressiveSizes, }; const { virtualizerLength, bufferItems, bufferSize, scrollRef, containerSizeRef } = useDynamicVirtualizerMeasure({ diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx index 7c675aaa8bad6..edd57432e89e7 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx @@ -5,9 +5,9 @@ import { useEffect } from 'react'; const useStyles = makeStyles({ child: { - lineHeight: '42px', + lineHeight: '25px', width: '100%', - minHeight: '42px', + minHeight: '25px', }, }); From 43120cee29a9cb52b06a9c4f8375cf2bd7745d1c Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Thu, 29 Aug 2024 15:48:37 -0700 Subject: [PATCH 27/42] Fix up --- .../components/Virtualizer/useVirtualizer.ts | 2 +- .../VirtualizerScrollViewDynamic.types.ts | 13 ++++++- .../VirtualizerContext/VirtualizerContext.ts | 39 ++++++------------- 3 files changed, 23 insertions(+), 31 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index 84ae960b98d27..27101c07cb1fe 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -514,7 +514,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // Only fire on mount (no deps). useEffect(() => { if (actualIndex < 0) { - batchUpdateNewIndex(0, 0); + batchUpdateNewIndex(0); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts index 8ff695b652275..5129994d08ab8 100644 --- a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts +++ b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts @@ -7,12 +7,17 @@ import type { import type { VirtualizerScrollViewSlots } from '../VirtualizerScrollView/VirtualizerScrollView.types'; import type { RefObject } from 'react'; -import type { ScrollToInterface } from '../../Utilities'; +import type { DynamicVirtualizerContextProps, ScrollToInterface } from '../../Utilities'; export type VirtualizerScrollViewDynamicSlots = VirtualizerScrollViewSlots; export type VirtualizerScrollViewDynamicProps = ComponentProps> & - Partial> & { + Partial< + Omit< + VirtualizerConfigProps, + 'itemSize' | 'numItems' | 'getItemSize' | 'children' | 'flagIndex' | 'virtualizerContext' + > + > & { /** * Set as the minimum item size. * Axis: 'vertical' = Height @@ -43,6 +48,10 @@ export type VirtualizerScrollViewDynamicProps = ComponentProps & diff --git a/packages/react-components/react-virtualizer/library/src/utilities/VirtualizerContext/VirtualizerContext.ts b/packages/react-components/react-virtualizer/library/src/utilities/VirtualizerContext/VirtualizerContext.ts index 4bf5c4d5ec2ed..63bec41d3f61e 100644 --- a/packages/react-components/react-virtualizer/library/src/utilities/VirtualizerContext/VirtualizerContext.ts +++ b/packages/react-components/react-virtualizer/library/src/utilities/VirtualizerContext/VirtualizerContext.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import type { VirtualizerContextProps } from './types'; +import type { DynamicVirtualizerContextProps, VirtualizerContextProps } from './types'; import { useMemo, useState, useRef } from 'react'; const VirtualizerContext = React.createContext( @@ -14,7 +14,7 @@ export const useVirtualizerContext_unstable = () => { export const useVirtualizerContextState_unstable = ( passedContext?: VirtualizerContextProps, -): VirtualizerContextProps => { +): DynamicVirtualizerContextProps => { const virtualizerContext = useVirtualizerContext_unstable(); const [_contextIndex, _setContextIndex] = useState(-1); const [_contextPosition, _setContextPosition] = useState(0); @@ -23,34 +23,17 @@ export const useVirtualizerContextState_unstable = ( /* We respect any wrapped providers while also ensuring defaults or passed through * Order of usage -> Passed Prop -> Provider Context -> Internal State default */ - const _context = useMemo( - () => - passedContext ?? - virtualizerContext ?? { - contextIndex: _contextIndex, - setContextIndex: _setContextIndex, - contextPosition: _contextPosition, - setContextPosition: _setContextPosition, - childProgressiveSizes, - }, + const context = useMemo( + () => ({ + contextIndex: passedContext?.contextIndex ?? virtualizerContext?.contextIndex ?? _contextIndex, + setContextIndex: passedContext?.setContextIndex ?? virtualizerContext?.setContextIndex ?? _setContextIndex, + contextPosition: passedContext?.contextPosition ?? virtualizerContext?.contextPosition ?? _contextPosition, + setContextPosition: + passedContext?.setContextPosition ?? virtualizerContext?.setContextPosition ?? _setContextPosition, + childProgressiveSizes, + }), [_contextIndex, _contextPosition, passedContext, virtualizerContext], ); - const context = useMemo(() => { - return { - contextIndex: _context.contextIndex, - setContextIndex: _context.setContextIndex, - contextPosition: _context.contextPosition, - setContextPosition: _context.setContextPosition, - childProgressiveSizes, - }; - }, [ - _context.contextIndex, - _context.contextPosition, - _context.setContextIndex, - _context.setContextPosition, - childProgressiveSizes, - ]); - return context; }; From 01139d9d17cc6031637e2da8383b79aced24988e Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Thu, 29 Aug 2024 16:00:53 -0700 Subject: [PATCH 28/42] Fix up flush sync --- .../src/components/Virtualizer/useVirtualizer.ts | 14 +++++++++----- .../stories/src/Virtualizer/Dynamic.stories.tsx | 1 + 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index 27101c07cb1fe..da4fb85ae94a8 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -5,6 +5,7 @@ import { useEffect, useRef, useCallback, useReducer, useImperativeHandle, useSta import { useIntersectionObserver } from '../../hooks/useIntersectionObserver'; import { useVirtualizerContextState_unstable } from '../../Utilities'; import { slot, useTimeout } from '@fluentui/react-utilities'; +import { flushSync } from 'react-dom'; export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerState { 'use no memo'; @@ -58,7 +59,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta /* We keep track of the progressive sizing/placement down the list, this helps us skip re-calculations unless children/size changes */ const childProgressiveSizes = useRef(new Array(getItemSize ? numItems : 0)); - if (virtualizerContext) { + if (virtualizerContext?.childProgressiveSizes) { virtualizerContext.childProgressiveSizes.current = childProgressiveSizes.current; } @@ -80,7 +81,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta if (numItems !== childProgressiveSizes.current.length) { childProgressiveSizes.current = new Array(numItems); - if (virtualizerContext) { + if (virtualizerContext?.childProgressiveSizes) { virtualizerContext.childProgressiveSizes.current = childProgressiveSizes.current; } } @@ -398,7 +399,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta let startIndex = getIndexFromScrollPosition(measurementPos) - bufferItems; const dirMod = latestEntry.target === beforeElementRef.current ? -1 : 1; - if (actualIndex === startIndex) { + if (getItemSize && actualIndex === startIndex) { /* We can't actually hit the same index twice, as IO only fires once per intersection * Usually this is caused by dynamically changing sizes causing us to recalc the same index * Instead, we buffer it -1:1 so that we can recalc the index with latest values @@ -420,7 +421,9 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta newStartIndex + virtualizerLength >= numItems; _virtualizerContext.setContextPosition(measurementPos); if (actualIndex !== newStartIndex && !endAlreadyReached) { - batchUpdateNewIndex(newStartIndex); + flushSync(() => { + batchUpdateNewIndex(newStartIndex); + }); } }, [ @@ -435,6 +438,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta calculateTotalSize, containerSizeRef, getIndexFromScrollPosition, + getItemSize, numItems, reversed, updateCurrentItemSizes, @@ -523,7 +527,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta useEffect(() => { if (actualIndex >= 0) { updateChildRows(actualIndex); - forceUpdate(); + // forceUpdate(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [renderChild, isScrolling]); diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx index 2473822ddaa7d..f315d215e9ffb 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx @@ -92,6 +92,7 @@ export const Dynamic = () => { virtualizerLength={virtualizerLength} itemSize={100} containerSizeRef={containerSizeRef} + virtualizerContext={contextState} > {useCallback( (index: number) => { From 3246d45cfd88d940beaf843dea220c554a71676f Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Fri, 30 Aug 2024 09:55:25 -0700 Subject: [PATCH 29/42] Remove force render and console log --- .../library/src/components/Virtualizer/useVirtualizer.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index da4fb85ae94a8..33704d5514c3d 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -66,9 +66,6 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // The internal tracking REF for child array (updates often). const childArray = useRef(new Array(virtualizerLength)); - // We want to be methodical about updating the render with child reference array - const forceUpdate = useReducer(() => ({}), {})[1]; - const populateSizeArrays = () => { if (!getItemSize) { // Static sizes, never mind! @@ -332,7 +329,6 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta */ let measurementPos = 0; if (latestEntry.target === afterElementRef.current) { - console.log('AFTER'); // Get after buffers position measurementPos = calculateTotalSize() - calculateAfter(); @@ -361,7 +357,6 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta measurementPos -= additionalOverflow; } } else if (latestEntry.target === beforeElementRef.current) { - console.log('BEFORE'); // Get before buffers position measurementPos = calculateBefore(); // Get exact intersection position based on overflow size (how far into window did we scroll IO?) @@ -527,7 +522,6 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta useEffect(() => { if (actualIndex >= 0) { updateChildRows(actualIndex); - // forceUpdate(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [renderChild, isScrolling]); From b7dd05f653f924d9a6cc0c84520530a727bfb021 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Fri, 30 Aug 2024 10:59:40 -0700 Subject: [PATCH 30/42] Update --- .../library/src/components/Virtualizer/useVirtualizer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index 33704d5514c3d..bb0e99646ffcc 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -1,7 +1,7 @@ import type { ReactNode } from 'react'; import type { VirtualizerProps, VirtualizerState } from './Virtualizer.types'; -import { useEffect, useRef, useCallback, useReducer, useImperativeHandle, useState } from 'react'; +import { useEffect, useRef, useCallback, useImperativeHandle, useState } from 'react'; import { useIntersectionObserver } from '../../hooks/useIntersectionObserver'; import { useVirtualizerContextState_unstable } from '../../Utilities'; import { slot, useTimeout } from '@fluentui/react-utilities'; From 4457e2d470cec3b22e5f60b37e72217b251c0131 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Fri, 30 Aug 2024 11:02:38 -0700 Subject: [PATCH 31/42] Remove optionals --- .../library/src/hooks/useDynamicVirtualizerMeasure.ts | 6 +++--- .../library/src/utilities/VirtualizerContext/types.ts | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts index 20b6b70155c11..e9771fd68973e 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts @@ -67,14 +67,14 @@ export const useDynamicVirtualizerMeasure = ( while (indexSizer <= sizeToBeat && i + virtualizerContext.contextIndex < numItems) { const iItemSize = getItemSize(indexRef.current + i); if ( - !virtualizerContext.childProgressiveSizes?.current || - virtualizerContext.childProgressiveSizes?.current.length < numItems + !virtualizerContext.childProgressiveSizes.current || + virtualizerContext.childProgressiveSizes.current.length < numItems ) { return virtualizerLength - virtualizerBufferSize * 2; } const currentScrollPos = virtualizerContext.contextPosition; - const currentItemPos = virtualizerContext.childProgressiveSizes?.current[indexRef.current + i] - iItemSize; + const currentItemPos = virtualizerContext.childProgressiveSizes.current[indexRef.current + i] - iItemSize; let variance = 0; if (currentScrollPos > currentItemPos + iItemSize) { diff --git a/packages/react-components/react-virtualizer/library/src/utilities/VirtualizerContext/types.ts b/packages/react-components/react-virtualizer/library/src/utilities/VirtualizerContext/types.ts index 02589559ce8db..47a91d04c71ee 100644 --- a/packages/react-components/react-virtualizer/library/src/utilities/VirtualizerContext/types.ts +++ b/packages/react-components/react-virtualizer/library/src/utilities/VirtualizerContext/types.ts @@ -13,4 +13,7 @@ export type VirtualizerContextProps = { childProgressiveSizes?: React.MutableRefObject; }; +/** + * Some props are optional on static virtualizer, but required for dynamic. + */ export type DynamicVirtualizerContextProps = Required; From c38f25b7b997be28d563dad8015bd7e8806cb114 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Fri, 30 Aug 2024 11:04:22 -0700 Subject: [PATCH 32/42] Comment and remove unnessecary check --- .../library/src/hooks/useDynamicVirtualizerMeasure.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts index e9771fd68973e..ded962abf88d3 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts @@ -66,10 +66,9 @@ export const useDynamicVirtualizerMeasure = ( const sizeToBeat = containerSizeRef.current + virtualizerBufferSize * 2; while (indexSizer <= sizeToBeat && i + virtualizerContext.contextIndex < numItems) { const iItemSize = getItemSize(indexRef.current + i); - if ( - !virtualizerContext.childProgressiveSizes.current || - virtualizerContext.childProgressiveSizes.current.length < numItems - ) { + if (virtualizerContext.childProgressiveSizes.current.length < numItems) { + /* We are in unknown territory, either an initial render or an update has occurred. + We need to let the new items render first then we can accurately assess.*/ return virtualizerLength - virtualizerBufferSize * 2; } From 21ae3fc7bab71cabfd6caaf4bdfb645fe4fc8a6d Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Fri, 30 Aug 2024 11:06:50 -0700 Subject: [PATCH 33/42] Cleanup --- .../library/src/hooks/useDynamicVirtualizerMeasure.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts index ded962abf88d3..2d509d667d0f6 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts @@ -67,7 +67,8 @@ export const useDynamicVirtualizerMeasure = ( while (indexSizer <= sizeToBeat && i + virtualizerContext.contextIndex < numItems) { const iItemSize = getItemSize(indexRef.current + i); if (virtualizerContext.childProgressiveSizes.current.length < numItems) { - /* We are in unknown territory, either an initial render or an update has occurred. + /* We are in unknown territory, either an initial render or an update + in virtualizer item length has occurred. We need to let the new items render first then we can accurately assess.*/ return virtualizerLength - virtualizerBufferSize * 2; } @@ -75,16 +76,16 @@ export const useDynamicVirtualizerMeasure = ( const currentScrollPos = virtualizerContext.contextPosition; const currentItemPos = virtualizerContext.childProgressiveSizes.current[indexRef.current + i] - iItemSize; - let variance = 0; if (currentScrollPos > currentItemPos + iItemSize) { // The item isn't in view, ignore for now. i++; continue; } else if (currentScrollPos > currentItemPos) { - // The item is partially out of view, ignore the out of bounds - variance = currentItemPos + iItemSize - currentScrollPos; + // The item is partially out of view, ignore the out of bounds portion + const variance = currentItemPos + iItemSize - currentScrollPos; indexSizer += variance; } else { + // Item is in view indexSizer += iItemSize; } // Increment From 3495ad449e958ff83d36ca7236adc49226a58a5d Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Fri, 30 Aug 2024 11:42:58 -0700 Subject: [PATCH 34/42] Update virtualizer api --- .../library/etc/react-virtualizer.api.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/etc/react-virtualizer.api.md b/packages/react-components/react-virtualizer/library/etc/react-virtualizer.api.md index 8837416b5e9db..b349220e69646 100644 --- a/packages/react-components/react-virtualizer/library/etc/react-virtualizer.api.md +++ b/packages/react-components/react-virtualizer/library/etc/react-virtualizer.api.md @@ -15,6 +15,9 @@ import type { SetStateAction } from 'react'; import type { Slot } from '@fluentui/react-utilities'; import type { SlotClassNames } from '@fluentui/react-utilities'; +// @public +export type DynamicVirtualizerContextProps = Required; + // @public (undocumented) export interface IndexedResizeCallbackElement { // (undocumented) @@ -142,6 +145,9 @@ export const virtualizerClassNames: SlotClassNames; export type VirtualizerContextProps = { contextIndex: number; setContextIndex: (index: number) => void; + contextPosition?: number; + setContextPosition?: (index: number) => void; + childProgressiveSizes?: React_2.MutableRefObject; }; // @public (undocumented) @@ -158,7 +164,7 @@ export type VirtualizerDataRef = { // @public (undocumented) export type VirtualizerMeasureDynamicProps = { defaultItemSize: number; - currentIndex: number; + virtualizerContext: DynamicVirtualizerContextProps; numItems: number; getItemSize: (index: number) => number; direction?: 'vertical' | 'horizontal'; @@ -190,13 +196,14 @@ export const VirtualizerScrollViewDynamic: React_2.FC; // @public (undocumented) -export type VirtualizerScrollViewDynamicProps = ComponentProps> & Partial> & { +export type VirtualizerScrollViewDynamicProps = ComponentProps> & Partial> & { itemSize: number; getItemSize?: (index: number) => number; numItems: number; children: VirtualizerChildRenderFunction; imperativeRef?: RefObject; enablePagination?: boolean; + virtualizerContext: DynamicVirtualizerContextProps; }; // @public (undocumented) From 6f743aef362909d6830839607d79a32bbb1df28f Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Fri, 30 Aug 2024 13:40:34 -0700 Subject: [PATCH 35/42] Final build cleanup --- .../library/etc/react-virtualizer.api.md | 4 +- .../Virtualizer/Virtualizer.types.ts | 17 ++++++- .../components/Virtualizer/useVirtualizer.ts | 50 +++++++++---------- .../VirtualizerScrollViewDynamic.types.ts | 2 +- .../src/hooks/useVirtualizerMeasure.ts | 4 +- .../src/Virtualizer/Default.stories.tsx | 12 ++--- .../Virtualizer/DefaultUnbounded.stories.tsx | 2 +- .../src/Virtualizer/Dynamic.stories.tsx | 8 +-- .../src/Virtualizer/Horizontal.stories.tsx | 2 +- .../Virtualizer/MultiUnbounded.stories.tsx | 2 +- .../stories/src/Virtualizer/RTL.stories.tsx | 2 +- .../src/Virtualizer/Reversed.stories.tsx | 2 +- .../ReversedHorizontal.stories.tsx | 2 +- .../src/Virtualizer/VirtualizerDescription.md | 2 +- .../stories/src/Virtualizer/index.stories.ts | 2 +- .../VirtualizerScrollView/Default.stories.tsx | 2 +- .../ScrollTo.stories.tsx | 4 +- .../SnapToAlignment.stories.tsx | 2 +- .../VirtualizerScrollViewDescription.md | 2 +- .../VirtualizerScrollView/index.stories.ts | 2 +- .../AutoMeasure.stories.tsx | 2 +- .../Default.stories.tsx | 2 +- .../ScrollLoading.stories.tsx | 3 +- .../ScrollTo.stories.tsx | 4 +- .../SnapToAlignment.stories.tsx | 2 +- ...VirtualizerScrollViewDynamicDescription.md | 2 +- .../index.stories.ts | 2 +- 27 files changed, 73 insertions(+), 69 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/etc/react-virtualizer.api.md b/packages/react-components/react-virtualizer/library/etc/react-virtualizer.api.md index b349220e69646..aa8396690534a 100644 --- a/packages/react-components/react-virtualizer/library/etc/react-virtualizer.api.md +++ b/packages/react-components/react-virtualizer/library/etc/react-virtualizer.api.md @@ -135,7 +135,7 @@ export const useVirtualizerStyles_unstable: (state: VirtualizerState) => Virtual // @public export const Virtualizer: FC; -// @public (undocumented) +// @public export type VirtualizerChildRenderFunction = (index: number, isScrolling: boolean) => React_2.ReactNode; // @public (undocumented) @@ -203,7 +203,7 @@ export type VirtualizerScrollViewDynamicProps = ComponentProps; enablePagination?: boolean; - virtualizerContext: DynamicVirtualizerContextProps; + virtualizerContext?: DynamicVirtualizerContextProps; }; // @public (undocumented) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts index e6c99c5db383d..645983256a061 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts @@ -52,6 +52,11 @@ export type VirtualizerConfigState = { * Tells the virtualizer to measure in the reverse direction (for column-reverse order etc.) */ reversed?: boolean; + /** + * Enables the isScrolling property in the child render function + * Default: false - to prevent nessecary render function calls + */ + enableScrollLoad?: boolean; /** * Pixel size of intersection observers and how much they 'cross over' into the bufferItems index. * Minimum 1px. @@ -69,8 +74,10 @@ export type VirtualizerConfigState = { export type VirtualizerState = ComponentState & VirtualizerConfigState; -// Virtualizer render function to procedurally generate children elements as rows or columns via index. -// Q: Use generic typing and passing through object data or a simple index system? +/** + * The main child render method of Virtualization + * isScrolling will only be enabled when enableScrollLoad is set to true. + */ export type VirtualizerChildRenderFunction = (index: number, isScrolling: boolean) => React.ReactNode; export type VirtualizerDataRef = { @@ -150,6 +157,12 @@ export type VirtualizerConfigProps = { */ reversed?: boolean; + /** + * Enables the isScrolling property in the child render function + * Default: false - to prevent nessecary render function calls + */ + enableScrollLoad?: boolean; + /** * Callback for acquiring size of individual items * @param index - the index of the requested size's child diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index bb0e99646ffcc..a5a4207ab115c 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -1,7 +1,7 @@ import type { ReactNode } from 'react'; import type { VirtualizerProps, VirtualizerState } from './Virtualizer.types'; -import { useEffect, useRef, useCallback, useImperativeHandle, useState } from 'react'; +import { useEffect, useRef, useCallback, useImperativeHandle, useState, useReducer } from 'react'; import { useIntersectionObserver } from '../../hooks/useIntersectionObserver'; import { useVirtualizerContextState_unstable } from '../../Utilities'; import { slot, useTimeout } from '@fluentui/react-utilities'; @@ -25,6 +25,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta imperativeVirtualizerRef, containerSizeRef, scrollViewRef, + enableScrollLoad, } = props; /* The context is optional, it's useful for injecting additional index logic, or performing uniform state updates*/ @@ -98,6 +99,10 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta const scrollCounter = useRef(0); const initializeScrollingTimer = useCallback(() => { + if (!enableScrollLoad) { + // Disabled by default for reduction of render callbacks + return; + } /* * This can be considered the 'velocity' required to start 'isScrolling' * INIT_SCROLL_FLAG_REQ: Number of renders required to activate isScrolling @@ -116,7 +121,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta setIsScrolling(false); scrollCounter.current = 0; }, INIT_SCROLL_FLAG_DELAY); - }, [clearScrollTimer, setScrollTimer]); + }, [clearScrollTimer, setScrollTimer, enableScrollLoad]); useEffect(() => { initializeScrollingTimer(); @@ -338,7 +343,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // Add to original after position measurementPos += overflowAmount; // Ignore buffer size (IO offset) - measurementPos += bufferSize; + measurementPos -= bufferSize; // we hit the after buffer and detected the end of view, we need to find the start index. measurementPos -= containerSizeRef.current; @@ -365,7 +370,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // Minus from original before position measurementPos -= overflowAmount; // Ignore buffer size (IO offset) - measurementPos -= bufferSize; + measurementPos += bufferSize; // Calculate how far past the window bounds we are (this will be zero if IO is within window) const hOverflow = latestEntry.boundingClientRect.bottom - latestEntry.intersectionRect.bottom; @@ -393,33 +398,15 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta let startIndex = getIndexFromScrollPosition(measurementPos) - bufferItems; - const dirMod = latestEntry.target === beforeElementRef.current ? -1 : 1; - if (getItemSize && actualIndex === startIndex) { - /* We can't actually hit the same index twice, as IO only fires once per intersection - * Usually this is caused by dynamically changing sizes causing us to recalc the same index - * Instead, we buffer it -1:1 so that we can recalc the index with latest values - */ - startIndex += dirMod; - } - // Safety limits const newStartIndex = Math.min(Math.max(startIndex, 0), maxIndex); - /* - * Sometimes the dynamic virtualizer length changes on the final index, - * if this occurs we technically have a 'new' start index - * however, it is not needed as we already have a valid index + length to reach the end. - */ - const endAlreadyReached = - actualIndex < newStartIndex && - actualIndex + virtualizerLength >= numItems && - newStartIndex + virtualizerLength >= numItems; - _virtualizerContext.setContextPosition(measurementPos); - if (actualIndex !== newStartIndex && !endAlreadyReached) { - flushSync(() => { + flushSync(() => { + _virtualizerContext.setContextPosition(measurementPos); + if (actualIndex !== newStartIndex) { batchUpdateNewIndex(newStartIndex); - }); - } + } + }); }, [ _virtualizerContext, @@ -518,10 +505,19 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + /* + * forceUpdate: + * We only want to trigger this when scrollLoading is enabled and set to false, + * it will force re-render all children elements + */ + const forceUpdate = useReducer(() => ({}), {})[1]; // If the user passes in an updated renderChild function - update current children useEffect(() => { if (actualIndex >= 0) { updateChildRows(actualIndex); + if (enableScrollLoad && !isScrolling) { + forceUpdate(); + } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [renderChild, isScrolling]); diff --git a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts index 5129994d08ab8..3121398286109 100644 --- a/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts +++ b/packages/react-components/react-virtualizer/library/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts @@ -51,7 +51,7 @@ export type VirtualizerScrollViewDynamicProps = ComponentProps & diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts index 58cfc64729ab4..026125d50647c 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useVirtualizerMeasure.ts @@ -42,12 +42,10 @@ export const useStaticVirtualizerMeasure = ( if (scrollRef.current !== targetDocument?.body) { // We have a local scroll container - const containerSize = + containerSizeRef.current = direction === 'vertical' ? scrollRef?.current.getBoundingClientRect().height : scrollRef?.current.getBoundingClientRect().width; - - containerSizeRef.current = containerSize; } else if (targetDocument?.defaultView) { // If our scroll ref is the document body, we should check window height containerSizeRef.current = diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx index 4dd40c349a9dc..e198568fe03f9 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Default.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Virtualizer, useStaticVirtualizerMeasure } from '@fluentui/react-components/unstable'; +import { Virtualizer, useStaticVirtualizerMeasure } from '@fluentui/react-virtualizer'; import { makeStyles } from '@fluentui/react-components'; const useStyles = makeStyles({ @@ -9,13 +9,13 @@ const useStyles = makeStyles({ overflowY: 'auto', width: '100%', height: '100%', - maxHeight: '60vh', + maxHeight: '80vh', }, child: { height: '25px', - lineHeight: '100px', + lineHeight: '25px', width: '100%', - minHeight: '100px', + minHeight: '25px', }, }); @@ -24,7 +24,7 @@ export const Default = () => { const childLength = 1000; const { virtualizerLength, bufferItems, bufferSize, scrollRef, containerSizeRef } = useStaticVirtualizerMeasure({ - defaultItemSize: 100, + defaultItemSize: 25, }); return ( @@ -35,7 +35,7 @@ export const Default = () => { virtualizerLength={virtualizerLength} bufferItems={bufferItems} bufferSize={bufferSize} - itemSize={100} + itemSize={25} containerSizeRef={containerSizeRef} > {index => { diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/DefaultUnbounded.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/DefaultUnbounded.stories.tsx index 231812d7754f9..6a160404fc5ab 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/DefaultUnbounded.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/DefaultUnbounded.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Virtualizer, useStaticVirtualizerMeasure } from '@fluentui/react-components/unstable'; +import { Virtualizer, useStaticVirtualizerMeasure } from '@fluentui/react-virtualizer'; import { makeStyles } from '@fluentui/react-components'; import { useFluent } from '@fluentui/react-components'; diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx index f315d215e9ffb..09f78c9ef429c 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Dynamic.stories.tsx @@ -1,10 +1,6 @@ import * as React from 'react'; -import { - Virtualizer, - useDynamicVirtualizerMeasure, - VirtualizerContextProvider, -} from '@fluentui/react-components/unstable'; -import type { DynamicVirtualizerContextProps } from '@fluentui/react-components/unstable'; +import { Virtualizer, useDynamicVirtualizerMeasure, VirtualizerContextProvider } from '@fluentui/react-virtualizer'; +import type { DynamicVirtualizerContextProps } from '@fluentui/react-virtualizer'; import { makeStyles } from '@fluentui/react-components'; import { useCallback, useRef } from 'react'; diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Horizontal.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Horizontal.stories.tsx index 8649668951f11..ca017a693e59d 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Horizontal.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Horizontal.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { useStaticVirtualizerMeasure, Virtualizer } from '@fluentui/react-components/unstable'; +import { useStaticVirtualizerMeasure, Virtualizer } from '@fluentui/react-virtualizer'; import { makeStyles } from '@fluentui/react-components'; const useStyles = makeStyles({ diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/MultiUnbounded.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/MultiUnbounded.stories.tsx index 8b40f1e64913c..bc03277a444b3 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/MultiUnbounded.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/MultiUnbounded.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Virtualizer, useStaticVirtualizerMeasure } from '@fluentui/react-components/unstable'; +import { Virtualizer, useStaticVirtualizerMeasure } from '@fluentui/react-virtualizer'; import { makeStyles, useFluent } from '@fluentui/react-components'; const useStyles = makeStyles({ diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/RTL.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/RTL.stories.tsx index aba7aa3f175c1..8f4f1333c46ca 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/RTL.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/RTL.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { useStaticVirtualizerMeasure, Virtualizer } from '@fluentui/react-components/unstable'; +import { useStaticVirtualizerMeasure, Virtualizer } from '@fluentui/react-virtualizer'; import { makeStyles } from '@fluentui/react-components'; const useStyles = makeStyles({ diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Reversed.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Reversed.stories.tsx index f78c80a4346d2..56cc09b6257ca 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/Reversed.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/Reversed.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { useStaticVirtualizerMeasure, Virtualizer } from '@fluentui/react-components/unstable'; +import { useStaticVirtualizerMeasure, Virtualizer } from '@fluentui/react-virtualizer'; import { makeStyles } from '@fluentui/react-components'; const useStyles = makeStyles({ diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/ReversedHorizontal.stories.tsx b/packages/react-components/react-virtualizer/stories/src/Virtualizer/ReversedHorizontal.stories.tsx index 8daeb44c1951b..0dca7e63f0d69 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/ReversedHorizontal.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/ReversedHorizontal.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { useStaticVirtualizerMeasure, Virtualizer } from '@fluentui/react-components/unstable'; +import { useStaticVirtualizerMeasure, Virtualizer } from '@fluentui/react-virtualizer'; import { makeStyles } from '@fluentui/react-components'; const useStyles = makeStyles({ diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/VirtualizerDescription.md b/packages/react-components/react-virtualizer/stories/src/Virtualizer/VirtualizerDescription.md index 1bfbae160e2c3..99a7bd52f594f 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/VirtualizerDescription.md +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/VirtualizerDescription.md @@ -4,7 +4,7 @@ > > ```jsx > -> import { Virtualizer } from '@fluentui/react-components/unstable'; +> import { Virtualizer } from '@fluentui/react-virtualizer'; > > ``` > diff --git a/packages/react-components/react-virtualizer/stories/src/Virtualizer/index.stories.ts b/packages/react-components/react-virtualizer/stories/src/Virtualizer/index.stories.ts index 6632b13e634bd..b5a8886213d2a 100644 --- a/packages/react-components/react-virtualizer/stories/src/Virtualizer/index.stories.ts +++ b/packages/react-components/react-virtualizer/stories/src/Virtualizer/index.stories.ts @@ -1,4 +1,4 @@ -import { Virtualizer } from '@fluentui/react-components/unstable'; +import { Virtualizer } from '@fluentui/react-virtualizer'; import descriptionMd from './VirtualizerDescription.md'; export { Default } from './Default.stories'; diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/Default.stories.tsx index 1c2b9490c7784..b3253bb6930ee 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/Default.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { VirtualizerScrollView } from '@fluentui/react-components/unstable'; +import { VirtualizerScrollView } from '@fluentui/react-virtualizer'; import { makeStyles } from '@fluentui/react-components'; const useStyles = makeStyles({ diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/ScrollTo.stories.tsx b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/ScrollTo.stories.tsx index b5e85a9c61121..bc5bcd0bcf183 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/ScrollTo.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/ScrollTo.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { VirtualizerScrollView } from '@fluentui/react-components/unstable'; -import type { ScrollToInterface } from '@fluentui/react-components/unstable'; +import { VirtualizerScrollView } from '@fluentui/react-virtualizer'; +import type { ScrollToInterface } from '@fluentui/react-virtualizer'; import { Text, Input, makeStyles } from '@fluentui/react-components'; import { Button } from '@fluentui/react-components'; diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/SnapToAlignment.stories.tsx b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/SnapToAlignment.stories.tsx index 963c6332a1187..cb1621b11c55b 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/SnapToAlignment.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/SnapToAlignment.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { VirtualizerScrollView } from '@fluentui/react-components/unstable'; +import { VirtualizerScrollView } from '@fluentui/react-virtualizer'; import { makeStyles } from '@fluentui/react-components'; const useStyles = makeStyles({ diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/VirtualizerScrollViewDescription.md b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/VirtualizerScrollViewDescription.md index 6e866566c1075..abd58d5aef2f3 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/VirtualizerScrollViewDescription.md +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/VirtualizerScrollViewDescription.md @@ -4,7 +4,7 @@ > > ```jsx > -> import { VirtualizerScrollView } from '@fluentui/react-components/unstable'; +> import { VirtualizerScrollView } from '@fluentui/react-virtualizer'; > > ``` > diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/index.stories.ts b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/index.stories.ts index 97a1c28f8afb4..9114b2abdc353 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/index.stories.ts +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollView/index.stories.ts @@ -1,4 +1,4 @@ -import { VirtualizerScrollView } from '@fluentui/react-components/unstable'; +import { VirtualizerScrollView } from '@fluentui/react-virtualizer'; import descriptionMd from './VirtualizerScrollViewDescription.md'; export { Default } from './Default.stories'; diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx index edd57432e89e7..728cb8e0b0d37 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/AutoMeasure.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { VirtualizerScrollViewDynamic } from '@fluentui/react-components/unstable'; +import { VirtualizerScrollViewDynamic } from '@fluentui/react-virtualizer'; import { makeStyles } from '@fluentui/react-components'; import { useEffect } from 'react'; diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/Default.stories.tsx index d0351a3dd1a43..940583aaeae1f 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/Default.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { VirtualizerScrollViewDynamic } from '@fluentui/react-components/unstable'; +import { VirtualizerScrollViewDynamic } from '@fluentui/react-virtualizer'; import { makeStyles } from '@fluentui/react-components'; import { useEffect } from 'react'; diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/ScrollLoading.stories.tsx b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/ScrollLoading.stories.tsx index 3f546f9d5e069..3fb548125546e 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/ScrollLoading.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/ScrollLoading.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { VirtualizerScrollViewDynamic } from '@fluentui/react-components/unstable'; +import { VirtualizerScrollViewDynamic } from '@fluentui/react-virtualizer'; import { makeStyles } from '@fluentui/react-components'; import { useEffect } from 'react'; @@ -43,6 +43,7 @@ export const ScrollLoading = () => { itemSize={minHeight} getItemSize={getItemSizeCallback} container={{ role: 'list', style: { maxHeight: '80vh' } }} + enableScrollLoad={true} > {(index: number, isScrolling = false) => { const backgroundColor = index % 2 ? '#FFFFFF' : '#ABABAB'; diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/ScrollTo.stories.tsx b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/ScrollTo.stories.tsx index 8fdb53122a9ee..52b43d38b0734 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/ScrollTo.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/ScrollTo.stories.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -import { VirtualizerScrollViewDynamic } from '@fluentui/react-components/unstable'; -import type { ScrollToInterface } from '@fluentui/react-components/unstable'; +import { VirtualizerScrollViewDynamic } from '@fluentui/react-virtualizer'; +import type { ScrollToInterface } from '@fluentui/react-virtualizer'; import type { VirtualizerDataRef } from '@fluentui/react-virtualizer'; import { Button, Input, makeStyles, Text } from '@fluentui/react-components'; import { useEffect } from 'react'; diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/SnapToAlignment.stories.tsx b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/SnapToAlignment.stories.tsx index 722ff97990641..c44577f587a7c 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/SnapToAlignment.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/SnapToAlignment.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { VirtualizerScrollViewDynamic } from '@fluentui/react-components/unstable'; +import { VirtualizerScrollViewDynamic } from '@fluentui/react-virtualizer'; import { makeStyles } from '@fluentui/react-components'; import { useEffect } from 'react'; diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamicDescription.md b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamicDescription.md index 78973fe04255e..bc2261aa25f03 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamicDescription.md +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamicDescription.md @@ -4,7 +4,7 @@ > > ```jsx > -> import { VirtualizerScrollViewDynamic } from '@fluentui/react-components/unstable'; +> import { VirtualizerScrollViewDynamic } from '@fluentui/react-virtualizer'; > > ``` > diff --git a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/index.stories.ts b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/index.stories.ts index 5aa34ef060e83..9dee7b7a99216 100644 --- a/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/index.stories.ts +++ b/packages/react-components/react-virtualizer/stories/src/VirtualizerScrollViewDynamic/index.stories.ts @@ -1,4 +1,4 @@ -import { VirtualizerScrollViewDynamic } from '@fluentui/react-components/unstable'; +import { VirtualizerScrollViewDynamic } from '@fluentui/react-virtualizer'; import descriptionMd from './VirtualizerScrollViewDynamicDescription.md'; export { AutoMeasure } from './AutoMeasure.stories'; From 4aed451fb8406d8dc86ca376c9beba0635baff74 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Fri, 30 Aug 2024 13:48:10 -0700 Subject: [PATCH 36/42] Update changelog --- ...-react-virtualizer-bab73d05-7372-4052-8d22-ca75b5fadfe2.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/change/@fluentui-react-virtualizer-bab73d05-7372-4052-8d22-ca75b5fadfe2.json b/change/@fluentui-react-virtualizer-bab73d05-7372-4052-8d22-ca75b5fadfe2.json index a3eaf6e136311..23f3859fc95bd 100644 --- a/change/@fluentui-react-virtualizer-bab73d05-7372-4052-8d22-ca75b5fadfe2.json +++ b/change/@fluentui-react-virtualizer-bab73d05-7372-4052-8d22-ca75b5fadfe2.json @@ -1,6 +1,6 @@ { "type": "prerelease", - "comment": "fix: Export useMeasureList and type for consumption", + "comment": "BREAKING CHANGE (useVirtualizerDynamicMeasure): optimized with scrollPos state and children height reference, updated algorithm to be more accurate, and exported measurement hook", "packageName": "@fluentui/react-virtualizer", "email": "mifraser@microsoft.com", "dependentChangeType": "patch" From edd7073846b1cca1b68e1d0a2bc240c7c801b6e6 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Fri, 30 Aug 2024 14:27:00 -0700 Subject: [PATCH 37/42] Lint --- .../library/src/components/Virtualizer/useVirtualizer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index a5a4207ab115c..6cc23146b5b71 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -396,7 +396,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta const maxIndex = Math.max(numItems - virtualizerLength, 0); - let startIndex = getIndexFromScrollPosition(measurementPos) - bufferItems; + const startIndex = getIndexFromScrollPosition(measurementPos) - bufferItems; // Safety limits const newStartIndex = Math.min(Math.max(startIndex, 0), maxIndex); @@ -420,7 +420,6 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta calculateTotalSize, containerSizeRef, getIndexFromScrollPosition, - getItemSize, numItems, reversed, updateCurrentItemSizes, From 2f75783920fd1cdab012186d65c91dc12d42eb3f Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Fri, 30 Aug 2024 16:07:00 -0700 Subject: [PATCH 38/42] Update virtualized combobox --- .../stories/src/Combobox/ComboboxVirtualizer.stories.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-components/react-combobox/stories/src/Combobox/ComboboxVirtualizer.stories.tsx b/packages/react-components/react-combobox/stories/src/Combobox/ComboboxVirtualizer.stories.tsx index e30a5a3d8e3c4..e2cc66dd232ee 100644 --- a/packages/react-components/react-combobox/stories/src/Combobox/ComboboxVirtualizer.stories.tsx +++ b/packages/react-components/react-combobox/stories/src/Combobox/ComboboxVirtualizer.stories.tsx @@ -20,7 +20,7 @@ export const ComboboxVirtualizer = (props: Partial) => { const itemHeight = 32; //This should match the height of each item in the listbox const numberOfItems = 10000; - const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useStaticVirtualizerMeasure({ + const { virtualizerLength, bufferItems, bufferSize, scrollRef, containerSizeRef } = useStaticVirtualizerMeasure({ defaultItemSize: itemHeight, direction: 'vertical', }); @@ -43,6 +43,7 @@ export const ComboboxVirtualizer = (props: Partial) => { bufferItems={bufferItems} bufferSize={bufferSize} itemSize={itemHeight} + containerSizeRef={containerSizeRef} > {index => { return ( From 5682f9fe511b73c9b156e1a5bcf05f329f2a2181 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Fri, 30 Aug 2024 18:25:23 -0700 Subject: [PATCH 39/42] Fix up height calc on combobox --- .../stories/src/Combobox/ComboboxVirtualizer.stories.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-components/react-combobox/stories/src/Combobox/ComboboxVirtualizer.stories.tsx b/packages/react-components/react-combobox/stories/src/Combobox/ComboboxVirtualizer.stories.tsx index e2cc66dd232ee..4de580c3b373c 100644 --- a/packages/react-components/react-combobox/stories/src/Combobox/ComboboxVirtualizer.stories.tsx +++ b/packages/react-components/react-combobox/stories/src/Combobox/ComboboxVirtualizer.stories.tsx @@ -17,7 +17,8 @@ const useStyles = makeStyles({ export const ComboboxVirtualizer = (props: Partial) => { const comboId = useId('combobox'); - const itemHeight = 32; //This should match the height of each item in the listbox + //This should include the item height (32px) and account for rowGap (2px) + const itemHeight = 34; const numberOfItems = 10000; const { virtualizerLength, bufferItems, bufferSize, scrollRef, containerSizeRef } = useStaticVirtualizerMeasure({ From 795e81f0fe9c67d3676919fd78f1d87bb17aea7f Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Wed, 4 Sep 2024 13:33:06 -0700 Subject: [PATCH 40/42] small comment updates --- .../stories/src/Carousel/CarouselDefault.stories.tsx | 4 ++-- .../react-virtualizer/library/src/hooks/useMeasureList.ts | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselDefault.stories.tsx b/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselDefault.stories.tsx index 51c95a302b55d..d734ec6efa2c7 100644 --- a/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselDefault.stories.tsx +++ b/packages/react-components/react-carousel-preview/stories/src/Carousel/CarouselDefault.stories.tsx @@ -14,7 +14,7 @@ const useClasses = makeStyles({ ...typographyStyles.largeTitle, alignContent: 'center', borderRadius: tokens.borderRadiusLarge, - minHeight: '450px', + height: '450px', textAlign: 'center', }, }); @@ -31,7 +31,7 @@ const TestComponent: React.FC<{ accentColor: string; children: React.ReactNode } }; export const Default = () => ( - + diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useMeasureList.ts b/packages/react-components/react-virtualizer/library/src/hooks/useMeasureList.ts index 72f2c62be9f33..318aa3e7f883e 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useMeasureList.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useMeasureList.ts @@ -60,7 +60,9 @@ export function useMeasureList< React.useEffect(() => { const newHeightLength = totalLength - heightArray.current.length; const newWidthLength = totalLength - widthArray.current.length; - // Ensure we grow or truncate arrays with prior properties + /* Ensure we grow or truncate arrays with prior properties, + keeping the existing values is important for whitespace assumptions. + Even if items in the 'middle' are deleted, we will recalc the whitespace as it is explored.*/ if (newWidthLength > 0) { widthArray.current = widthArray.current.concat(new Array(newWidthLength).fill(defaultItemSize)); } else if (newWidthLength < 0) { From e6284168a07f5ad461804cd8ab8a05ef2c155078 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Fri, 6 Sep 2024 08:32:42 -0700 Subject: [PATCH 41/42] Update comment --- .../library/src/components/Virtualizer/Virtualizer.types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts index 645983256a061..f2b58e8e565d1 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts @@ -192,6 +192,7 @@ export type VirtualizerConfigProps = { /** * A ref that provides the size of container (vertical - height, horizontal - width), set by a resize observer. + * Virtualizer Measure hooks provide a suitable reference. */ containerSizeRef: MutableRefObject; }; From 9e8be3244ea28dbe22c4d37a798c458cfb630dcc Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Mon, 23 Sep 2024 10:11:26 -0700 Subject: [PATCH 42/42] Set ref to non-mutable --- .../react-virtualizer/library/etc/react-virtualizer.api.md | 2 +- .../library/src/components/Virtualizer/Virtualizer.types.ts | 2 +- .../library/src/components/Virtualizer/useVirtualizer.ts | 2 +- .../library/src/hooks/useDynamicVirtualizerMeasure.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-components/react-virtualizer/library/etc/react-virtualizer.api.md b/packages/react-components/react-virtualizer/library/etc/react-virtualizer.api.md index aa8396690534a..62e0171de50f1 100644 --- a/packages/react-components/react-virtualizer/library/etc/react-virtualizer.api.md +++ b/packages/react-components/react-virtualizer/library/etc/react-virtualizer.api.md @@ -80,7 +80,7 @@ export const useDynamicVirtualizerMeasure: (virtua bufferItems: number; bufferSize: number; scrollRef: (instance: TElement | null) => void; - containerSizeRef: React_2.MutableRefObject; + containerSizeRef: React_2.RefObject; }; // @public diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts index f2b58e8e565d1..cd5661c86eec2 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/Virtualizer.types.ts @@ -194,7 +194,7 @@ export type VirtualizerConfigProps = { * A ref that provides the size of container (vertical - height, horizontal - width), set by a resize observer. * Virtualizer Measure hooks provide a suitable reference. */ - containerSizeRef: MutableRefObject; + containerSizeRef: RefObject; }; export type VirtualizerProps = ComponentProps> & VirtualizerConfigProps; diff --git a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts index 6cc23146b5b71..bd6976b03c132 100644 --- a/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/library/src/components/Virtualizer/useVirtualizer.ts @@ -345,7 +345,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // Ignore buffer size (IO offset) measurementPos -= bufferSize; // we hit the after buffer and detected the end of view, we need to find the start index. - measurementPos -= containerSizeRef.current; + measurementPos -= containerSizeRef.current ?? 0; // Calculate how far past the window bounds we are (this will be zero if IO is within window) const hOverflow = latestEntry.boundingClientRect.top - latestEntry.intersectionRect.top; diff --git a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts index 2d509d667d0f6..f980e99e20425 100644 --- a/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/library/src/hooks/useDynamicVirtualizerMeasure.ts @@ -15,7 +15,7 @@ export const useDynamicVirtualizerMeasure = ( bufferItems: number; bufferSize: number; scrollRef: (instance: TElement | null) => void; - containerSizeRef: React.MutableRefObject; + containerSizeRef: React.RefObject; } => { const { defaultItemSize,