diff --git a/change/@fluentui-react-components-bdaf87d0-783b-403a-a2fe-d4d1f43890c4.json b/change/@fluentui-react-components-bdaf87d0-783b-403a-a2fe-d4d1f43890c4.json new file mode 100644 index 00000000000000..4ac7839ee23d95 --- /dev/null +++ b/change/@fluentui-react-components-bdaf87d0-783b-403a-a2fe-d4d1f43890c4.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "[FEATURE] Imperative scrolling functionality for react-virtualizer", + "packageName": "@fluentui/react-components", + "email": "mifraser@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-virtualizer-132ffdc3-4c38-49e6-a75e-a019828a0ec8.json b/change/@fluentui-react-virtualizer-132ffdc3-4c38-49e6-a75e-a019828a0ec8.json new file mode 100644 index 00000000000000..afe6c2a1cd744a --- /dev/null +++ b/change/@fluentui-react-virtualizer-132ffdc3-4c38-49e6-a75e-a019828a0ec8.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "feature: Add scrollTo index hook and callbacks, isScrolling flag", + "packageName": "@fluentui/react-virtualizer", + "email": "mifraser@microsoft.com", + "dependentChangeType": "patch" +} 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 e39140d00fd301..560f9df2de9bb6 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 @@ -80,6 +80,11 @@ import { renderVirtualizer_unstable } from '@fluentui/react-virtualizer'; import { renderVirtualizerScrollView_unstable } from '@fluentui/react-virtualizer'; import { renderVirtualizerScrollViewDynamic_unstable } from '@fluentui/react-virtualizer'; import { ResizeCallbackWithRef } from '@fluentui/react-virtualizer'; +import { ScrollToInterface } from '@fluentui/react-virtualizer'; +import { scrollToItemDynamic } from '@fluentui/react-virtualizer'; +import { ScrollToItemDynamicParams } from '@fluentui/react-virtualizer'; +import { scrollToItemStatic } from '@fluentui/react-virtualizer'; +import { ScrollToItemStaticParams } from '@fluentui/react-virtualizer'; import { Tree } from '@fluentui/react-tree'; import { treeClassNames } from '@fluentui/react-tree'; import { TreeContextValue } from '@fluentui/react-tree'; @@ -331,6 +336,16 @@ export { renderVirtualizerScrollViewDynamic_unstable } export { ResizeCallbackWithRef } +export { ScrollToInterface } + +export { scrollToItemDynamic } + +export { ScrollToItemDynamicParams } + +export { scrollToItemStatic } + +export { ScrollToItemStaticParams } + export { Tree } export { treeClassNames } diff --git a/packages/react-components/react-components/src/unstable/index.ts b/packages/react-components/react-components/src/unstable/index.ts index 8a42419d3cb178..8f7655d85cfa59 100644 --- a/packages/react-components/react-components/src/unstable/index.ts +++ b/packages/react-components/react-components/src/unstable/index.ts @@ -52,7 +52,10 @@ export { useVirtualizerScrollViewDynamic_unstable, renderVirtualizerScrollViewDynamic_unstable, useVirtualizerScrollViewDynamicStyles_unstable, + scrollToItemDynamic, + scrollToItemStatic, } from '@fluentui/react-virtualizer'; + export type { VirtualizerProps, VirtualizerState, @@ -68,6 +71,9 @@ export type { VirtualizerMeasureDynamicProps, VirtualizerMeasureProps, ResizeCallbackWithRef, + ScrollToInterface, + ScrollToItemDynamicParams, + ScrollToItemStaticParams, } 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 7fef26dc827602..069fc45c7692df 100644 --- a/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md +++ b/packages/react-components/react-virtualizer/etc/react-virtualizer.api.md @@ -4,14 +4,15 @@ ```ts -import { ComponentProps } from '@fluentui/react-utilities'; -import { ComponentState } from '@fluentui/react-utilities'; +import type { ComponentProps } from '@fluentui/react-utilities'; +import type { ComponentState } from '@fluentui/react-utilities'; import type { Dispatch } from 'react'; import type { FC } from 'react'; import { MutableRefObject } from 'react'; import * as React_2 from 'react'; +import type { RefObject } from 'react'; import type { SetStateAction } from 'react'; -import { Slot } from '@fluentui/react-utilities'; +import type { Slot } from '@fluentui/react-utilities'; import type { SlotClassNames } from '@fluentui/react-utilities'; // @public (undocumented) @@ -29,6 +30,39 @@ export interface ResizeCallbackWithRef { (entries: ResizeObserverEntry[], observer: ResizeObserver, scrollRef?: MutableRefObject): void; } +// @public (undocumented) +export type ScrollToInterface = { + scrollTo: (index: number, behavior?: ScrollBehavior, callback?: (index: number) => void) => void; +}; + +// @public (undocumented) +export const scrollToItemDynamic: (params: ScrollToItemDynamicParams) => void; + +// @public (undocumented) +export type ScrollToItemDynamicParams = { + index: number; + itemSizes: RefObject; + totalSize: number; + scrollViewRef: RefObject; + axis?: 'horizontal' | 'vertical'; + reversed?: boolean; + behavior?: ScrollBehavior; +}; + +// @public (undocumented) +export const scrollToItemStatic: (params: ScrollToItemStaticParams) => void; + +// @public (undocumented) +export type ScrollToItemStaticParams = { + index: number; + itemSize: number; + totalItems: number; + scrollViewRef: RefObject; + axis?: 'horizontal' | 'vertical'; + reversed?: boolean; + behavior?: ScrollBehavior; +}; + // @public export const useDynamicVirtualizerMeasure: (virtualizerProps: VirtualizerMeasureDynamicProps) => { virtualizerLength: number; @@ -80,7 +114,7 @@ export const useVirtualizerStyles_unstable: (state: VirtualizerState) => Virtual export const Virtualizer: FC; // @public (undocumented) -export type VirtualizerChildRenderFunction = (index: number) => React_2.ReactNode; +export type VirtualizerChildRenderFunction = (index: number, isScrolling: boolean) => React_2.ReactNode; // @public (undocumented) export const virtualizerClassNames: SlotClassNames; @@ -94,6 +128,13 @@ export type VirtualizerContextProps = { // @public (undocumented) export const VirtualizerContextProvider: React_2.Provider; +// @public (undocumented) +export type VirtualizerDataRef = { + progressiveSizes: RefObject; + nodeSizes: RefObject; + setFlaggedIndex: (index: number | null) => void; +}; + // @public (undocumented) export type VirtualizerMeasureDynamicProps = { defaultItemSize: number; @@ -125,11 +166,12 @@ export const VirtualizerScrollViewDynamic: React_2.FC; // @public (undocumented) -export type VirtualizerScrollViewDynamicProps = ComponentProps> & Partial> & { +export type VirtualizerScrollViewDynamicProps = ComponentProps> & Partial> & { itemSize: number; getItemSize: (index: number) => number; numItems: number; children: VirtualizerChildRenderFunction; + imperativeRef?: RefObject; }; // @public (undocumented) @@ -139,10 +181,11 @@ export type VirtualizerScrollViewDynamicSlots = VirtualizerScrollViewSlots; export type VirtualizerScrollViewDynamicState = ComponentState & VirtualizerConfigState; // @public (undocumented) -export type VirtualizerScrollViewProps = ComponentProps> & Partial> & { +export type VirtualizerScrollViewProps = ComponentProps> & Partial> & { itemSize: number; numItems: number; children: VirtualizerChildRenderFunction; + imperativeRef?: RefObject; }; // @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 0eb98a460ec899..35c9ee94d0ce5e 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 @@ -1,6 +1,7 @@ import * as React from 'react'; import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; -import { VirtualizerContextProps } from '../../Utilities'; +import type { VirtualizerContextProps } from '../../Utilities'; +import type { RefObject, MutableRefObject } from 'react'; export type VirtualizerSlots = { /** @@ -62,7 +63,13 @@ export type VirtualizerState = ComponentState & VirtualizerCon // 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 VirtualizerChildRenderFunction = (index: number, isScrolling: boolean) => React.ReactNode; + +export type VirtualizerDataRef = { + progressiveSizes: RefObject; + nodeSizes: RefObject; + setFlaggedIndex: (index: number | null) => void; +}; export type VirtualizerConfigProps = { /** @@ -139,6 +146,21 @@ export type VirtualizerConfigProps = { * Virtualizer context can be passed as a prop for extended class use */ virtualizerContext?: VirtualizerContextProps; + + /** + * Callback for notifying when a flagged index has been rendered + */ + onRenderedFlaggedIndex?: (index: number) => void; + + /* + * Callback for notifying when a flagged index has been rendered + */ + flaggedIndex?: MutableRefObject; + + /** + * Imperative ref contains our scrollTo index functionality for user control. + */ + imperativeVirtualizerRef?: RefObject; }; export type VirtualizerProps = ComponentProps> & VirtualizerConfigProps; diff --git a/packages/react-components/react-virtualizer/src/components/Virtualizer/renderVirtualizer.tsx b/packages/react-components/react-virtualizer/src/components/Virtualizer/renderVirtualizer.tsx index 0dd75e7dc6ad66..81d8e3024994ba 100644 --- a/packages/react-components/react-virtualizer/src/components/Virtualizer/renderVirtualizer.tsx +++ b/packages/react-components/react-virtualizer/src/components/Virtualizer/renderVirtualizer.tsx @@ -2,10 +2,11 @@ /** @jsx createElement */ import * as React from 'react'; +import type { VirtualizerSlots, VirtualizerState } from './Virtualizer.types'; +import type { ReactNode } from 'react'; + import { createElement } from '@fluentui/react-jsx-runtime'; import { getSlotsNext } from '@fluentui/react-utilities'; -import { VirtualizerSlots, VirtualizerState } from './Virtualizer.types'; -import { ReactNode } from 'react'; export const renderVirtualizer_unstable = (state: VirtualizerState) => { const { slots, slotProps } = getSlotsNext(state); 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 0d5732a7a1c22b..9f6c4d24b07430 100644 --- a/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts +++ b/packages/react-components/react-virtualizer/src/components/Virtualizer/useVirtualizer.ts @@ -1,11 +1,10 @@ -import { useIntersectionObserver } from '../../hooks/useIntersectionObserver'; import type { ReactNode } from 'react'; -import { useEffect, useRef, useCallback, useReducer } from 'react'; - import type { VirtualizerProps, VirtualizerState } from './Virtualizer.types'; + +import { useEffect, useRef, useCallback, useReducer, useImperativeHandle, useState } from 'react'; +import { useIntersectionObserver } from '../../hooks/useIntersectionObserver'; import { resolveShorthand } from '@fluentui/react-utilities'; import { flushSync } from 'react-dom'; - import { useVirtualizerContextState_unstable } from '../../Utilities'; import { renderVirtualizerChildPlaceholder } from './renderVirtualizer'; @@ -22,11 +21,15 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta axis = 'vertical', reversed = false, virtualizerContext, + onRenderedFlaggedIndex, + imperativeVirtualizerRef, } = props; /* The context is optional, it's useful for injecting additional index logic, or performing uniform state updates*/ const _virtualizerContext = useVirtualizerContextState_unstable(virtualizerContext); + const flaggedIndex = useRef(null); + const actualIndex = _virtualizerContext.contextIndex; const setActualIndex = _virtualizerContext.setContextIndex; @@ -75,6 +78,37 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta } }; + const [isScrolling, setIsScrolling] = useState(false); + const scrollTimer = useRef | null>(); + const scrollCounter = useRef(0); + + const initializeScrollingTimer = () => { + /* + * This can be considered the 'velocity' required to start 'isScrolling' + * INIT_SCROLL_FLAG_REQ: Number of renders required to activate isScrolling + * INIT_SCROLL_FLAG_DELAY: Amount of time (ms) before current number of renders is reset + * - Maybe we should let users customize these in the future. + */ + const INIT_SCROLL_FLAG_REQ = 10; + const INIT_SCROLL_FLAG_DELAY = 100; + + scrollCounter.current++; + if (scrollCounter.current >= INIT_SCROLL_FLAG_REQ) { + setIsScrolling(true); + } + if (scrollTimer.current) { + clearTimeout(scrollTimer.current); + } + scrollTimer.current = setTimeout(() => { + setIsScrolling(false); + scrollCounter.current = 0; + }, INIT_SCROLL_FLAG_DELAY); + }; + + useEffect(() => { + initializeScrollingTimer(); + }, [actualIndex]); + const batchUpdateNewIndex = (index: number) => { // Local updates updateChildRows(index); @@ -233,16 +267,16 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta return getIndexFromSizeArray(scrollPos); }; - const calculateTotalSize = () => { + const calculateTotalSize = useCallback(() => { if (!getItemSize) { return itemSize * numItems; } // Time for custom size calcs return childProgressiveSizes.current[numItems - 1]; - }; + }, [getItemSize, itemSize, numItems]); - const calculateBefore = () => { + const calculateBefore = useCallback(() => { const currentIndex = Math.min(actualIndex, numItems); if (!getItemSize) { @@ -256,9 +290,9 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // Time for custom size calcs return childProgressiveSizes.current[currentIndex - 1]; - }; + }, [actualIndex, getItemSize, itemSize, numItems]); - const calculateAfter = () => { + const calculateAfter = useCallback(() => { if (numItems === 0) { return 0; } @@ -272,27 +306,27 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // Time for custom size calcs return childProgressiveSizes.current[numItems - 1] - childProgressiveSizes.current[lastItemIndex]; - }; + }, [actualIndex, getItemSize, itemSize, numItems, virtualizerLength]); const updateChildRows = useCallback( (newIndex: number) => { if (numItems === 0) { /* Nothing to virtualize */ - - return []; + return; } - if (childArray.current.length !== numItems) { - childArray.current = new Array(virtualizerLength); - } + /* + We reset the array every time to ensure children are re-rendered + This function should only be called when update is nessecary + */ + childArray.current = new Array(virtualizerLength); const _actualIndex = Math.max(newIndex, 0); const end = Math.min(_actualIndex + virtualizerLength, numItems); - for (let i = _actualIndex; i < end; i++) { - childArray.current[i - _actualIndex] = renderVirtualizerChildPlaceholder(renderChild(i), i); + childArray.current[i - _actualIndex] = renderVirtualizerChildPlaceholder(renderChild(i, isScrolling), i); } }, - [numItems, renderChild, virtualizerLength], + [isScrolling, numItems, renderChild, virtualizerLength], ); const setBeforeRef = useCallback( @@ -372,6 +406,18 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta } }; + useImperativeHandle( + imperativeVirtualizerRef, + () => { + return { + progressiveSizes: childProgressiveSizes, + nodeSizes: childSizes, + setFlaggedIndex: (index: number | null) => (flaggedIndex.current = index), + }; + }, + [childProgressiveSizes, childSizes], + ); + // Initialization on mount - update array index to 0 (ready state). // Only fire on mount (no deps). useEffect(() => { @@ -398,6 +444,17 @@ export function useVirtualizer_unstable(props: VirtualizerProps): VirtualizerSta // eslint-disable-next-line react-hooks/exhaustive-deps }, [getItemSize]); + // Effect to check flag index on updates + useEffect(() => { + if (!onRenderedFlaggedIndex || flaggedIndex.current === null) { + return; + } + if (actualIndex <= flaggedIndex.current && actualIndex + virtualizerLength >= flaggedIndex.current) { + onRenderedFlaggedIndex(flaggedIndex.current); + flaggedIndex.current = null; + } + }, [actualIndex, onRenderedFlaggedIndex, virtualizerLength]); + // Ensure we have run through and updated the whole size list array at least once. initializeSizeArray(); 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 ef7e0c04517bb2..80adcbe1d0a004 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/VirtualizerScrollView.ts @@ -1,4 +1,4 @@ -import { VirtualizerScrollViewProps } from './VirtualizerScrollView.types'; +import type { VirtualizerScrollViewProps } from './VirtualizerScrollView.types'; import { useVirtualizerScrollView_unstable } from './useVirtualizerScrollView'; import { renderVirtualizerScrollView_unstable } from './renderVirtualizerScrollView'; import { useVirtualizerScrollViewStyles_unstable } from './useVirtualizerScrollViewStyles.styles'; 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 198789fef9c81d..0f2aeabe424727 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,10 +1,12 @@ -import { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; -import { +import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; +import type { VirtualizerSlots, VirtualizerConfigProps, VirtualizerConfigState, VirtualizerChildRenderFunction, } from '../Virtualizer/Virtualizer.types'; +import type { ScrollToInterface } from '../../Utilities'; +import type { RefObject } from 'react'; export type VirtualizerScrollViewSlots = VirtualizerSlots & { /** @@ -14,7 +16,12 @@ export type VirtualizerScrollViewSlots = VirtualizerSlots & { }; export type VirtualizerScrollViewProps = ComponentProps> & - Partial> & { + Partial< + Omit< + VirtualizerConfigProps, + 'itemSize' | 'numItems' | 'getItemSize' | 'children' | 'flagIndex' | 'imperativeVirtualizerRef' + > + > & { /** * Virtualizer item size in pixels - static. * Axis: 'vertical' = Height @@ -31,6 +38,10 @@ export type VirtualizerScrollViewProps = ComponentProps; }; export type VirtualizerScrollViewState = ComponentState & VirtualizerConfigState; 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 40e416c54ed5cc..834375891eacde 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/renderVirtualizerScrollView.tsx +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/renderVirtualizerScrollView.tsx @@ -3,8 +3,9 @@ import { createElement } from '@fluentui/react-jsx-runtime'; +import type { VirtualizerScrollViewSlots, VirtualizerScrollViewState } from './VirtualizerScrollView.types'; + import { getSlotsNext } from '@fluentui/react-utilities'; -import { VirtualizerScrollViewSlots, VirtualizerScrollViewState } from './VirtualizerScrollView.types'; import { renderVirtualizer_unstable } from '../Virtualizer/renderVirtualizer'; export const renderVirtualizerScrollView_unstable = (state: VirtualizerScrollViewState) => { 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 331752a1376f31..9cfdd9629da76f 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollView.ts @@ -1,23 +1,59 @@ import * as React from 'react'; import { resolveShorthand, useMergedRefs } from '@fluentui/react-utilities'; import { useVirtualizer_unstable } from '../Virtualizer/useVirtualizer'; -import { VirtualizerScrollViewProps, VirtualizerScrollViewState } from './VirtualizerScrollView.types'; +import type { VirtualizerScrollViewProps, VirtualizerScrollViewState } from './VirtualizerScrollView.types'; import { useStaticVirtualizerMeasure } from '../../Hooks'; +import { useImperativeHandle } from 'react'; +import { scrollToItemStatic } from '../../Utilities'; +import type { VirtualizerDataRef } from '../Virtualizer/Virtualizer.types'; export function useVirtualizerScrollView_unstable(props: VirtualizerScrollViewProps): VirtualizerScrollViewState { + const { imperativeRef, itemSize, numItems, axis = 'vertical', reversed } = props; const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useStaticVirtualizerMeasure({ defaultItemSize: props.itemSize, direction: props.axis ?? 'vertical', }); - const iScrollRef = useMergedRefs(React.useRef(null), scrollRef); + const scrollViewRef = useMergedRefs(React.useRef(null), scrollRef) as React.RefObject; + const imperativeVirtualizerRef = React.useRef(null); + const scrollCallbackRef = React.useRef void)>(null); + + useImperativeHandle( + imperativeRef, + () => { + return { + scrollTo(index: number, behavior = 'auto', callback: ((index: number) => void) | undefined) { + scrollCallbackRef.current = callback ?? null; + imperativeVirtualizerRef.current?.setFlaggedIndex(index); + scrollToItemStatic({ + index, + itemSize, + totalItems: numItems, + scrollViewRef, + axis, + reversed, + behavior, + }); + }, + }; + }, + [axis, scrollViewRef, itemSize, numItems, reversed], + ); + + const handleRenderedIndex = (index: number) => { + if (scrollCallbackRef.current) { + scrollCallbackRef.current(index); + } + }; const virtualizerState = useVirtualizer_unstable({ ...props, virtualizerLength, bufferItems, bufferSize, - scrollViewRef: iScrollRef, + scrollViewRef, + onRenderedFlaggedIndex: handleRenderedIndex, + imperativeVirtualizerRef, }); return { @@ -29,7 +65,7 @@ export function useVirtualizerScrollView_unstable(props: VirtualizerScrollViewPr container: resolveShorthand(props.container, { required: true, defaultProps: { - ref: iScrollRef as React.RefObject, + ref: scrollViewRef as React.RefObject, }, }), }; diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollViewStyles.styles.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollViewStyles.styles.ts index 4e31f6705c4baa..cd0221a8cff35a 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollViewStyles.styles.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollView/useVirtualizerScrollViewStyles.styles.ts @@ -1,6 +1,6 @@ -import { VirtualizerScrollViewState } from './VirtualizerScrollView.types'; +import type { VirtualizerScrollViewState } from './VirtualizerScrollView.types'; import type { SlotClassNames } from '@fluentui/react-utilities'; -import { VirtualizerScrollViewSlots } from './VirtualizerScrollView.types'; +import type { VirtualizerScrollViewSlots } from './VirtualizerScrollView.types'; import { useVirtualizerStyles_unstable, virtualizerClassNames } from '../Virtualizer/useVirtualizerStyles.styles'; import { makeStyles, mergeClasses } from '@griffel/react'; diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.ts index 97762bb5648aad..5a24f23dda002b 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.ts @@ -1,9 +1,9 @@ -import { VirtualizerScrollViewDynamicProps } from './VirtualizerScrollViewDynamic.types'; +import type { VirtualizerScrollViewDynamicProps } from './VirtualizerScrollViewDynamic.types'; import { useVirtualizerScrollViewDynamic_unstable } from './useVirtualizerScrollViewDynamic'; import { renderVirtualizerScrollViewDynamic_unstable } from './renderVirtualizerScrollViewDynamic'; import { useVirtualizerScrollViewDynamicStyles_unstable } from './useVirtualizerScrollViewDynamicStyles.styles'; import * as React from 'react'; -import { VirtualizerContextProps } from '../../Utilities'; +import type { VirtualizerContextProps } from '../../Utilities'; /** * Virtualizer ScrollView 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 index 0df4061f12ed7d..b0665ce1420f86 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollViewDynamic/VirtualizerScrollViewDynamic.types.ts @@ -1,16 +1,18 @@ -import { ComponentProps, ComponentState } from '@fluentui/react-utilities'; -import { +import type { ComponentProps, ComponentState } from '@fluentui/react-utilities'; +import type { VirtualizerConfigProps, VirtualizerConfigState, VirtualizerChildRenderFunction, } from '../Virtualizer/Virtualizer.types'; -import { VirtualizerScrollViewSlots } from '../VirtualizerScrollView/VirtualizerScrollView.types'; +import type { VirtualizerScrollViewSlots } from '../VirtualizerScrollView/VirtualizerScrollView.types'; +import type { RefObject } from 'react'; +import type { ScrollToInterface } from '../../Utilities'; export type VirtualizerScrollViewDynamicSlots = VirtualizerScrollViewSlots; export type VirtualizerScrollViewDynamicProps = ComponentProps> & - Partial> & { + Partial> & { /** * Set as the minimum item size. * Axis: 'vertical' = Height @@ -32,6 +34,10 @@ export type VirtualizerScrollViewDynamicProps = ComponentProps; }; export type VirtualizerScrollViewDynamicState = ComponentState & diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.ts index 2f2f9eb1aa6bbb..f4498755f4e5fc 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamic.ts @@ -1,17 +1,20 @@ import * as React from 'react'; import { resolveShorthand, useMergedRefs } from '@fluentui/react-utilities'; import { useVirtualizer_unstable } from '../Virtualizer/useVirtualizer'; -import { +import type { VirtualizerScrollViewDynamicProps, VirtualizerScrollViewDynamicState, } from './VirtualizerScrollViewDynamic.types'; import { useDynamicVirtualizerMeasure } from '../../Hooks'; -import { useVirtualizerContextState_unstable } from '../../Utilities'; +import { useVirtualizerContextState_unstable, scrollToItemDynamic } from '../../Utilities'; +import type { VirtualizerDataRef } from '../Virtualizer/Virtualizer.types'; +import { useImperativeHandle } from 'react'; export function useVirtualizerScrollViewDynamic_unstable( props: VirtualizerScrollViewDynamicProps, ): VirtualizerScrollViewDynamicState { const contextState = useVirtualizerContextState_unstable(props.virtualizerContext); + const { imperativeRef, axis = 'vertical', reversed, imperativeVirtualizerRef } = props; const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useDynamicVirtualizerMeasure({ defaultItemSize: props.itemSize, @@ -21,15 +24,56 @@ export function useVirtualizerScrollViewDynamic_unstable( numItems: props.numItems, }); - const iScrollRef = useMergedRefs(React.useRef(null), scrollRef) as React.RefObject; + const scrollViewRef = useMergedRefs(React.useRef(null), scrollRef) as React.RefObject; + const scrollCallbackRef = React.useRef void)>(null); + + const _imperativeVirtualizerRef = useMergedRefs(React.useRef(null), imperativeVirtualizerRef); + + useImperativeHandle( + imperativeRef, + () => { + return { + scrollTo(index: number, behavior = 'auto', callback: undefined | ((index: number) => void)) { + scrollCallbackRef.current = callback ?? null; + if (_imperativeVirtualizerRef.current) { + const progressiveSizes = _imperativeVirtualizerRef.current.progressiveSizes.current; + const totalSize = + progressiveSizes && progressiveSizes?.length > 0 + ? progressiveSizes[Math.max(progressiveSizes.length - 1, 0)] + : 0; + + _imperativeVirtualizerRef.current.setFlaggedIndex(index); + scrollToItemDynamic({ + index, + itemSizes: _imperativeVirtualizerRef.current?.nodeSizes, + totalSize, + scrollViewRef, + axis, + reversed, + behavior, + }); + } + }, + }; + }, + [axis, scrollViewRef, reversed, _imperativeVirtualizerRef], + ); + + const handleRenderedIndex = (index: number) => { + if (scrollCallbackRef.current) { + scrollCallbackRef.current(index); + } + }; const virtualizerState = useVirtualizer_unstable({ ...props, virtualizerLength, bufferItems, bufferSize, - scrollViewRef: iScrollRef, + scrollViewRef, virtualizerContext: contextState, + imperativeVirtualizerRef: _imperativeVirtualizerRef, + onRenderedFlaggedIndex: handleRenderedIndex, }); return { @@ -41,7 +85,7 @@ export function useVirtualizerScrollViewDynamic_unstable( container: resolveShorthand(props.container, { required: true, defaultProps: { - ref: iScrollRef, + ref: scrollViewRef, }, }), }; diff --git a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamicStyles.styles.ts b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamicStyles.styles.ts index a2dda65da6367e..40c3c2a9b18ded 100644 --- a/packages/react-components/react-virtualizer/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamicStyles.styles.ts +++ b/packages/react-components/react-virtualizer/src/components/VirtualizerScrollViewDynamic/useVirtualizerScrollViewDynamicStyles.styles.ts @@ -1,5 +1,5 @@ import type { SlotClassNames } from '@fluentui/react-utilities'; -import { +import type { VirtualizerScrollViewDynamicSlots, VirtualizerScrollViewDynamicState, } from './VirtualizerScrollViewDynamic.types'; diff --git a/packages/react-components/react-virtualizer/src/hooks/useResizeObserverRef.ts b/packages/react-components/react-virtualizer/src/hooks/useResizeObserverRef.ts index 00ae0e555ff881..6734eb52d1f4f4 100644 --- a/packages/react-components/react-virtualizer/src/hooks/useResizeObserverRef.ts +++ b/packages/react-components/react-virtualizer/src/hooks/useResizeObserverRef.ts @@ -20,6 +20,7 @@ export const useResizeObserverRef_unstable = (resizeCallback: ResizeCallbackWith React.useEffect(() => { // Update our state when resizeCallback changes + container.current = null; resizeObserver?.disconnect(); setResizeObserver(canUseDOM() ? new ResizeObserver(handleResize) : undefined); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -27,6 +28,7 @@ export const useResizeObserverRef_unstable = (resizeCallback: ResizeCallbackWith React.useEffect(() => { return () => { + container.current = null; resizeObserver?.disconnect(); }; // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/packages/react-components/react-virtualizer/src/index.ts b/packages/react-components/react-virtualizer/src/index.ts index 2a739c6eb1ff7e..bad0518470b8fe 100644 --- a/packages/react-components/react-virtualizer/src/index.ts +++ b/packages/react-components/react-virtualizer/src/index.ts @@ -5,11 +5,13 @@ export { renderVirtualizer_unstable, useVirtualizerStyles_unstable, } from './Virtualizer'; + export type { VirtualizerProps, VirtualizerState, VirtualizerSlots, VirtualizerChildRenderFunction, + VirtualizerDataRef, } from './Virtualizer'; export { @@ -21,7 +23,14 @@ export { export type { ResizeCallbackWithRef, VirtualizerMeasureDynamicProps, VirtualizerMeasureProps } from './Hooks'; -export { VirtualizerContextProvider, useVirtualizerContext_unstable } from './Utilities'; +export type { ScrollToItemDynamicParams, ScrollToItemStaticParams, ScrollToInterface } from './Utilities'; + +export { + VirtualizerContextProvider, + useVirtualizerContext_unstable, + scrollToItemStatic, + scrollToItemDynamic, +} from './Utilities'; export type { VirtualizerContextProps } from './Utilities'; diff --git a/packages/react-components/react-virtualizer/src/utilities/ImperativeScrolling/imperativeScrolling.ts b/packages/react-components/react-virtualizer/src/utilities/ImperativeScrolling/imperativeScrolling.ts new file mode 100644 index 00000000000000..befebe9ea3707c --- /dev/null +++ b/packages/react-components/react-virtualizer/src/utilities/ImperativeScrolling/imperativeScrolling.ts @@ -0,0 +1,31 @@ +import { ScrollToItemStaticParams } from './imperativeScrolling.types'; + +export const scrollToItemStatic = (params: ScrollToItemStaticParams) => { + const { index, itemSize, totalItems, scrollViewRef, axis = 'vertical', reversed = false, behavior = 'auto' } = params; + + if (axis === 'horizontal') { + if (reversed) { + scrollViewRef.current?.scrollTo({ + left: totalItems * itemSize - itemSize * index, + behavior, + }); + } else { + scrollViewRef.current?.scrollTo({ + left: itemSize * index, + behavior, + }); + } + } else { + if (reversed) { + scrollViewRef.current?.scrollTo({ + top: totalItems * itemSize - itemSize * index, + behavior, + }); + } else { + scrollViewRef.current?.scrollTo({ + top: itemSize * index, + behavior, + }); + } + } +}; diff --git a/packages/react-components/react-virtualizer/src/utilities/ImperativeScrolling/imperativeScrolling.types.ts b/packages/react-components/react-virtualizer/src/utilities/ImperativeScrolling/imperativeScrolling.types.ts new file mode 100644 index 00000000000000..cfb151d0201314 --- /dev/null +++ b/packages/react-components/react-virtualizer/src/utilities/ImperativeScrolling/imperativeScrolling.types.ts @@ -0,0 +1,25 @@ +import type { RefObject } from 'react'; + +export type ScrollToItemStaticParams = { + index: number; + itemSize: number; + totalItems: number; + scrollViewRef: RefObject; + axis?: 'horizontal' | 'vertical'; + reversed?: boolean; + behavior?: ScrollBehavior; +}; + +export type ScrollToItemDynamicParams = { + index: number; + itemSizes: RefObject; + totalSize: number; + scrollViewRef: RefObject; + axis?: 'horizontal' | 'vertical'; + reversed?: boolean; + behavior?: ScrollBehavior; +}; + +export type ScrollToInterface = { + scrollTo: (index: number, behavior?: ScrollBehavior, callback?: (index: number) => void) => void; +}; diff --git a/packages/react-components/react-virtualizer/src/utilities/ImperativeScrolling/imperativeScrollingDynamic.ts b/packages/react-components/react-virtualizer/src/utilities/ImperativeScrolling/imperativeScrollingDynamic.ts new file mode 100644 index 00000000000000..7d506e00e9e041 --- /dev/null +++ b/packages/react-components/react-virtualizer/src/utilities/ImperativeScrolling/imperativeScrollingDynamic.ts @@ -0,0 +1,46 @@ +import { ScrollToItemDynamicParams } from './imperativeScrolling.types'; + +export const scrollToItemDynamic = (params: ScrollToItemDynamicParams) => { + const { index, itemSizes, totalSize, scrollViewRef, axis = 'vertical', reversed = false, behavior = 'auto' } = params; + if (!itemSizes.current) { + return; + } + + if (itemSizes.current === null || itemSizes.current.length < index) { + // null check - abort + return; + } + + let itemDepth = 0; + for (let i = 0; i < index; i++) { + if (i < index) { + itemDepth += itemSizes.current[i]; + } + } + + if (axis === 'horizontal') { + if (reversed) { + scrollViewRef.current?.scrollTo({ + left: totalSize - itemDepth, + behavior, + }); + } else { + scrollViewRef.current?.scrollTo({ + left: itemDepth, + behavior, + }); + } + } else { + if (reversed) { + scrollViewRef.current?.scrollTo({ + top: totalSize - itemDepth, + behavior, + }); + } else { + scrollViewRef.current?.scrollTo({ + top: itemDepth, + behavior, + }); + } + } +}; diff --git a/packages/react-components/react-virtualizer/src/utilities/ImperativeScrolling/index.ts b/packages/react-components/react-virtualizer/src/utilities/ImperativeScrolling/index.ts new file mode 100644 index 00000000000000..a9c61a1ea2e47b --- /dev/null +++ b/packages/react-components/react-virtualizer/src/utilities/ImperativeScrolling/index.ts @@ -0,0 +1,3 @@ +export * from './imperativeScrolling'; +export * from './imperativeScrolling.types'; +export * from './imperativeScrollingDynamic'; diff --git a/packages/react-components/react-virtualizer/src/utilities/index.ts b/packages/react-components/react-virtualizer/src/utilities/index.ts index a4f06905012b63..7d8ab179c12bcf 100644 --- a/packages/react-components/react-virtualizer/src/utilities/index.ts +++ b/packages/react-components/react-virtualizer/src/utilities/index.ts @@ -1 +1,2 @@ export * from './VirtualizerContext'; +export * from './ImperativeScrolling'; 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 ded4e0d6c1f553..799190d010d26b 100644 --- a/packages/react-components/react-virtualizer/stories/Virtualizer/DefaultUnbounded.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/Virtualizer/DefaultUnbounded.stories.tsx @@ -6,9 +6,16 @@ import { useFluent } from '@fluentui/react-components'; const useStyles = makeStyles({ container: { + /* + * This is an 'unbounded' example intended to show virtualization in Body doc scroll + * However, we need a sensible height limit + * this would be enforced by a browser window, + * but iFrames can break this if not capped. + */ display: 'flex', flexDirection: 'column', overflowAnchor: 'none', + maxHeight: '300VH', width: '100%', height: '100%', }, @@ -30,7 +37,6 @@ const useStyles = makeStyles({ export const DefaultUnbounded = () => { const styles = useStyles(); const childLength = 1000; - const { virtualizerLength, bufferItems, bufferSize, scrollRef } = useStaticVirtualizerMeasure({ defaultItemSize: 100, }); @@ -50,7 +56,7 @@ export const DefaultUnbounded = () => { bufferSize={bufferSize} itemSize={100} > - {index => { + {(index, isScrolling) => { return ( { + const styles = useStyles(); + const childLength = 1000; + const scrollRef = React.useRef(null); + const [goToIndex, setGoToIndex] = React.useState(0); + const [message, setMessage] = React.useState(''); + + const scrollToIndex = () => { + if (scrollRef?.current?.scrollTo) { + setMessage(`Going to index: ${goToIndex}`); + scrollRef.current.scrollTo(goToIndex, 'smooth', (index: number) => { + setMessage(`Reached index: ${index}`); + }); + } + }; + + const onChangeGoToIndex = (ev?: React.FormEvent) => { + const indexValue = ev ? (ev.currentTarget as HTMLInputElement).value : ''; + const newIndex = Math.min(Math.max(parseInt(indexValue, 10), 0), childLength - 1); + setGoToIndex(newIndex); + }; + + return ( +
+ + + {message} + + {(index: number) => { + return ( +
{`Node-${index}`}
+ ); + }} +
+
+ ); +}; diff --git a/packages/react-components/react-virtualizer/stories/VirtualizerScrollView/index.stories.ts b/packages/react-components/react-virtualizer/stories/VirtualizerScrollView/index.stories.ts index db5040009bd414..456f3e7ae733f8 100644 --- a/packages/react-components/react-virtualizer/stories/VirtualizerScrollView/index.stories.ts +++ b/packages/react-components/react-virtualizer/stories/VirtualizerScrollView/index.stories.ts @@ -2,6 +2,7 @@ import { VirtualizerScrollView } from '../../src/VirtualizerScrollView'; import descriptionMd from './VirtualizerScrollViewDescription.md'; export { Default } from './Default.stories'; +export { ScrollTo } from './ScrollTo.stories'; export default { title: 'Preview Components/VirtualizerScrollView', diff --git a/packages/react-components/react-virtualizer/stories/VirtualizerScrollViewDynamic/Default.stories.tsx b/packages/react-components/react-virtualizer/stories/VirtualizerScrollViewDynamic/Default.stories.tsx index 0b166fd70ca617..6c804c6c2cef65 100644 --- a/packages/react-components/react-virtualizer/stories/VirtualizerScrollViewDynamic/Default.stories.tsx +++ b/packages/react-components/react-virtualizer/stories/VirtualizerScrollViewDynamic/Default.stories.tsx @@ -16,9 +16,9 @@ export const Default = () => { const childLength = 1000; const minHeight = 42; // Array size ref stores a list of random num for div sizing and callbacks - const arraySize = React.useRef(new Array(childLength).fill(minHeight)); + const arraySize = React.useRef(new Array(childLength).fill(minHeight)); // totalSize flag drives our callback update - const [totalSize, setTotalSize] = React.useState(minHeight * childLength); + const [totalSize, setTotalSize] = React.useState(minHeight * childLength); useEffect(() => { let _totalSize = 0; diff --git a/packages/react-components/react-virtualizer/stories/VirtualizerScrollViewDynamic/ScrollLoading.stories.tsx b/packages/react-components/react-virtualizer/stories/VirtualizerScrollViewDynamic/ScrollLoading.stories.tsx new file mode 100644 index 00000000000000..c3e30de72025bb --- /dev/null +++ b/packages/react-components/react-virtualizer/stories/VirtualizerScrollViewDynamic/ScrollLoading.stories.tsx @@ -0,0 +1,64 @@ +import * as React from 'react'; +import { VirtualizerScrollViewDynamic } from '@fluentui/react-components/unstable'; +import { makeStyles } from '@fluentui/react-components'; +import { useEffect } from 'react'; + +const useStyles = makeStyles({ + child: { + lineHeight: '42px', + width: '100%', + minHeight: '42px', + }, +}); + +export const ScrollLoading = () => { + const styles = useStyles(); + const childLength = 1000; + const minHeight = 42; + // Array size ref stores a list of random num for div sizing and callbacks + const arraySize = React.useRef(new Array(childLength).fill(minHeight)); + // totalSize flag drives our callback update + const [totalSize, setTotalSize] = React.useState(minHeight * childLength); + + useEffect(() => { + let _totalSize = 0; + for (let i = 0; i < childLength; i++) { + arraySize.current[i] = Math.random() * 150 + minHeight; + _totalSize += arraySize.current[i]; + } + setTotalSize(_totalSize); + }, []); + + const getItemSizeCallback = React.useCallback( + (index: number) => { + return arraySize.current[index]; + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [arraySize, totalSize], + ); + + return ( + + {(index: number, isScrolling = false) => { + const backgroundColor = index % 2 ? '#FFFFFF' : '#ABABAB'; + return isScrolling ? ( +
LOADING
+ ) : ( +
{`Node-${index} - size: ${arraySize.current[index]}`}
+ ); + }} +
+ ); +}; diff --git a/packages/react-components/react-virtualizer/stories/VirtualizerScrollViewDynamic/ScrollTo.stories.tsx b/packages/react-components/react-virtualizer/stories/VirtualizerScrollViewDynamic/ScrollTo.stories.tsx new file mode 100644 index 00000000000000..8b67e09a8fa637 --- /dev/null +++ b/packages/react-components/react-virtualizer/stories/VirtualizerScrollViewDynamic/ScrollTo.stories.tsx @@ -0,0 +1,91 @@ +import * as React from 'react'; +import { VirtualizerScrollViewDynamic } from '@fluentui/react-components/unstable'; +import { Button, Input, makeStyles, Text } from '@fluentui/react-components'; +import { useEffect } from 'react'; +import { ScrollToInterface } from '../../src/Utilities'; +import { VirtualizerDataRef } from '../../src/Virtualizer'; + +const useStyles = makeStyles({ + child: { + lineHeight: '42px', + width: '100%', + minHeight: '42px', + }, +}); + +export const ScrollTo = () => { + const styles = useStyles(); + const childLength = 1000; + const minHeight = 42; + // Array size ref stores a list of random num for div sizing and callbacks + const arraySize = React.useRef(new Array(childLength).fill(minHeight)); + // totalSize flag drives our callback update + const [totalSize, setTotalSize] = React.useState(minHeight * childLength); + + const scrollRef = React.useRef(null); + const sizeRef = React.useRef(null); + const [goToIndex, setGoToIndex] = React.useState(0); + const [message, setMessage] = React.useState(''); + + const scrollToIndex = () => { + if (scrollRef?.current?.scrollTo) { + setMessage(`Going to index: ${goToIndex}`); + scrollRef.current.scrollTo(goToIndex, 'smooth', (index: number) => { + setMessage(`Reached index: ${index}`); + }); + } + }; + + const onChangeGoToIndex = (ev?: React.FormEvent) => { + const indexValue = ev ? (ev.currentTarget as HTMLInputElement).value : ''; + const newIndex = Math.min(Math.max(parseInt(indexValue, 10), 0), childLength - 1); + setGoToIndex(newIndex); + }; + + useEffect(() => { + let _totalSize = 0; + for (let i = 0; i < childLength; i++) { + arraySize.current[i] = Math.random() * 250 + minHeight; + _totalSize += arraySize.current[i]; + } + setTotalSize(_totalSize); + }, []); + + const getItemSizeCallback = React.useCallback( + (index: number) => { + return arraySize.current[index]; + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [arraySize, totalSize], + ); + + return ( +
+ + + {message} + + {(index: number) => { + const backgroundColor = index % 2 ? '#FFFFFF' : '#ABABAB'; + return ( +
{`Node-${index} - size: ${arraySize.current[index]}`}
+ ); + }} +
+
+ ); +}; diff --git a/packages/react-components/react-virtualizer/stories/VirtualizerScrollViewDynamic/index.stories.ts b/packages/react-components/react-virtualizer/stories/VirtualizerScrollViewDynamic/index.stories.ts index ce2edca660abcd..cf5ac78e1cf052 100644 --- a/packages/react-components/react-virtualizer/stories/VirtualizerScrollViewDynamic/index.stories.ts +++ b/packages/react-components/react-virtualizer/stories/VirtualizerScrollViewDynamic/index.stories.ts @@ -2,6 +2,8 @@ import { VirtualizerScrollViewDynamic } from '../../src/VirtualizerScrollViewDyn import descriptionMd from './VirtualizerScrollViewDynamicDescription.md'; export { Default } from './Default.stories'; +export { ScrollTo } from './ScrollTo.stories'; +export { ScrollLoading } from './ScrollLoading.stories'; export default { title: 'Preview Components/VirtualizerScrollViewDynamic',