From d488c926cc9b17b655377569d04bc0014a7c8db3 Mon Sep 17 00:00:00 2001 From: Brian Ingles Date: Tue, 9 Apr 2024 08:56:22 -0500 Subject: [PATCH] Only render ListView when non-zero height (#1909) --- package-lock.json | 2 + .../src/spectrum/listView/ListView.tsx | 49 ++++++++++++++----- packages/jsapi-components/package.json | 1 + packages/react-hooks/src/index.ts | 1 + packages/react-hooks/src/useContentRect.ts | 44 +++++++++++++++++ 5 files changed, 85 insertions(+), 12 deletions(-) create mode 100644 packages/react-hooks/src/useContentRect.ts diff --git a/package-lock.json b/package-lock.json index a9d06f5266..acfb66a308 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29360,6 +29360,7 @@ "version": "0.72.0", "license": "Apache-2.0", "dependencies": { + "@adobe/react-spectrum": "^3.34.1", "@deephaven/components": "file:../components", "@deephaven/jsapi-bootstrap": "file:../jsapi-bootstrap", "@deephaven/jsapi-types": "1.0.0-dev0.33.1", @@ -31564,6 +31565,7 @@ "@deephaven/jsapi-components": { "version": "file:packages/jsapi-components", "requires": { + "@adobe/react-spectrum": "^3.34.1", "@deephaven/components": "file:../components", "@deephaven/jsapi-bootstrap": "file:../jsapi-bootstrap", "@deephaven/jsapi-shim": "file:../jsapi-shim", diff --git a/packages/components/src/spectrum/listView/ListView.tsx b/packages/components/src/spectrum/listView/ListView.tsx index d867e2b6b9..723394f219 100644 --- a/packages/components/src/spectrum/listView/ListView.tsx +++ b/packages/components/src/spectrum/listView/ListView.tsx @@ -1,11 +1,13 @@ import { useMemo } from 'react'; import { + Flex, ListView as SpectrumListView, SpectrumListViewProps, } from '@adobe/react-spectrum'; import { EMPTY_FUNCTION } from '@deephaven/utils'; import { extractSpectrumHTMLElement, + useContentRect, useOnScrollRef, } from '@deephaven/react-hooks'; import cl from 'classnames'; @@ -68,7 +70,7 @@ export function ListView({ onScroll = EMPTY_FUNCTION, onSelectionChange, ...spectrumListViewProps -}: ListViewProps): JSX.Element { +}: ListViewProps): JSX.Element | null { const normalizedItems = useMemo( () => normalizeItemList(children), [children] @@ -96,20 +98,43 @@ export function ListView({ const scrollRef = useOnScrollRef(onScroll, extractSpectrumHTMLElement); + // Spectrum ListView crashes when it has zero height. Trac the contentRect + // of the parent container and only render the ListView when it has a height. + const { ref: contentRectRef, contentRect } = useContentRect( + extractSpectrumHTMLElement + ); + return ( - - {renderNormalizedItem} - + {contentRect.height === 0 ? ( + // Ensure content has a non-zero height so that the container has a height + // whenever it is visible. This helps differentiate when the container + // height has been set to zero (e.g. when a tab is not visible) vs when + // the container height has not been constrained but has not yet rendered + // the ListView. + <>  + ) : ( + + {renderNormalizedItem} + + )} + ); } diff --git a/packages/jsapi-components/package.json b/packages/jsapi-components/package.json index f849061bd6..ab301c3b9a 100644 --- a/packages/jsapi-components/package.json +++ b/packages/jsapi-components/package.json @@ -22,6 +22,7 @@ "build:sass": "sass --embed-sources --load-path=../../node_modules ./src:./dist" }, "dependencies": { + "@adobe/react-spectrum": "^3.34.1", "@deephaven/components": "file:../components", "@deephaven/jsapi-bootstrap": "file:../jsapi-bootstrap", "@deephaven/jsapi-types": "1.0.0-dev0.33.1", diff --git a/packages/react-hooks/src/index.ts b/packages/react-hooks/src/index.ts index bec624de9e..a3d98b2498 100644 --- a/packages/react-hooks/src/index.ts +++ b/packages/react-hooks/src/index.ts @@ -4,6 +4,7 @@ export * from './SpectrumUtils'; export * from './useAsyncInterval'; export * from './useCallbackWithAction'; export * from './useCheckOverflow'; +export * from './useContentRect'; export { default as useContextOrThrow } from './useContextOrThrow'; export * from './useDebouncedCallback'; export * from './useDelay'; diff --git a/packages/react-hooks/src/useContentRect.ts b/packages/react-hooks/src/useContentRect.ts new file mode 100644 index 0000000000..2018e2a3ab --- /dev/null +++ b/packages/react-hooks/src/useContentRect.ts @@ -0,0 +1,44 @@ +import { identityExtractHTMLElement } from '@deephaven/utils'; +import { useCallback, useRef, useState } from 'react'; +import useMappedRef from './useMappedRef'; +import useResizeObserver from './useResizeObserver'; + +export interface UseContentRectResult { + contentRect: DOMRectReadOnly; + ref: (refValue: T) => void; +} + +/** + * Returns a callback ref that will track the `contentRect` of a given refValue. + * If the `contentRect` is undefined, it will be set to a new `DOMRect` with + * zeros for all dimensions. + * @param map Optional mapping function to extract an HTMLElement from the given + * refValue + * @returns Content rect and a ref callback + */ +export function useContentRect( + map: (ref: T) => HTMLElement | null = identityExtractHTMLElement +): UseContentRectResult { + const [contentRect, setContentRect] = useState( + new DOMRect() + ); + + const handleResize = useCallback( + ([firstEntry]: ResizeObserverEntry[], _observer: ResizeObserver): void => { + setContentRect(firstEntry?.contentRect ?? new DOMRect()); + }, + [] + ); + + const observerRef = useRef(null); + useResizeObserver(observerRef.current, handleResize); + + const ref = useMappedRef(observerRef, map); + + return { + ref, + contentRect, + }; +} + +export default useContentRect;