From dbba330dad03fe5bc10e4d488fa166a7bc634c73 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Wed, 15 Feb 2023 16:27:39 -0800 Subject: [PATCH 01/33] Initial static size measurement --- .../react-components/src/unstable/index.ts | 1 + .../etc/react-virtualizer.api.md | 7 ++ .../react-virtualizer/src/hooks/index.ts | 1 + .../src/hooks/useVirtualizerMeasure.ts | 81 +++++++++++++++++++ .../react-virtualizer/src/index.ts | 2 +- .../stories/Virtualizer/Default.stories.tsx | 24 +++++- 6 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts diff --git a/packages/react-components/react-components/src/unstable/index.ts b/packages/react-components/react-components/src/unstable/index.ts index 60eb1ef64ef74f..def7276390aca5 100644 --- a/packages/react-components/react-components/src/unstable/index.ts +++ b/packages/react-components/react-components/src/unstable/index.ts @@ -120,6 +120,7 @@ export { renderVirtualizer_unstable, useVirtualizerStyles_unstable, useIntersectionObserver, + useStaticVirtualizerMeasure, } from '@fluentui/react-virtualizer'; export type { VirtualizerProps, diff --git a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md index 9c9a26dbbc2f84..d0600016af960f 100644 --- a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md +++ b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md @@ -24,6 +24,13 @@ export const useIntersectionObserver: (callback: IntersectionObserverCallback, o observer: MutableRefObject; }; +// @public +export const useStaticVirtualizerMeasure: (defaultItemSize: number, scrollView: HTMLElement | null) => { + virtualizerLength: number; + virtualizerBufferItems: number; + virtualizerBufferSize: number; +}; + // @public (undocumented) export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerState; diff --git a/packages/react-components/react-virtualizer/src/hooks/index.ts b/packages/react-components/react-virtualizer/src/hooks/index.ts index 97e1994797fa28..6e09744b3c9783 100644 --- a/packages/react-components/react-virtualizer/src/hooks/index.ts +++ b/packages/react-components/react-virtualizer/src/hooks/index.ts @@ -1 +1,2 @@ export * from './useIntersectionObserver'; +export * from './useVirtualizerMeasure'; diff --git a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts new file mode 100644 index 00000000000000..15b88635385bde --- /dev/null +++ b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts @@ -0,0 +1,81 @@ +import * as React from 'react'; +/** + * React hook that measures virtualized space based on a static size to ensure optimized virtualization length. + */ + +export const useStaticVirtualizerMeasure = ( + defaultItemSize: number, + scrollView: HTMLElement | null, +): { + virtualizerLength: number; + virtualizerBufferItems: number; + virtualizerBufferSize: number; +} => { + const [virtualizerLength, setVirtualizerLength] = React.useState(0); + const [virtualizerBufferItems, setVirtualizerBufferItems] = React.useState(0); + const [virtualizerBufferSize, setVirtualizerBufferSize] = React.useState(0); + + // We should always return something valid, document.body provides safe initialization until defined. + const _scrollView = scrollView ?? document.body; + + const container = React.useRef(null); + + // the handler for resize observer + const handleResize = React.useCallback(() => { + const containerHeight = container.current?.getBoundingClientRect().height; + + if (!containerHeight) { + // Error? ignore? + return; + } + + const length = Math.ceil((containerHeight / defaultItemSize) * 1.5); + + const bufferItems = Math.max(Math.floor(length / 8), 2); + + // Buffer has to be at least 1px in size + const bufferSize = Math.max(Math.floor((length / 16) * defaultItemSize), 1); + + setVirtualizerLength(length); + setVirtualizerBufferSize(bufferSize); + setVirtualizerBufferItems(bufferItems); + }, [defaultItemSize]); + + // Keep the reference of ResizeObserver in the state, as it should live through renders + const [resizeObserver] = React.useState(new ResizeObserver(handleResize)); + + if (_scrollView !== container.current) { + if (container.current) { + resizeObserver.unobserve(container.current); + } + // Update + container.current = _scrollView; + + // Only observe if not null + if (container.current) { + resizeObserver.observe(container.current); + handleResize(); + } + } + + // Do we want to use a dispatch here? + return { virtualizerLength, virtualizerBufferItems, virtualizerBufferSize }; +}; + +/** + * React hook that measures virtualized space dynamically to ensure optimized virtualization length. + */ + +export const useDynamicVirtualizerMeasure = ( + defaultItemSize: number, + currentIndex: number, + getItemSize: (index: number) => number, + scrollView?: React.ReactNode, +): { + virtualizerLength: number; + virtualizerBufferItems: number; + virtualizerBufferSize: number; +} => { + // TODO Add resize observer and update sizes as resize observer callback occurs - dispatch? + return { virtualizerLength: 10, virtualizerBufferItems: 10, virtualizerBufferSize: 10 }; +}; diff --git a/packages/react-components/react-virtualizer/src/index.ts b/packages/react-components/react-virtualizer/src/index.ts index 3d8f72a15d351c..e103e4a7b4462c 100644 --- a/packages/react-components/react-virtualizer/src/index.ts +++ b/packages/react-components/react-virtualizer/src/index.ts @@ -11,4 +11,4 @@ export type { VirtualizerSlots, VirtualizerChildRenderFunction, } from './Virtualizer'; -export { useIntersectionObserver } from './Hooks'; +export { useIntersectionObserver, useStaticVirtualizerMeasure } from './Hooks'; diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx index cbeabffb5cbc05..6b960f3b8d3474 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Virtualizer } from '@fluentui/react-components/unstable'; +import { Virtualizer, useStaticVirtualizerMeasure } from '@fluentui/react-components/unstable'; import { makeStyles } from '@fluentui/react-components'; const useStyles = makeStyles({ @@ -22,10 +22,26 @@ const useStyles = makeStyles({ export const Default = () => { const styles = useStyles(); const childLength = 1000; + const scrollView = React.useRef(null); + + const { virtualizerLength, virtualizerBufferItems, virtualizerBufferSize } = useStaticVirtualizerMeasure( + 100, + scrollView.current, + ); + + console.log('Got length:', virtualizerLength); + console.log('Got virtualizerBufferItems:', virtualizerBufferItems); + console.log('Got virtualizerBufferSize:', virtualizerBufferSize); return ( -
- + + {index => { return ( { ); }} -
+ ); }; From bd18c96532efe2147950f2497c588bb4703ecfe0 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Wed, 15 Feb 2023 16:55:55 -0800 Subject: [PATCH 02/33] Fix up algo to ensure algo is unidirectional when scroll is --- .../components/Virtualizer/useVirtualizer.ts | 1 + .../src/hooks/useVirtualizerMeasure.ts | 20 ++++++++++++++----- .../stories/Virtualizer/Default.stories.tsx | 2 +- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts index 208ec69c3f20ff..f5b13516605259 100644 --- a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts @@ -72,6 +72,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta }; const batchUpdateNewIndex = (index: number) => { + console.log('UPDATING NEW INDEX: ', index); // Local updates onUpdateIndex?.(index, virtualizerStartIndex); updateChildRows(index); diff --git a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts index 15b88635385bde..7b7ee5626afb4a 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts @@ -29,14 +29,24 @@ export const useStaticVirtualizerMeasure = ( return; } - const length = Math.ceil((containerHeight / defaultItemSize) * 1.5); + /* + * Number of items required to cover viewport. + */ + const length = Math.ceil(containerHeight / defaultItemSize + 1); - const bufferItems = Math.max(Math.floor(length / 8), 2); + /* + * Number of items to append at each end, i.e. 'preload' each side before entering view. + */ + const bufferItems = Math.max(Math.floor(length / 4), 2); - // Buffer has to be at least 1px in size - const bufferSize = Math.max(Math.floor((length / 16) * defaultItemSize), 1); + /* + * This is how far we deviate into the bufferItems to detect a redraw. + */ + const bufferSize = Math.max(Math.floor((length / 8) * defaultItemSize), 1); - setVirtualizerLength(length); + const totalLength = length + bufferItems * 2; + + setVirtualizerLength(totalLength); setVirtualizerBufferSize(bufferSize); setVirtualizerBufferItems(bufferItems); }, [defaultItemSize]); diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx index 6b960f3b8d3474..18e815decad6d3 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx @@ -10,7 +10,7 @@ const useStyles = makeStyles({ overflowY: 'auto', width: '100%', height: '100%', - maxHeight: '750px', + maxHeight: '60vh', }, child: { height: '100px', From 687f7becdc65dd53dbc0ba08bd059ce1519ebb53 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Tue, 21 Feb 2023 16:43:04 -0800 Subject: [PATCH 03/33] Add scroll container and implement built in hooks --- .../etc/react-components.unstable.api.md | 27 +++++++ .../react-components/src/unstable/index.ts | 8 +++ .../etc/react-virtualizer.api.md | 63 +++++++++-------- .../react-virtualizer/package.json | 1 + .../src/VirtualizerScrollView.ts | 1 + .../Virtualizer/Virtualizer.types.ts | 11 ++- .../VirtualizerScrollView.ts | 36 ++++++++++ .../VirtualizerScrollView.types.ts | 13 ++++ .../components/VirtualizerScrollView/index.ts | 5 ++ .../renderVirtualizerScrollView.tsx | 26 +++++++ .../useVirtualizerScrollView.ts | 21 ++++++ .../useVirtualizerScrollViewStyles.ts | 70 +++++++++++++++++++ .../src/hooks/useVirtualizerMeasure.ts | 37 ++++++---- .../react-virtualizer/src/index.ts | 14 ++++ .../stories/Virtualizer/Default.stories.tsx | 13 ++-- .../VirtualizerScrollView.stories.tsx | 51 ++++++++++++++ .../stories/Virtualizer/index.stories.ts | 1 + 17 files changed, 345 insertions(+), 53 deletions(-) create mode 100644 packages/react-components/react-virtualizer/src/VirtualizerScrollView.ts create mode 100644 packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.ts create mode 100644 packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts create mode 100644 packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/index.ts create mode 100644 packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/renderVirtualizerScrollView.tsx create mode 100644 packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts create mode 100644 packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollViewStyles.ts create mode 100644 packages/react-components/react-virtualizer/stories/Virtualizer/VirtualizerScrollView.stories.tsx diff --git a/packages/react-components/react-components/etc/react-components.unstable.api.md b/packages/react-components/react-components/etc/react-components.unstable.api.md index 536698590ea8a5..ae3faf7f0c1d27 100644 --- a/packages/react-components/react-components/etc/react-components.unstable.api.md +++ b/packages/react-components/react-components/etc/react-components.unstable.api.md @@ -56,6 +56,7 @@ import { renderTreeItem_unstable } from '@fluentui/react-tree'; import { renderTreeItemLayout_unstable } from '@fluentui/react-tree'; import { renderTreeItemPersonaLayout_unstable } from '@fluentui/react-tree'; import { renderVirtualizer_unstable } from '@fluentui/react-virtualizer'; +import { renderVirtualizerScrollView_unstable } from '@fluentui/react-virtualizer'; import { SelectField_unstable as SelectField } from '@fluentui/react-select'; import { selectFieldClassNames } from '@fluentui/react-select'; import { SelectFieldProps_unstable as SelectFieldProps } from '@fluentui/react-select'; @@ -137,12 +138,20 @@ import { useTreeItemPersonaLayout_unstable } from '@fluentui/react-tree'; import { useTreeItemPersonaLayoutStyles_unstable } from '@fluentui/react-tree'; import { useTreeItemStyles_unstable } from '@fluentui/react-tree'; import { useTreeStyles_unstable } from '@fluentui/react-tree'; +import { useStaticVirtualizerMeasure } from '@fluentui/react-virtualizer'; import { useVirtualizer_unstable } from '@fluentui/react-virtualizer'; +import { useVirtualizerScrollView_unstable } from '@fluentui/react-virtualizer'; +import { useVirtualizerScrollViewStyles_unstable } from '@fluentui/react-virtualizer'; import { useVirtualizerStyles_unstable } from '@fluentui/react-virtualizer'; import { Virtualizer } from '@fluentui/react-virtualizer'; import { VirtualizerChildRenderFunction } from '@fluentui/react-virtualizer'; import { virtualizerClassNames } from '@fluentui/react-virtualizer'; import { VirtualizerProps } from '@fluentui/react-virtualizer'; +import { VirtualizerScrollView } from '@fluentui/react-virtualizer'; +import { virtualizerScrollViewClassNames } from '@fluentui/react-virtualizer'; +import { VirtualizerScrollViewProps } from '@fluentui/react-virtualizer'; +import { VirtualizerScrollViewSlots } from '@fluentui/react-virtualizer'; +import { VirtualizerScrollViewState } from '@fluentui/react-virtualizer'; import { VirtualizerSlots } from '@fluentui/react-virtualizer'; import { VirtualizerState } from '@fluentui/react-virtualizer'; @@ -250,6 +259,8 @@ export { renderTreeItemPersonaLayout_unstable } export { renderVirtualizer_unstable } +export { renderVirtualizerScrollView_unstable } + export { SelectField } export { selectFieldClassNames } @@ -412,8 +423,14 @@ export { useTreeItemStyles_unstable } export { useTreeStyles_unstable } +export { useStaticVirtualizerMeasure } + export { useVirtualizer_unstable } +export { useVirtualizerScrollView_unstable } + +export { useVirtualizerScrollViewStyles_unstable } + export { useVirtualizerStyles_unstable } export { Virtualizer } @@ -424,6 +441,16 @@ export { virtualizerClassNames } export { VirtualizerProps } +export { VirtualizerScrollView } + +export { virtualizerScrollViewClassNames } + +export { VirtualizerScrollViewProps } + +export { VirtualizerScrollViewSlots } + +export { VirtualizerScrollViewState } + export { VirtualizerSlots } export { VirtualizerState } diff --git a/packages/react-components/react-components/src/unstable/index.ts b/packages/react-components/react-components/src/unstable/index.ts index def7276390aca5..f6728de03d1b2e 100644 --- a/packages/react-components/react-components/src/unstable/index.ts +++ b/packages/react-components/react-components/src/unstable/index.ts @@ -121,12 +121,20 @@ export { useVirtualizerStyles_unstable, useIntersectionObserver, useStaticVirtualizerMeasure, + VirtualizerScrollView, + virtualizerScrollViewClassNames, + useVirtualizerScrollView_unstable, + renderVirtualizerScrollView_unstable, + useVirtualizerScrollViewStyles_unstable, } from '@fluentui/react-virtualizer'; export type { VirtualizerProps, VirtualizerState, VirtualizerSlots, VirtualizerChildRenderFunction, + VirtualizerScrollViewProps, + VirtualizerScrollViewState, + VirtualizerScrollViewSlots, } from '@fluentui/react-virtualizer'; export { diff --git a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md index d0600016af960f..43abcd2f78f911 100644 --- a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md +++ b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md @@ -4,19 +4,23 @@ ```ts -import type { ComponentProps } from '@fluentui/react-utilities'; -import type { ComponentState } from '@fluentui/react-utilities'; +import { ComponentProps } from '@fluentui/react-utilities'; +import { ComponentState } from '@fluentui/react-utilities'; import type { Dispatch } from 'react'; import type { FC } from 'react'; +import { ForwardRefComponent } from '@fluentui/react-utilities'; import type { MutableRefObject } from 'react'; import * as React_2 from 'react'; import type { SetStateAction } from 'react'; -import type { Slot } from '@fluentui/react-utilities'; +import { Slot } from '@fluentui/react-utilities'; import type { SlotClassNames } from '@fluentui/react-utilities'; // @public (undocumented) export const renderVirtualizer_unstable: (state: VirtualizerState) => JSX.Element; +// @public (undocumented) +export const renderVirtualizerScrollView_unstable: (state: VirtualizerScrollViewState) => JSX.Element; + // @public export const useIntersectionObserver: (callback: IntersectionObserverCallback, options?: IntersectionObserverInit | undefined) => { setObserverList: Dispatch>; @@ -25,15 +29,21 @@ export const useIntersectionObserver: (callback: IntersectionObserverCallback, o }; // @public -export const useStaticVirtualizerMeasure: (defaultItemSize: number, scrollView: HTMLElement | null) => { +export const useStaticVirtualizerMeasure: (defaultItemSize: number, scrollView: HTMLElement | null, direction: 'vertical' | 'horizontal') => { virtualizerLength: number; - virtualizerBufferItems: number; - virtualizerBufferSize: number; + bufferItems: number; + bufferSize: number; }; // @public (undocumented) export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerState; +// @public (undocumented) +export function useVirtualizerScrollView_unstable(props: VirtualizerScrollViewProps, ref: React_2.Ref): VirtualizerScrollViewState; + +// @public +export const useVirtualizerScrollViewStyles_unstable: (state: VirtualizerScrollViewState) => VirtualizerScrollViewState; + // @public export const useVirtualizerStyles_unstable: (state: VirtualizerState) => VirtualizerState; @@ -47,21 +57,25 @@ export type VirtualizerChildRenderFunction = (index: number) => React_2.ReactNod export const virtualizerClassNames: SlotClassNames; // @public (undocumented) -export type VirtualizerProps = ComponentProps> & { - children: VirtualizerChildRenderFunction; - itemSize: number; - numItems: number; - virtualizerLength: number; - bufferItems?: number; - bufferSize?: number; - intersectionObserverRoot?: React_2.MutableRefObject; - axis?: 'vertical' | 'horizontal'; - reversed?: boolean; - getItemSize?: (index: number) => number; - onUpdateIndex?: (index: number, prevIndex: number) => void; - onCalculateIndex?: (newIndex: number) => number; +export type VirtualizerProps = ComponentProps> & IVirtualizerProps; + +// @public +export const VirtualizerScrollView: ForwardRefComponent; + +// @public (undocumented) +export const virtualizerScrollViewClassNames: SlotClassNames; + +// @public (undocumented) +export type VirtualizerScrollViewProps = ComponentProps> & IVirtualizerProps; + +// @public (undocumented) +export type VirtualizerScrollViewSlots = VirtualizerSlots & { + root: NonNullable>; }; +// @public (undocumented) +export type VirtualizerScrollViewState = ComponentState & IVirtualizerState; + // @public (undocumented) export type VirtualizerSlots = { before: NonNullable>; @@ -71,16 +85,7 @@ export type VirtualizerSlots = { }; // @public (undocumented) -export type VirtualizerState = ComponentState & { - virtualizedChildren: React_2.ReactNode[]; - virtualizerStartIndex: number; - afterBufferHeight: number; - beforeBufferHeight: number; - totalVirtualizerHeight: number; - axis?: 'vertical' | 'horizontal'; - reversed?: boolean; - bufferSize: number; -}; +export type VirtualizerState = ComponentState & IVirtualizerState; // (No @packageDocumentation comment for this package) diff --git a/packages/react-components/react-virtualizer/package.json b/packages/react-components/react-virtualizer/package.json index e37d7010c1fdee..61dfa58f24cc4f 100644 --- a/packages/react-components/react-virtualizer/package.json +++ b/packages/react-components/react-virtualizer/package.json @@ -32,6 +32,7 @@ }, "dependencies": { "@fluentui/react-utilities": "^9.7.2", + "@fluentui/react-shared-contexts": "^9.2.0", "@griffel/react": "^1.5.2", "@swc/helpers": "^0.4.14" }, diff --git a/packages/react-components/react-virtualizer/src/VirtualizerScrollView.ts b/packages/react-components/react-virtualizer/src/VirtualizerScrollView.ts new file mode 100644 index 00000000000000..20bfbaf6a9fcba --- /dev/null +++ b/packages/react-components/react-virtualizer/src/VirtualizerScrollView.ts @@ -0,0 +1 @@ +export * from './components/VirtualizerScrollView/index'; diff --git a/packages/react-components/react-virtualizer/src/components/Virtualizer/Virtualizer.types.ts b/packages/react-components/react-virtualizer/src/components/Virtualizer/Virtualizer.types.ts index 16bce33460eb9e..6419a7160171c5 100644 --- a/packages/react-components/react-virtualizer/src/components/Virtualizer/Virtualizer.types.ts +++ b/packages/react-components/react-virtualizer/src/components/Virtualizer/Virtualizer.types.ts @@ -20,7 +20,7 @@ export type VirtualizerSlots = { afterContainer: NonNullable>; }; -export type VirtualizerState = ComponentState & { +export type IVirtualizerState = { /** * The current virtualized array of children to show in the DOM. */ @@ -51,16 +51,19 @@ export type VirtualizerState = ComponentState & { */ reversed?: boolean; /** - * Tells the virtualizer how much + * Pixel size of intersection observers and how much they 'cross over' into the bufferItems index. + * Minimum 1px. */ bufferSize: number; }; +export type VirtualizerState = ComponentState & IVirtualizerState; + // 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? export type VirtualizerChildRenderFunction = (index: number) => React.ReactNode; -export type VirtualizerProps = ComponentProps> & { +export type IVirtualizerProps = { /** * Child render function. * Iteratively called to return current virtualizer DOM children. @@ -142,3 +145,5 @@ export type VirtualizerProps = ComponentProps> & { */ onCalculateIndex?: (newIndex: number) => number; }; + +export type VirtualizerProps = ComponentProps> & IVirtualizerProps; diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.ts new file mode 100644 index 00000000000000..e1acb784aefcc1 --- /dev/null +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.ts @@ -0,0 +1,36 @@ +import { VirtualizerScrollViewProps } from './VirtualizerScrollView.types'; +import { useVirtualizerScrollView_unstable } from './useVirtualizerScrollView'; +import { renderVirtualizerScrollView_unstable } from './renderVirtualizerScrollView'; +import { useVirtualizerScrollViewStyles_unstable } from './useVirtualizerScrollViewStyles'; +import { useStaticVirtualizerMeasure } from '../../Hooks'; +import * as React from 'react'; +import { ForwardRefComponent, useMergedRefs } from '@fluentui/react-utilities'; + +/** + * Virtualizer ScrollView + */ + +export const VirtualizerScrollView: ForwardRefComponent = React.forwardRef((props, ref) => { + const scrollViewRef = React.useRef(null); + const { virtualizerLength, bufferItems, bufferSize } = useStaticVirtualizerMeasure( + props.itemSize, + scrollViewRef.current, + props.axis ?? 'vertical', + ); + + const state = useVirtualizerScrollView_unstable( + { + ...props, + virtualizerLength, + bufferItems, + bufferSize, + }, + useMergedRefs(ref, scrollViewRef), + ); + + useVirtualizerScrollViewStyles_unstable(state); + + return renderVirtualizerScrollView_unstable(state); +}); + +VirtualizerScrollView.displayName = 'VirtualizerScrollView'; diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts new file mode 100644 index 00000000000000..86011c2b9378c9 --- /dev/null +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts @@ -0,0 +1,13 @@ +import { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; +import { VirtualizerSlots, IVirtualizerProps, IVirtualizerState } from '../Virtualizer/Virtualizer.types'; + +export type VirtualizerScrollViewSlots = VirtualizerSlots & { + /** + * The root container that provides scrolling. + */ + root: NonNullable>; +}; + +export type VirtualizerScrollViewProps = ComponentProps> & IVirtualizerProps; + +export type VirtualizerScrollViewState = ComponentState & IVirtualizerState; diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/index.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/index.ts new file mode 100644 index 00000000000000..bd57151d9a7d42 --- /dev/null +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/index.ts @@ -0,0 +1,5 @@ +export * from './VirtualizerScrollView'; +export * from './VirtualizerScrollView.types'; +export * from './useVirtualizerScrollView'; +export * from './renderVirtualizerScrollView'; +export * from './useVirtualizerScrollViewStyles'; diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/renderVirtualizerScrollView.tsx b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/renderVirtualizerScrollView.tsx new file mode 100644 index 00000000000000..748b5209f1a10c --- /dev/null +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/renderVirtualizerScrollView.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; +import { getSlots } from '@fluentui/react-utilities'; +import { VirtualizerScrollViewSlots, VirtualizerScrollViewState } from './VirtualizerScrollView.types'; + +export const renderVirtualizerScrollView_unstable = (state: VirtualizerScrollViewState) => { + const { slots, slotProps } = getSlots(state); + + console.log('RENDER: ', state); + console.log('slots: ', slots); + console.log('slotProps: ', slotProps); + + return ( + + {/* The 'before' bookend to hold items in place and detect scroll previous */} + + + + {/* The reduced list of non-virtualized children to be rendered */} + {state.virtualizedChildren} + {/* The 'after' bookend to hold items in place and detect scroll next */} + + + + + ); +}; diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts new file mode 100644 index 00000000000000..d0ba678f960bbf --- /dev/null +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts @@ -0,0 +1,21 @@ +import * as React from 'react'; +import { getNativeElementProps } from '@fluentui/react-utilities'; +import { useVirtualizer_unstable } from '../Virtualizer/useVirtualizer'; +import { VirtualizerScrollViewProps, VirtualizerScrollViewState } from './VirtualizerScrollView.types'; + +export function useVirtualizerScrollView_unstable( + props: VirtualizerScrollViewProps, + ref: React.Ref, +): VirtualizerScrollViewState { + const virtualizerState = useVirtualizer_unstable(props); + const containerComponent = props.as ?? 'div'; + + return { + ...virtualizerState, + components: { + ...virtualizerState.components, + root: containerComponent, + }, + root: getNativeElementProps(containerComponent, { ref, ...props }), + }; +} diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollViewStyles.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollViewStyles.ts new file mode 100644 index 00000000000000..cfaf1a9f7846cf --- /dev/null +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollViewStyles.ts @@ -0,0 +1,70 @@ +import { VirtualizerScrollViewState } from './VirtualizerScrollView.types'; +import type { SlotClassNames } from '@fluentui/react-utilities'; +import { VirtualizerScrollViewSlots } from './VirtualizerScrollView.types'; +import { useVirtualizerStyles_unstable, virtualizerClassNames } from '../Virtualizer/useVirtualizerStyles'; +import { makeStyles, mergeClasses } from '@griffel/react'; + +const virtualizerScrollViewClassName = 'fui-Virtualizer-Scroll-View'; + +export const virtualizerScrollViewClassNames: SlotClassNames = { + ...virtualizerClassNames, + root: `${virtualizerScrollViewClassName}__root`, +}; + +const useStyles = makeStyles({ + base: { + display: 'flex', + width: '100%', + height: '100%', + overflowAnchor: 'none', + }, + vertical: { + flexDirection: 'column', + overflowAnchor: 'none', + overflowY: 'auto', + }, + horizontal: { + flexDirection: 'row', + overflowX: 'auto', + }, + verticalReversed: { + flexDirection: 'column-reverse', + overflowY: 'auto', + }, + horizontalReversed: { + flexDirection: 'row-reverse', + overflowX: 'auto', + }, +}); + +/** + * Apply styling to the Virtualizer states + */ +export const useVirtualizerScrollViewStyles_unstable = ( + state: VirtualizerScrollViewState, +): VirtualizerScrollViewState => { + const styles = useStyles(); + + // For now - just return default style mods + useVirtualizerStyles_unstable(state); + + const containerStyle = + state.axis === 'horizontal' + ? state.reversed + ? styles.horizontalReversed + : styles.horizontal + : state.reversed + ? styles.verticalReversed + : styles.vertical; + + state.root.className = mergeClasses( + virtualizerScrollViewClassNames.root, + styles.base, + containerStyle, + state.root.className, + ); + + console.log('STYLE: ', state); + + return state; +}; diff --git a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts index 7b7ee5626afb4a..b9249fb7a6c707 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts @@ -1,30 +1,37 @@ import * as React from 'react'; +import { canUseDOM } from '@fluentui/react-utilities'; +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. */ - export const useStaticVirtualizerMeasure = ( defaultItemSize: number, scrollView: HTMLElement | null, + direction: 'vertical' | 'horizontal', ): { virtualizerLength: number; - virtualizerBufferItems: number; - virtualizerBufferSize: number; + bufferItems: number; + bufferSize: number; } => { const [virtualizerLength, setVirtualizerLength] = React.useState(0); const [virtualizerBufferItems, setVirtualizerBufferItems] = React.useState(0); const [virtualizerBufferSize, setVirtualizerBufferSize] = React.useState(0); - // We should always return something valid, document.body provides safe initialization until defined. - const _scrollView = scrollView ?? document.body; + const { targetDocument } = useFluent(); + // We should always return something valid, document.body provides safe initialization until defined. + const _scrollView = scrollView ?? targetDocument?.body ?? null; const container = React.useRef(null); // the handler for resize observer const handleResize = React.useCallback(() => { - const containerHeight = container.current?.getBoundingClientRect().height; + const containerSize = + direction === 'vertical' + ? container.current?.getBoundingClientRect().height + : container.current?.getBoundingClientRect().width; - if (!containerHeight) { + if (!containerSize) { // Error? ignore? return; } @@ -32,7 +39,7 @@ export const useStaticVirtualizerMeasure = ( /* * Number of items required to cover viewport. */ - const length = Math.ceil(containerHeight / defaultItemSize + 1); + const length = Math.ceil(containerSize / defaultItemSize + 1); /* * Number of items to append at each end, i.e. 'preload' each side before entering view. @@ -49,27 +56,31 @@ export const useStaticVirtualizerMeasure = ( setVirtualizerLength(totalLength); setVirtualizerBufferSize(bufferSize); setVirtualizerBufferItems(bufferItems); - }, [defaultItemSize]); + }, [defaultItemSize, direction]); // Keep the reference of ResizeObserver in the state, as it should live through renders - const [resizeObserver] = React.useState(new ResizeObserver(handleResize)); + const [resizeObserver] = React.useState(canUseDOM() ? new ResizeObserver(handleResize) : undefined); if (_scrollView !== container.current) { if (container.current) { - resizeObserver.unobserve(container.current); + resizeObserver?.unobserve(container.current); } // Update container.current = _scrollView; // Only observe if not null if (container.current) { - resizeObserver.observe(container.current); + resizeObserver?.observe(container.current); handleResize(); } } + if (!_scrollView) { + console.log('NO SCROLL VIEW FOUND'); + return { virtualizerLength: 3, bufferItems: 1, bufferSize: 1 }; + } // Do we want to use a dispatch here? - return { virtualizerLength, virtualizerBufferItems, virtualizerBufferSize }; + return { virtualizerLength, bufferItems: virtualizerBufferItems, bufferSize: virtualizerBufferSize }; }; /** diff --git a/packages/react-components/react-virtualizer/src/index.ts b/packages/react-components/react-virtualizer/src/index.ts index e103e4a7b4462c..eff1e6574b1946 100644 --- a/packages/react-components/react-virtualizer/src/index.ts +++ b/packages/react-components/react-virtualizer/src/index.ts @@ -12,3 +12,17 @@ export type { VirtualizerChildRenderFunction, } from './Virtualizer'; export { useIntersectionObserver, useStaticVirtualizerMeasure } from './Hooks'; + +export { + VirtualizerScrollView, + virtualizerScrollViewClassNames, + useVirtualizerScrollView_unstable, + renderVirtualizerScrollView_unstable, + useVirtualizerScrollViewStyles_unstable, +} from './VirtualizerScrollView'; + +export type { + VirtualizerScrollViewProps, + VirtualizerScrollViewState, + VirtualizerScrollViewSlots, +} from './VirtualizerScrollView'; diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx index 18e815decad6d3..71ae87e6791609 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx @@ -24,22 +24,19 @@ export const Default = () => { const childLength = 1000; const scrollView = React.useRef(null); - const { virtualizerLength, virtualizerBufferItems, virtualizerBufferSize } = useStaticVirtualizerMeasure( - 100, - scrollView.current, - ); + const { virtualizerLength, bufferItems, bufferSize } = useStaticVirtualizerMeasure(100, scrollView.current); console.log('Got length:', virtualizerLength); - console.log('Got virtualizerBufferItems:', virtualizerBufferItems); - console.log('Got virtualizerBufferSize:', virtualizerBufferSize); + console.log('Got virtualizerBufferItems:', bufferItems); + console.log('Got virtualizerBufferSize:', bufferSize); return ( {index => { diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/VirtualizerScrollView.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/VirtualizerScrollView.stories.tsx new file mode 100644 index 00000000000000..90b4e3e44dd087 --- /dev/null +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/VirtualizerScrollView.stories.tsx @@ -0,0 +1,51 @@ +import * as React from 'react'; +import { VirtualizerScrollView } from '@fluentui/react-components/unstable'; +import { makeStyles } from '@fluentui/react-components'; + +const useStyles = makeStyles({ + container: { + display: 'flex', + flexDirection: 'column', + overflowAnchor: 'none', + overflowY: 'auto', + width: '100%', + height: '100%', + maxHeight: '60vh', + }, + child: { + height: '100px', + lineHeight: '100px', + width: '100%', + }, +}); + +export const VirtualizerScrollViewDefault = () => { + const styles = useStyles(); + const childLength = 1000; + + return ( +
+ + {(index: number) => { + return ( + {`Node-${index}`} + ); + }} + +
+ ); +}; diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/index.stories.ts b/packages/react-components/react-virtualizer/stories/Virtualizer/index.stories.ts index af6b333589a673..0254d502722073 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/index.stories.ts +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/index.stories.ts @@ -7,6 +7,7 @@ export { Horizontal } from './Horizontal.stories'; export { ReversedHorizontal } from './ReversedHorizontal.stories'; export { Reversed } from './Reversed.stories'; export { RTL } from './RTL.stories'; +export { VirtualizerScrollViewDefault } from './VirtualizerScrollView.stories'; export default { title: 'Preview Components/Virtualizer', From cdbbc7d81c9c1c8912018f5ca278a812fa744ec8 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Tue, 21 Feb 2023 17:30:25 -0800 Subject: [PATCH 04/33] Fix up container and scroll refs --- .../VirtualizerScrollView.ts | 13 +++++---- .../VirtualizerScrollView.types.ts | 8 ++++-- .../renderVirtualizerScrollView.tsx | 8 ++---- .../useVirtualizerScrollView.ts | 27 ++++++++++++++----- .../useVirtualizerScrollViewStyles.ts | 10 +++---- .../src/hooks/useVirtualizerMeasure.ts | 8 ++++-- .../stories/Virtualizer/Default.stories.tsx | 4 +-- .../VirtualizerScrollView.stories.tsx | 18 +++---------- 8 files changed, 50 insertions(+), 46 deletions(-) diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.ts index e1acb784aefcc1..c3b6cf2ad88b7d 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.ts @@ -4,33 +4,32 @@ import { renderVirtualizerScrollView_unstable } from './renderVirtualizerScrollV import { useVirtualizerScrollViewStyles_unstable } from './useVirtualizerScrollViewStyles'; import { useStaticVirtualizerMeasure } from '../../Hooks'; import * as React from 'react'; -import { ForwardRefComponent, useMergedRefs } from '@fluentui/react-utilities'; /** * Virtualizer ScrollView */ -export const VirtualizerScrollView: ForwardRefComponent = React.forwardRef((props, ref) => { - const scrollViewRef = React.useRef(null); +export const VirtualizerScrollView: React.FC = (props: VirtualizerScrollViewProps) => { + const scrollRef = React.useRef(null); const { virtualizerLength, bufferItems, bufferSize } = useStaticVirtualizerMeasure( props.itemSize, - scrollViewRef.current, + scrollRef.current ?? null, props.axis ?? 'vertical', ); const state = useVirtualizerScrollView_unstable( { ...props, - virtualizerLength, bufferItems, bufferSize, + scrollViewRef: scrollRef, }, - useMergedRefs(ref, scrollViewRef), + virtualizerLength, ); useVirtualizerScrollViewStyles_unstable(state); return renderVirtualizerScrollView_unstable(state); -}); +}; VirtualizerScrollView.displayName = 'VirtualizerScrollView'; diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts index 86011c2b9378c9..97f5fdde82eccb 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts @@ -1,13 +1,17 @@ import { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; +import { MutableRefObject } from 'react'; import { VirtualizerSlots, IVirtualizerProps, IVirtualizerState } from '../Virtualizer/Virtualizer.types'; export type VirtualizerScrollViewSlots = VirtualizerSlots & { /** * The root container that provides scrolling. */ - root: NonNullable>; + container: NonNullable>; }; -export type VirtualizerScrollViewProps = ComponentProps> & IVirtualizerProps; +export type VirtualizerScrollViewProps = ComponentProps> & + Omit & { + scrollViewRef?: MutableRefObject; + }; export type VirtualizerScrollViewState = ComponentState & IVirtualizerState; diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/renderVirtualizerScrollView.tsx b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/renderVirtualizerScrollView.tsx index 748b5209f1a10c..8581248fbcd11a 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/renderVirtualizerScrollView.tsx +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/renderVirtualizerScrollView.tsx @@ -5,12 +5,8 @@ import { VirtualizerScrollViewSlots, VirtualizerScrollViewState } from './Virtua export const renderVirtualizerScrollView_unstable = (state: VirtualizerScrollViewState) => { const { slots, slotProps } = getSlots(state); - console.log('RENDER: ', state); - console.log('slots: ', slots); - console.log('slotProps: ', slotProps); - return ( - + {/* The 'before' bookend to hold items in place and detect scroll previous */} @@ -21,6 +17,6 @@ export const renderVirtualizerScrollView_unstable = (state: VirtualizerScrollVie - + ); }; diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts index d0ba678f960bbf..6d98b6b9016c7e 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts @@ -1,21 +1,36 @@ import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; +import { resolveShorthand } from '@fluentui/react-utilities'; import { useVirtualizer_unstable } from '../Virtualizer/useVirtualizer'; import { VirtualizerScrollViewProps, VirtualizerScrollViewState } from './VirtualizerScrollView.types'; export function useVirtualizerScrollView_unstable( props: VirtualizerScrollViewProps, - ref: React.Ref, + virtualizerLength: number, ): VirtualizerScrollViewState { - const virtualizerState = useVirtualizer_unstable(props); - const containerComponent = props.as ?? 'div'; + const virtualizerState = useVirtualizer_unstable({ ...props, virtualizerLength }); + + const setScrollRef = React.useCallback( + (element: HTMLDivElement) => { + if (!element || !props.scrollViewRef || props.scrollViewRef.current === element) { + return; + } + props.scrollViewRef.current = element; + }, + [props.scrollViewRef], + ); return { ...virtualizerState, components: { ...virtualizerState.components, - root: containerComponent, + container: 'div', }, - root: getNativeElementProps(containerComponent, { ref, ...props }), + // container: getNativeElementProps(containerComponent, { ref, ...props }), + container: resolveShorthand(props.container, { + required: true, + defaultProps: { + ref: setScrollRef, + }, + }), }; } diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollViewStyles.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollViewStyles.ts index cfaf1a9f7846cf..b457f20bcef4c1 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollViewStyles.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollViewStyles.ts @@ -8,7 +8,7 @@ const virtualizerScrollViewClassName = 'fui-Virtualizer-Scroll-View'; export const virtualizerScrollViewClassNames: SlotClassNames = { ...virtualizerClassNames, - root: `${virtualizerScrollViewClassName}__root`, + container: `${virtualizerScrollViewClassName}__container`, }; const useStyles = makeStyles({ @@ -57,14 +57,12 @@ export const useVirtualizerScrollViewStyles_unstable = ( ? styles.verticalReversed : styles.vertical; - state.root.className = mergeClasses( - virtualizerScrollViewClassNames.root, + state.container.className = mergeClasses( + virtualizerScrollViewClassNames.container, styles.base, containerStyle, - state.root.className, + state.container.className, ); - console.log('STYLE: ', state); - return state; }; diff --git a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts index b9249fb7a6c707..661c209c7ca331 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts @@ -26,6 +26,7 @@ export const useStaticVirtualizerMeasure = ( // the handler for resize observer const handleResize = React.useCallback(() => { + console.log('HANDLING RESIZE!'); const containerSize = direction === 'vertical' ? container.current?.getBoundingClientRect().height @@ -56,7 +57,9 @@ export const useStaticVirtualizerMeasure = ( setVirtualizerLength(totalLength); setVirtualizerBufferSize(bufferSize); setVirtualizerBufferItems(bufferItems); - }, [defaultItemSize, direction]); + + console.log('New RESIZE Length:', totalLength); + }, [defaultItemSize, direction, scrollView]); // Keep the reference of ResizeObserver in the state, as it should live through renders const [resizeObserver] = React.useState(canUseDOM() ? new ResizeObserver(handleResize) : undefined); @@ -71,8 +74,9 @@ export const useStaticVirtualizerMeasure = ( // Only observe if not null if (container.current) { resizeObserver?.observe(container.current); - handleResize(); } + + handleResize(); } if (!_scrollView) { diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx index 71ae87e6791609..8660318eb1b92a 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx @@ -31,7 +31,7 @@ export const Default = () => { console.log('Got virtualizerBufferSize:', bufferSize); return ( - +
{ ); }} - +
); }; diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/VirtualizerScrollView.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/VirtualizerScrollView.stories.tsx index 90b4e3e44dd087..d1f15fcf5b8b45 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/VirtualizerScrollView.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/VirtualizerScrollView.stories.tsx @@ -4,12 +4,8 @@ import { makeStyles } from '@fluentui/react-components'; const useStyles = makeStyles({ container: { - display: 'flex', - flexDirection: 'column', - overflowAnchor: 'none', - overflowY: 'auto', width: '100%', - height: '100%', + height: '60vh', maxHeight: '60vh', }, child: { @@ -24,16 +20,8 @@ export const VirtualizerScrollViewDefault = () => { const childLength = 1000; return ( -
- +
+ {(index: number) => { return ( Date: Wed, 22 Feb 2023 09:56:08 -0800 Subject: [PATCH 05/33] Fix up virtualizer length updates to ensure they always propogate --- .../components/Virtualizer/useVirtualizer.ts | 37 ++++++++++++------- .../VirtualizerScrollView.ts | 1 + .../src/hooks/useVirtualizerMeasure.ts | 4 +- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts index f5b13516605259..6884bbab78ba76 100644 --- a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts @@ -277,23 +277,26 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta return childProgressiveSizes.current[numItems - 1] - childProgressiveSizes.current[lastItemIndex]; }; - const updateChildRows = (newIndex: number) => { - if (numItems === 0) { - /* Nothing to virtualize */ + const updateChildRows = useCallback( + (newIndex: number) => { + if (numItems === 0) { + /* Nothing to virtualize */ - return []; - } + return []; + } - if (childArray.current.length !== numItems) { - childArray.current = new Array(virtualizerLength); - } - const actualIndex = Math.max(newIndex, 0); - const end = Math.min(actualIndex + virtualizerLength, numItems); + if (childArray.current.length !== numItems) { + 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); - } - }; + for (let i = actualIndex; i < end; i++) { + childArray.current[i - actualIndex] = renderChild(i); + } + }, + [numItems, renderChild, virtualizerLength], + ); const setBeforeRef = useCallback( (element: HTMLDivElement) => { @@ -398,6 +401,12 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta populateSizeArrays(); } + // Ensure we recalc if virtualizer length changes + const maxCompare = Math.min(virtualizerLength, numItems); + if (childArray.current.length !== maxCompare && virtualizerStartIndex + childArray.current.length < numItems) { + updateChildRows(virtualizerStartIndex); + } + const isFullyInitialized = hasInitialized.current && virtualizerStartIndex >= 0; return { components: { diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.ts index c3b6cf2ad88b7d..11b20ffe69c315 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.ts @@ -29,6 +29,7 @@ export const VirtualizerScrollView: React.FC = (prop useVirtualizerScrollViewStyles_unstable(state); + console.log('Re-rendering: ', virtualizerLength); return renderVirtualizerScrollView_unstable(state); }; diff --git a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts index 661c209c7ca331..aa4c83c2023561 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts @@ -52,14 +52,14 @@ export const useStaticVirtualizerMeasure = ( */ const bufferSize = Math.max(Math.floor((length / 8) * defaultItemSize), 1); - const totalLength = length + bufferItems * 2; + const totalLength = length + bufferItems * 2 + 1; setVirtualizerLength(totalLength); setVirtualizerBufferSize(bufferSize); setVirtualizerBufferItems(bufferItems); console.log('New RESIZE Length:', totalLength); - }, [defaultItemSize, direction, scrollView]); + }, [defaultItemSize, direction]); // Keep the reference of ResizeObserver in the state, as it should live through renders const [resizeObserver] = React.useState(canUseDOM() ? new ResizeObserver(handleResize) : undefined); From 74f6f259ed0eb366d914131d9f0631e511b80515 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Wed, 22 Feb 2023 14:35:15 -0800 Subject: [PATCH 06/33] Add new functionality and hooks --- .../etc/react-virtualizer.api.md | 15 ++-- .../components/Virtualizer/useVirtualizer.ts | 7 +- .../VirtualizerScrollView.ts | 1 - .../useVirtualizerScrollView.ts | 2 +- .../src/hooks/useVirtualizerMeasure.ts | 14 +-- .../stories/Virtualizer/Default.stories.tsx | 12 +-- .../Virtualizer/DefaultUnbounded.stories.tsx | 67 +++++++++++++++ .../Virtualizer/Horizontal.stories.tsx | 21 ++++- .../Virtualizer/MultiUnbounded.stories.tsx | 85 +++++++++++++++++++ .../stories/Virtualizer/RTL.stories.tsx | 21 ++++- .../stories/Virtualizer/Reversed.stories.tsx | 17 +++- .../ReversedHorizontal.stories.tsx | 23 ++++- .../stories/Virtualizer/index.stories.ts | 3 +- .../Default.stories.tsx} | 19 ++--- .../VirtualizerScrollViewDescription.md | 12 +++ .../VirtualizerScrollView/index.stories.ts | 16 ++++ 16 files changed, 287 insertions(+), 48 deletions(-) create mode 100644 packages/react-components/react-virtualizer/stories/Virtualizer/DefaultUnbounded.stories.tsx create mode 100644 packages/react-components/react-virtualizer/stories/Virtualizer/MultiUnbounded.stories.tsx rename packages/react-components/react-virtualizer/stories/{Virtualizer/VirtualizerScrollView.stories.tsx => VirtualizerScrollView/Default.stories.tsx} (62%) create mode 100644 packages/react-components/react-virtualizer/stories/VirtualizerScrollView/VirtualizerScrollViewDescription.md create mode 100644 packages/react-components/react-virtualizer/stories/VirtualizerScrollView/index.stories.ts diff --git a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md index 43abcd2f78f911..ef40ff1c5ff9e1 100644 --- a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md +++ b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md @@ -8,8 +8,7 @@ import { ComponentProps } from '@fluentui/react-utilities'; import { ComponentState } from '@fluentui/react-utilities'; import type { Dispatch } from 'react'; import type { FC } from 'react'; -import { ForwardRefComponent } from '@fluentui/react-utilities'; -import type { MutableRefObject } from 'react'; +import { MutableRefObject } from 'react'; import * as React_2 from 'react'; import type { SetStateAction } from 'react'; import { Slot } from '@fluentui/react-utilities'; @@ -29,7 +28,7 @@ export const useIntersectionObserver: (callback: IntersectionObserverCallback, o }; // @public -export const useStaticVirtualizerMeasure: (defaultItemSize: number, scrollView: HTMLElement | null, direction: 'vertical' | 'horizontal') => { +export const useStaticVirtualizerMeasure: (defaultItemSize: number, scrollView?: HTMLElement | null, direction?: 'vertical' | 'horizontal') => { virtualizerLength: number; bufferItems: number; bufferSize: number; @@ -39,7 +38,7 @@ export const useStaticVirtualizerMeasure: (defaultItemSize: number, scrollView: export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerState; // @public (undocumented) -export function useVirtualizerScrollView_unstable(props: VirtualizerScrollViewProps, ref: React_2.Ref): VirtualizerScrollViewState; +export function useVirtualizerScrollView_unstable(props: VirtualizerScrollViewProps, virtualizerLength: number): VirtualizerScrollViewState; // @public export const useVirtualizerScrollViewStyles_unstable: (state: VirtualizerScrollViewState) => VirtualizerScrollViewState; @@ -60,17 +59,19 @@ export const virtualizerClassNames: SlotClassNames; export type VirtualizerProps = ComponentProps> & IVirtualizerProps; // @public -export const VirtualizerScrollView: ForwardRefComponent; +export const VirtualizerScrollView: React_2.FC; // @public (undocumented) export const virtualizerScrollViewClassNames: SlotClassNames; // @public (undocumented) -export type VirtualizerScrollViewProps = ComponentProps> & IVirtualizerProps; +export type VirtualizerScrollViewProps = ComponentProps> & Omit & { + scrollViewRef?: MutableRefObject; +}; // @public (undocumented) export type VirtualizerScrollViewSlots = VirtualizerSlots & { - root: NonNullable>; + container: NonNullable>; }; // @public (undocumented) diff --git a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts index 6884bbab78ba76..00ccb90e6eb47c 100644 --- a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts @@ -3,7 +3,7 @@ import type { ReactNode } from 'react'; import { useEffect, useRef, useState, useCallback, useReducer } from 'react'; import type { VirtualizerProps, VirtualizerState } from './Virtualizer.types'; -import { resolveShorthand } from '@fluentui/react-utilities'; +import { resolveShorthand, useId } from '@fluentui/react-utilities'; import { flushSync } from 'react-dom'; export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerState { @@ -72,7 +72,6 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta }; const batchUpdateNewIndex = (index: number) => { - console.log('UPDATING NEW INDEX: ', index); // Local updates onUpdateIndex?.(index, virtualizerStartIndex); updateChildRows(index); @@ -408,6 +407,8 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta } const isFullyInitialized = hasInitialized.current && virtualizerStartIndex >= 0; + const beforeKey = useId('virtualizer-before-'); + const afterKey = useId('virtualizer-after-'); return { components: { before: 'div', @@ -434,12 +435,14 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta required: true, defaultProps: { role: 'none', + key: beforeKey, }, }), afterContainer: resolveShorthand(props.afterContainer, { required: true, defaultProps: { role: 'none', + key: afterKey, }, }), beforeBufferHeight: isFullyInitialized ? calculateBefore() : 0, diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.ts index 11b20ffe69c315..c3b6cf2ad88b7d 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.ts @@ -29,7 +29,6 @@ export const VirtualizerScrollView: React.FC = (prop useVirtualizerScrollViewStyles_unstable(state); - console.log('Re-rendering: ', virtualizerLength); return renderVirtualizerScrollView_unstable(state); }; diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts index 6d98b6b9016c7e..ffc88923d38d31 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts @@ -25,11 +25,11 @@ export function useVirtualizerScrollView_unstable( ...virtualizerState.components, container: 'div', }, - // container: getNativeElementProps(containerComponent, { ref, ...props }), container: resolveShorthand(props.container, { required: true, defaultProps: { ref: setScrollRef, + role: 'list', }, }), }; diff --git a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts index aa4c83c2023561..f54a3a31a5968d 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts @@ -7,8 +7,8 @@ import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts */ export const useStaticVirtualizerMeasure = ( defaultItemSize: number, - scrollView: HTMLElement | null, - direction: 'vertical' | 'horizontal', + scrollView: HTMLElement | null = null, + direction: 'vertical' | 'horizontal' = 'vertical', ): { virtualizerLength: number; bufferItems: number; @@ -21,12 +21,11 @@ export const useStaticVirtualizerMeasure = ( const { targetDocument } = useFluent(); // We should always return something valid, document.body provides safe initialization until defined. - const _scrollView = scrollView ?? targetDocument?.body ?? null; + const _scrollView = scrollView ?? targetDocument?.documentElement ?? null; const container = React.useRef(null); // the handler for resize observer const handleResize = React.useCallback(() => { - console.log('HANDLING RESIZE!'); const containerSize = direction === 'vertical' ? container.current?.getBoundingClientRect().height @@ -57,8 +56,7 @@ export const useStaticVirtualizerMeasure = ( setVirtualizerLength(totalLength); setVirtualizerBufferSize(bufferSize); setVirtualizerBufferItems(bufferItems); - - console.log('New RESIZE Length:', totalLength); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [defaultItemSize, direction]); // Keep the reference of ResizeObserver in the state, as it should live through renders @@ -79,10 +77,6 @@ export const useStaticVirtualizerMeasure = ( handleResize(); } - if (!_scrollView) { - console.log('NO SCROLL VIEW FOUND'); - return { virtualizerLength: 3, bufferItems: 1, bufferSize: 1 }; - } // Do we want to use a dispatch here? return { virtualizerLength, bufferItems: virtualizerBufferItems, bufferSize: virtualizerBufferSize }; }; diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx index 8660318eb1b92a..c17c011b9dc966 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx @@ -17,21 +17,23 @@ const useStyles = makeStyles({ lineHeight: '100px', width: '100%', }, + block: { + height: '1250px', + width: '100%', + backgroundColor: 'red', + }, }); export const Default = () => { const styles = useStyles(); const childLength = 1000; - const scrollView = React.useRef(null); + const scrollView = React.useRef(null); const { virtualizerLength, bufferItems, bufferSize } = useStaticVirtualizerMeasure(100, scrollView.current); - console.log('Got length:', virtualizerLength); - console.log('Got virtualizerBufferItems:', bufferItems); - console.log('Got virtualizerBufferSize:', bufferSize); - return (
+
{ + const styles = useStyles(); + const childLength = 1000; + + const { virtualizerLength, bufferItems, bufferSize } = useStaticVirtualizerMeasure(100, null); + + return ( + +
+
{`Virtualizer`}
+ + {index => { + return ( + {`Node-${index}`} + ); + }} + +
+ Footer +
+
+
+ ); +}; diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/Horizontal.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/Horizontal.stories.tsx index 4832d1c21491aa..aa4d2afd15a858 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/Horizontal.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/Horizontal.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Virtualizer } from '@fluentui/react-components/unstable'; +import { useStaticVirtualizerMeasure, Virtualizer } from '@fluentui/react-components/unstable'; import { makeStyles } from '@fluentui/react-components'; const useStyles = makeStyles({ @@ -22,10 +22,25 @@ const useStyles = makeStyles({ export const Horizontal = () => { const styles = useStyles(); const childLength = 1000; + const itemWidth = 100; + const scrollView = React.useRef(null); + + const { virtualizerLength, bufferItems, bufferSize } = useStaticVirtualizerMeasure( + itemWidth, + scrollView.current, + 'horizontal', + ); return ( -
- +
+ {index => { return ( { + const styles = useStyles(); + const childLength = 100; + const repeatingVirtualizers = 5; + + const { virtualizerLength, bufferItems, bufferSize } = useStaticVirtualizerMeasure(100, null); + + const renderVirtualization = (index: number) => { + return ( + <> +
{`Virtualizer Instance - ${index}`}
+ + {rowIndex => { + return ( + {`Node-${index}-${rowIndex}`} + ); + }} + + + ); + }; + + const renderVirtualizerLoop = () => { + // Virtualizer instances can all run independently, even inline a single scroll view. + const array = []; + for (let i = 0; i < repeatingVirtualizers; i++) { + array.push(renderVirtualization(i)); + } + return array; + }; + + return ( + +
+ {renderVirtualizerLoop()} +
+ Footer +
+
+
+ ); +}; diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/RTL.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/RTL.stories.tsx index 20b75d0dbc3877..5b07f00848db69 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/RTL.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/RTL.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Virtualizer } from '@fluentui/react-components/unstable'; +import { useStaticVirtualizerMeasure, Virtualizer } from '@fluentui/react-components/unstable'; import { makeStyles } from '@fluentui/react-components'; const useStyles = makeStyles({ @@ -24,9 +24,26 @@ export const RTL = () => { const styles = useStyles(); const childLength = 1000; + const itemWidth = 100; + const scrollView = React.useRef(null); + + const { virtualizerLength, bufferItems, bufferSize } = useStaticVirtualizerMeasure( + itemWidth, + scrollView.current, + 'horizontal', + ); + return (
- + {index => { return ( { const styles = useStyles(); const childLength = 1000; + const itemSize = 100; + const scrollView = React.useRef(null); + + const { virtualizerLength, bufferItems, bufferSize } = useStaticVirtualizerMeasure(itemSize, scrollView.current); return ( -
- +
+ {index => { return ( { const styles = useStyles(); const childLength = 1000; + const itemWidth = 100; + const scrollView = React.useRef(null); + + const { virtualizerLength, bufferItems, bufferSize } = useStaticVirtualizerMeasure( + itemWidth, + scrollView.current, + 'horizontal', + ); + return ( -
- +
+ {index => { return ( { +export const Default = () => { const styles = useStyles(); const childLength = 1000; return ( -
- + + {(index: number) => { return ( - {`Node-${index}`} + >{`Node-${index}`}
); }} -
+ ); }; diff --git a/packages/react-components/react-virtualizer/stories/VirtualizerScrollView/VirtualizerScrollViewDescription.md b/packages/react-components/react-virtualizer/stories/VirtualizerScrollView/VirtualizerScrollViewDescription.md new file mode 100644 index 00000000000000..6e866566c10755 --- /dev/null +++ b/packages/react-components/react-virtualizer/stories/VirtualizerScrollView/VirtualizerScrollViewDescription.md @@ -0,0 +1,12 @@ + + +> **⚠️ Preview components are considered unstable:** +> +> ```jsx +> +> import { VirtualizerScrollView } from '@fluentui/react-components/unstable'; +> +> ``` +> +> - Features and APIs may change before final release +> - Please contact us if you intend to use this in your product diff --git a/packages/react-components/react-virtualizer/stories/VirtualizerScrollView/index.stories.ts b/packages/react-components/react-virtualizer/stories/VirtualizerScrollView/index.stories.ts new file mode 100644 index 00000000000000..db5040009bd414 --- /dev/null +++ b/packages/react-components/react-virtualizer/stories/VirtualizerScrollView/index.stories.ts @@ -0,0 +1,16 @@ +import { VirtualizerScrollView } from '../../src/VirtualizerScrollView'; +import descriptionMd from './VirtualizerScrollViewDescription.md'; + +export { Default } from './Default.stories'; + +export default { + title: 'Preview Components/VirtualizerScrollView', + component: VirtualizerScrollView, + parameters: { + docs: { + description: { + component: [descriptionMd].join('\n'), + }, + }, + }, +}; From c0cb6adade9b105939f09cf02aa835f1d0ebef9f Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Mon, 27 Feb 2023 10:46:24 -0800 Subject: [PATCH 07/33] Tidy up types and add change files --- ...-267e8104-5e4e-487c-b7aa-766926d9c6bc.json | 7 +++++ ...-4e79b7e8-b7bf-493b-b8cb-fba5a70fecf7.json | 7 +++++ .../etc/react-virtualizer.api.md | 8 +++-- .../Virtualizer/Virtualizer.types.ts | 2 +- .../components/Virtualizer/useVirtualizer.ts | 4 +-- .../VirtualizerScrollView.types.ts | 29 +++++++++++++++---- 6 files changed, 46 insertions(+), 11 deletions(-) create mode 100644 change/@fluentui-react-components-267e8104-5e4e-487c-b7aa-766926d9c6bc.json create mode 100644 change/@fluentui-react-virtualizer-4e79b7e8-b7bf-493b-b8cb-fba5a70fecf7.json diff --git a/change/@fluentui-react-components-267e8104-5e4e-487c-b7aa-766926d9c6bc.json b/change/@fluentui-react-components-267e8104-5e4e-487c-b7aa-766926d9c6bc.json new file mode 100644 index 00000000000000..865ad8c40384c5 --- /dev/null +++ b/change/@fluentui-react-components-267e8104-5e4e-487c-b7aa-766926d9c6bc.json @@ -0,0 +1,7 @@ +{ + "type": "patch", + "comment": "feat: Add static measurement hooks and embedded scroll option to react-virtualizer", + "packageName": "@fluentui/react-components", + "email": "mifraser@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-virtualizer-4e79b7e8-b7bf-493b-b8cb-fba5a70fecf7.json b/change/@fluentui-react-virtualizer-4e79b7e8-b7bf-493b-b8cb-fba5a70fecf7.json new file mode 100644 index 00000000000000..61e2e2f85db972 --- /dev/null +++ b/change/@fluentui-react-virtualizer-4e79b7e8-b7bf-493b-b8cb-fba5a70fecf7.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "[feat] Add static measurement hooks and embedded scroll option", + "packageName": "@fluentui/react-virtualizer", + "email": "mifraser@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md index ef40ff1c5ff9e1..7e0530de719754 100644 --- a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md +++ b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md @@ -8,7 +8,7 @@ import { ComponentProps } from '@fluentui/react-utilities'; import { ComponentState } from '@fluentui/react-utilities'; import type { Dispatch } from 'react'; import type { FC } from 'react'; -import { MutableRefObject } from 'react'; +import type { MutableRefObject } from 'react'; import * as React_2 from 'react'; import type { SetStateAction } from 'react'; import { Slot } from '@fluentui/react-utilities'; @@ -65,8 +65,10 @@ export const VirtualizerScrollView: React_2.FC; export const virtualizerScrollViewClassNames: SlotClassNames; // @public (undocumented) -export type VirtualizerScrollViewProps = ComponentProps> & Omit & { - scrollViewRef?: MutableRefObject; +export type VirtualizerScrollViewProps = ComponentProps> & Partial> & { + itemSize: number; + numItems: number; + children: VirtualizerChildRenderFunction; }; // @public (undocumented) diff --git a/packages/react-components/react-virtualizer/src/components/Virtualizer/Virtualizer.types.ts b/packages/react-components/react-virtualizer/src/components/Virtualizer/Virtualizer.types.ts index 6419a7160171c5..cf94ee370ae8eb 100644 --- a/packages/react-components/react-virtualizer/src/components/Virtualizer/Virtualizer.types.ts +++ b/packages/react-components/react-virtualizer/src/components/Virtualizer/Virtualizer.types.ts @@ -115,7 +115,7 @@ export type IVirtualizerProps = { * Enables users to override the intersectionObserverRoot. * @default null */ - intersectionObserverRoot?: React.MutableRefObject; + scrollViewRef?: React.MutableRefObject; /** * The scroll direction diff --git a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts index 00ccb90e6eb47c..372912683ffe46 100644 --- a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts @@ -15,7 +15,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta getItemSize, bufferItems = Math.round(virtualizerLength / 4.0), bufferSize = Math.floor(bufferItems / 2.0) * itemSize, - intersectionObserverRoot, + scrollViewRef, axis = 'vertical', reversed = false, onUpdateIndex, @@ -180,7 +180,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta } }, { - root: intersectionObserverRoot ? intersectionObserverRoot?.current : null, + root: scrollViewRef ? scrollViewRef?.current : null, rootMargin: '0px', threshold: 0, }, diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts index 97f5fdde82eccb..39acf5775363a3 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts @@ -1,17 +1,36 @@ import { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; -import { MutableRefObject } from 'react'; -import { VirtualizerSlots, IVirtualizerProps, IVirtualizerState } from '../Virtualizer/Virtualizer.types'; +import { + VirtualizerSlots, + IVirtualizerProps, + IVirtualizerState, + VirtualizerChildRenderFunction, +} from '../Virtualizer/Virtualizer.types'; export type VirtualizerScrollViewSlots = VirtualizerSlots & { /** - * The root container that provides scrolling. + * The root container that provides embedded scrolling. */ container: NonNullable>; }; export type VirtualizerScrollViewProps = ComponentProps> & - Omit & { - scrollViewRef?: MutableRefObject; + Partial> & { + /** + * Virtualizer item size in pixels - static. + * Axis: 'vertical' = Height + * Axis: 'horizontal' = Width + */ + itemSize: number; + /** + * The total number of items to be virtualized. + */ + numItems: number; + /** + * Child render function. + * Iteratively called to return current virtualizer DOM children. + * Will act as a row or column indexer depending on Virtualizer settings. + */ + children: VirtualizerChildRenderFunction; }; export type VirtualizerScrollViewState = ComponentState & IVirtualizerState; From 5b3ea6b1b297615457d79a58c8b40eae10c7ec43 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Mon, 27 Feb 2023 11:33:25 -0800 Subject: [PATCH 08/33] Add fluent/react package as dev dep for demos --- packages/react-components/react-virtualizer/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-components/react-virtualizer/package.json b/packages/react-components/react-virtualizer/package.json index 61dfa58f24cc4f..55c017659825cd 100644 --- a/packages/react-components/react-virtualizer/package.json +++ b/packages/react-components/react-virtualizer/package.json @@ -28,7 +28,8 @@ "@fluentui/react-conformance": "*", "@fluentui/react-conformance-griffel": "9.0.0-beta.20", "@fluentui/scripts-api-extractor": "*", - "@fluentui/scripts-tasks": "*" + "@fluentui/scripts-tasks": "*", + "@fluentui/react": "^8.106.1" }, "dependencies": { "@fluentui/react-utilities": "^9.7.2", From 67dbfe6eb413f7046447a069ef52a6fbc2671b30 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Mon, 27 Feb 2023 12:18:40 -0800 Subject: [PATCH 09/33] Fix up key indexing on multi-unbounded demo --- .../Virtualizer/MultiUnbounded.stories.tsx | 56 +++++++++++-------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/MultiUnbounded.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/MultiUnbounded.stories.tsx index f51d04eac5c7b0..5416932350c7cf 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/MultiUnbounded.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/MultiUnbounded.stories.tsx @@ -36,30 +36,32 @@ export const MultiUnbounded = () => { const { virtualizerLength, bufferItems, bufferSize } = useStaticVirtualizerMeasure(100, null); + const renderHeader = (index: number) => { + return
{`Virtualizer Instance - ${index}`}
; + }; + const renderVirtualization = (index: number) => { return ( - <> -
{`Virtualizer Instance - ${index}`}
- - {rowIndex => { - return ( - {`Node-${index}-${rowIndex}`} - ); - }} - - + + {rowIndex => { + return ( + {`Node-${index}-${rowIndex}`} + ); + }} + ); }; @@ -67,14 +69,20 @@ export const MultiUnbounded = () => { // Virtualizer instances can all run independently, even inline a single scroll view. const array = []; for (let i = 0; i < repeatingVirtualizers; i++) { + array.push(renderHeader(i)); array.push(renderVirtualization(i)); } return array; }; return ( - -
+ +
{renderVirtualizerLoop()}
Footer From a019eccc645a286b10d19ca9fd82fda7b3866134 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Mon, 27 Feb 2023 15:39:21 -0800 Subject: [PATCH 10/33] Add fluentui/react as dep --- packages/react-components/react-virtualizer/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-components/react-virtualizer/package.json b/packages/react-components/react-virtualizer/package.json index 55c017659825cd..27ad5052b6a5e2 100644 --- a/packages/react-components/react-virtualizer/package.json +++ b/packages/react-components/react-virtualizer/package.json @@ -28,11 +28,11 @@ "@fluentui/react-conformance": "*", "@fluentui/react-conformance-griffel": "9.0.0-beta.20", "@fluentui/scripts-api-extractor": "*", - "@fluentui/scripts-tasks": "*", - "@fluentui/react": "^8.106.1" + "@fluentui/scripts-tasks": "*" }, "dependencies": { "@fluentui/react-utilities": "^9.7.2", + "@fluentui/react": "^8.106.1", "@fluentui/react-shared-contexts": "^9.2.0", "@griffel/react": "^1.5.2", "@swc/helpers": "^0.4.14" From 9ed155d2694eef3d5fcea3997ed08d3f3036dfc8 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Tue, 28 Feb 2023 10:38:23 -0800 Subject: [PATCH 11/33] Ensure resize observer is only enabled once mounted (SSR tests) --- .../src/hooks/useVirtualizerMeasure.ts | 30 ++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts index f54a3a31a5968d..6f977d9269dbd7 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts @@ -20,8 +20,9 @@ export const useStaticVirtualizerMeasure = ( const { targetDocument } = useFluent(); - // We should always return something valid, document.body provides safe initialization until defined. + // If no ref is provided, the virtualizer should be in the main body document's scroll. const _scrollView = scrollView ?? targetDocument?.documentElement ?? null; + const container = React.useRef(null); // the handler for resize observer @@ -56,26 +57,27 @@ export const useStaticVirtualizerMeasure = ( setVirtualizerLength(totalLength); setVirtualizerBufferSize(bufferSize); setVirtualizerBufferItems(bufferItems); - // eslint-disable-next-line react-hooks/exhaustive-deps }, [defaultItemSize, direction]); // Keep the reference of ResizeObserver in the state, as it should live through renders const [resizeObserver] = React.useState(canUseDOM() ? new ResizeObserver(handleResize) : undefined); - if (_scrollView !== container.current) { - if (container.current) { - resizeObserver?.unobserve(container.current); - } - // Update - container.current = _scrollView; + React.useEffect(() => { + if (_scrollView !== container.current) { + if (container.current) { + resizeObserver?.unobserve(container.current); + } + // Update + container.current = _scrollView; - // Only observe if not null - if (container.current) { - resizeObserver?.observe(container.current); - } + // Only observe if not null + if (container.current) { + resizeObserver?.observe(container.current); + } - handleResize(); - } + handleResize(); + } + }, [_scrollView, handleResize, resizeObserver]); // Do we want to use a dispatch here? return { virtualizerLength, bufferItems: virtualizerBufferItems, bufferSize: virtualizerBufferSize }; From a8ba1785c5eaa0532ef77592761c4aac88f58c60 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Tue, 14 Mar 2023 13:06:59 -0700 Subject: [PATCH 12/33] Touch up PR comments --- .../src/components/Virtualizer/useVirtualizer.ts | 6 ++---- .../VirtualizerScrollView/useVirtualizerScrollView.ts | 1 - .../src/hooks/useIntersectionObserver.ts | 8 -------- .../stories/VirtualizerScrollView/Default.stories.tsx | 6 +++++- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts index 372912683ffe46..ebe723442c0983 100644 --- a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts @@ -407,8 +407,6 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta } const isFullyInitialized = hasInitialized.current && virtualizerStartIndex >= 0; - const beforeKey = useId('virtualizer-before-'); - const afterKey = useId('virtualizer-after-'); return { components: { before: 'div', @@ -435,14 +433,14 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta required: true, defaultProps: { role: 'none', - key: beforeKey, + key: 'fui-virtualizer-before', }, }), afterContainer: resolveShorthand(props.afterContainer, { required: true, defaultProps: { role: 'none', - key: afterKey, + key: 'fui-virtualizer-after', }, }), beforeBufferHeight: isFullyInitialized ? calculateBefore() : 0, diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts index ffc88923d38d31..1ccd0c76acb8b7 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts @@ -29,7 +29,6 @@ export function useVirtualizerScrollView_unstable( required: true, defaultProps: { ref: setScrollRef, - role: 'list', }, }), }; diff --git a/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts b/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts index cfceb58f66a022..f0838f3b459b2d 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useIntersectionObserver.ts @@ -22,14 +22,6 @@ export const useIntersectionObserver = ( setObserverInit: Dispatch>; observer: MutableRefObject; } => { - // export const useIntersectionObserver = ( - // callback: IntersectionObserverCallback, - // options?: IntersectionObserverInit, - // ): [ - // Dispatch>, - // Dispatch>, - // MutableRefObject, - // ] => { const observer = useRef(); const [observerList, setObserverList] = useState(); const [observerInit, setObserverInit] = useState(options); diff --git a/packages/react-components/react-virtualizer/stories/VirtualizerScrollView/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/VirtualizerScrollView/Default.stories.tsx index 3244b0c306756e..9ea10c6ee8d342 100644 --- a/packages/react-components/react-virtualizer/stories/VirtualizerScrollView/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/VirtualizerScrollView/Default.stories.tsx @@ -20,7 +20,11 @@ export const Default = () => { return ( - + {(index: number) => { return (
Date: Tue, 14 Mar 2023 14:50:27 -0700 Subject: [PATCH 13/33] Add ref callback and touch up PR comments --- .../etc/react-virtualizer.api.md | 2 +- .../Virtualizer/Virtualizer.types.ts | 1 - .../VirtualizerScrollView.ts | 18 +-- .../useVirtualizerScrollView.ts | 38 +++-- .../src/hooks/useVirtualizerMeasure.ts | 130 ++++++++++-------- .../src/hooks/useVirtualizerMeasure.types.ts | 12 ++ .../src/utilities/debounce.ts | 20 +++ .../stories/Virtualizer/Default.stories.tsx | 10 +- .../Virtualizer/DefaultUnbounded.stories.tsx | 7 +- .../Virtualizer/Horizontal.stories.tsx | 11 +- .../Virtualizer/MultiUnbounded.stories.tsx | 7 +- .../stories/Virtualizer/RTL.stories.tsx | 13 +- .../stories/Virtualizer/Reversed.stories.tsx | 5 +- .../ReversedHorizontal.stories.tsx | 11 +- 14 files changed, 169 insertions(+), 116 deletions(-) create mode 100644 packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.types.ts create mode 100644 packages/react-components/react-virtualizer/src/utilities/debounce.ts diff --git a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md index 7e0530de719754..f1836cd9befa8d 100644 --- a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md +++ b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md @@ -28,7 +28,7 @@ export const useIntersectionObserver: (callback: IntersectionObserverCallback, o }; // @public -export const useStaticVirtualizerMeasure: (defaultItemSize: number, scrollView?: HTMLElement | null, direction?: 'vertical' | 'horizontal') => { +export const useStaticVirtualizerMeasure: (defaultItemSize: number, scrollView?: HTMLElement, direction?: 'vertical' | 'horizontal') => { virtualizerLength: number; bufferItems: number; bufferSize: number; diff --git a/packages/react-components/react-virtualizer/src/components/Virtualizer/Virtualizer.types.ts b/packages/react-components/react-virtualizer/src/components/Virtualizer/Virtualizer.types.ts index cf94ee370ae8eb..42a99da5170274 100644 --- a/packages/react-components/react-virtualizer/src/components/Virtualizer/Virtualizer.types.ts +++ b/packages/react-components/react-virtualizer/src/components/Virtualizer/Virtualizer.types.ts @@ -113,7 +113,6 @@ export type IVirtualizerProps = { /** * Enables users to override the intersectionObserverRoot. - * @default null */ scrollViewRef?: React.MutableRefObject; diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.ts index c3b6cf2ad88b7d..0a141260dc61ee 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.ts @@ -2,7 +2,6 @@ import { VirtualizerScrollViewProps } from './VirtualizerScrollView.types'; import { useVirtualizerScrollView_unstable } from './useVirtualizerScrollView'; import { renderVirtualizerScrollView_unstable } from './renderVirtualizerScrollView'; import { useVirtualizerScrollViewStyles_unstable } from './useVirtualizerScrollViewStyles'; -import { useStaticVirtualizerMeasure } from '../../Hooks'; import * as React from 'react'; /** @@ -10,22 +9,7 @@ import * as React from 'react'; */ export const VirtualizerScrollView: React.FC = (props: VirtualizerScrollViewProps) => { - const scrollRef = React.useRef(null); - const { virtualizerLength, bufferItems, bufferSize } = useStaticVirtualizerMeasure( - props.itemSize, - scrollRef.current ?? null, - props.axis ?? 'vertical', - ); - - const state = useVirtualizerScrollView_unstable( - { - ...props, - bufferItems, - bufferSize, - scrollViewRef: scrollRef, - }, - virtualizerLength, - ); + const state = useVirtualizerScrollView_unstable(props); useVirtualizerScrollViewStyles_unstable(state); diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts index 1ccd0c76acb8b7..ec32f820c22b8a 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts @@ -2,22 +2,32 @@ import * as React from 'react'; import { resolveShorthand } from '@fluentui/react-utilities'; import { useVirtualizer_unstable } from '../Virtualizer/useVirtualizer'; import { VirtualizerScrollViewProps, VirtualizerScrollViewState } from './VirtualizerScrollView.types'; +import { useStaticVirtualizerMeasure } from '../../Hooks'; -export function useVirtualizerScrollView_unstable( - props: VirtualizerScrollViewProps, - virtualizerLength: number, -): VirtualizerScrollViewState { - const virtualizerState = useVirtualizer_unstable({ ...props, virtualizerLength }); +export function useVirtualizerScrollView_unstable(props: VirtualizerScrollViewProps): VirtualizerScrollViewState { + const { virtualizerLength, bufferItems, bufferSize, useScrollRef } = useStaticVirtualizerMeasure({ + defaultItemSize: props.itemSize, + direction: props.axis ?? 'vertical', + }); - const setScrollRef = React.useCallback( - (element: HTMLDivElement) => { - if (!element || !props.scrollViewRef || props.scrollViewRef.current === element) { - return; - } - props.scrollViewRef.current = element; - }, - [props.scrollViewRef], - ); + const iScrollRef = React.useRef(null); + + const setScrollRef = React.useCallback((element: HTMLDivElement) => { + if (!element || !iScrollRef || iScrollRef.current === element) { + return; + } + iScrollRef.current = element; + }, []); + + useScrollRef(iScrollRef); + + const virtualizerState = useVirtualizer_unstable({ + ...props, + virtualizerLength, + bufferItems, + bufferSize, + scrollViewRef: iScrollRef, + }); return { ...virtualizerState, diff --git a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts index 6f977d9269dbd7..468d505d22008f 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts @@ -1,86 +1,97 @@ import * as React from 'react'; import { canUseDOM } from '@fluentui/react-utilities'; -import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; +import { IVirtualizerMeasureDynamicProps, IVirtualizerMeasureProps } from './useVirtualizerMeasure.types'; +import { debounce } from '../utilities/debounce'; /** * React hook that measures virtualized space based on a static size to ensure optimized virtualization length. */ export const useStaticVirtualizerMeasure = ( - defaultItemSize: number, - scrollView: HTMLElement | null = null, - direction: 'vertical' | 'horizontal' = 'vertical', + virtualizerProps: IVirtualizerMeasureProps, ): { virtualizerLength: number; bufferItems: number; bufferSize: number; + useScrollRef: (ref: React.MutableRefObject) => void; } => { + const { defaultItemSize, direction = 'vertical' } = virtualizerProps; + const [virtualizerLength, setVirtualizerLength] = React.useState(0); const [virtualizerBufferItems, setVirtualizerBufferItems] = React.useState(0); const [virtualizerBufferSize, setVirtualizerBufferSize] = React.useState(0); - const { targetDocument } = useFluent(); + // A ref for the previously set container, we use this to check if the container has changed (re-observe). + // const prevContainer: React.MutableRefObject = React.useRef(null); - // If no ref is provided, the virtualizer should be in the main body document's scroll. - const _scrollView = scrollView ?? targetDocument?.documentElement ?? null; + // // The ref the user sets on their scrollView. + const container: React.MutableRefObject = React.useRef(null); - const container = React.useRef(null); + const useVirtualizerScrollRef = (ref: React.MutableRefObject) => { + React.useEffect(() => { + if (ref.current !== container.current) { + if (container.current) { + resizeObserver?.unobserve(container.current); + } - // the handler for resize observer - const handleResize = React.useCallback(() => { - const containerSize = - direction === 'vertical' - ? container.current?.getBoundingClientRect().height - : container.current?.getBoundingClientRect().width; - - if (!containerSize) { - // Error? ignore? - return; - } - - /* - * Number of items required to cover viewport. - */ - const length = Math.ceil(containerSize / defaultItemSize + 1); - - /* - * Number of items to append at each end, i.e. 'preload' each side before entering view. - */ - const bufferItems = Math.max(Math.floor(length / 4), 2); - - /* - * 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; - - setVirtualizerLength(totalLength); - setVirtualizerBufferSize(bufferSize); - setVirtualizerBufferItems(bufferItems); - }, [defaultItemSize, direction]); + // Update + container.current = ref.current; - // Keep the reference of ResizeObserver in the state, as it should live through renders - const [resizeObserver] = React.useState(canUseDOM() ? new ResizeObserver(handleResize) : undefined); + // Only observe if not null + if (container.current) { + resizeObserver?.observe(container.current); + } - React.useEffect(() => { - if (_scrollView !== container.current) { - if (container.current) { - resizeObserver?.unobserve(container.current); + handleResize(); } - // Update - container.current = _scrollView; + }); + }; - // Only observe if not null - if (container.current) { - resizeObserver?.observe(container.current); + // the handler for resize observer + const handleResize = debounce( + React.useCallback(() => { + const containerSize = + direction === 'vertical' + ? container.current?.getBoundingClientRect().height + : container.current?.getBoundingClientRect().width; + + if (!containerSize) { + // Error? ignore? + return; } - handleResize(); - } - }, [_scrollView, handleResize, resizeObserver]); + /* + * Number of items required to cover viewport. + */ + const length = Math.ceil(containerSize / defaultItemSize + 1); + + /* + * Number of items to append at each end, i.e. 'preload' each side before entering view. + */ + const bufferItems = Math.max(Math.floor(length / 4), 2); + + /* + * 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; + + console.log('Setting new length:', totalLength); + setVirtualizerLength(totalLength); + setVirtualizerBufferSize(bufferSize); + setVirtualizerBufferItems(bufferItems); + }, [defaultItemSize, direction]), + ); + + // Keep the reference of ResizeObserver in the state, as it should live through renders + const [resizeObserver] = React.useState(canUseDOM() ? new ResizeObserver(handleResize) : undefined); - // Do we want to use a dispatch here? - return { virtualizerLength, bufferItems: virtualizerBufferItems, bufferSize: virtualizerBufferSize }; + return { + virtualizerLength, + bufferItems: virtualizerBufferItems, + bufferSize: virtualizerBufferSize, + useScrollRef: useVirtualizerScrollRef, + }; }; /** @@ -88,10 +99,7 @@ export const useStaticVirtualizerMeasure = ( */ export const useDynamicVirtualizerMeasure = ( - defaultItemSize: number, - currentIndex: number, - getItemSize: (index: number) => number, - scrollView?: React.ReactNode, + virtualizerProps: IVirtualizerMeasureDynamicProps, ): { virtualizerLength: number; virtualizerBufferItems: number; diff --git a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.types.ts b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.types.ts new file mode 100644 index 00000000000000..f6686d2f0c23bc --- /dev/null +++ b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.types.ts @@ -0,0 +1,12 @@ +export type IVirtualizerMeasureProps = { + defaultItemSize: number; + direction?: 'vertical' | 'horizontal'; +}; + +export type IVirtualizerMeasureDynamicProps = { + defaultItemSize: number; + currentIndex: number; + numItems: number; + getItemSize: (index: number) => number; + direction?: 'vertical' | 'horizontal'; +}; diff --git a/packages/react-components/react-virtualizer/src/utilities/debounce.ts b/packages/react-components/react-virtualizer/src/utilities/debounce.ts new file mode 100644 index 00000000000000..966da2e0d50315 --- /dev/null +++ b/packages/react-components/react-virtualizer/src/utilities/debounce.ts @@ -0,0 +1,20 @@ +/** + * Microtask debouncer + * https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide + * @param fn - Function to debounce + * @returns debounced function + */ +export function debounce(fn: Function) { + let pending: boolean; + return () => { + if (!pending) { + pending = true; + queueMicrotask(() => { + // Need to set pending to `false` before the debounced function is run. + // React can actually interrupt the function while it's running! + pending = false; + fn(); + }); + } + }; +} diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx index c17c011b9dc966..1eaffa764a02e9 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx @@ -27,12 +27,16 @@ const useStyles = makeStyles({ export const Default = () => { const styles = useStyles(); const childLength = 1000; - const scrollView = React.useRef(null); - const { virtualizerLength, bufferItems, bufferSize } = useStaticVirtualizerMeasure(100, scrollView.current); + const scrollRef = React.useRef(null); + const { virtualizerLength, bufferItems, bufferSize, useScrollRef } = useStaticVirtualizerMeasure({ + defaultItemSize: 100, + }); + + useScrollRef(scrollRef); return ( -
+
{ const styles = useStyles(); const childLength = 1000; - const { virtualizerLength, bufferItems, bufferSize } = useStaticVirtualizerMeasure(100, null); + const { virtualizerLength, bufferItems, bufferSize, useScrollRef } = useStaticVirtualizerMeasure({ + defaultItemSize: 100, + }); + + const scrollRef = React.useRef(document.body); + useScrollRef(scrollRef); return ( diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/Horizontal.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/Horizontal.stories.tsx index aa4d2afd15a858..901b41d07e5efe 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/Horizontal.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/Horizontal.stories.tsx @@ -25,11 +25,12 @@ export const Horizontal = () => { const itemWidth = 100; const scrollView = React.useRef(null); - const { virtualizerLength, bufferItems, bufferSize } = useStaticVirtualizerMeasure( - itemWidth, - scrollView.current, - 'horizontal', - ); + const { virtualizerLength, bufferItems, bufferSize, useScrollRef } = useStaticVirtualizerMeasure({ + defaultItemSize: itemWidth, + direction: 'horizontal', + }); + + useScrollRef(scrollView); return (
diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/MultiUnbounded.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/MultiUnbounded.stories.tsx index 5416932350c7cf..fc7c3fb3a6f8e2 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/MultiUnbounded.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/MultiUnbounded.stories.tsx @@ -34,7 +34,12 @@ export const MultiUnbounded = () => { const childLength = 100; const repeatingVirtualizers = 5; - const { virtualizerLength, bufferItems, bufferSize } = useStaticVirtualizerMeasure(100, null); + const { virtualizerLength, bufferItems, bufferSize, useScrollRef } = useStaticVirtualizerMeasure({ + defaultItemSize: 100, + }); + + const scrollView = React.useRef(document.body); + useScrollRef(scrollView); const renderHeader = (index: number) => { return
{`Virtualizer Instance - ${index}`}
; diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/RTL.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/RTL.stories.tsx index 5b07f00848db69..6fc1901e602adf 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/RTL.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/RTL.stories.tsx @@ -27,14 +27,15 @@ export const RTL = () => { const itemWidth = 100; const scrollView = React.useRef(null); - const { virtualizerLength, bufferItems, bufferSize } = useStaticVirtualizerMeasure( - itemWidth, - scrollView.current, - 'horizontal', - ); + const { virtualizerLength, bufferItems, bufferSize, useScrollRef } = useStaticVirtualizerMeasure({ + defaultItemSize: itemWidth, + direction: 'horizontal', + }); + + useScrollRef(scrollView); return ( -
+
{ const itemSize = 100; const scrollView = React.useRef(null); - const { virtualizerLength, bufferItems, bufferSize } = useStaticVirtualizerMeasure(itemSize, scrollView.current); + const { virtualizerLength, bufferItems, bufferSize, useScrollRef } = useStaticVirtualizerMeasure({ + defaultItemSize: itemSize, + }); + useScrollRef(scrollView); return (
diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/ReversedHorizontal.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/ReversedHorizontal.stories.tsx index 561d2a9f55a635..fe4f8c5ce24871 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/ReversedHorizontal.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/ReversedHorizontal.stories.tsx @@ -26,11 +26,12 @@ export const ReversedHorizontal = () => { const itemWidth = 100; const scrollView = React.useRef(null); - const { virtualizerLength, bufferItems, bufferSize } = useStaticVirtualizerMeasure( - itemWidth, - scrollView.current, - 'horizontal', - ); + const { virtualizerLength, bufferItems, bufferSize, useScrollRef } = useStaticVirtualizerMeasure({ + defaultItemSize: itemWidth, + direction: 'horizontal', + }); + + useScrollRef(scrollView); return (
From 8c0b3316522f6cf816a5ba13c85ea0d9d7268f1f Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Tue, 14 Mar 2023 15:48:40 -0700 Subject: [PATCH 14/33] Remove block --- .../stories/Virtualizer/Default.stories.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx index 1eaffa764a02e9..a3b71a7fbe947b 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx @@ -17,11 +17,6 @@ const useStyles = makeStyles({ lineHeight: '100px', width: '100%', }, - block: { - height: '1250px', - width: '100%', - backgroundColor: 'red', - }, }); export const Default = () => { @@ -37,7 +32,6 @@ export const Default = () => { return (
-
Date: Tue, 14 Mar 2023 16:33:56 -0700 Subject: [PATCH 15/33] Test render cycles --- .../components/Virtualizer/useVirtualizer.ts | 1 + .../src/hooks/useVirtualizerMeasure.ts | 22 +++++++++++++------ .../stories/Virtualizer/Default.stories.tsx | 5 +++++ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts index ebe723442c0983..9634ecf3396c96 100644 --- a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts @@ -77,6 +77,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta updateChildRows(index); updateCurrentItemSizes(index); + console.log('NEW INDEX:', index); // State setters setVirtualizerStartIndex(index); }; diff --git a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts index 468d505d22008f..271f4b9c075919 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts @@ -2,6 +2,8 @@ import * as React from 'react'; import { canUseDOM } from '@fluentui/react-utilities'; import { IVirtualizerMeasureDynamicProps, IVirtualizerMeasureProps } from './useVirtualizerMeasure.types'; import { debounce } from '../utilities/debounce'; +import { flushSync } from 'react-dom'; +import * as ReactDOM from 'react-dom'; /** * React hook that measures virtualized space based on a static size to ensure optimized virtualization length. @@ -20,15 +22,14 @@ export const useStaticVirtualizerMeasure = ( const [virtualizerBufferItems, setVirtualizerBufferItems] = React.useState(0); const [virtualizerBufferSize, setVirtualizerBufferSize] = React.useState(0); - // A ref for the previously set container, we use this to check if the container has changed (re-observe). - // const prevContainer: React.MutableRefObject = React.useRef(null); - - // // The ref the user sets on their scrollView. + // The ref the user sets on their scrollView. const container: React.MutableRefObject = React.useRef(null); const useVirtualizerScrollRef = (ref: React.MutableRefObject) => { + console.log('Using virtualizer'); React.useEffect(() => { if (ref.current !== container.current) { + console.log('ref.current !== container.current'); if (container.current) { resizeObserver?.unobserve(container.current); } @@ -38,9 +39,11 @@ export const useStaticVirtualizerMeasure = ( // Only observe if not null if (container.current) { + console.log('Observing'); resizeObserver?.observe(container.current); } + console.log('Call init resize'); handleResize(); } }); @@ -77,9 +80,14 @@ export const useStaticVirtualizerMeasure = ( const totalLength = length + bufferItems * 2 + 1; console.log('Setting new length:', totalLength); - setVirtualizerLength(totalLength); - setVirtualizerBufferSize(bufferSize); - setVirtualizerBufferItems(bufferItems); + console.log('Setting new bufferSize:', bufferSize); + console.log('Setting new bufferItems:', bufferItems); + + ReactDOM.unstable_batchedUpdates(() => { + setVirtualizerLength(totalLength); + setVirtualizerBufferSize(bufferSize); + setVirtualizerBufferItems(bufferItems); + }); }, [defaultItemSize, direction]), ); diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx index a3b71a7fbe947b..8edc7354f3040a 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx @@ -28,6 +28,11 @@ export const Default = () => { defaultItemSize: 100, }); + console.log('RENDER - virtualizerLength', virtualizerLength); + console.log('RENDER - bufferItems', bufferItems); + console.log('RENDER - bufferSize', bufferSize); + console.log('RENDER - useScrollRef', useScrollRef); + useScrollRef(scrollRef); return ( From 74635fa831ff4befd416f1f4d0e5034ec1be21c2 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Wed, 15 Mar 2023 09:39:48 -0700 Subject: [PATCH 16/33] Add PR comments --- .../components/Virtualizer/useVirtualizer.ts | 1 - .../src/hooks/useVirtualizerMeasure.ts | 28 ++++--------------- .../stories/Virtualizer/Default.stories.tsx | 5 ---- 3 files changed, 6 insertions(+), 28 deletions(-) diff --git a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts index 9634ecf3396c96..ebe723442c0983 100644 --- a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts @@ -77,7 +77,6 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta updateChildRows(index); updateCurrentItemSizes(index); - console.log('NEW INDEX:', index); // State setters setVirtualizerStartIndex(index); }; diff --git a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts index 271f4b9c075919..60780b04d4f35b 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts @@ -2,7 +2,6 @@ import * as React from 'react'; import { canUseDOM } from '@fluentui/react-utilities'; import { IVirtualizerMeasureDynamicProps, IVirtualizerMeasureProps } from './useVirtualizerMeasure.types'; import { debounce } from '../utilities/debounce'; -import { flushSync } from 'react-dom'; import * as ReactDOM from 'react-dom'; /** @@ -14,7 +13,7 @@ export const useStaticVirtualizerMeasure = ( virtualizerLength: number; bufferItems: number; bufferSize: number; - useScrollRef: (ref: React.MutableRefObject) => void; + useScrollRef: (ref: React.MutableRefObject) => void; } => { const { defaultItemSize, direction = 'vertical' } = virtualizerProps; @@ -22,14 +21,12 @@ export const useStaticVirtualizerMeasure = ( const [virtualizerBufferItems, setVirtualizerBufferItems] = React.useState(0); const [virtualizerBufferSize, setVirtualizerBufferSize] = React.useState(0); - // The ref the user sets on their scrollView. - const container: React.MutableRefObject = React.useRef(null); + // The ref the user sets on their scrollView - Defaults to document.body to ensure no null on init + const container: React.MutableRefObject = React.useRef(document.body); - const useVirtualizerScrollRef = (ref: React.MutableRefObject) => { - console.log('Using virtualizer'); + const useVirtualizerScrollRef = (ref: React.MutableRefObject) => { React.useEffect(() => { if (ref.current !== container.current) { - console.log('ref.current !== container.current'); if (container.current) { resizeObserver?.unobserve(container.current); } @@ -39,12 +36,8 @@ export const useStaticVirtualizerMeasure = ( // Only observe if not null if (container.current) { - console.log('Observing'); resizeObserver?.observe(container.current); } - - console.log('Call init resize'); - handleResize(); } }); }; @@ -54,13 +47,8 @@ export const useStaticVirtualizerMeasure = ( React.useCallback(() => { const containerSize = direction === 'vertical' - ? container.current?.getBoundingClientRect().height - : container.current?.getBoundingClientRect().width; - - if (!containerSize) { - // Error? ignore? - return; - } + ? container.current.getBoundingClientRect().height + : container.current.getBoundingClientRect().width; /* * Number of items required to cover viewport. @@ -79,10 +67,6 @@ export const useStaticVirtualizerMeasure = ( const totalLength = length + bufferItems * 2 + 1; - console.log('Setting new length:', totalLength); - console.log('Setting new bufferSize:', bufferSize); - console.log('Setting new bufferItems:', bufferItems); - ReactDOM.unstable_batchedUpdates(() => { setVirtualizerLength(totalLength); setVirtualizerBufferSize(bufferSize); diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx index 8edc7354f3040a..a3b71a7fbe947b 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx @@ -28,11 +28,6 @@ export const Default = () => { defaultItemSize: 100, }); - console.log('RENDER - virtualizerLength', virtualizerLength); - console.log('RENDER - bufferItems', bufferItems); - console.log('RENDER - bufferSize', bufferSize); - console.log('RENDER - useScrollRef', useScrollRef); - useScrollRef(scrollRef); return ( From e0326cceb9a9ed34811001ef878e2c2c2bb67615 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Wed, 15 Mar 2023 10:01:55 -0700 Subject: [PATCH 17/33] Rebase and touch up --- .../react-virtualizer/etc/react-virtualizer.api.md | 5 +++-- packages/react-components/react-virtualizer/package.json | 1 - .../src/components/Virtualizer/useVirtualizer.ts | 2 +- .../VirtualizerScrollView/useVirtualizerScrollView.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md index f1836cd9befa8d..201da58a8fb3a4 100644 --- a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md +++ b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md @@ -28,17 +28,18 @@ export const useIntersectionObserver: (callback: IntersectionObserverCallback, o }; // @public -export const useStaticVirtualizerMeasure: (defaultItemSize: number, scrollView?: HTMLElement, direction?: 'vertical' | 'horizontal') => { +export const useStaticVirtualizerMeasure: (virtualizerProps: IVirtualizerMeasureProps) => { virtualizerLength: number; bufferItems: number; bufferSize: number; + useScrollRef: (ref: React_2.MutableRefObject) => void; }; // @public (undocumented) export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerState; // @public (undocumented) -export function useVirtualizerScrollView_unstable(props: VirtualizerScrollViewProps, virtualizerLength: number): VirtualizerScrollViewState; +export function useVirtualizerScrollView_unstable(props: VirtualizerScrollViewProps): VirtualizerScrollViewState; // @public export const useVirtualizerScrollViewStyles_unstable: (state: VirtualizerScrollViewState) => VirtualizerScrollViewState; diff --git a/packages/react-components/react-virtualizer/package.json b/packages/react-components/react-virtualizer/package.json index 27ad5052b6a5e2..925d79fa043a78 100644 --- a/packages/react-components/react-virtualizer/package.json +++ b/packages/react-components/react-virtualizer/package.json @@ -33,7 +33,6 @@ "dependencies": { "@fluentui/react-utilities": "^9.7.2", "@fluentui/react": "^8.106.1", - "@fluentui/react-shared-contexts": "^9.2.0", "@griffel/react": "^1.5.2", "@swc/helpers": "^0.4.14" }, diff --git a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts index ebe723442c0983..cda3ccafdf9356 100644 --- a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts @@ -3,7 +3,7 @@ import type { ReactNode } from 'react'; import { useEffect, useRef, useState, useCallback, useReducer } from 'react'; import type { VirtualizerProps, VirtualizerState } from './Virtualizer.types'; -import { resolveShorthand, useId } from '@fluentui/react-utilities'; +import { resolveShorthand } from '@fluentui/react-utilities'; import { flushSync } from 'react-dom'; export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerState { diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts index ec32f820c22b8a..2eabece2a0a7e5 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts @@ -10,7 +10,7 @@ export function useVirtualizerScrollView_unstable(props: VirtualizerScrollViewPr direction: props.axis ?? 'vertical', }); - const iScrollRef = React.useRef(null); + const iScrollRef = React.useRef(document.body); const setScrollRef = React.useCallback((element: HTMLDivElement) => { if (!element || !iScrollRef || iScrollRef.current === element) { From 72adb3aba35d994333e6aef87f7389ab6ab46cc2 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Wed, 15 Mar 2023 10:14:22 -0700 Subject: [PATCH 18/33] Update package dep --- packages/react-components/react-virtualizer/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-components/react-virtualizer/package.json b/packages/react-components/react-virtualizer/package.json index 925d79fa043a78..e9a0e8f0ad3afe 100644 --- a/packages/react-components/react-virtualizer/package.json +++ b/packages/react-components/react-virtualizer/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@fluentui/react-utilities": "^9.7.2", - "@fluentui/react": "^8.106.1", + "@fluentui/react": "^8.106.6", "@griffel/react": "^1.5.2", "@swc/helpers": "^0.4.14" }, From 02a142d2f0ddd6cfd36a629b925c6645fbb3ef17 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Wed, 15 Mar 2023 10:22:24 -0700 Subject: [PATCH 19/33] Fix up undefined ref --- .../src/hooks/useVirtualizerMeasure.ts | 10 +++++++--- .../stories/Virtualizer/Default.stories.tsx | 2 +- .../stories/Virtualizer/DefaultUnbounded.stories.tsx | 2 +- .../stories/Virtualizer/MultiUnbounded.stories.tsx | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts index 60780b04d4f35b..ef1b870d015146 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts @@ -13,7 +13,7 @@ export const useStaticVirtualizerMeasure = ( virtualizerLength: number; bufferItems: number; bufferSize: number; - useScrollRef: (ref: React.MutableRefObject) => void; + useScrollRef: (ref: React.MutableRefObject) => void; } => { const { defaultItemSize, direction = 'vertical' } = virtualizerProps; @@ -22,9 +22,9 @@ export const useStaticVirtualizerMeasure = ( const [virtualizerBufferSize, setVirtualizerBufferSize] = React.useState(0); // The ref the user sets on their scrollView - Defaults to document.body to ensure no null on init - const container: React.MutableRefObject = React.useRef(document.body); + const container: React.MutableRefObject = React.useRef(null); - const useVirtualizerScrollRef = (ref: React.MutableRefObject) => { + const useVirtualizerScrollRef = (ref: React.MutableRefObject) => { React.useEffect(() => { if (ref.current !== container.current) { if (container.current) { @@ -45,6 +45,10 @@ export const useStaticVirtualizerMeasure = ( // the handler for resize observer const handleResize = debounce( React.useCallback(() => { + if (!container.current) { + return; + } + const containerSize = direction === 'vertical' ? container.current.getBoundingClientRect().height diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx index a3b71a7fbe947b..7f335421832b00 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx @@ -23,7 +23,7 @@ export const Default = () => { const styles = useStyles(); const childLength = 1000; - const scrollRef = React.useRef(null); + const scrollRef = React.useRef(); const { virtualizerLength, bufferItems, bufferSize, useScrollRef } = useStaticVirtualizerMeasure({ defaultItemSize: 100, }); diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/DefaultUnbounded.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/DefaultUnbounded.stories.tsx index bb9772ce159ddb..7c5f8cf2e71d99 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/DefaultUnbounded.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/DefaultUnbounded.stories.tsx @@ -37,7 +37,7 @@ export const DefaultUnbounded = () => { defaultItemSize: 100, }); - const scrollRef = React.useRef(document.body); + const scrollRef = React.useRef(null); useScrollRef(scrollRef); return ( diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/MultiUnbounded.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/MultiUnbounded.stories.tsx index fc7c3fb3a6f8e2..f4464e2877a232 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/MultiUnbounded.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/MultiUnbounded.stories.tsx @@ -38,7 +38,7 @@ export const MultiUnbounded = () => { defaultItemSize: 100, }); - const scrollView = React.useRef(document.body); + const scrollView = React.useRef(null); useScrollRef(scrollView); const renderHeader = (index: number) => { From b0e53cfdff172e937ba2a575f6fd5f42f0f8da5e Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Wed, 15 Mar 2023 10:39:38 -0700 Subject: [PATCH 20/33] Fix up API --- .../react-virtualizer/etc/react-virtualizer.api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md index 201da58a8fb3a4..4d65c040e54ec5 100644 --- a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md +++ b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md @@ -32,7 +32,7 @@ export const useStaticVirtualizerMeasure: (virtualizerProps: IVirtualizerMeasure virtualizerLength: number; bufferItems: number; bufferSize: number; - useScrollRef: (ref: React_2.MutableRefObject) => void; + useScrollRef: (ref: React_2.MutableRefObject) => void; }; // @public (undocumented) From 7e6bd3c7e4fce180d2bd69ae23bbee4379f188be Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Wed, 15 Mar 2023 10:49:59 -0700 Subject: [PATCH 21/33] Fix up example type --- .../react-virtualizer/src/hooks/useVirtualizerMeasure.ts | 2 +- .../react-virtualizer/stories/Virtualizer/Default.stories.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts index ef1b870d015146..14aa51fa1ce5f3 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts @@ -13,7 +13,7 @@ export const useStaticVirtualizerMeasure = ( virtualizerLength: number; bufferItems: number; bufferSize: number; - useScrollRef: (ref: React.MutableRefObject) => void; + useScrollRef: (ref: React.MutableRefObject) => void; } => { const { defaultItemSize, direction = 'vertical' } = virtualizerProps; diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx index 7f335421832b00..a3b71a7fbe947b 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx @@ -23,7 +23,7 @@ export const Default = () => { const styles = useStyles(); const childLength = 1000; - const scrollRef = React.useRef(); + const scrollRef = React.useRef(null); const { virtualizerLength, bufferItems, bufferSize, useScrollRef } = useStaticVirtualizerMeasure({ defaultItemSize: 100, }); From e38bb492e6937932b566da4dfabb5aa9ffc23531 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Wed, 15 Mar 2023 12:03:47 -0700 Subject: [PATCH 22/33] Update api --- .../react-components/etc/react-components.unstable.api.md | 6 +++--- .../react-virtualizer/etc/react-virtualizer.api.md | 2 +- .../VirtualizerScrollView/useVirtualizerScrollView.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/react-components/react-components/etc/react-components.unstable.api.md b/packages/react-components/react-components/etc/react-components.unstable.api.md index ae3faf7f0c1d27..16f7745139fa90 100644 --- a/packages/react-components/react-components/etc/react-components.unstable.api.md +++ b/packages/react-components/react-components/etc/react-components.unstable.api.md @@ -128,6 +128,7 @@ import { useSkeletonContext } from '@fluentui/react-skeleton'; import { useSkeletonItem_unstable } from '@fluentui/react-skeleton'; import { useSkeletonItemStyles_unstable } from '@fluentui/react-skeleton'; import { useSkeletonStyles_unstable } from '@fluentui/react-skeleton'; +import { useStaticVirtualizerMeasure } from '@fluentui/react-virtualizer'; import { useTree_unstable } from '@fluentui/react-tree'; import { useTreeContext_unstable } from '@fluentui/react-tree'; import { useTreeItem_unstable } from '@fluentui/react-tree'; @@ -138,7 +139,6 @@ import { useTreeItemPersonaLayout_unstable } from '@fluentui/react-tree'; import { useTreeItemPersonaLayoutStyles_unstable } from '@fluentui/react-tree'; import { useTreeItemStyles_unstable } from '@fluentui/react-tree'; import { useTreeStyles_unstable } from '@fluentui/react-tree'; -import { useStaticVirtualizerMeasure } from '@fluentui/react-virtualizer'; import { useVirtualizer_unstable } from '@fluentui/react-virtualizer'; import { useVirtualizerScrollView_unstable } from '@fluentui/react-virtualizer'; import { useVirtualizerScrollViewStyles_unstable } from '@fluentui/react-virtualizer'; @@ -403,6 +403,8 @@ export { useSkeletonItemStyles_unstable } export { useSkeletonStyles_unstable } +export { useStaticVirtualizerMeasure } + export { useTree_unstable } export { useTreeContext_unstable } @@ -423,8 +425,6 @@ export { useTreeItemStyles_unstable } export { useTreeStyles_unstable } -export { useStaticVirtualizerMeasure } - export { useVirtualizer_unstable } export { useVirtualizerScrollView_unstable } diff --git a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md index 4d65c040e54ec5..257a852de8f7eb 100644 --- a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md +++ b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md @@ -32,7 +32,7 @@ export const useStaticVirtualizerMeasure: (virtualizerProps: IVirtualizerMeasure virtualizerLength: number; bufferItems: number; bufferSize: number; - useScrollRef: (ref: React_2.MutableRefObject) => void; + useScrollRef: (ref: React_2.MutableRefObject) => void; }; // @public (undocumented) diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts index 2eabece2a0a7e5..ec32f820c22b8a 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts @@ -10,7 +10,7 @@ export function useVirtualizerScrollView_unstable(props: VirtualizerScrollViewPr direction: props.axis ?? 'vertical', }); - const iScrollRef = React.useRef(document.body); + const iScrollRef = React.useRef(null); const setScrollRef = React.useCallback((element: HTMLDivElement) => { if (!element || !iScrollRef || iScrollRef.current === element) { From c4f92fea62c408f40f8487590a1e54c86fe86299 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Thu, 16 Mar 2023 11:03:21 -0700 Subject: [PATCH 23/33] Add document.body call --- .../stories/Virtualizer/DefaultUnbounded.stories.tsx | 2 +- .../stories/Virtualizer/MultiUnbounded.stories.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/DefaultUnbounded.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/DefaultUnbounded.stories.tsx index 7c5f8cf2e71d99..bb9772ce159ddb 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/DefaultUnbounded.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/DefaultUnbounded.stories.tsx @@ -37,7 +37,7 @@ export const DefaultUnbounded = () => { defaultItemSize: 100, }); - const scrollRef = React.useRef(null); + const scrollRef = React.useRef(document.body); useScrollRef(scrollRef); return ( diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/MultiUnbounded.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/MultiUnbounded.stories.tsx index f4464e2877a232..fc7c3fb3a6f8e2 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/MultiUnbounded.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/MultiUnbounded.stories.tsx @@ -38,7 +38,7 @@ export const MultiUnbounded = () => { defaultItemSize: 100, }); - const scrollView = React.useRef(null); + const scrollView = React.useRef(document.body); useScrollRef(scrollView); const renderHeader = (index: number) => { From 6d9059d80a4cdf87c6fb9ffeca4b21fb3d5b269f Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Mon, 20 Mar 2023 10:44:13 -0700 Subject: [PATCH 24/33] Fix up ref callback to be an actual callback func --- .../useVirtualizerScrollView.ts | 20 +++++----- .../src/hooks/useVirtualizerMeasure.ts | 38 +++++++++---------- .../stories/Virtualizer/Default.stories.tsx | 5 +-- .../Virtualizer/DefaultUnbounded.stories.tsx | 5 +-- .../Virtualizer/Horizontal.stories.tsx | 6 +-- .../Virtualizer/MultiUnbounded.stories.tsx | 5 +-- .../stories/Virtualizer/RTL.stories.tsx | 8 +--- .../stories/Virtualizer/Reversed.stories.tsx | 6 +-- .../ReversedHorizontal.stories.tsx | 7 +--- 9 files changed, 42 insertions(+), 58 deletions(-) diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts index ec32f820c22b8a..cd690bb012319b 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts @@ -5,21 +5,23 @@ import { VirtualizerScrollViewProps, VirtualizerScrollViewState } from './Virtua import { useStaticVirtualizerMeasure } from '../../Hooks'; export function useVirtualizerScrollView_unstable(props: VirtualizerScrollViewProps): VirtualizerScrollViewState { - const { virtualizerLength, bufferItems, bufferSize, useScrollRef } = useStaticVirtualizerMeasure({ + const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useStaticVirtualizerMeasure({ defaultItemSize: props.itemSize, direction: props.axis ?? 'vertical', }); const iScrollRef = React.useRef(null); - const setScrollRef = React.useCallback((element: HTMLDivElement) => { - if (!element || !iScrollRef || iScrollRef.current === element) { - return; - } - iScrollRef.current = element; - }, []); - - useScrollRef(iScrollRef); + const setScrollRef = React.useCallback( + (element: HTMLDivElement) => { + if (iScrollRef.current === element) { + return; + } + scrollRef(element); + iScrollRef.current = element; + }, + [scrollRef], + ); const virtualizerState = useVirtualizer_unstable({ ...props, diff --git a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts index 14aa51fa1ce5f3..6a501af1274504 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts @@ -13,7 +13,7 @@ export const useStaticVirtualizerMeasure = ( virtualizerLength: number; bufferItems: number; bufferSize: number; - useScrollRef: (ref: React.MutableRefObject) => void; + scrollRef: (instance: HTMLElement | HTMLDivElement | null) => void; } => { const { defaultItemSize, direction = 'vertical' } = virtualizerProps; @@ -24,24 +24,6 @@ export const useStaticVirtualizerMeasure = ( // The ref the user sets on their scrollView - Defaults to document.body to ensure no null on init const container: React.MutableRefObject = React.useRef(null); - const useVirtualizerScrollRef = (ref: React.MutableRefObject) => { - React.useEffect(() => { - if (ref.current !== container.current) { - if (container.current) { - resizeObserver?.unobserve(container.current); - } - - // Update - container.current = ref.current; - - // Only observe if not null - if (container.current) { - resizeObserver?.observe(container.current); - } - } - }); - }; - // the handler for resize observer const handleResize = debounce( React.useCallback(() => { @@ -82,11 +64,27 @@ export const useStaticVirtualizerMeasure = ( // Keep the reference of ResizeObserver in the state, as it should live through renders const [resizeObserver] = React.useState(canUseDOM() ? new ResizeObserver(handleResize) : undefined); + const scrollRef = React.useCallback( + (el: HTMLElement | null) => { + if (container.current !== el) { + if (container.current) { + resizeObserver?.unobserve(container.current); + } + + container.current = el; + if (container.current) { + resizeObserver?.observe(container.current); + } + } + }, + [resizeObserver], + ); + return { virtualizerLength, bufferItems: virtualizerBufferItems, bufferSize: virtualizerBufferSize, - useScrollRef: useVirtualizerScrollRef, + scrollRef, }; }; diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx index a3b71a7fbe947b..00ab141a061181 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/Default.stories.tsx @@ -23,13 +23,10 @@ export const Default = () => { const styles = useStyles(); const childLength = 1000; - const scrollRef = React.useRef(null); - const { virtualizerLength, bufferItems, bufferSize, useScrollRef } = useStaticVirtualizerMeasure({ + const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useStaticVirtualizerMeasure({ defaultItemSize: 100, }); - useScrollRef(scrollRef); - return (
{ const styles = useStyles(); const childLength = 1000; - const { virtualizerLength, bufferItems, bufferSize, useScrollRef } = useStaticVirtualizerMeasure({ + const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useStaticVirtualizerMeasure({ defaultItemSize: 100, }); - const scrollRef = React.useRef(document.body); - useScrollRef(scrollRef); + scrollRef(document.body); return ( diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/Horizontal.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/Horizontal.stories.tsx index 901b41d07e5efe..aa86d8c87b7c61 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/Horizontal.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/Horizontal.stories.tsx @@ -25,15 +25,13 @@ export const Horizontal = () => { const itemWidth = 100; const scrollView = React.useRef(null); - const { virtualizerLength, bufferItems, bufferSize, useScrollRef } = useStaticVirtualizerMeasure({ + const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useStaticVirtualizerMeasure({ defaultItemSize: itemWidth, direction: 'horizontal', }); - useScrollRef(scrollView); - return ( -
+
{ const childLength = 100; const repeatingVirtualizers = 5; - const { virtualizerLength, bufferItems, bufferSize, useScrollRef } = useStaticVirtualizerMeasure({ + const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useStaticVirtualizerMeasure({ defaultItemSize: 100, }); - const scrollView = React.useRef(document.body); - useScrollRef(scrollView); + scrollRef(document.body); const renderHeader = (index: number) => { return
{`Virtualizer Instance - ${index}`}
; diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/RTL.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/RTL.stories.tsx index 6fc1901e602adf..9f6d296753b312 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/RTL.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/RTL.stories.tsx @@ -25,17 +25,13 @@ export const RTL = () => { const childLength = 1000; const itemWidth = 100; - const scrollView = React.useRef(null); - - const { virtualizerLength, bufferItems, bufferSize, useScrollRef } = useStaticVirtualizerMeasure({ + const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useStaticVirtualizerMeasure({ defaultItemSize: itemWidth, direction: 'horizontal', }); - useScrollRef(scrollView); - return ( -
+
{ const styles = useStyles(); const childLength = 1000; const itemSize = 100; - const scrollView = React.useRef(null); - const { virtualizerLength, bufferItems, bufferSize, useScrollRef } = useStaticVirtualizerMeasure({ + const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useStaticVirtualizerMeasure({ defaultItemSize: itemSize, }); - useScrollRef(scrollView); return ( -
+
{ const childLength = 1000; const itemWidth = 100; - const scrollView = React.useRef(null); - const { virtualizerLength, bufferItems, bufferSize, useScrollRef } = useStaticVirtualizerMeasure({ + const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useStaticVirtualizerMeasure({ defaultItemSize: itemWidth, direction: 'horizontal', }); - useScrollRef(scrollView); - return ( -
+
Date: Mon, 20 Mar 2023 10:54:30 -0700 Subject: [PATCH 25/33] Update api --- .../react-virtualizer/etc/react-virtualizer.api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md index 257a852de8f7eb..20a7b11118db79 100644 --- a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md +++ b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md @@ -32,7 +32,7 @@ export const useStaticVirtualizerMeasure: (virtualizerProps: IVirtualizerMeasure virtualizerLength: number; bufferItems: number; bufferSize: number; - useScrollRef: (ref: React_2.MutableRefObject) => void; + scrollRef: (instance: HTMLElement | HTMLDivElement | null) => void; }; // @public (undocumented) From fd242398db1175a0d77514a7e50ee5ac6d4c836f Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Mon, 20 Mar 2023 12:04:38 -0700 Subject: [PATCH 26/33] PR Comments and update version --- .../etc/react-virtualizer.api.md | 2 +- .../react-virtualizer/package.json | 4 +- .../src/hooks/useVirtualizerMeasure.ts | 103 ++++++++---------- .../src/hooks/useVirtualizerMeasure.types.ts | 10 +- 4 files changed, 50 insertions(+), 69 deletions(-) diff --git a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md index 20a7b11118db79..324cab379172e6 100644 --- a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md +++ b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md @@ -28,7 +28,7 @@ export const useIntersectionObserver: (callback: IntersectionObserverCallback, o }; // @public -export const useStaticVirtualizerMeasure: (virtualizerProps: IVirtualizerMeasureProps) => { +export const useStaticVirtualizerMeasure: (virtualizerProps: VirtualizerMeasureProps) => { virtualizerLength: number; bufferItems: number; bufferSize: number; diff --git a/packages/react-components/react-virtualizer/package.json b/packages/react-components/react-virtualizer/package.json index e9a0e8f0ad3afe..a06f8cc2b8473a 100644 --- a/packages/react-components/react-virtualizer/package.json +++ b/packages/react-components/react-virtualizer/package.json @@ -31,8 +31,8 @@ "@fluentui/scripts-tasks": "*" }, "dependencies": { - "@fluentui/react-utilities": "^9.7.2", - "@fluentui/react": "^8.106.6", + "@fluentui/react-utilities": "^9.7.1", + "@fluentui/react": "^8.106.7", "@griffel/react": "^1.5.2", "@swc/helpers": "^0.4.14" }, diff --git a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts index 6a501af1274504..7ebef60e39b801 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { canUseDOM } from '@fluentui/react-utilities'; -import { IVirtualizerMeasureDynamicProps, IVirtualizerMeasureProps } from './useVirtualizerMeasure.types'; +import { VirtualizerMeasureProps } from './useVirtualizerMeasure.types'; import { debounce } from '../utilities/debounce'; import * as ReactDOM from 'react-dom'; @@ -8,7 +8,7 @@ import * as ReactDOM from 'react-dom'; * React hook that measures virtualized space based on a static size to ensure optimized virtualization length. */ export const useStaticVirtualizerMeasure = ( - virtualizerProps: IVirtualizerMeasureProps, + virtualizerProps: VirtualizerMeasureProps, ): { virtualizerLength: number; bufferItems: number; @@ -17,49 +17,53 @@ export const useStaticVirtualizerMeasure = ( } => { const { defaultItemSize, direction = 'vertical' } = virtualizerProps; - const [virtualizerLength, setVirtualizerLength] = React.useState(0); - const [virtualizerBufferItems, setVirtualizerBufferItems] = React.useState(0); - const [virtualizerBufferSize, setVirtualizerBufferSize] = React.useState(0); + const [state, setState] = React.useState({ + virtualizerLength: 0, + bufferSize: 0, + bufferItems: 0, + }); + + const { virtualizerLength, bufferItems, bufferSize } = state; // The ref the user sets on their scrollView - Defaults to document.body to ensure no null on init const container: React.MutableRefObject = React.useRef(null); - // the handler for resize observer - const handleResize = debounce( - React.useCallback(() => { - if (!container.current) { - return; - } + const resizeCallback = () => { + if (!container.current) { + return; + } + + const containerSize = + direction === 'vertical' + ? container.current.getBoundingClientRect().height + : container.current.getBoundingClientRect().width; + + /* + * Number of items required to cover viewport. + */ + const length = Math.ceil(containerSize / defaultItemSize + 1); + + /* + * 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); + + /* + * This is how far we deviate into the bufferItems to detect a redraw. + */ + const newBufferSize = Math.max(Math.floor((length / 8) * defaultItemSize), 1); + + const totalLength = length + newBufferSize * 2 + 1; + + setState({ + virtualizerLength: totalLength, + bufferItems: newBufferItems, + bufferSize: newBufferSize, + }); + }; - const containerSize = - direction === 'vertical' - ? container.current.getBoundingClientRect().height - : container.current.getBoundingClientRect().width; - - /* - * Number of items required to cover viewport. - */ - const length = Math.ceil(containerSize / defaultItemSize + 1); - - /* - * Number of items to append at each end, i.e. 'preload' each side before entering view. - */ - const bufferItems = Math.max(Math.floor(length / 4), 2); - - /* - * 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; - - ReactDOM.unstable_batchedUpdates(() => { - setVirtualizerLength(totalLength); - setVirtualizerBufferSize(bufferSize); - setVirtualizerBufferItems(bufferItems); - }); - }, [defaultItemSize, direction]), - ); + // the handler for resize observer + const handleResize = debounce(resizeCallback); // Keep the reference of ResizeObserver in the state, as it should live through renders const [resizeObserver] = React.useState(canUseDOM() ? new ResizeObserver(handleResize) : undefined); @@ -82,23 +86,8 @@ export const useStaticVirtualizerMeasure = ( return { virtualizerLength, - bufferItems: virtualizerBufferItems, - bufferSize: virtualizerBufferSize, + bufferItems, + bufferSize, scrollRef, }; }; - -/** - * React hook that measures virtualized space dynamically to ensure optimized virtualization length. - */ - -export const useDynamicVirtualizerMeasure = ( - virtualizerProps: IVirtualizerMeasureDynamicProps, -): { - virtualizerLength: number; - virtualizerBufferItems: number; - virtualizerBufferSize: number; -} => { - // TODO Add resize observer and update sizes as resize observer callback occurs - dispatch? - return { virtualizerLength: 10, virtualizerBufferItems: 10, virtualizerBufferSize: 10 }; -}; diff --git a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.types.ts b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.types.ts index f6686d2f0c23bc..dffd7c801c7b89 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.types.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.types.ts @@ -1,12 +1,4 @@ -export type IVirtualizerMeasureProps = { +export type VirtualizerMeasureProps = { defaultItemSize: number; direction?: 'vertical' | 'horizontal'; }; - -export type IVirtualizerMeasureDynamicProps = { - defaultItemSize: number; - currentIndex: number; - numItems: number; - getItemSize: (index: number) => number; - direction?: 'vertical' | 'horizontal'; -}; From 214ca9054b74df780b5939de5e6a64685958cfbf Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Mon, 20 Mar 2023 12:24:29 -0700 Subject: [PATCH 27/33] Remove I notation from types --- .../etc/react-virtualizer.api.md | 8 ++-- .../Virtualizer/Virtualizer.types.ts | 8 ++-- .../VirtualizerScrollView.types.ts | 8 ++-- .../VirtualizerScrollViewDynamic.types.ts | 38 +++++++++++++++++++ 4 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 packages/react-components/react-virtualizer/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts diff --git a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md index 324cab379172e6..a00c9dec69b41e 100644 --- a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md +++ b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md @@ -57,7 +57,7 @@ export type VirtualizerChildRenderFunction = (index: number) => React_2.ReactNod export const virtualizerClassNames: SlotClassNames; // @public (undocumented) -export type VirtualizerProps = ComponentProps> & IVirtualizerProps; +export type VirtualizerProps = ComponentProps> & VirtualizerConfigProps; // @public export const VirtualizerScrollView: React_2.FC; @@ -66,7 +66,7 @@ export const VirtualizerScrollView: React_2.FC; export const virtualizerScrollViewClassNames: SlotClassNames; // @public (undocumented) -export type VirtualizerScrollViewProps = ComponentProps> & Partial> & { +export type VirtualizerScrollViewProps = ComponentProps> & Partial> & { itemSize: number; numItems: number; children: VirtualizerChildRenderFunction; @@ -78,7 +78,7 @@ export type VirtualizerScrollViewSlots = VirtualizerSlots & { }; // @public (undocumented) -export type VirtualizerScrollViewState = ComponentState & IVirtualizerState; +export type VirtualizerScrollViewState = ComponentState & VirtualizerConfigState; // @public (undocumented) export type VirtualizerSlots = { @@ -89,7 +89,7 @@ export type VirtualizerSlots = { }; // @public (undocumented) -export type VirtualizerState = ComponentState & IVirtualizerState; +export type VirtualizerState = ComponentState & VirtualizerConfigState; // (No @packageDocumentation comment for this package) diff --git a/packages/react-components/react-virtualizer/src/components/Virtualizer/Virtualizer.types.ts b/packages/react-components/react-virtualizer/src/components/Virtualizer/Virtualizer.types.ts index 42a99da5170274..dbdfb52d21c222 100644 --- a/packages/react-components/react-virtualizer/src/components/Virtualizer/Virtualizer.types.ts +++ b/packages/react-components/react-virtualizer/src/components/Virtualizer/Virtualizer.types.ts @@ -20,7 +20,7 @@ export type VirtualizerSlots = { afterContainer: NonNullable>; }; -export type IVirtualizerState = { +export type VirtualizerConfigState = { /** * The current virtualized array of children to show in the DOM. */ @@ -57,13 +57,13 @@ export type IVirtualizerState = { bufferSize: number; }; -export type VirtualizerState = ComponentState & IVirtualizerState; +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? export type VirtualizerChildRenderFunction = (index: number) => React.ReactNode; -export type IVirtualizerProps = { +export type VirtualizerConfigProps = { /** * Child render function. * Iteratively called to return current virtualizer DOM children. @@ -145,4 +145,4 @@ export type IVirtualizerProps = { onCalculateIndex?: (newIndex: number) => number; }; -export type VirtualizerProps = ComponentProps> & IVirtualizerProps; +export type VirtualizerProps = ComponentProps> & VirtualizerConfigProps; diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts index 39acf5775363a3..198789fef9c81d 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.types.ts @@ -1,8 +1,8 @@ import { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; import { VirtualizerSlots, - IVirtualizerProps, - IVirtualizerState, + VirtualizerConfigProps, + VirtualizerConfigState, VirtualizerChildRenderFunction, } from '../Virtualizer/Virtualizer.types'; @@ -14,7 +14,7 @@ export type VirtualizerScrollViewSlots = VirtualizerSlots & { }; export type VirtualizerScrollViewProps = ComponentProps> & - Partial> & { + Partial> & { /** * Virtualizer item size in pixels - static. * Axis: 'vertical' = Height @@ -33,4 +33,4 @@ export type VirtualizerScrollViewProps = ComponentProps & IVirtualizerState; +export type VirtualizerScrollViewState = ComponentState & VirtualizerConfigState; diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts new file mode 100644 index 00000000000000..0df4061f12ed7d --- /dev/null +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts @@ -0,0 +1,38 @@ +import { ComponentProps, ComponentState } from '@fluentui/react-utilities'; +import { + VirtualizerConfigProps, + VirtualizerConfigState, + VirtualizerChildRenderFunction, +} from '../Virtualizer/Virtualizer.types'; + +import { VirtualizerScrollViewSlots } from '../VirtualizerScrollView/VirtualizerScrollView.types'; + +export type VirtualizerScrollViewDynamicSlots = VirtualizerScrollViewSlots; + +export type VirtualizerScrollViewDynamicProps = ComponentProps> & + Partial> & { + /** + * Set as the minimum item size. + * Axis: 'vertical' = Height + * Axis: 'horizontal' = Width + */ + itemSize: number; + /** + * Callback for acquiring size of individual items + * @param index - the index of the requested size's child + */ + getItemSize: (index: number) => number; + /** + * The total number of items to be virtualized. + */ + numItems: number; + /** + * Child render function. + * Iteratively called to return current virtualizer DOM children. + * Will act as a row or column indexer depending on Virtualizer settings. + */ + children: VirtualizerChildRenderFunction; + }; + +export type VirtualizerScrollViewDynamicState = ComponentState & + VirtualizerConfigState; From 318abe17f0af757e09014d0865c3865e76459e8b Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Mon, 20 Mar 2023 12:47:48 -0700 Subject: [PATCH 28/33] Fix up virtualizer length --- .../react-virtualizer/src/hooks/useVirtualizerMeasure.ts | 3 +-- .../stories/Virtualizer/DefaultUnbounded.stories.tsx | 7 ++++++- .../stories/Virtualizer/MultiUnbounded.stories.tsx | 7 +++++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts index 7ebef60e39b801..4601aa7ea2b510 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts @@ -2,7 +2,6 @@ import * as React from 'react'; import { canUseDOM } from '@fluentui/react-utilities'; import { VirtualizerMeasureProps } from './useVirtualizerMeasure.types'; import { debounce } from '../utilities/debounce'; -import * as ReactDOM from 'react-dom'; /** * React hook that measures virtualized space based on a static size to ensure optimized virtualization length. @@ -53,7 +52,7 @@ export const useStaticVirtualizerMeasure = ( */ const newBufferSize = Math.max(Math.floor((length / 8) * defaultItemSize), 1); - const totalLength = length + newBufferSize * 2 + 1; + const totalLength = length + newBufferItems * 2 + 1; setState({ virtualizerLength: totalLength, diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/DefaultUnbounded.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/DefaultUnbounded.stories.tsx index be679e9c2a3f87..06aa9cc66be6dc 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/DefaultUnbounded.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/DefaultUnbounded.stories.tsx @@ -3,6 +3,8 @@ import { Virtualizer, useStaticVirtualizerMeasure } from '@fluentui/react-compon import { makeStyles } from '@fluentui/react-components'; import { ThemeProvider } from '@fluentui/react'; +import { useFluent } from '@fluentui/react-components'; + const useStyles = makeStyles({ root: { maxHeight: '100vh', @@ -37,7 +39,10 @@ export const DefaultUnbounded = () => { defaultItemSize: 100, }); - scrollRef(document.body); + const { targetDocument } = useFluent(); + if (targetDocument) { + scrollRef(targetDocument.body); + } return ( diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/MultiUnbounded.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/MultiUnbounded.stories.tsx index dfb2e70979941b..f903f16723a8ab 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/MultiUnbounded.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/MultiUnbounded.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, useFluent } from '@fluentui/react-components'; import { ThemeProvider } from '@fluentui/react'; const useStyles = makeStyles({ @@ -38,7 +38,10 @@ export const MultiUnbounded = () => { defaultItemSize: 100, }); - scrollRef(document.body); + const { targetDocument } = useFluent(); + if (targetDocument) { + scrollRef(targetDocument.body); + } const renderHeader = (index: number) => { return
{`Virtualizer Instance - ${index}`}
; From 99dac13f35119b1dc71131f4873f620fe78ecc84 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Mon, 20 Mar 2023 12:56:51 -0700 Subject: [PATCH 29/33] Remove dynamic type file --- .../VirtualizerScrollViewDynamic.types.ts | 38 ------------------- 1 file changed, 38 deletions(-) delete mode 100644 packages/react-components/react-virtualizer/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts deleted file mode 100644 index 0df4061f12ed7d..00000000000000 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { ComponentProps, ComponentState } from '@fluentui/react-utilities'; -import { - VirtualizerConfigProps, - VirtualizerConfigState, - VirtualizerChildRenderFunction, -} from '../Virtualizer/Virtualizer.types'; - -import { VirtualizerScrollViewSlots } from '../VirtualizerScrollView/VirtualizerScrollView.types'; - -export type VirtualizerScrollViewDynamicSlots = VirtualizerScrollViewSlots; - -export type VirtualizerScrollViewDynamicProps = ComponentProps> & - Partial> & { - /** - * Set as the minimum item size. - * Axis: 'vertical' = Height - * Axis: 'horizontal' = Width - */ - itemSize: number; - /** - * Callback for acquiring size of individual items - * @param index - the index of the requested size's child - */ - getItemSize: (index: number) => number; - /** - * The total number of items to be virtualized. - */ - numItems: number; - /** - * Child render function. - * Iteratively called to return current virtualizer DOM children. - * Will act as a row or column indexer depending on Virtualizer settings. - */ - children: VirtualizerChildRenderFunction; - }; - -export type VirtualizerScrollViewDynamicState = ComponentState & - VirtualizerConfigState; From 2bca704de735a6e894f0149a48cef6b2895e2ba4 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Mon, 20 Mar 2023 12:59:17 -0700 Subject: [PATCH 30/33] Ensure resize observer disconnects on unload --- .../react-virtualizer/src/hooks/useVirtualizerMeasure.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts index 4601aa7ea2b510..44bab4146bed03 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts @@ -67,6 +67,13 @@ export const useStaticVirtualizerMeasure = ( // Keep the reference of ResizeObserver in the state, as it should live through renders const [resizeObserver] = React.useState(canUseDOM() ? new ResizeObserver(handleResize) : undefined); + React.useEffect(() => { + return () => { + resizeObserver?.disconnect(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const scrollRef = React.useCallback( (el: HTMLElement | null) => { if (container.current !== el) { From 1e8178e3c9786e687ea222fd5f97c9cf6c4312d1 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Mon, 20 Mar 2023 13:21:04 -0700 Subject: [PATCH 31/33] Remove unused ref in horizontal example --- .../react-virtualizer/stories/Virtualizer/Horizontal.stories.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-components/react-virtualizer/stories/Virtualizer/Horizontal.stories.tsx b/packages/react-components/react-virtualizer/stories/Virtualizer/Horizontal.stories.tsx index aa86d8c87b7c61..9b0539346f3384 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/Horizontal.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/Horizontal.stories.tsx @@ -23,7 +23,6 @@ export const Horizontal = () => { const styles = useStyles(); const childLength = 1000; const itemWidth = 100; - const scrollView = React.useRef(null); const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useStaticVirtualizerMeasure({ defaultItemSize: itemWidth, From 65ec330a5d0551b60b9ffb8a824846d22ffe3478 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Wed, 22 Mar 2023 09:44:21 -0700 Subject: [PATCH 32/33] Add PR comments --- .../components/Virtualizer/useVirtualizer.ts | 4 +--- .../renderVirtualizerScrollView.tsx | 16 ++-------------- .../useVirtualizerScrollView.ts | 17 +++-------------- .../src/hooks/useVirtualizerMeasure.ts | 2 +- 4 files changed, 7 insertions(+), 32 deletions(-) diff --git a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts index cda3ccafdf9356..90577e0efc412e 100644 --- a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts @@ -390,7 +390,7 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta forceUpdate(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [renderChild]); + }, [renderChild, updateChildRows]); // Ensure we have run through and updated the whole size list array at least once. initializeSizeArray(); @@ -433,14 +433,12 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta required: true, defaultProps: { role: 'none', - key: 'fui-virtualizer-before', }, }), afterContainer: resolveShorthand(props.afterContainer, { required: true, defaultProps: { role: 'none', - key: 'fui-virtualizer-after', }, }), beforeBufferHeight: isFullyInitialized ? calculateBefore() : 0, diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/renderVirtualizerScrollView.tsx b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/renderVirtualizerScrollView.tsx index 8581248fbcd11a..1283838d46fc60 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/renderVirtualizerScrollView.tsx +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/renderVirtualizerScrollView.tsx @@ -1,22 +1,10 @@ import * as React from 'react'; import { getSlots } from '@fluentui/react-utilities'; import { VirtualizerScrollViewSlots, VirtualizerScrollViewState } from './VirtualizerScrollView.types'; +import { renderVirtualizer_unstable } from '../Virtualizer/renderVirtualizer'; export const renderVirtualizerScrollView_unstable = (state: VirtualizerScrollViewState) => { const { slots, slotProps } = getSlots(state); - return ( - - {/* The 'before' bookend to hold items in place and detect scroll previous */} - - - - {/* The reduced list of non-virtualized children to be rendered */} - {state.virtualizedChildren} - {/* The 'after' bookend to hold items in place and detect scroll next */} - - - - - ); + return {renderVirtualizer_unstable(state)}; }; diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts index cd690bb012319b..331752a1376f31 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { resolveShorthand } from '@fluentui/react-utilities'; +import { resolveShorthand, useMergedRefs } from '@fluentui/react-utilities'; import { useVirtualizer_unstable } from '../Virtualizer/useVirtualizer'; import { VirtualizerScrollViewProps, VirtualizerScrollViewState } from './VirtualizerScrollView.types'; import { useStaticVirtualizerMeasure } from '../../Hooks'; @@ -10,18 +10,7 @@ export function useVirtualizerScrollView_unstable(props: VirtualizerScrollViewPr direction: props.axis ?? 'vertical', }); - const iScrollRef = React.useRef(null); - - const setScrollRef = React.useCallback( - (element: HTMLDivElement) => { - if (iScrollRef.current === element) { - return; - } - scrollRef(element); - iScrollRef.current = element; - }, - [scrollRef], - ); + const iScrollRef = useMergedRefs(React.useRef(null), scrollRef); const virtualizerState = useVirtualizer_unstable({ ...props, @@ -40,7 +29,7 @@ export function useVirtualizerScrollView_unstable(props: VirtualizerScrollViewPr container: resolveShorthand(props.container, { required: true, defaultProps: { - ref: setScrollRef, + ref: iScrollRef as React.RefObject, }, }), }; diff --git a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts index 44bab4146bed03..d5f92320ecb233 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useVirtualizerMeasure.ts @@ -25,7 +25,7 @@ export const useStaticVirtualizerMeasure = ( const { virtualizerLength, bufferItems, bufferSize } = state; // The ref the user sets on their scrollView - Defaults to document.body to ensure no null on init - const container: React.MutableRefObject = React.useRef(null); + const container = React.useRef(null); const resizeCallback = () => { if (!container.current) { From 3a81dc1e39ae51d7b5eb4287d161dc9fbb6ad157 Mon Sep 17 00:00:00 2001 From: Mitch-At-Work Date: Wed, 22 Mar 2023 10:07:04 -0700 Subject: [PATCH 33/33] Update package deps --- packages/react-components/react-virtualizer/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-components/react-virtualizer/package.json b/packages/react-components/react-virtualizer/package.json index a06f8cc2b8473a..692ed37c0a6b20 100644 --- a/packages/react-components/react-virtualizer/package.json +++ b/packages/react-components/react-virtualizer/package.json @@ -31,8 +31,8 @@ "@fluentui/scripts-tasks": "*" }, "dependencies": { - "@fluentui/react-utilities": "^9.7.1", - "@fluentui/react": "^8.106.7", + "@fluentui/react-utilities": "^9.7.2", + "@fluentui/react": "^8.106.8", "@griffel/react": "^1.5.2", "@swc/helpers": "^0.4.14" },