diff --git a/packages/eui/.loki/reference/chrome_desktop_Editors_Syntax_EuiCodeBlock_Virtualized_Code_Block_Scrolling.png b/packages/eui/.loki/reference/chrome_desktop_Editors_Syntax_EuiCodeBlock_Virtualized_Code_Block_Scrolling.png new file mode 100644 index 00000000000..82bd8e647dc Binary files /dev/null and b/packages/eui/.loki/reference/chrome_desktop_Editors_Syntax_EuiCodeBlock_Virtualized_Code_Block_Scrolling.png differ diff --git a/packages/eui/.loki/reference/chrome_mobile_Editors_Syntax_EuiCodeBlock_Virtualized_Code_Block_Scrolling.png b/packages/eui/.loki/reference/chrome_mobile_Editors_Syntax_EuiCodeBlock_Virtualized_Code_Block_Scrolling.png new file mode 100644 index 00000000000..cde7dc97114 Binary files /dev/null and b/packages/eui/.loki/reference/chrome_mobile_Editors_Syntax_EuiCodeBlock_Virtualized_Code_Block_Scrolling.png differ diff --git a/packages/eui/changelogs/upcoming/9196.md b/packages/eui/changelogs/upcoming/9196.md new file mode 100644 index 00000000000..25e65cad883 --- /dev/null +++ b/packages/eui/changelogs/upcoming/9196.md @@ -0,0 +1,3 @@ +**Bug fixes** + +- Fixed virtualized `EuiCodeBlock` rendering blank lines when content updates if scrolled. diff --git a/packages/eui/src/components/code/code_block.stories.tsx b/packages/eui/src/components/code/code_block.stories.tsx index 8baced4b9d4..12fdb3ec6b3 100644 --- a/packages/eui/src/components/code/code_block.stories.tsx +++ b/packages/eui/src/components/code/code_block.stories.tsx @@ -6,15 +6,19 @@ * Side Public License, v 1. */ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import type { Meta, StoryObj, ReactRenderer } from '@storybook/react'; import type { PlayFunctionContext } from '@storybook/csf'; +import { css } from '@emotion/react'; import { within } from '../../../.storybook/test'; import { LOKI_SELECTORS } from '../../../.storybook/loki'; +import { mathWithUnits } from '../../global_styling'; import { EuiCodeBlock, EuiCodeBlockProps } from './code_block'; import { expect, userEvent } from '@storybook/test'; +import { EuiButton } from '../button'; +import { EuiFlexGroup, EuiFlexItem } from '../flex'; const meta: Meta = { title: 'Editors & Syntax/EuiCodeBlock', @@ -148,3 +152,97 @@ export const HighContrast: Story = { ],`, }, }; + +/* This Story verifies that updated data in a virtualized code block is +rendered correctly and not cut-off after scrolling */ +export const VirtualizedCodeBlockScrolling: Story = { + tags: ['vrt-only'], + parameters: { + loki: { + chromeSelector: LOKI_SELECTORS.portal, + }, + codeSnippet: { + skip: true, + }, + }, + // use dark mode to better visualize container boundaries + globals: { colorMode: 'dark' }, + render: function Render() { + const [response, setResponse] = useState('{}'); + const [isLoading, setIsLoading] = useState(false); + + const handleSubmit = async () => { + setIsLoading(true); + + try { + const res = await fetch('https://jsonplaceholder.typicode.com/posts'); + const data = await res.json(); + + setResponse(JSON.stringify(data, null, 2)); + } catch (error) { + console.error(error); + } finally { + // delay to keep VRT images more stable + setTimeout(() => { + setIsLoading(false); + }); + } + }; + + useEffect(() => { + handleSubmit(); + }, []); + + useEffect(() => { + // scroll the code block after response updates to test if virtualization + // calculates the scroll height and position correctly + const pre = document.querySelector('.euiCodeBlock__pre'); + if (pre) { + pre.scrollBy({ + top: 75, + }); + + // trigger a second load after scroll to simulate potential issues + setTimeout(() => { + handleSubmit(); + }, 10); + } + }, [response]); + + return ( + css` + block-size: calc( + 100vh - ${mathWithUnits(euiTheme.size.base, (x) => x * 2)} + ); + `} + > + + + Submit + + + + + {response} + + + + ); + }, +}; diff --git a/packages/eui/src/components/code/code_block_virtualized.tsx b/packages/eui/src/components/code/code_block_virtualized.tsx index bf81f69fe7d..1c2756c1261 100644 --- a/packages/eui/src/components/code/code_block_virtualized.tsx +++ b/packages/eui/src/components/code/code_block_virtualized.tsx @@ -32,28 +32,31 @@ export const EuiCodeBlockVirtualized = ({ preProps: HTMLAttributes; codeProps: HTMLAttributes; }) => { + // NOTE: Don't inject other content (e.g. a label) inside this outer virtualized + // container as react-window requires this to be stable for scroll calculations. + // Instead, inject it into the inner virtualized element. const VirtualizedOuterElement = useMemo( () => - forwardRef(({ style, children, ...props }, ref) => ( -
-          {label}
-          {children}
-        
+ forwardRef(({ style, ...props }, ref) => ( +
       )),
-    [preProps, label]
+    [preProps]
   );
 
   const VirtualizedInnerElement = useMemo(
     () =>
       forwardRef(({ style, ...props }, ref) => (
-        
+        <>
+          {label}
+          
+        
       )),
-    [codeProps]
+    [codeProps, label]
   );
 
   const virtualizationProps = {