Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions packages/eui/changelogs/upcoming/9196.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
**Bug fixes**

- Fixed virtualized `EuiCodeBlock` rendering blank lines when content updates if scrolled.
100 changes: 99 additions & 1 deletion packages/eui/src/components/code/code_block.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<EuiCodeBlockProps> = {
title: 'Editors & Syntax/EuiCodeBlock',
Expand Down Expand Up @@ -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 (
<EuiFlexGroup
direction="row"
gutterSize="m"
css={({ euiTheme }) => css`
block-size: calc(
100vh - ${mathWithUnits(euiTheme.size.base, (x) => x * 2)}
);
`}
>
<EuiFlexItem grow={false}>
<EuiButton
type="submit"
iconType="sortRight"
isLoading={isLoading}
onClick={handleSubmit}
>
Submit
</EuiButton>
</EuiFlexItem>
<EuiFlexItem grow={2}>
<EuiCodeBlock
language="json"
fontSize="s"
paddingSize="m"
isCopyable={true}
lineNumbers
isVirtualized
overflowHeight="100%"
>
{response}
</EuiCodeBlock>
</EuiFlexItem>
</EuiFlexGroup>
);
},
};
29 changes: 16 additions & 13 deletions packages/eui/src/components/code/code_block_virtualized.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,31 @@ export const EuiCodeBlockVirtualized = ({
preProps: HTMLAttributes<HTMLPreElement>;
codeProps: HTMLAttributes<HTMLElement>;
}) => {
// 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<any, any>(({ style, children, ...props }, ref) => (
<pre style={logicalStyles(style)} {...props} ref={ref} {...preProps}>
{label}
{children}
</pre>
forwardRef<any, any>(({ style, ...props }, ref) => (
<pre style={logicalStyles(style)} {...props} ref={ref} {...preProps} />
)),
[preProps, label]
[preProps]
);

const VirtualizedInnerElement = useMemo(
() =>
forwardRef<any, any>(({ style, ...props }, ref) => (
<code
style={logicalStyles(style)}
{...props}
ref={ref}
{...codeProps}
/>
<>
{label}
<code
style={logicalStyles(style)}
{...props}
ref={ref}
{...codeProps}
/>
</>
)),
[codeProps]
[codeProps, label]
);

const virtualizationProps = {
Expand Down