diff --git a/packages/react-aria-components/stories/GridList.stories.tsx b/packages/react-aria-components/stories/GridList.stories.tsx index 9710c97e31f..92d62ab384a 100644 --- a/packages/react-aria-components/stories/GridList.stories.tsx +++ b/packages/react-aria-components/stories/GridList.stories.tsx @@ -1141,3 +1141,27 @@ GridListWithTextfield.story = { } } }; + +function VirtualizedDisplayNoneToggleRender() { + let [visible, setVisible] = useState(true); + + return ( +
+ +
+ +
+
+ ); +} + +export const VirtualizedDisplayNoneToggle: StoryObj = { + render: VirtualizedDisplayNoneToggleRender, + parameters: { + description: { + data: 'toggling hide and show should not cause the items to disappear' + } + } +}; diff --git a/packages/react-aria-components/test/GridList.browser.test.tsx b/packages/react-aria-components/test/GridList.browser.test.tsx index d7b944e1f73..a8f13c11242 100644 --- a/packages/react-aria-components/test/GridList.browser.test.tsx +++ b/packages/react-aria-components/test/GridList.browser.test.tsx @@ -11,10 +11,13 @@ */ import {expect, it} from 'vitest'; +import {GridLayout} from '../src/GridLayout'; import {GridList, GridListItem} from '../src/GridList'; -import React from 'react'; +import React, {useState} from 'react'; import {render} from 'vitest-browser-react'; +import {Size} from 'react-stately/useVirtualizerState'; import {User} from '@react-aria/test-utils'; +import {Virtualizer} from '../src/Virtualizer'; function Grid() { return ( @@ -36,6 +39,37 @@ function Grid() { ); } +function VirtualizedDisplayNone() { + let [visible, setVisible] = useState(true); + let items = Array.from({length: 100}, (_, i) => ({id: i, name: `Item ${i}`})); + return ( +
+ +
+ + + {(item: {id: number; name: string}) => ( + {item.name} + )} + + +
+
+ ); +} + it.each` interactionType ${'mouse'} @@ -64,3 +98,25 @@ it.each` expect(rows[8].getAttribute('aria-selected')).toBe('true'); expect(document.activeElement).toBe(rows[8]); }); + +it('virtualizer renders items after toggling display:none', async () => { + let testUtilUser = new User(); + let {container} = await render(); + + let gridlist = container.querySelector('[role=grid]') as HTMLElement; + let tester = testUtilUser.createTester('GridList', { + root: gridlist, + layout: 'grid' + }); + + await expect(tester.getRows().length).toBeGreaterThan(0); + let button = container.querySelector('[data-testid=toggle]') as HTMLElement; + + await button.click(); + await button.click(); + await expect(tester.getRows().length).toBeGreaterThan(0); + + await button.click(); + await button.click(); + await expect(tester.getRows().length).toBeGreaterThan(0); +}); diff --git a/packages/react-aria/src/virtualizer/useVirtualizerItem.ts b/packages/react-aria/src/virtualizer/useVirtualizerItem.ts index a76a6e74431..ad973570b53 100644 --- a/packages/react-aria/src/virtualizer/useVirtualizerItem.ts +++ b/packages/react-aria/src/virtualizer/useVirtualizerItem.ts @@ -10,6 +10,7 @@ * governing permissions and limitations under the License. */ +import {isElementVisible} from '../utils/isElementVisible'; import {Key, RefObject} from '@react-types/shared'; import {LayoutInfo, Size} from 'react-stately/useVirtualizerState'; import {useCallback} from 'react'; @@ -31,6 +32,12 @@ export function useVirtualizerItem(options: VirtualizerItemOptions): {updateSize let updateSize = useCallback(() => { if (key != null && ref.current) { + // if the virtualized item is not visible (aka display none on virtualized collection), + // we want to avoid reporting size 0 otherwise we get into a state where the virtualizer renders 0 items + // when it is hidden and thus won't remeasure when it is is unhidden + if (!isElementVisible(ref.current)) { + return; + } let size = getSize(ref.current); virtualizer.updateItemSize(key, size); } diff --git a/vitest.browser.config.ts b/vitest.browser.config.ts index aeede49a7a6..3dfb572a81e 100644 --- a/vitest.browser.config.ts +++ b/vitest.browser.config.ts @@ -171,6 +171,10 @@ declare module 'vitest/browser' { } export default defineConfig({ + define: { + // run in dev mode so virtualizer and other test-env shortcuts are disabled + 'process.env.NODE_ENV': '"development"' + }, plugins: [ // @ts-expect-error macros.vite(), // Must be first!