diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6446084df96..f33aba07049 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@
- Updated `EuiText`s `color` prop to accept `inherit` and custom colors. Updated the `size` prop to accept `relative` ([#4663](https://github.com/elastic/eui/pull/4663))
- Updated `EuiText`s `blockquote` font-size/line-height to match the base font-size/line-height which is the same as paragraphs ([#4663](https://github.com/elastic/eui/pull/4663))
- Added `markdownFormatProps` prop to `EuiMarkdownEditor` to extend the props passed to the rendered `EuiMarkdownFormat` ([#4663](https://github.com/elastic/eui/pull/4663))
+- Added optional virtualized line rendering to `EuiCodeBlock` ([#4952](https://github.com/elastic/eui/pull/4952))
## [`36.0.0`](https://github.com/elastic/eui/tree/v36.0.0)
diff --git a/src-docs/src/views/code/code_example.js b/src-docs/src/views/code/code_example.js
index a41b0e056ad..0e93e848dba 100644
--- a/src-docs/src/views/code/code_example.js
+++ b/src-docs/src/views/code/code_example.js
@@ -1,6 +1,5 @@
import React from 'react';
-
-import { renderToHtml } from '../../services';
+import { Link } from 'react-router-dom';
import { GuideSectionTypes } from '../../components';
@@ -14,20 +13,27 @@ import { codeBlockConfig, codeConfig } from './playground';
import Code from './code';
const codeSource = require('!!raw-loader!./code');
-const codeHtml = renderToHtml(Code);
const codeSnippet = 'Text to be formatted ';
import CodeBlock from './code_block';
const codeBlockSource = require('!!raw-loader!./code_block');
-const codeBlockHtml = renderToHtml(CodeBlock);
const codeBlockSnippet = `
{ \`Title \` }
`;
+import CodeBlockVirtualized from './virtualized';
+const codeBlockVirtualizedSource = require('!!raw-loader!./virtualized');
+const codeBlockVirtualizedSnippet = `
+{ \`{}\` }
+
+`;
+
+import CodeBlockVirtualizedFlyout from './virtualized_flyout';
+const codeBlockVirtualizedFlyoutSource = require('!!raw-loader!./virtualized_flyout');
+
import CodeBlockPre from './code_block_pre';
const codeBlockPreSource = require('!!raw-loader!./code_block_pre');
-const codeBlockPreHtml = renderToHtml(CodeBlockPre);
export const CodeExample = {
title: 'Code',
@@ -37,7 +43,7 @@ export const CodeExample = {
The EuiCode and EuiCodeBlock {' '}
components support{' '}
-
+
all language syntaxes
{' '}
supported by the
@@ -67,10 +73,6 @@ export const CodeExample = {
type: GuideSectionTypes.JS,
code: codeSource,
},
- {
- type: GuideSectionTypes.HTML,
- code: codeHtml,
- },
],
text: (
@@ -90,10 +92,6 @@ export const CodeExample = {
type: GuideSectionTypes.JS,
code: codeBlockSource,
},
- {
- type: GuideSectionTypes.HTML,
- code: codeBlockHtml,
- },
],
text: (
@@ -109,15 +107,48 @@ export const CodeExample = {
playground: codeBlockConfig,
},
{
- title: 'Code block and white-space',
+ title: 'Code block virtualization',
source: [
{
type: GuideSectionTypes.JS,
- code: codeBlockPreSource,
+ code: codeBlockVirtualizedSource,
+ },
+ ],
+ text: (
+
+ For large blocks of code, add isVirtualized to
+ reduce the number of rendered rows and improve load times. Note that{' '}
+ overflowHeight is required when using this
+ configuration.
+
+ ),
+ props: { EuiCodeBlock },
+ snippet: codeBlockVirtualizedSnippet,
+ demo: ,
+ },
+ {
+ source: [
+ {
+ type: GuideSectionTypes.JS,
+ code: codeBlockVirtualizedFlyoutSource,
},
+ ],
+ text: (
+
+ In places like flyouts, you can use{' '}
+ {'overflowHeight="100%"'} to stretch
+ the code block to fill the space. Just be sure that it's parent
+ container is also {'height: 100%'} .
+
+ ),
+ demo: ,
+ },
+ {
+ title: 'Code block and white-space',
+ source: [
{
- type: GuideSectionTypes.HTML,
- code: codeBlockPreHtml,
+ type: GuideSectionTypes.JS,
+ code: codeBlockPreSource,
},
],
text: (
diff --git a/src-docs/src/views/code/virtualized.js b/src-docs/src/views/code/virtualized.js
new file mode 100644
index 00000000000..f5a25a93c20
--- /dev/null
+++ b/src-docs/src/views/code/virtualized.js
@@ -0,0 +1,824 @@
+import React from 'react';
+
+import { EuiCodeBlock } from '../../../../src/components';
+
+export default () => (
+
+
+ {`{
+ "id": "1",
+ "rawResponse": {
+ "took": 19,
+ "timed_out": false,
+ "_shards": {
+ "total": 1,
+ "successful": 1,
+ "skipped": 0,
+ "failed": 0
+ },
+ "hits": {
+ "total": 7,
+ "max_score": null,
+ "hits": [
+ {
+ "_index": "kibana_sample_data_flights",
+ "_id": "i5-sr3oB9JvwH6mY-m2Q",
+ "_version": 1,
+ "_score": null,
+ "fields": {
+ "Origin": [
+ "Wichita Mid Continent Airport"
+ ],
+ "OriginLocation": [
+ {
+ "coordinates": [
+ -97.43309784,
+ 37.64989853
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightNum": [
+ "Q4UQIF3"
+ ],
+ "DestLocation": [
+ {
+ "coordinates": [
+ 8.54917,
+ 47.464699
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightDelay": [
+ true
+ ],
+ "DistanceMiles": [
+ 5013.5835
+ ],
+ "FlightTimeMin": [
+ 822.3817
+ ],
+ "OriginWeather": [
+ "Cloudy"
+ ],
+ "dayOfWeek": [
+ 4
+ ],
+ "AvgTicketPrice": [
+ 276.2003
+ ],
+ "Carrier": [
+ "JetBeats"
+ ],
+ "FlightDelayMin": [
+ 150
+ ],
+ "OriginRegion": [
+ "US-KS"
+ ],
+ "DestAirportID": [
+ "ZRH"
+ ],
+ "FlightDelayType": [
+ "Carrier Delay"
+ ],
+ "hour_of_day": [
+ 14
+ ],
+ "timestamp": [
+ "2021-07-16T14:15:29.000Z"
+ ],
+ "Dest": [
+ "Zurich Airport"
+ ],
+ "FlightTimeHour": [
+ "13.706362235285443"
+ ],
+ "Cancelled": [
+ false
+ ],
+ "DistanceKilometers": [
+ 8068.5806
+ ],
+ "OriginCityName": [
+ "Wichita"
+ ],
+ "DestWeather": [
+ "Rain"
+ ],
+ "OriginCountry": [
+ "US"
+ ],
+ "DestCountry": [
+ "CH"
+ ],
+ "DestRegion": [
+ "CH-ZH"
+ ],
+ "OriginAirportID": [
+ "ICT"
+ ],
+ "DestCityName": [
+ "Zurich"
+ ]
+ },
+ "sort": [
+ 1626444929000
+ ]
+ },
+ {
+ "_index": "kibana_sample_data_flights",
+ "_id": "AZ-sr3oB9JvwH6mY-m2P",
+ "_version": 1,
+ "_score": null,
+ "fields": {
+ "Origin": [
+ "Turin Airport"
+ ],
+ "OriginLocation": [
+ {
+ "coordinates": [
+ 7.64963,
+ 45.200802
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightNum": [
+ "WR15PZZ"
+ ],
+ "DestLocation": [
+ {
+ "coordinates": [
+ 20.96710014,
+ 52.16569901
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightDelay": [
+ false
+ ],
+ "DistanceMiles": [
+ 774.4176
+ ],
+ "FlightTimeMin": [
+ 95.86956
+ ],
+ "OriginWeather": [
+ "Clear"
+ ],
+ "dayOfWeek": [
+ 4
+ ],
+ "AvgTicketPrice": [
+ 360.27106
+ ],
+ "Carrier": [
+ "Kibana Airlines"
+ ],
+ "FlightDelayMin": [
+ 0
+ ],
+ "OriginRegion": [
+ "IT-21"
+ ],
+ "DestAirportID": [
+ "WAW"
+ ],
+ "FlightDelayType": [
+ "No Delay"
+ ],
+ "hour_of_day": [
+ 14
+ ],
+ "timestamp": [
+ "2021-07-16T14:14:06.000Z"
+ ],
+ "Dest": [
+ "Warsaw Chopin Airport"
+ ],
+ "FlightTimeHour": [
+ "1.597826006729792"
+ ],
+ "Cancelled": [
+ false
+ ],
+ "DistanceKilometers": [
+ 1246.3043
+ ],
+ "OriginCityName": [
+ "Torino"
+ ],
+ "DestWeather": [
+ "Thunder & Lightning"
+ ],
+ "OriginCountry": [
+ "IT"
+ ],
+ "DestCountry": [
+ "PL"
+ ],
+ "DestRegion": [
+ "PL-MZ"
+ ],
+ "OriginAirportID": [
+ "TO11"
+ ],
+ "DestCityName": [
+ "Warsaw"
+ ]
+ },
+ "sort": [
+ 1626444846000
+ ]
+ },
+ {
+ "_index": "kibana_sample_data_flights",
+ "_id": "LJ-sr3oB9JvwH6mY-m6Q",
+ "_version": 1,
+ "_score": null,
+ "fields": {
+ "Origin": [
+ "Chhatrapati Shivaji International Airport"
+ ],
+ "OriginLocation": [
+ {
+ "coordinates": [
+ 72.86789703,
+ 19.08869934
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightNum": [
+ "VZNTLIZ"
+ ],
+ "DestLocation": [
+ {
+ "coordinates": [
+ 33.46390152,
+ 68.15180206
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightDelay": [
+ false
+ ],
+ "DistanceMiles": [
+ 3791.431
+ ],
+ "FlightTimeMin": [
+ 305.08582
+ ],
+ "OriginWeather": [
+ "Cloudy"
+ ],
+ "dayOfWeek": [
+ 4
+ ],
+ "AvgTicketPrice": [
+ 845.4707
+ ],
+ "Carrier": [
+ "JetBeats"
+ ],
+ "FlightDelayMin": [
+ 0
+ ],
+ "OriginRegion": [
+ "SE-BD"
+ ],
+ "DestAirportID": [
+ "XLMO"
+ ],
+ "FlightDelayType": [
+ "No Delay"
+ ],
+ "hour_of_day": [
+ 14
+ ],
+ "timestamp": [
+ "2021-07-16T14:05:44.000Z"
+ ],
+ "Dest": [
+ "Olenya Air Base"
+ ],
+ "FlightTimeHour": [
+ "5.084763808440013"
+ ],
+ "Cancelled": [
+ false
+ ],
+ "DistanceKilometers": [
+ 6101.717
+ ],
+ "OriginCityName": [
+ "Mumbai"
+ ],
+ "DestWeather": [
+ "Heavy Fog"
+ ],
+ "OriginCountry": [
+ "IN"
+ ],
+ "DestCountry": [
+ "RU"
+ ],
+ "DestRegion": [
+ "RU-MUR"
+ ],
+ "OriginAirportID": [
+ "BOM"
+ ],
+ "DestCityName": [
+ "Olenegorsk"
+ ]
+ },
+ "sort": [
+ 1626444344000
+ ]
+ },
+ {
+ "_index": "kibana_sample_data_flights",
+ "_id": "Hp-sr3oB9JvwH6mY-m2P",
+ "_version": 1,
+ "_score": null,
+ "fields": {
+ "Origin": [
+ "Buffalo Niagara International Airport"
+ ],
+ "OriginLocation": [
+ {
+ "coordinates": [
+ -78.73220062,
+ 42.94049835
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightNum": [
+ "QAXVRPQ"
+ ],
+ "DestLocation": [
+ {
+ "coordinates": [
+ -78.3575,
+ -0.129166667
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightDelay": [
+ false
+ ],
+ "DistanceMiles": [
+ 2964.2756
+ ],
+ "FlightTimeMin": [
+ 227.16853
+ ],
+ "OriginWeather": [
+ "Sunny"
+ ],
+ "dayOfWeek": [
+ 4
+ ],
+ "AvgTicketPrice": [
+ 719.76935
+ ],
+ "Carrier": [
+ "JetBeats"
+ ],
+ "FlightDelayMin": [
+ 0
+ ],
+ "OriginRegion": [
+ "US-NY"
+ ],
+ "DestAirportID": [
+ "UIO"
+ ],
+ "FlightDelayType": [
+ "No Delay"
+ ],
+ "hour_of_day": [
+ 13
+ ],
+ "timestamp": [
+ "2021-07-16T13:57:30.000Z"
+ ],
+ "Dest": [
+ "Mariscal Sucre International Airport"
+ ],
+ "FlightTimeHour": [
+ "3.7861423240197563"
+ ],
+ "Cancelled": [
+ false
+ ],
+ "DistanceKilometers": [
+ 4770.5396
+ ],
+ "OriginCityName": [
+ "Buffalo"
+ ],
+ "DestWeather": [
+ "Clear"
+ ],
+ "OriginCountry": [
+ "US"
+ ],
+ "DestCountry": [
+ "EC"
+ ],
+ "DestRegion": [
+ "EC-P"
+ ],
+ "OriginAirportID": [
+ "BUF"
+ ],
+ "DestCityName": [
+ "Quito"
+ ]
+ },
+ "sort": [
+ 1626443850000
+ ]
+ },
+ {
+ "_index": "kibana_sample_data_flights",
+ "_id": "U5-sr3oB9JvwH6mY-m2Q",
+ "_version": 1,
+ "_score": null,
+ "fields": {
+ "Origin": [
+ "Dubai International Airport"
+ ],
+ "OriginLocation": [
+ {
+ "coordinates": [
+ 55.36439896,
+ 25.25279999
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightNum": [
+ "TJQKCKN"
+ ],
+ "DestLocation": [
+ {
+ "coordinates": [
+ -73.74079895,
+ 45.47060013
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightDelay": [
+ false
+ ],
+ "DistanceMiles": [
+ 6611.2646
+ ],
+ "FlightTimeMin": [
+ 709.31995
+ ],
+ "OriginWeather": [
+ "Rain"
+ ],
+ "dayOfWeek": [
+ 4
+ ],
+ "AvgTicketPrice": [
+ 756.61444
+ ],
+ "Carrier": [
+ "ES-Air"
+ ],
+ "FlightDelayMin": [
+ 0
+ ],
+ "OriginRegion": [
+ "SE-BD"
+ ],
+ "DestAirportID": [
+ "YUL"
+ ],
+ "FlightDelayType": [
+ "No Delay"
+ ],
+ "hour_of_day": [
+ 13
+ ],
+ "timestamp": [
+ "2021-07-16T13:51:52.000Z"
+ ],
+ "Dest": [
+ "Montreal / Pierre Elliott Trudeau International Airport"
+ ],
+ "FlightTimeHour": [
+ "11.821998598483413"
+ ],
+ "Cancelled": [
+ false
+ ],
+ "DistanceKilometers": [
+ 10639.799
+ ],
+ "OriginCityName": [
+ "Dubai"
+ ],
+ "DestWeather": [
+ "Clear"
+ ],
+ "OriginCountry": [
+ "AE"
+ ],
+ "DestCountry": [
+ "CA"
+ ],
+ "DestRegion": [
+ "CA-QC"
+ ],
+ "OriginAirportID": [
+ "DXB"
+ ],
+ "DestCityName": [
+ "Montreal"
+ ]
+ },
+ "sort": [
+ 1626443512000
+ ]
+ },
+ {
+ "_index": "kibana_sample_data_flights",
+ "_id": "TJ-sr3oB9JvwH6mY-m2Q",
+ "_version": 1,
+ "_score": null,
+ "fields": {
+ "Origin": [
+ "Jorge Chavez International Airport"
+ ],
+ "OriginLocation": [
+ {
+ "coordinates": [
+ -77.114304,
+ -12.0219
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightNum": [
+ "8B6BGMO"
+ ],
+ "DestLocation": [
+ {
+ "coordinates": [
+ 128.445007,
+ 51.169997
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightDelay": [
+ true
+ ],
+ "DistanceMiles": [
+ 9375.942
+ ],
+ "FlightTimeMin": [
+ 824.16406
+ ],
+ "OriginWeather": [
+ "Cloudy"
+ ],
+ "dayOfWeek": [
+ 4
+ ],
+ "AvgTicketPrice": [
+ 463.068
+ ],
+ "Carrier": [
+ "Logstash Airways"
+ ],
+ "FlightDelayMin": [
+ 30
+ ],
+ "OriginRegion": [
+ "SE-BD"
+ ],
+ "DestAirportID": [
+ "XHBU"
+ ],
+ "FlightDelayType": [
+ "Late Aircraft Delay"
+ ],
+ "hour_of_day": [
+ 13
+ ],
+ "timestamp": [
+ "2021-07-16T13:50:55.000Z"
+ ],
+ "Dest": [
+ "Ukrainka Air Base"
+ ],
+ "FlightTimeHour": [
+ "13.736067768266615"
+ ],
+ "Cancelled": [
+ false
+ ],
+ "DistanceKilometers": [
+ 15089.117
+ ],
+ "OriginCityName": [
+ "Lima"
+ ],
+ "DestWeather": [
+ "Clear"
+ ],
+ "OriginCountry": [
+ "PE"
+ ],
+ "DestCountry": [
+ "RU"
+ ],
+ "DestRegion": [
+ "RU-AMU"
+ ],
+ "OriginAirportID": [
+ "LIM"
+ ],
+ "DestCityName": [
+ "Belogorsk"
+ ]
+ },
+ "sort": [
+ 1626443455000
+ ]
+ },
+ {
+ "_index": "kibana_sample_data_flights",
+ "_id": "3J-sr3oB9JvwH6mY-m2Q",
+ "_version": 1,
+ "_score": null,
+ "fields": {
+ "Origin": [
+ "Sydney Kingsford Smith International Airport"
+ ],
+ "OriginLocation": [
+ {
+ "coordinates": [
+ 151.177002,
+ -33.94609833
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightNum": [
+ "PASAN8N"
+ ],
+ "DestLocation": [
+ {
+ "coordinates": [
+ 8.54917,
+ 47.464699
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightDelay": [
+ false
+ ],
+ "DistanceMiles": [
+ 10293.209
+ ],
+ "FlightTimeMin": [
+ 1380.4429
+ ],
+ "OriginWeather": [
+ "Sunny"
+ ],
+ "dayOfWeek": [
+ 4
+ ],
+ "AvgTicketPrice": [
+ 380.29593
+ ],
+ "Carrier": [
+ "Logstash Airways"
+ ],
+ "FlightDelayMin": [
+ 0
+ ],
+ "OriginRegion": [
+ "SE-BD"
+ ],
+ "DestAirportID": [
+ "ZRH"
+ ],
+ "FlightDelayType": [
+ "No Delay"
+ ],
+ "hour_of_day": [
+ 13
+ ],
+ "timestamp": [
+ "2021-07-16T13:49:20.000Z"
+ ],
+ "Dest": [
+ "Zurich Airport"
+ ],
+ "FlightTimeHour": [
+ "23.007380215402044"
+ ],
+ "Cancelled": [
+ false
+ ],
+ "DistanceKilometers": [
+ 16565.314
+ ],
+ "OriginCityName": [
+ "Sydney"
+ ],
+ "DestWeather": [
+ "Rain"
+ ],
+ "OriginCountry": [
+ "AU"
+ ],
+ "DestCountry": [
+ "CH"
+ ],
+ "DestRegion": [
+ "CH-ZH"
+ ],
+ "OriginAirportID": [
+ "SYD"
+ ],
+ "DestCityName": [
+ "Zurich"
+ ]
+ },
+ "sort": [
+ 1626443360000
+ ]
+ }
+ ]
+ },
+ "aggregations": {
+ "2": {
+ "buckets": [
+ {
+ "key_as_string": "2021-07-16T08:49:00.000-05:00",
+ "key": 1626443340000,
+ "doc_count": 1
+ },
+ {
+ "key_as_string": "2021-07-16T08:50:30.000-05:00",
+ "key": 1626443430000,
+ "doc_count": 1
+ },
+ {
+ "key_as_string": "2021-07-16T08:51:30.000-05:00",
+ "key": 1626443490000,
+ "doc_count": 1
+ },
+ {
+ "key_as_string": "2021-07-16T08:57:30.000-05:00",
+ "key": 1626443850000,
+ "doc_count": 1
+ },
+ {
+ "key_as_string": "2021-07-16T09:05:30.000-05:00",
+ "key": 1626444330000,
+ "doc_count": 1
+ },
+ {
+ "key_as_string": "2021-07-16T09:14:00.000-05:00",
+ "key": 1626444840000,
+ "doc_count": 1
+ },
+ {
+ "key_as_string": "2021-07-16T09:15:00.000-05:00",
+ "key": 1626444900000,
+ "doc_count": 1
+ }
+ ]
+ }
+ }
+ },
+ "isPartial": false,
+ "isRunning": false,
+ "total": 1,
+ "loaded": 1,
+ "isRestored": false
+}`}
+
+
+);
diff --git a/src-docs/src/views/code/virtualized_flyout.js b/src-docs/src/views/code/virtualized_flyout.js
new file mode 100644
index 00000000000..8b0ece97bec
--- /dev/null
+++ b/src-docs/src/views/code/virtualized_flyout.js
@@ -0,0 +1,861 @@
+import React, { useState } from 'react';
+
+import {
+ EuiFlyout,
+ EuiFlyoutHeader,
+ EuiButton,
+ EuiTitle,
+ EuiCodeBlock,
+} from '../../../../src/components';
+
+export default () => {
+ const [isFlyoutVisible, setIsFlyoutVisible] = useState(false);
+
+ let flyout;
+
+ if (isFlyoutVisible) {
+ flyout = (
+ setIsFlyoutVisible(false)}
+ aria-labelledby="flyoutTitle">
+
+
+ A flyout with just code
+
+
+
+
+ {`{
+ "id": "1",
+ "rawResponse": {
+ "took": 19,
+ "timed_out": false,
+ "_shards": {
+ "total": 1,
+ "successful": 1,
+ "skipped": 0,
+ "failed": 0
+ },
+ "hits": {
+ "total": 7,
+ "max_score": null,
+ "hits": [
+ {
+ "_index": "kibana_sample_data_flights",
+ "_id": "i5-sr3oB9JvwH6mY-m2Q",
+ "_version": 1,
+ "_score": null,
+ "fields": {
+ "Origin": [
+ "Wichita Mid Continent Airport"
+ ],
+ "OriginLocation": [
+ {
+ "coordinates": [
+ -97.43309784,
+ 37.64989853
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightNum": [
+ "Q4UQIF3"
+ ],
+ "DestLocation": [
+ {
+ "coordinates": [
+ 8.54917,
+ 47.464699
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightDelay": [
+ true
+ ],
+ "DistanceMiles": [
+ 5013.5835
+ ],
+ "FlightTimeMin": [
+ 822.3817
+ ],
+ "OriginWeather": [
+ "Cloudy"
+ ],
+ "dayOfWeek": [
+ 4
+ ],
+ "AvgTicketPrice": [
+ 276.2003
+ ],
+ "Carrier": [
+ "JetBeats"
+ ],
+ "FlightDelayMin": [
+ 150
+ ],
+ "OriginRegion": [
+ "US-KS"
+ ],
+ "DestAirportID": [
+ "ZRH"
+ ],
+ "FlightDelayType": [
+ "Carrier Delay"
+ ],
+ "hour_of_day": [
+ 14
+ ],
+ "timestamp": [
+ "2021-07-16T14:15:29.000Z"
+ ],
+ "Dest": [
+ "Zurich Airport"
+ ],
+ "FlightTimeHour": [
+ "13.706362235285443"
+ ],
+ "Cancelled": [
+ false
+ ],
+ "DistanceKilometers": [
+ 8068.5806
+ ],
+ "OriginCityName": [
+ "Wichita"
+ ],
+ "DestWeather": [
+ "Rain"
+ ],
+ "OriginCountry": [
+ "US"
+ ],
+ "DestCountry": [
+ "CH"
+ ],
+ "DestRegion": [
+ "CH-ZH"
+ ],
+ "OriginAirportID": [
+ "ICT"
+ ],
+ "DestCityName": [
+ "Zurich"
+ ]
+ },
+ "sort": [
+ 1626444929000
+ ]
+ },
+ {
+ "_index": "kibana_sample_data_flights",
+ "_id": "AZ-sr3oB9JvwH6mY-m2P",
+ "_version": 1,
+ "_score": null,
+ "fields": {
+ "Origin": [
+ "Turin Airport"
+ ],
+ "OriginLocation": [
+ {
+ "coordinates": [
+ 7.64963,
+ 45.200802
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightNum": [
+ "WR15PZZ"
+ ],
+ "DestLocation": [
+ {
+ "coordinates": [
+ 20.96710014,
+ 52.16569901
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightDelay": [
+ false
+ ],
+ "DistanceMiles": [
+ 774.4176
+ ],
+ "FlightTimeMin": [
+ 95.86956
+ ],
+ "OriginWeather": [
+ "Clear"
+ ],
+ "dayOfWeek": [
+ 4
+ ],
+ "AvgTicketPrice": [
+ 360.27106
+ ],
+ "Carrier": [
+ "Kibana Airlines"
+ ],
+ "FlightDelayMin": [
+ 0
+ ],
+ "OriginRegion": [
+ "IT-21"
+ ],
+ "DestAirportID": [
+ "WAW"
+ ],
+ "FlightDelayType": [
+ "No Delay"
+ ],
+ "hour_of_day": [
+ 14
+ ],
+ "timestamp": [
+ "2021-07-16T14:14:06.000Z"
+ ],
+ "Dest": [
+ "Warsaw Chopin Airport"
+ ],
+ "FlightTimeHour": [
+ "1.597826006729792"
+ ],
+ "Cancelled": [
+ false
+ ],
+ "DistanceKilometers": [
+ 1246.3043
+ ],
+ "OriginCityName": [
+ "Torino"
+ ],
+ "DestWeather": [
+ "Thunder & Lightning"
+ ],
+ "OriginCountry": [
+ "IT"
+ ],
+ "DestCountry": [
+ "PL"
+ ],
+ "DestRegion": [
+ "PL-MZ"
+ ],
+ "OriginAirportID": [
+ "TO11"
+ ],
+ "DestCityName": [
+ "Warsaw"
+ ]
+ },
+ "sort": [
+ 1626444846000
+ ]
+ },
+ {
+ "_index": "kibana_sample_data_flights",
+ "_id": "LJ-sr3oB9JvwH6mY-m6Q",
+ "_version": 1,
+ "_score": null,
+ "fields": {
+ "Origin": [
+ "Chhatrapati Shivaji International Airport"
+ ],
+ "OriginLocation": [
+ {
+ "coordinates": [
+ 72.86789703,
+ 19.08869934
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightNum": [
+ "VZNTLIZ"
+ ],
+ "DestLocation": [
+ {
+ "coordinates": [
+ 33.46390152,
+ 68.15180206
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightDelay": [
+ false
+ ],
+ "DistanceMiles": [
+ 3791.431
+ ],
+ "FlightTimeMin": [
+ 305.08582
+ ],
+ "OriginWeather": [
+ "Cloudy"
+ ],
+ "dayOfWeek": [
+ 4
+ ],
+ "AvgTicketPrice": [
+ 845.4707
+ ],
+ "Carrier": [
+ "JetBeats"
+ ],
+ "FlightDelayMin": [
+ 0
+ ],
+ "OriginRegion": [
+ "SE-BD"
+ ],
+ "DestAirportID": [
+ "XLMO"
+ ],
+ "FlightDelayType": [
+ "No Delay"
+ ],
+ "hour_of_day": [
+ 14
+ ],
+ "timestamp": [
+ "2021-07-16T14:05:44.000Z"
+ ],
+ "Dest": [
+ "Olenya Air Base"
+ ],
+ "FlightTimeHour": [
+ "5.084763808440013"
+ ],
+ "Cancelled": [
+ false
+ ],
+ "DistanceKilometers": [
+ 6101.717
+ ],
+ "OriginCityName": [
+ "Mumbai"
+ ],
+ "DestWeather": [
+ "Heavy Fog"
+ ],
+ "OriginCountry": [
+ "IN"
+ ],
+ "DestCountry": [
+ "RU"
+ ],
+ "DestRegion": [
+ "RU-MUR"
+ ],
+ "OriginAirportID": [
+ "BOM"
+ ],
+ "DestCityName": [
+ "Olenegorsk"
+ ]
+ },
+ "sort": [
+ 1626444344000
+ ]
+ },
+ {
+ "_index": "kibana_sample_data_flights",
+ "_id": "Hp-sr3oB9JvwH6mY-m2P",
+ "_version": 1,
+ "_score": null,
+ "fields": {
+ "Origin": [
+ "Buffalo Niagara International Airport"
+ ],
+ "OriginLocation": [
+ {
+ "coordinates": [
+ -78.73220062,
+ 42.94049835
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightNum": [
+ "QAXVRPQ"
+ ],
+ "DestLocation": [
+ {
+ "coordinates": [
+ -78.3575,
+ -0.129166667
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightDelay": [
+ false
+ ],
+ "DistanceMiles": [
+ 2964.2756
+ ],
+ "FlightTimeMin": [
+ 227.16853
+ ],
+ "OriginWeather": [
+ "Sunny"
+ ],
+ "dayOfWeek": [
+ 4
+ ],
+ "AvgTicketPrice": [
+ 719.76935
+ ],
+ "Carrier": [
+ "JetBeats"
+ ],
+ "FlightDelayMin": [
+ 0
+ ],
+ "OriginRegion": [
+ "US-NY"
+ ],
+ "DestAirportID": [
+ "UIO"
+ ],
+ "FlightDelayType": [
+ "No Delay"
+ ],
+ "hour_of_day": [
+ 13
+ ],
+ "timestamp": [
+ "2021-07-16T13:57:30.000Z"
+ ],
+ "Dest": [
+ "Mariscal Sucre International Airport"
+ ],
+ "FlightTimeHour": [
+ "3.7861423240197563"
+ ],
+ "Cancelled": [
+ false
+ ],
+ "DistanceKilometers": [
+ 4770.5396
+ ],
+ "OriginCityName": [
+ "Buffalo"
+ ],
+ "DestWeather": [
+ "Clear"
+ ],
+ "OriginCountry": [
+ "US"
+ ],
+ "DestCountry": [
+ "EC"
+ ],
+ "DestRegion": [
+ "EC-P"
+ ],
+ "OriginAirportID": [
+ "BUF"
+ ],
+ "DestCityName": [
+ "Quito"
+ ]
+ },
+ "sort": [
+ 1626443850000
+ ]
+ },
+ {
+ "_index": "kibana_sample_data_flights",
+ "_id": "U5-sr3oB9JvwH6mY-m2Q",
+ "_version": 1,
+ "_score": null,
+ "fields": {
+ "Origin": [
+ "Dubai International Airport"
+ ],
+ "OriginLocation": [
+ {
+ "coordinates": [
+ 55.36439896,
+ 25.25279999
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightNum": [
+ "TJQKCKN"
+ ],
+ "DestLocation": [
+ {
+ "coordinates": [
+ -73.74079895,
+ 45.47060013
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightDelay": [
+ false
+ ],
+ "DistanceMiles": [
+ 6611.2646
+ ],
+ "FlightTimeMin": [
+ 709.31995
+ ],
+ "OriginWeather": [
+ "Rain"
+ ],
+ "dayOfWeek": [
+ 4
+ ],
+ "AvgTicketPrice": [
+ 756.61444
+ ],
+ "Carrier": [
+ "ES-Air"
+ ],
+ "FlightDelayMin": [
+ 0
+ ],
+ "OriginRegion": [
+ "SE-BD"
+ ],
+ "DestAirportID": [
+ "YUL"
+ ],
+ "FlightDelayType": [
+ "No Delay"
+ ],
+ "hour_of_day": [
+ 13
+ ],
+ "timestamp": [
+ "2021-07-16T13:51:52.000Z"
+ ],
+ "Dest": [
+ "Montreal / Pierre Elliott Trudeau International Airport"
+ ],
+ "FlightTimeHour": [
+ "11.821998598483413"
+ ],
+ "Cancelled": [
+ false
+ ],
+ "DistanceKilometers": [
+ 10639.799
+ ],
+ "OriginCityName": [
+ "Dubai"
+ ],
+ "DestWeather": [
+ "Clear"
+ ],
+ "OriginCountry": [
+ "AE"
+ ],
+ "DestCountry": [
+ "CA"
+ ],
+ "DestRegion": [
+ "CA-QC"
+ ],
+ "OriginAirportID": [
+ "DXB"
+ ],
+ "DestCityName": [
+ "Montreal"
+ ]
+ },
+ "sort": [
+ 1626443512000
+ ]
+ },
+ {
+ "_index": "kibana_sample_data_flights",
+ "_id": "TJ-sr3oB9JvwH6mY-m2Q",
+ "_version": 1,
+ "_score": null,
+ "fields": {
+ "Origin": [
+ "Jorge Chavez International Airport"
+ ],
+ "OriginLocation": [
+ {
+ "coordinates": [
+ -77.114304,
+ -12.0219
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightNum": [
+ "8B6BGMO"
+ ],
+ "DestLocation": [
+ {
+ "coordinates": [
+ 128.445007,
+ 51.169997
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightDelay": [
+ true
+ ],
+ "DistanceMiles": [
+ 9375.942
+ ],
+ "FlightTimeMin": [
+ 824.16406
+ ],
+ "OriginWeather": [
+ "Cloudy"
+ ],
+ "dayOfWeek": [
+ 4
+ ],
+ "AvgTicketPrice": [
+ 463.068
+ ],
+ "Carrier": [
+ "Logstash Airways"
+ ],
+ "FlightDelayMin": [
+ 30
+ ],
+ "OriginRegion": [
+ "SE-BD"
+ ],
+ "DestAirportID": [
+ "XHBU"
+ ],
+ "FlightDelayType": [
+ "Late Aircraft Delay"
+ ],
+ "hour_of_day": [
+ 13
+ ],
+ "timestamp": [
+ "2021-07-16T13:50:55.000Z"
+ ],
+ "Dest": [
+ "Ukrainka Air Base"
+ ],
+ "FlightTimeHour": [
+ "13.736067768266615"
+ ],
+ "Cancelled": [
+ false
+ ],
+ "DistanceKilometers": [
+ 15089.117
+ ],
+ "OriginCityName": [
+ "Lima"
+ ],
+ "DestWeather": [
+ "Clear"
+ ],
+ "OriginCountry": [
+ "PE"
+ ],
+ "DestCountry": [
+ "RU"
+ ],
+ "DestRegion": [
+ "RU-AMU"
+ ],
+ "OriginAirportID": [
+ "LIM"
+ ],
+ "DestCityName": [
+ "Belogorsk"
+ ]
+ },
+ "sort": [
+ 1626443455000
+ ]
+ },
+ {
+ "_index": "kibana_sample_data_flights",
+ "_id": "3J-sr3oB9JvwH6mY-m2Q",
+ "_version": 1,
+ "_score": null,
+ "fields": {
+ "Origin": [
+ "Sydney Kingsford Smith International Airport"
+ ],
+ "OriginLocation": [
+ {
+ "coordinates": [
+ 151.177002,
+ -33.94609833
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightNum": [
+ "PASAN8N"
+ ],
+ "DestLocation": [
+ {
+ "coordinates": [
+ 8.54917,
+ 47.464699
+ ],
+ "type": "Point"
+ }
+ ],
+ "FlightDelay": [
+ false
+ ],
+ "DistanceMiles": [
+ 10293.209
+ ],
+ "FlightTimeMin": [
+ 1380.4429
+ ],
+ "OriginWeather": [
+ "Sunny"
+ ],
+ "dayOfWeek": [
+ 4
+ ],
+ "AvgTicketPrice": [
+ 380.29593
+ ],
+ "Carrier": [
+ "Logstash Airways"
+ ],
+ "FlightDelayMin": [
+ 0
+ ],
+ "OriginRegion": [
+ "SE-BD"
+ ],
+ "DestAirportID": [
+ "ZRH"
+ ],
+ "FlightDelayType": [
+ "No Delay"
+ ],
+ "hour_of_day": [
+ 13
+ ],
+ "timestamp": [
+ "2021-07-16T13:49:20.000Z"
+ ],
+ "Dest": [
+ "Zurich Airport"
+ ],
+ "FlightTimeHour": [
+ "23.007380215402044"
+ ],
+ "Cancelled": [
+ false
+ ],
+ "DistanceKilometers": [
+ 16565.314
+ ],
+ "OriginCityName": [
+ "Sydney"
+ ],
+ "DestWeather": [
+ "Rain"
+ ],
+ "OriginCountry": [
+ "AU"
+ ],
+ "DestCountry": [
+ "CH"
+ ],
+ "DestRegion": [
+ "CH-ZH"
+ ],
+ "OriginAirportID": [
+ "SYD"
+ ],
+ "DestCityName": [
+ "Zurich"
+ ]
+ },
+ "sort": [
+ 1626443360000
+ ]
+ }
+ ]
+ },
+ "aggregations": {
+ "2": {
+ "buckets": [
+ {
+ "key_as_string": "2021-07-16T08:49:00.000-05:00",
+ "key": 1626443340000,
+ "doc_count": 1
+ },
+ {
+ "key_as_string": "2021-07-16T08:50:30.000-05:00",
+ "key": 1626443430000,
+ "doc_count": 1
+ },
+ {
+ "key_as_string": "2021-07-16T08:51:30.000-05:00",
+ "key": 1626443490000,
+ "doc_count": 1
+ },
+ {
+ "key_as_string": "2021-07-16T08:57:30.000-05:00",
+ "key": 1626443850000,
+ "doc_count": 1
+ },
+ {
+ "key_as_string": "2021-07-16T09:05:30.000-05:00",
+ "key": 1626444330000,
+ "doc_count": 1
+ },
+ {
+ "key_as_string": "2021-07-16T09:14:00.000-05:00",
+ "key": 1626444840000,
+ "doc_count": 1
+ },
+ {
+ "key_as_string": "2021-07-16T09:15:00.000-05:00",
+ "key": 1626444900000,
+ "doc_count": 1
+ }
+ ]
+ }
+ }
+ },
+ "isPartial": false,
+ "isRunning": false,
+ "total": 1,
+ "loaded": 1,
+ "isRestored": false
+}`}
+
+
+
+ );
+ }
+
+ return (
+
+ setIsFlyoutVisible(true)}>
+ Show flyout example
+
+ {flyout}
+
+ );
+};
diff --git a/src/components/code/__snapshots__/_code_block.test.tsx.snap b/src/components/code/__snapshots__/_code_block.test.tsx.snap
index 644a2d0e1e0..9c684ffb5e5 100644
--- a/src/components/code/__snapshots__/_code_block.test.tsx.snap
+++ b/src/components/code/__snapshots__/_code_block.test.tsx.snap
@@ -73,6 +73,33 @@ exports[`EuiCodeBlockImpl block renders a pre block tag with a css class modifie
`;
+exports[`EuiCodeBlockImpl block renders a virtualized code block 1`] = `
+
+`;
+
exports[`EuiCodeBlockImpl block renders with transparent background 1`] = `
+`;
+
exports[`EuiCodeBlock dynamic content updates DOM when input changes 1`] = `
"
diff --git a/src/components/code/_code_block.scss b/src/components/code/_code_block.scss
index 73407b1dba7..0dc9cbc04f0 100644
--- a/src/components/code/_code_block.scss
+++ b/src/components/code/_code_block.scss
@@ -20,6 +20,11 @@
white-space: pre-wrap;
}
+ // Necessary for virtualized code blocks to have appropriate padding
+ .euiCodeBlock__pre--isVirtualized {
+ position: relative;
+ }
+
.euiCodeBlock__code {
@include euiCodeFont;
display: block;
diff --git a/src/components/code/_code_block.test.tsx b/src/components/code/_code_block.test.tsx
index ca8c5088324..da29ac57276 100644
--- a/src/components/code/_code_block.test.tsx
+++ b/src/components/code/_code_block.test.tsx
@@ -87,5 +87,18 @@ describe('EuiCodeBlockImpl', () => {
);
expect(component).toMatchSnapshot();
});
+
+ test('renders a virtualized code block', () => {
+ const component = render(
+
+ {code}
+
+ );
+ expect(component).toMatchSnapshot();
+ });
});
});
diff --git a/src/components/code/_code_block.tsx b/src/components/code/_code_block.tsx
index 9ff85f59e89..a3b1af5f3fb 100644
--- a/src/components/code/_code_block.tsx
+++ b/src/components/code/_code_block.tsx
@@ -8,18 +8,24 @@
import React, {
CSSProperties,
+ HTMLAttributes,
FunctionComponent,
KeyboardEvent,
+ ReactElement,
ReactNode,
+ memo,
+ forwardRef,
useEffect,
useMemo,
useState,
} from 'react';
import classNames from 'classnames';
-import { highlight, AST, RefractorNode, listLanguages } from 'refractor';
+import { highlight, RefractorNode, listLanguages } from 'refractor';
+import { FixedSizeList, ListChildComponentProps } from 'react-window';
+import AutoSizer from 'react-virtualized-auto-sizer';
import { keys, useCombinedRefs } from '../../services';
import { EuiButtonIcon } from '../button';
-import { keysOf } from '../common';
+import { keysOf, CommonProps, ExclusiveUnion } from '../common';
import { EuiCopy } from '../copy';
import { EuiFocusTrap } from '../focus_trap';
import { EuiI18n } from '../i18n';
@@ -27,117 +33,43 @@ import { useInnerText } from '../inner_text';
import { useMutationObserver } from '../observer/mutation_observer';
import { useResizeObserver } from '../observer/resize_observer';
import { EuiOverlayMask } from '../overlay_mask';
+import { highlightByLine, nodeToHtml } from './utils';
-type ExtendedRefractorNode = RefractorNode & {
- lineStart?: number;
- lineEnd?: number;
-};
-
-const SUPPORTED_LANGUAGES = listLanguages();
-const DEFAULT_LANGUAGE = 'text';
-
-const isAstElement = (node: RefractorNode): node is AST.Element =>
- node.hasOwnProperty('type') && node.type === 'element';
-
-const nodeToHtml = (
- node: RefractorNode,
- idx: number,
- nodes: RefractorNode[],
- depth: number = 0
-): ReactNode => {
- if (isAstElement(node)) {
- const { properties, tagName, children } = node;
-
- return React.createElement(
- tagName,
- {
- ...properties,
- key: `node-${depth}-${idx}`,
- className: classNames(properties.className),
- },
- children && children.map((el, i) => nodeToHtml(el, i, nodes, depth + 1))
- );
- }
-
- return node.value;
-};
-
-const addLineData = (
- nodes: ExtendedRefractorNode[],
- data = { lineNumber: 1 }
-): ExtendedRefractorNode[] => {
- return nodes.reduce
((result, node) => {
- const lineStart = data.lineNumber;
- if (node.type === 'text') {
- if (!node.value.match(/\r\n?|\n/)) {
- node.lineStart = lineStart;
- node.lineEnd = lineStart;
- result.push(node);
- } else {
- const lines = node.value.split(/\r\n?|\n/);
- lines.forEach((line, i) => {
- const num = i === 0 ? data.lineNumber : ++data.lineNumber;
- result.push({
- type: 'text',
- value: i === lines.length - 1 ? line : `${line}\n`,
- lineStart: num,
- lineEnd: num,
- });
- });
- }
- return result;
- }
+// eslint-disable-next-line local/forward-ref
+const virtualizedOuterElement = ({
+ className,
+}: HTMLAttributes) =>
+ memo(
+ forwardRef((props, ref) => (
+
+ ))
+ );
- if (node.children && node.children.length) {
- const children = addLineData(node.children, data);
- const first = children[0];
- const last = children[children.length - 1];
- const start = first.lineStart ?? lineStart;
- const end = last.lineEnd ?? lineStart;
- if (start !== end) {
- children.forEach((node) => {
- result.push(node);
- });
- } else {
- node.lineStart = start;
- node.lineEnd = end;
- node.children = children;
- result.push(node);
- }
- return result;
- }
+// eslint-disable-next-line local/forward-ref
+const virtualizedInnerElement = ({
+ className,
+ onKeyDown,
+}: HTMLAttributes) =>
+ memo(
+ forwardRef((props, ref) => (
+
+ ))
+ );
- result.push(node);
- return result;
- }, []);
+const ListRow = ({ data, index, style }: ListChildComponentProps) => {
+ const row = data[index];
+ row.properties.style = style;
+ return nodeToHtml(row, index, data, 0);
};
-function wrapLines(nodes: ExtendedRefractorNode[]) {
- const grouped: ExtendedRefractorNode[][] = [];
- nodes.forEach((node) => {
- const lineStart = node.lineStart! - 1;
- if (grouped[lineStart]) {
- grouped[lineStart].push(node);
- } else {
- grouped[lineStart] = [node];
- }
- });
- const wrapped: RefractorNode[] = [];
- grouped.forEach((node) => {
- wrapped.push({
- type: 'element',
- tagName: 'span',
- properties: {
- className: ['euiCodeBlock__line'],
- },
- children: node,
- });
- });
- return wrapped;
-}
+const SUPPORTED_LANGUAGES = listLanguages();
+const DEFAULT_LANGUAGE = 'text';
-const highlightByLine = (children: string, language: string) => {
- return wrapLines(addLineData(highlight(children, language)));
+// Based on observed line height for non-virtualized code blocks
+const fontSizeToRowHeightMap = {
+ s: 16,
+ m: 19,
+ l: 21,
};
const fontSizeToClassNameMap = {
@@ -160,7 +92,29 @@ const paddingSizeToClassNameMap: { [paddingSize in PaddingSize]: string } = {
export const PADDING_SIZES = keysOf(paddingSizeToClassNameMap);
-export interface EuiCodeBlockImplProps {
+// overflowHeight is required when using virtualization
+type VirtualizedOptionProps = ExclusiveUnion<
+ {
+ /**
+ * Renders code block lines virtually.
+ * Useful for improving load times of large code blocks.
+ * `overflowHeight` is required when using this configuration.
+ */
+ isVirtualized: true;
+ /**
+ * Sets the maximum container height.
+ * Accepts a pixel value (`300`) or a percentage (`'100%'`)
+ * Ensure the container has calcuable height when using a percentage
+ */
+ overflowHeight: number | string;
+ },
+ {
+ isVirtualized?: boolean;
+ overflowHeight?: number | string;
+ }
+>;
+
+export type EuiCodeBlockImplProps = CommonProps & {
className?: string;
fontSize?: FontSize;
@@ -176,11 +130,10 @@ export interface EuiCodeBlockImplProps {
/**
* Sets the syntax highlighting for a specific language
- * @see https://github.com/wooorm/refractor#syntaxes
+ * @see https://prismjs.com/#supported-languages
* for options
*/
language?: string;
- overflowHeight?: number;
paddingSize?: PaddingSize;
transparentBackground?: boolean;
/**
@@ -189,7 +142,7 @@ export interface EuiCodeBlockImplProps {
* `pre-wrap` respects line breaks/white space but does force them to wrap the line when necessary.
*/
whiteSpace?: 'pre' | 'pre-wrap';
-}
+} & VirtualizedOptionProps;
/**
* This is the base component extended by EuiCode and EuiCodeBlock.
@@ -206,6 +159,7 @@ export const EuiCodeBlockImpl: FunctionComponent = ({
children,
className,
overflowHeight,
+ isVirtualized: _isVirtualized,
...rest
}) => {
const language: string = useMemo(
@@ -226,17 +180,31 @@ export const EuiCodeBlockImpl: FunctionComponent = ({
setWrapperRef,
]);
const { width, height } = useResizeObserver(wrapperRef);
+ const rowHeight = useMemo(() => fontSizeToRowHeightMap[fontSize], [fontSize]);
- const content = useMemo(() => {
+ // Used by `FixedSizeList` when `isVirtualized=true` or `children` is parsable (`isVirtualized=true`)
+ const data: RefractorNode[] = useMemo(() => {
if (typeof children !== 'string') {
- return children;
+ return [];
}
- const nodes = inline
+ return inline
? highlight(children, language)
: highlightByLine(children, language);
- return nodes.length === 0 ? children : nodes.map(nodeToHtml);
}, [children, language, inline]);
+ const isVirtualized = useMemo(() => _isVirtualized && Array.isArray(data), [
+ _isVirtualized,
+ data,
+ ]);
+
+ // Used by `pre` when `isVirtualized=false` or `children` is not parsable (`isVirtualized=false`)
+ const content: ReactElement[] | ReactNode = useMemo(() => {
+ if (!Array.isArray(data) || data.length < 1) {
+ return children;
+ }
+ return data.map(nodeToHtml);
+ }, [data, children]);
+
const doesOverflow = () => {
if (!wrapperRef) return;
@@ -291,12 +259,15 @@ export const EuiCodeBlockImpl: FunctionComponent = ({
const preClasses = classNames('euiCodeBlock__pre', {
'euiCodeBlock__pre--whiteSpacePre': whiteSpace === 'pre',
'euiCodeBlock__pre--whiteSpacePreWrap': whiteSpace === 'pre-wrap',
+ 'euiCodeBlock__pre--isVirtualized': isVirtualized,
});
const optionalStyles: CSSProperties = {};
if (overflowHeight) {
- optionalStyles.maxHeight = overflowHeight;
+ const property =
+ typeof overflowHeight === 'string' ? 'height' : 'maxHeight';
+ optionalStyles[property] = overflowHeight;
}
const codeSnippet = (
@@ -314,8 +285,10 @@ export const EuiCodeBlockImpl: FunctionComponent = ({
return {codeSnippet} ;
}
- const getCopyButton = (textToCopy?: string) => {
+ const getCopyButton = (_textToCopy?: string) => {
let copyButton: JSX.Element | undefined;
+ // Fallback to `children` in the case of virtualized blocks.
+ const textToCopy = _textToCopy || `${children}`;
if (isCopyable && textToCopy) {
copyButton = (
@@ -397,11 +370,33 @@ export const EuiCodeBlockImpl: FunctionComponent = ({
-
-
- {content}
-
-
+ {isVirtualized ? (
+
+ {({ height, width }) => (
+
+ {ListRow}
+
+ )}
+
+ ) : (
+
+
+ {content}
+
+
+ )}
{codeBlockControls}
@@ -416,13 +411,35 @@ export const EuiCodeBlockImpl: FunctionComponent = ({
const codeBlockControls = getCodeBlockControls(innerText);
return (
-
- {codeSnippet}
-
+ {isVirtualized ? (
+
+ {({ height, width }) => (
+
+ {ListRow}
+
+ )}
+
+ ) : (
+
+ {codeSnippet}
+
+ )}
{/*
If the below fullScreen code renders, it actually attaches to the body because of
EuiOverlayMask's React portal usage.
diff --git a/src/components/code/code_block.test.tsx b/src/components/code/code_block.test.tsx
index 5af6ebfd1e1..d867afe7541 100644
--- a/src/components/code/code_block.test.tsx
+++ b/src/components/code/code_block.test.tsx
@@ -154,5 +154,17 @@ describe('EuiCodeBlock', () => {
'const value = "hello"'
);
});
+
+ test('renders a virtualized code block', () => {
+ const component = render(
+
+ {code}
+
+ );
+ expect(component).toMatchSnapshot();
+ });
});
});
diff --git a/src/components/code/utils.tsx b/src/components/code/utils.tsx
new file mode 100644
index 00000000000..4c784074bc0
--- /dev/null
+++ b/src/components/code/utils.tsx
@@ -0,0 +1,122 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { createElement, ReactElement } from 'react';
+import { highlight, AST, RefractorNode } from 'refractor';
+import classNames from 'classnames';
+
+type ExtendedRefractorNode = RefractorNode & {
+ lineStart?: number;
+ lineEnd?: number;
+};
+
+const isAstElement = (node: RefractorNode): node is AST.Element =>
+ node.hasOwnProperty('type') && node.type === 'element';
+
+const addLineData = (
+ nodes: ExtendedRefractorNode[],
+ data = { lineNumber: 1 }
+): ExtendedRefractorNode[] => {
+ return nodes.reduce
((result, node) => {
+ const lineStart = data.lineNumber;
+ if (node.type === 'text') {
+ if (!node.value.match(/\r\n?|\n/)) {
+ node.lineStart = lineStart;
+ node.lineEnd = lineStart;
+ result.push(node);
+ } else {
+ const lines = node.value.split(/\r\n?|\n/);
+ lines.forEach((line, i) => {
+ const num = i === 0 ? data.lineNumber : ++data.lineNumber;
+ result.push({
+ type: 'text',
+ value: i === lines.length - 1 ? line : `${line}\n`,
+ lineStart: num,
+ lineEnd: num,
+ });
+ });
+ }
+ return result;
+ }
+
+ if (node.children && node.children.length) {
+ const children = addLineData(node.children, data);
+ const first = children[0];
+ const last = children[children.length - 1];
+ const start = first.lineStart ?? lineStart;
+ const end = last.lineEnd ?? lineStart;
+ if (start !== end) {
+ children.forEach((node) => {
+ result.push(node);
+ });
+ } else {
+ node.lineStart = start;
+ node.lineEnd = end;
+ node.children = children;
+ result.push(node);
+ }
+ return result;
+ }
+
+ result.push(node);
+ return result;
+ }, []);
+};
+
+function wrapLines(nodes: ExtendedRefractorNode[]) {
+ const grouped: ExtendedRefractorNode[][] = [];
+ nodes.forEach((node) => {
+ const lineStart = node.lineStart! - 1;
+ if (grouped[lineStart]) {
+ grouped[lineStart].push(node);
+ } else {
+ grouped[lineStart] = [node];
+ }
+ });
+ const wrapped: RefractorNode[] = [];
+ grouped.forEach((node) => {
+ wrapped.push({
+ type: 'element',
+ tagName: 'span',
+ properties: {
+ className: ['euiCodeBlock__line'],
+ },
+ children: node,
+ });
+ });
+ return wrapped;
+}
+
+export const nodeToHtml = (
+ node: RefractorNode,
+ idx: number,
+ nodes: RefractorNode[],
+ depth: number = 0
+): ReactElement => {
+ const key = `node-${depth}-${idx}`;
+
+ if (isAstElement(node)) {
+ const { properties, tagName, children } = node;
+
+ return createElement(
+ tagName,
+ {
+ ...properties,
+ key,
+ className: classNames(properties.className),
+ },
+ children && children.map((el, i) => nodeToHtml(el, i, nodes, depth + 1))
+ );
+ }
+
+ return {node.value} ;
+};
+
+export const highlightByLine = (children: string, language: string) => {
+ return wrapLines(addLineData(highlight(children, language)));
+};