From fc625d0de5732b079a6d017ad61c00022f85b3d4 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Sat, 18 Nov 2023 03:29:01 +0000 Subject: [PATCH 01/17] Add rowCellContext POC --- .../datagrid/advanced/custom_renderer.tsx | 61 ++++++++++--------- src-docs/src/views/datagrid/advanced/ref.tsx | 8 ++- .../cell_popover_is_expandable.tsx | 29 +++++---- .../cell_popover_rendercellpopover.tsx | 6 +- .../cells_popovers/visible_cell_actions.tsx | 6 +- .../datagrid/styling/row_height_auto.tsx | 4 +- .../datagrid/styling/row_height_fixed.tsx | 4 +- .../datagrid/styling/row_line_height.tsx | 4 +- .../toolbar/render_custom_toolbar.tsx | 6 +- .../datagrid/body/data_grid_body_custom.tsx | 2 + .../body/data_grid_body_virtualized.tsx | 2 + .../datagrid/body/data_grid_cell.tsx | 19 +++++- .../datagrid/body/data_grid_cell_wrapper.tsx | 3 + src/components/datagrid/data_grid.tsx | 2 + src/components/datagrid/data_grid_types.ts | 20 +++++- 15 files changed, 120 insertions(+), 56 deletions(-) diff --git a/src-docs/src/views/datagrid/advanced/custom_renderer.tsx b/src-docs/src/views/datagrid/advanced/custom_renderer.tsx index 5f2359059af..381f99160c7 100644 --- a/src-docs/src/views/datagrid/advanced/custom_renderer.tsx +++ b/src-docs/src/views/datagrid/advanced/custom_renderer.tsx @@ -19,6 +19,7 @@ import { EuiDataGridPaginationProps, EuiDataGridSorting, EuiDataGridColumnSortingConfig, + renderCellValue, } from '../../../../../src'; const raw_data: Array<{ [key: string]: string }> = []; @@ -67,6 +68,14 @@ const columns = [ }, ]; +const checkboxRowCellRender: renderCellValue = ({ rowIndex }) => ( + {}} + /> +); + const leadingControlColumns: EuiDataGridProps['leadingControlColumns'] = [ { id: 'selection', @@ -78,13 +87,7 @@ const leadingControlColumns: EuiDataGridProps['leadingControlColumns'] = [ onChange={() => {}} /> ), - rowCellRender: ({ rowIndex }) => ( - {}} - /> - ), + rowCellRender: checkboxRowCellRender, }, ]; @@ -103,6 +106,22 @@ const trailingControlColumns: EuiDataGridProps['trailingControlColumns'] = [ }, ]; +const RowCellRender: renderCellValue = ({ setCellProps, rowIndex }) => { + setCellProps({ style: { width: '100%', height: 'auto' } }); + + const firstName = raw_data[rowIndex].name.split(', ')[1]; + const isGood = faker.datatype.boolean(); + return ( + <> + {firstName}'s account has {isGood ? 'no' : ''} outstanding fees.{' '} + + + ); +}; + // The custom row details is actually a trailing control column cell with // a hidden header. This is important for accessibility and markup reasons // @see https://fuschia-stretch.glitch.me/ for more @@ -120,21 +139,7 @@ const rowDetails: EuiDataGridProps['trailingControlColumns'] = [ // When rendering this custom cell, we'll want to override // the automatic width/heights calculated by EuiDataGrid - rowCellRender: ({ setCellProps, rowIndex }) => { - setCellProps({ style: { width: '100%', height: 'auto' } }); - - const firstName = raw_data[rowIndex].name.split(', ')[1]; - const isGood = faker.datatype.boolean(); - return ( - <> - {firstName}'s account has {isGood ? 'no' : ''} outstanding fees.{' '} - - - ); - }, + rowCellRender: RowCellRender, }, ]; @@ -144,10 +149,10 @@ const footerCellValues: { [key: string]: string } = { .toLocaleString('en-US', { style: 'currency', currency: 'USD' })}`, }; -const RenderFooterCellValue: EuiDataGridProps['renderFooterCellValue'] = ({ - columnId, - setCellProps, -}) => { +const renderCellValue: renderCellValue = ({ rowIndex, columnId }) => + raw_data[rowIndex][columnId]; + +const RenderFooterCellValue: renderCellValue = ({ columnId, setCellProps }) => { const value = footerCellValues[columnId]; useEffect(() => { @@ -298,9 +303,7 @@ export default () => { onChangeItemsPerPage: onChangePageSize, }} rowCount={raw_data.length} - renderCellValue={({ rowIndex, columnId }) => - raw_data[rowIndex][columnId] - } + renderCellValue={renderCellValue} renderFooterCellValue={RenderFooterCellValue} renderCustomGridBody={RenderCustomGridBody} height={autoHeight ? undefined : 400} diff --git a/src-docs/src/views/datagrid/advanced/ref.tsx b/src-docs/src/views/datagrid/advanced/ref.tsx index 256ca249ec1..fb4400539e8 100644 --- a/src-docs/src/views/datagrid/advanced/ref.tsx +++ b/src-docs/src/views/datagrid/advanced/ref.tsx @@ -20,6 +20,7 @@ import { EuiDataGridColumnSortingConfig, EuiDataGridPaginationProps, EuiDataGridSorting, + renderCellValue, } from '../../../../../src'; const raw_data: Array<{ [key: string]: string }> = []; @@ -33,6 +34,9 @@ for (let i = 1; i < 100; i++) { }); } +const renderCellValue: renderCellValue = ({ rowIndex, columnId }) => + raw_data[rowIndex][columnId]; + export default () => { const dataGridRef = useRef(null); @@ -219,9 +223,7 @@ export default () => { sorting={{ columns: sortingColumns, onSort }} inMemory={{ level: 'sorting' }} rowCount={raw_data.length} - renderCellValue={({ rowIndex, columnId }) => - raw_data[rowIndex][columnId] - } + renderCellValue={renderCellValue} pagination={{ ...pagination, onChangePage: onChangePage, diff --git a/src-docs/src/views/datagrid/cells_popovers/cell_popover_is_expandable.tsx b/src-docs/src/views/datagrid/cells_popovers/cell_popover_is_expandable.tsx index deaf3080358..4a56ba7a4e9 100644 --- a/src-docs/src/views/datagrid/cells_popovers/cell_popover_is_expandable.tsx +++ b/src-docs/src/views/datagrid/cells_popovers/cell_popover_is_expandable.tsx @@ -5,6 +5,7 @@ import { EuiDataGrid, EuiDataGridColumnCellAction, EuiDataGridColumn, + renderCellValue, } from '../../../../../src'; const cellActions: EuiDataGridColumnCellAction[] = [ @@ -50,6 +51,22 @@ for (let i = 1; i < 5; i++) { }); } +const RenderCellValue: renderCellValue = ({ + rowIndex, + columnId, + setCellProps, +}) => { + const value = data[rowIndex][columnId]; + + useEffect(() => { + if (columnId === 'boolean' && value === 'false') { + setCellProps({ isExpandable: false }); + } + }, [columnId, value, setCellProps]); + + return value; +}; + export default () => { const [visibleColumns, setVisibleColumns] = useState( columns.map(({ id }) => id) @@ -61,17 +78,7 @@ export default () => { columns={columns} columnVisibility={{ visibleColumns, setVisibleColumns }} rowCount={data.length} - renderCellValue={({ rowIndex, columnId, setCellProps }) => { - const value = data[rowIndex][columnId]; - - useEffect(() => { - if (columnId === 'boolean' && value === 'false') { - setCellProps({ isExpandable: false }); - } - }, [columnId, value, setCellProps]); - - return value; - }} + renderCellValue={RenderCellValue} /> ); }; diff --git a/src-docs/src/views/datagrid/cells_popovers/cell_popover_rendercellpopover.tsx b/src-docs/src/views/datagrid/cells_popovers/cell_popover_rendercellpopover.tsx index 6171f46f9d1..51faf46e290 100644 --- a/src-docs/src/views/datagrid/cells_popovers/cell_popover_rendercellpopover.tsx +++ b/src-docs/src/views/datagrid/cells_popovers/cell_popover_rendercellpopover.tsx @@ -14,6 +14,7 @@ import { EuiCopy, EuiText, EuiImage, + renderCellValue, } from '../../../../../src'; const cellActions: EuiDataGridColumnCellAction[] = [ @@ -163,6 +164,9 @@ const RenderCellPopover = (props: EuiDataGridCellPopoverElementProps) => { ); }; +const renderCellValue: renderCellValue = ({ rowIndex, columnId }) => + data[rowIndex][columnId]; + export default () => { const [visibleColumns, setVisibleColumns] = useState( columns.map(({ id }) => id) @@ -174,7 +178,7 @@ export default () => { columns={columns} columnVisibility={{ visibleColumns, setVisibleColumns }} rowCount={data.length} - renderCellValue={({ rowIndex, columnId }) => data[rowIndex][columnId]} + renderCellValue={renderCellValue} renderCellPopover={RenderCellPopover} /> ); diff --git a/src-docs/src/views/datagrid/cells_popovers/visible_cell_actions.tsx b/src-docs/src/views/datagrid/cells_popovers/visible_cell_actions.tsx index 91d76498619..def02bd6a4e 100644 --- a/src-docs/src/views/datagrid/cells_popovers/visible_cell_actions.tsx +++ b/src-docs/src/views/datagrid/cells_popovers/visible_cell_actions.tsx @@ -5,6 +5,7 @@ import { EuiDataGrid, EuiDataGridColumnCellAction, EuiDataGridColumn, + renderCellValue, } from '../../../../../src/components'; const cellActions1: EuiDataGridColumnCellAction[] = [ @@ -79,6 +80,9 @@ for (let i = 1; i < 5; i++) { }); } +const renderCellValue: renderCellValue = ({ rowIndex, columnId }) => + data[rowIndex][columnId]; + export default () => { const [visibleColumns, setVisibleColumns] = useState( columns.map(({ id }) => id) @@ -90,7 +94,7 @@ export default () => { columns={columns} columnVisibility={{ visibleColumns, setVisibleColumns }} rowCount={data.length} - renderCellValue={({ rowIndex, columnId }) => data[rowIndex][columnId]} + renderCellValue={renderCellValue} /> ); }; diff --git a/src-docs/src/views/datagrid/styling/row_height_auto.tsx b/src-docs/src/views/datagrid/styling/row_height_auto.tsx index b963efcdfc3..a72b95c8950 100644 --- a/src-docs/src/views/datagrid/styling/row_height_auto.tsx +++ b/src-docs/src/views/datagrid/styling/row_height_auto.tsx @@ -10,7 +10,7 @@ import githubData from '../_row_auto_height_data.json'; import { EuiDataGrid, - EuiDataGridProps, + renderCellValue, EuiLink, EuiAvatar, EuiBadge, @@ -68,7 +68,7 @@ const columns = [ // instead of loading up front, generate entries on the fly const raw_data: DataShape[] = githubData; -const RenderCellValue: EuiDataGridProps['renderCellValue'] = ({ +const RenderCellValue: renderCellValue = ({ rowIndex, columnId, isDetails, diff --git a/src-docs/src/views/datagrid/styling/row_height_fixed.tsx b/src-docs/src/views/datagrid/styling/row_height_fixed.tsx index ba96749e2d0..bde39af0a4c 100644 --- a/src-docs/src/views/datagrid/styling/row_height_fixed.tsx +++ b/src-docs/src/views/datagrid/styling/row_height_fixed.tsx @@ -10,7 +10,7 @@ import githubData from '../_row_auto_height_data.json'; import { EuiDataGrid, - EuiDataGridProps, + renderCellValue, EuiLink, EuiAvatar, EuiBadge, @@ -68,7 +68,7 @@ const columns = [ // instead of loading up front, generate entries on the fly const raw_data: DataShape[] = githubData; -const RenderCellValue: EuiDataGridProps['renderCellValue'] = ({ +const RenderCellValue: renderCellValue = ({ rowIndex, columnId, isDetails, diff --git a/src-docs/src/views/datagrid/styling/row_line_height.tsx b/src-docs/src/views/datagrid/styling/row_line_height.tsx index b2f87a92ef2..69f42b894a6 100644 --- a/src-docs/src/views/datagrid/styling/row_line_height.tsx +++ b/src-docs/src/views/datagrid/styling/row_line_height.tsx @@ -12,7 +12,7 @@ import { EuiDataGrid, EuiDataGridColumnSortingConfig, EuiDataGridPaginationProps, - EuiDataGridProps, + renderCellValue, EuiDataGridSorting, formatDate, } from '../../../../../src'; @@ -62,7 +62,7 @@ const columns = [ // instead of loading up front, generate entries on the fly const raw_data: DataShape[] = githubData; -const RenderCellValue: EuiDataGridProps['renderCellValue'] = ({ +const RenderCellValue: renderCellValue = ({ rowIndex, columnId, isDetails, diff --git a/src-docs/src/views/datagrid/toolbar/render_custom_toolbar.tsx b/src-docs/src/views/datagrid/toolbar/render_custom_toolbar.tsx index 6f32b3a895a..16ae3856a56 100644 --- a/src-docs/src/views/datagrid/toolbar/render_custom_toolbar.tsx +++ b/src-docs/src/views/datagrid/toolbar/render_custom_toolbar.tsx @@ -13,6 +13,7 @@ import { EuiFlexGroup, EuiFlexItem, euiScreenReaderOnly, + renderCellValue, } from '../../../../../src'; const raw_data: Array<{ [key: string]: string }> = []; @@ -78,6 +79,9 @@ const renderCustomToolbar: EuiDataGridToolbarProps['renderCustomToolbar'] = ({ ); }; +const renderCellValue: renderCellValue = ({ rowIndex, columnId }) => + raw_data[rowIndex][columnId]; + // Some additional custom settings to show in the Display popover const AdditionalDisplaySettings = () => { const [exampleSettingValue, setExampleSettingValue] = useState(10); @@ -122,7 +126,7 @@ export default () => { columnVisibility={{ visibleColumns, setVisibleColumns }} sorting={{ columns: sortingColumns, onSort }} rowCount={raw_data.length} - renderCellValue={({ rowIndex, columnId }) => raw_data[rowIndex][columnId]} + renderCellValue={renderCellValue} gridStyle={{ border: 'none', header: 'underline' }} renderCustomToolbar={renderCustomToolbar} toolbarVisibility={{ diff --git a/src/components/datagrid/body/data_grid_body_custom.tsx b/src/components/datagrid/body/data_grid_body_custom.tsx index 881deb3f642..a8fc84d7331 100644 --- a/src/components/datagrid/body/data_grid_body_custom.tsx +++ b/src/components/datagrid/body/data_grid_body_custom.tsx @@ -38,6 +38,7 @@ export const EuiDataGridBodyCustomRender: FunctionComponent< schemaDetectors, visibleRows, renderCellValue, + renderCellContext, renderCellPopover, renderFooterCellValue, interactiveCellId, @@ -134,6 +135,7 @@ export const EuiDataGridBodyCustomRender: FunctionComponent< columnWidths, defaultColumnWidth, renderCellValue, + renderCellContext, renderCellPopover, interactiveCellId, setRowHeight, diff --git a/src/components/datagrid/body/data_grid_body_virtualized.tsx b/src/components/datagrid/body/data_grid_body_virtualized.tsx index c85c7c52cc8..212c2b858bf 100644 --- a/src/components/datagrid/body/data_grid_body_virtualized.tsx +++ b/src/components/datagrid/body/data_grid_body_virtualized.tsx @@ -113,6 +113,7 @@ export const EuiDataGridBodyVirtualized: FunctionComponent< rowCount, visibleRows: { startRow, endRow, visibleRowCount }, renderCellValue, + renderCellContext, renderCellPopover, renderFooterCellValue, interactiveCellId, @@ -330,6 +331,7 @@ export const EuiDataGridBodyVirtualized: FunctionComponent< columnWidths, defaultColumnWidth, renderCellValue, + renderCellContext, renderCellPopover, interactiveCellId, rowHeightsOptions, diff --git a/src/components/datagrid/body/data_grid_cell.tsx b/src/components/datagrid/body/data_grid_cell.tsx index 23ec939abb3..e9d0fd5915f 100644 --- a/src/components/datagrid/body/data_grid_cell.tsx +++ b/src/components/datagrid/body/data_grid_cell.tsx @@ -18,6 +18,7 @@ import React, { memo, MutableRefObject, ReactNode, + useMemo, } from 'react'; import { createPortal } from 'react-dom'; import { tabbable } from 'tabbable'; @@ -61,6 +62,7 @@ const EuiDataGridCellContent: FunctionComponent< > = memo( ({ renderCellValue, + renderCellContext, column, setCellContentsRef, setPopoverAnchorRef, @@ -92,6 +94,19 @@ const EuiDataGridCellContent: FunctionComponent< } ); + const mergedProps = useMemo(() => { + if (renderCellContext) { + return { + ...rest, + ...renderCellContext(), + }; + } else { + return { + ...rest, + }; + } + }, [rest, renderCellContext]); + let cellContent = (
{ @@ -115,7 +130,7 @@ const EuiDataGridCellContent: FunctionComponent< rowIndex={rowIndex} colIndex={colIndex} schema={column?.schema || rest.columnType} - {...rest} + {...mergedProps} />
); @@ -537,6 +552,7 @@ export class EuiDataGridCell extends Component< const { renderCellPopover, renderCellValue, + renderCellContext, rowIndex, colIndex, column, @@ -552,6 +568,7 @@ export class EuiDataGridCell extends Component< colIndex, columnId, schema: column?.schema || columnType, + ...renderCellContext?.(), }; const popoverContent = ( = ({ columnWidths, defaultColumnWidth, renderCellValue, + renderCellContext, renderCellPopover, interactiveCellId, setRowHeight, @@ -120,6 +122,7 @@ export const Cell: FunctionComponent = ({ rowManager, popoverContext, pagination, + renderCellContext, }; if (isLeadingControlColumn) { diff --git a/src/components/datagrid/data_grid.tsx b/src/components/datagrid/data_grid.tsx index a98630ce39a..a8698e03f75 100644 --- a/src/components/datagrid/data_grid.tsx +++ b/src/components/datagrid/data_grid.tsx @@ -114,6 +114,7 @@ export const EuiDataGrid = memo( schemaDetectors, rowCount, renderCellValue, + renderCellContext, renderCellPopover, renderFooterCellValue, className, @@ -455,6 +456,7 @@ export const EuiDataGrid = memo( schemaDetectors={allSchemaDetectors} pagination={pagination} renderCellValue={renderCellValue} + renderCellContext={renderCellContext} renderCellPopover={renderCellPopover} renderFooterCellValue={renderFooterCellValue} rowCount={rowCount} diff --git a/src/components/datagrid/data_grid_types.ts b/src/components/datagrid/data_grid_types.ts index 3935a7c47b8..25c17f44569 100644 --- a/src/components/datagrid/data_grid_types.ts +++ b/src/components/datagrid/data_grid_types.ts @@ -262,6 +262,7 @@ export type CommonGridProps = CommonProps & * as its only argument. */ renderCellValue: EuiDataGridCellProps['renderCellValue']; + renderCellContext?: EuiDataGridCellProps['renderCellContext']; /** * An optional function that can be used to completely customize the rendering of cell popovers. * @@ -447,6 +448,7 @@ export interface EuiDataGridBodyProps { rowCount: number; visibleRows: EuiDataGridVisibleRows; renderCellValue: EuiDataGridCellProps['renderCellValue']; + renderCellContext?: EuiDataGridCellProps['renderCellContext']; renderCellPopover?: EuiDataGridCellProps['renderCellPopover']; renderFooterCellValue?: EuiDataGridCellProps['renderCellValue']; renderCustomGridBody?: EuiDataGridProps['renderCustomGridBody']; @@ -593,6 +595,19 @@ export interface EuiDataGridCellPopoverElementProps ) => void; } +type RenderCellContext = (args?: unknown) => T; + +export type EuiDataGridCellValueElementPropsWithContext = + EuiDataGridCellValueElementProps & ReturnType>; + +export type renderCellValue = + | ((props: EuiDataGridCellValueElementProps) => ReactNode) + | ComponentClass; + +export type renderCellValueWithContext = + | ((props: EuiDataGridCellValueElementPropsWithContext) => ReactNode) + | ComponentClass; + export interface EuiDataGridCellProps { rowIndex: number; visibleRowIndex: number; @@ -605,9 +620,8 @@ export interface EuiDataGridCellProps { isExpandable: boolean; className?: string; popoverContext: DataGridCellPopoverContextShape; - renderCellValue: - | ((props: EuiDataGridCellValueElementProps) => ReactNode) - | ComponentClass; + renderCellValue: renderCellValue | renderCellValueWithContext; + renderCellContext?: RenderCellContext; renderCellPopover?: | JSXElementConstructor | ((props: EuiDataGridCellPopoverElementProps) => ReactNode); From 3248acc75dce991edb8b45e6ae277e72af97bda6 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Sat, 18 Nov 2023 04:16:03 +0000 Subject: [PATCH 02/17] Fix types in docs/tests --- package.json | 3 +- .../datagrid/toolbar/additional_controls.tsx | 6 +- .../body/data_grid_body_custom.test.tsx | 8 +- src/components/datagrid/data_grid.test.tsx | 325 +++++++++--------- src/components/datagrid/data_grid_types.ts | 17 +- 5 files changed, 178 insertions(+), 181 deletions(-) diff --git a/package.json b/package.json index ee2c402b0e5..887a03e3299 100644 --- a/package.json +++ b/package.json @@ -21,10 +21,11 @@ "build-docs": "cross-env BABEL_MODULES=false cross-env NODE_ENV=production NODE_OPTIONS=--max-old-space-size=4096 webpack --config=src-docs/webpack.config.js", "build": "yarn extract-i18n-strings && node ./scripts/compile-clean.js && node ./scripts/compile-eui.js && yarn compile-scss", "build-pack": "yarn build && npm pack", + "check-types": "yarn tsc --noEmit", "compile-icons": "node ./scripts/compile-icons.js && prettier --write --loglevel=warn \"./src/components/icon/assets/**/*.tsx\"", "compile-scss": "node ./scripts/compile-scss.js", "extract-i18n-strings": "node ./scripts/babel/fetch-i18n-strings", - "lint": "yarn tsc --noEmit && yarn lint-es && yarn lint-css-in-js && yarn lint-sass", + "lint": "yarn check-types && yarn lint-es && yarn lint-css-in-js && yarn lint-sass", "lint-fix": "yarn lint-es --fix && yarn lint-css-in-js --fix", "lint-es": "eslint --cache \"{src,src-docs}/**/*.{ts,tsx,js}\" --max-warnings 0", "lint-css-in-js": "yarn stylelint \"**/*.styles.ts\" \"./src/themes/**/*.ts\" \"./src/global_styling/**/*.ts\" --quiet-deprecation-warnings", diff --git a/src-docs/src/views/datagrid/toolbar/additional_controls.tsx b/src-docs/src/views/datagrid/toolbar/additional_controls.tsx index d91c17d43d8..bb3a30b275a 100644 --- a/src-docs/src/views/datagrid/toolbar/additional_controls.tsx +++ b/src-docs/src/views/datagrid/toolbar/additional_controls.tsx @@ -17,6 +17,7 @@ import { EuiContextMenuPanel, EuiPopover, EuiDataGridPaginationProps, + renderCellValue, } from '../../../../../src'; const columns = [ @@ -49,6 +50,9 @@ for (let i = 1; i < 20; i++) { }); } +const renderCellValue: renderCellValue = ({ rowIndex, columnId }) => + data[rowIndex][columnId]; + export default () => { const [pagination, setPagination] = useState({ pageIndex: 0 }); const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); @@ -126,7 +130,7 @@ export default () => { border: 'horizontal', header: 'underline', }} - renderCellValue={({ rowIndex, columnId }) => data[rowIndex][columnId]} + renderCellValue={renderCellValue} pagination={{ ...pagination, onChangeItemsPerPage: setPageSize, diff --git a/src/components/datagrid/body/data_grid_body_custom.test.tsx b/src/components/datagrid/body/data_grid_body_custom.test.tsx index 43012117206..b8f33eca654 100644 --- a/src/components/datagrid/body/data_grid_body_custom.test.tsx +++ b/src/components/datagrid/body/data_grid_body_custom.test.tsx @@ -10,7 +10,7 @@ import React, { useEffect } from 'react'; import { fireEvent } from '@testing-library/react'; import { render } from '../../../test/rtl'; -import { EuiDataGridProps } from '../data_grid_types'; +import { EuiDataGridProps, renderCellValue } from '../data_grid_types'; import { dataGridBodyProps } from './data_grid_body.test'; import { EuiDataGridBodyCustomRender } from './data_grid_body_custom'; @@ -21,10 +21,8 @@ describe('EuiDataGridBodyCustomRender', () => { { columnA: 'hello', columnB: 'world' }, { columnA: 'lorem', columnB: 'ipsum' }, ]; - const RenderCellValue: EuiDataGridProps['renderCellValue'] = ({ - rowIndex, - columnId, - }) => raw_data[rowIndex][columnId as keyof DataType]; + const RenderCellValue: renderCellValue = ({ rowIndex, columnId }) => + raw_data[rowIndex][columnId as keyof DataType]; const bodyProps = { ...dataGridBodyProps, diff --git a/src/components/datagrid/data_grid.test.tsx b/src/components/datagrid/data_grid.test.tsx index fac6ca20d3f..40b7708a34a 100644 --- a/src/components/datagrid/data_grid.test.tsx +++ b/src/components/datagrid/data_grid.test.tsx @@ -8,7 +8,7 @@ import React, { useEffect, useState } from 'react'; import { mount, ReactWrapper } from 'enzyme'; -import { EuiDataGrid } from './'; +import { EuiDataGrid, renderCellValue } from './'; import { EuiDataGridProps } from './data_grid_types'; import { findTestSubject, requiredProps } from '../../test'; import { render } from '../../test/rtl'; @@ -386,6 +386,43 @@ function setColumnVisibility( closeColumnSelector(datagrid); } +const renderCellValueRowAndColumnCount: renderCellValue = ({ + rowIndex, + columnId, +}) => `${rowIndex}, ${columnId}`; + +const RenderCellValueSetCellProps: renderCellValue = ({ + rowIndex, + columnId, + setCellProps, +}) => { + useEffect(() => { + setCellProps({ + className: 'customClass', + 'data-test-subj': `cell-${rowIndex}-${columnId}`, + style: { color: columnId === 'A' ? 'red' : 'blue' }, + }); + }, [columnId, rowIndex, setCellProps]); + + return `${rowIndex}, ${columnId}`; +}; + +const renderCellBasedOnColumnId: renderCellValue = ({ columnId }) => { + if (columnId === 'A') { + return 5.5; + } else if (columnId === 'B') { + return 'true'; + } else { + return 'asdf'; + } +}; + +const renderCellRowAsValue: renderCellValue = ({ rowIndex }) => rowIndex; + +const renderCellValueALowBHigh: renderCellValue = ({ rowIndex, columnId }) => + // render A as 0, 1, 0, 1, 0 and B as 9->5 + columnId === 'A' ? rowIndex % 2 : 9 - rowIndex; + function moveColumnToIndex( datagrid: ReactWrapper, columnId: string, @@ -472,9 +509,7 @@ describe('EuiDataGrid', () => { setVisibleColumns: () => {}, }} rowCount={3} - renderCellValue={({ rowIndex, columnId }) => - `${rowIndex}, ${columnId}` - } + renderCellValue={renderCellValueRowAndColumnCount} /> ); @@ -494,9 +529,7 @@ describe('EuiDataGrid', () => { setVisibleColumns: () => {}, }} rowCount={3} - renderCellValue={({ rowIndex, columnId }) => - `${rowIndex}, ${columnId}` - } + renderCellValue={renderCellValueRowAndColumnCount} /> ); @@ -513,17 +546,7 @@ describe('EuiDataGrid', () => { setVisibleColumns: () => {}, }} rowCount={2} - renderCellValue={({ rowIndex, columnId, setCellProps }) => { - useEffect(() => { - setCellProps({ - className: 'customClass', - 'data-test-subj': `cell-${rowIndex}-${columnId}`, - style: { color: columnId === 'A' ? 'red' : 'blue' }, - }); - }, [columnId, rowIndex, setCellProps]); - - return `${rowIndex}, ${columnId}`; - }} + renderCellValue={RenderCellValueSetCellProps} /> ); @@ -653,9 +676,7 @@ describe('EuiDataGrid', () => { setVisibleColumns: () => {}, }} rowCount={3} - renderCellValue={({ rowIndex, columnId }) => - `${rowIndex}, ${columnId}` - } + renderCellValue={renderCellValueRowAndColumnCount} toolbarVisibility={{ additionalControls: }} /> ); @@ -678,7 +699,7 @@ describe('EuiDataGrid', () => { width: 50, headerCellRender: () => leading heading, headerCellProps: { className: 'leadingControlCol' }, - rowCellRender: ({ rowIndex }) => rowIndex, + rowCellRender: renderCellRowAsValue, }, ]} trailingControlColumns={[ @@ -687,13 +708,11 @@ describe('EuiDataGrid', () => { width: 50, headerCellRender: () => trailing heading, headerCellProps: { className: 'trailingControlCol' }, - rowCellRender: ({ rowIndex }) => rowIndex, + rowCellRender: renderCellRowAsValue, }, ]} rowCount={3} - renderCellValue={({ rowIndex, columnId }) => - `${rowIndex}, ${columnId}` - } + renderCellValue={renderCellValueRowAndColumnCount} toolbarVisibility={{ additionalControls: }} /> ); @@ -766,9 +785,7 @@ describe('EuiDataGrid', () => { setVisibleColumns: () => {}, }} rowCount={3} - renderCellValue={({ rowIndex, columnId }) => - `${rowIndex}, ${columnId}` - } + renderCellValue={renderCellValueRowAndColumnCount} /> ); @@ -804,15 +821,7 @@ describe('EuiDataGrid', () => { }} inMemory={{ level: 'pagination' }} rowCount={2} - renderCellValue={({ columnId }) => { - if (columnId === 'A') { - return 5.5; - } else if (columnId === 'B') { - return 'true'; - } else { - return 'asdf'; - } - }} + renderCellValue={renderCellBasedOnColumnId} /> ); @@ -842,9 +851,7 @@ describe('EuiDataGrid', () => { }} inMemory={{ level: 'pagination' }} rowCount={2} - renderCellValue={({ columnId }) => - columnId === 'A' ? 5.5 : 'true' - } + renderCellValue={renderCellBasedOnColumnId} /> ); @@ -871,6 +878,8 @@ describe('EuiDataGrid', () => { G: '2019-09-18T12:31:28.234', H: '2019-09-18T12:31:28.234+0300', }; + const renderCellValue: renderCellValue = ({ columnId }) => + values[columnId]; const component = mount( { }} inMemory={{ level: 'pagination' }} rowCount={1} - renderCellValue={({ columnId }) => values[columnId]} + renderCellValue={renderCellValue} /> ); @@ -906,6 +915,8 @@ describe('EuiDataGrid', () => { A: '-5.80', B: '127.0.0.1', }; + const renderCellValue: renderCellValue = ({ columnId }) => + values[columnId]; const component = mount( { ]} inMemory={{ level: 'pagination' }} rowCount={1} - renderCellValue={({ columnId }) => values[columnId]} + renderCellValue={renderCellValue} /> ); @@ -949,6 +960,13 @@ describe('EuiDataGrid', () => { describe('cell rendering', () => { it('supports hooks', () => { + const RenderCellValueWithHooks: renderCellValue = ({ + rowIndex, + columnId, + }) => { + const [value] = useState(`Hello, Row ${rowIndex}-${columnId}!`); + return {value}; + }; const component = mount( { setVisibleColumns: () => {}, }} rowCount={2} - renderCellValue={({ rowIndex, columnId }) => { - const [value] = useState(`Hello, Row ${rowIndex}-${columnId}!`); - return {value}; - }} + renderCellValue={RenderCellValueWithHooks} /> ); expect(extractGridData(component)).toMatchInlineSnapshot(` @@ -994,7 +1009,7 @@ describe('EuiDataGrid', () => { setVisibleColumns: () => {}, }} rowCount={10} - renderCellValue={({ rowIndex }) => rowIndex} + renderCellValue={renderCellRowAsValue} pagination={{ pageIndex: 1, pageSize: 6, @@ -1019,7 +1034,7 @@ describe('EuiDataGrid', () => { setVisibleColumns: () => {}, }} rowCount={8} - renderCellValue={({ rowIndex }) => rowIndex} + renderCellValue={renderCellRowAsValue} pagination={{ pageIndex: 0, pageSize: 3, @@ -1080,7 +1095,7 @@ describe('EuiDataGrid', () => { setVisibleColumns: () => {}, }} rowCount={8} - renderCellValue={({ rowIndex }) => rowIndex} + renderCellValue={renderCellRowAsValue} pagination={{ pageIndex: 0, pageSize: 3, @@ -1144,7 +1159,7 @@ describe('EuiDataGrid', () => { setVisibleColumns: () => {}, }} rowCount={8} - renderCellValue={({ rowIndex }) => rowIndex} + renderCellValue={renderCellRowAsValue} pagination={{ pageIndex: 0, pageSize: 3, @@ -1366,30 +1381,28 @@ describe('EuiDataGrid', () => { columns={[{ id: 'ColumnA' }, { id: 'ColumnB' }]} columnVisibility={columnVisibility} rowCount={2} - renderCellValue={({ rowIndex, columnId }) => - `${rowIndex}-${columnId}` - } + renderCellValue={renderCellValueRowAndColumnCount} /> ); expect(extractGridData(component)).toEqual([ ['ColumnA', 'ColumnB'], - ['0-ColumnA', '0-ColumnB'], - ['1-ColumnA', '1-ColumnB'], + ['0, ColumnA', '0, ColumnB'], + ['1, ColumnA', '1, ColumnB'], ]); setColumnVisibility(component, 'ColumnA', false); expect(extractGridData(component)).toEqual([ ['ColumnB'], - ['0-ColumnB'], - ['1-ColumnB'], + ['0, ColumnB'], + ['1, ColumnB'], ]); setColumnVisibility(component, 'ColumnA', true); expect(extractGridData(component)).toEqual([ ['ColumnA', 'ColumnB'], - ['0-ColumnA', '0-ColumnB'], - ['1-ColumnA', '1-ColumnB'], + ['0, ColumnA', '0, ColumnB'], + ['1, ColumnA', '1, ColumnB'], ]); }); @@ -1408,24 +1421,22 @@ describe('EuiDataGrid', () => { columns={[{ id: 'ColumnA' }, { id: 'ColumnB' }]} columnVisibility={columnVisibility} rowCount={2} - renderCellValue={({ rowIndex, columnId }) => - `${rowIndex}-${columnId}` - } + renderCellValue={renderCellValueRowAndColumnCount} /> ); expect(extractGridData(component)).toEqual([ ['ColumnA', 'ColumnB'], - ['0-ColumnA', '0-ColumnB'], - ['1-ColumnA', '1-ColumnB'], + ['0, ColumnA', '0, ColumnB'], + ['1, ColumnA', '1, ColumnB'], ]); moveColumnToIndex(component, 'ColumnB', 0); expect(extractGridData(component)).toEqual([ ['ColumnB', 'ColumnA'], - ['0-ColumnB', '0-ColumnA'], - ['1-ColumnB', '1-ColumnA'], + ['0, ColumnB', '0, ColumnA'], + ['1, ColumnB', '1, ColumnA'], ]); }); @@ -1438,21 +1449,27 @@ describe('EuiDataGrid', () => { }, }; + const RenderCellValue: renderCellValue = ({ + rowIndex, + columnId, + setCellProps, + }) => { + useEffect(() => { + if (columnId === 'ColumnB') { + setCellProps({ style: { color: 'blue' } }); + } + }, [columnId, rowIndex, setCellProps]); + + return `${rowIndex}-${columnId}`; + }; + const component = mount( { - useEffect(() => { - if (columnId === 'ColumnB') { - setCellProps({ style: { color: 'blue' } }); - } - }, [columnId, rowIndex, setCellProps]); - - return `${rowIndex}-${columnId}`; - }} + renderCellValue={RenderCellValue} /> ); @@ -1488,9 +1505,7 @@ describe('EuiDataGrid', () => { }, ]} rowCount={1} - renderCellValue={({ rowIndex, columnId }) => - `${rowIndex}, ${columnId}` - } + renderCellValue={renderCellValueRowAndColumnCount} /> ); @@ -1542,6 +1557,9 @@ describe('EuiDataGrid', () => { describe('in-memory sorting', () => { it('sorts on initial render', () => { + const renderCellValue: renderCellValue = ({ rowIndex, columnId }) => + // render A 0->4 and B 9->5 + columnId === 'A' ? rowIndex : 9 - rowIndex; const component = mount( { setVisibleColumns: () => {}, }} rowCount={5} - renderCellValue={({ rowIndex, columnId }) => - // render A 0->4 and B 9->5 - columnId === 'A' ? rowIndex : 9 - rowIndex - } + renderCellValue={renderCellValue} inMemory={{ level: 'sorting' }} sorting={{ columns: [{ id: 'A', direction: 'desc' }], @@ -1583,10 +1598,7 @@ describe('EuiDataGrid', () => { setVisibleColumns: () => {}, }} rowCount={5} - renderCellValue={({ rowIndex, columnId }) => - // render A as 0, 1, 0, 1, 0 and B as 9->5 - columnId === 'A' ? rowIndex % 2 : 9 - rowIndex - } + renderCellValue={renderCellValueALowBHigh} inMemory={{ level: 'sorting' }} sorting={{ columns: [ @@ -1623,10 +1635,7 @@ describe('EuiDataGrid', () => { setVisibleColumns: () => {}, }} rowCount={5} - renderCellValue={({ rowIndex, columnId }) => - // render A as 0, 1, 0, 1, 0 and B as 9->5 - columnId === 'A' ? rowIndex % 2 : 9 - rowIndex - } + renderCellValue={renderCellValueALowBHigh} inMemory={{ level: 'sorting' }} sorting={{ columns: [], @@ -1670,6 +1679,8 @@ describe('EuiDataGrid', () => { component.setProps({ sorting: { columns, onSort } }); component.update(); }); + const renderCellValue: renderCellValue = ({ rowIndex }) => + `1.0.${(rowIndex % 3) + rowIndex}`; // computes as 0,2,4,3,5 const component = mount( { setVisibleColumns: () => {}, }} rowCount={5} - renderCellValue={ - ({ rowIndex }) => `1.0.${(rowIndex % 3) + rowIndex}` // computes as 0,2,4,3,5 - } + renderCellValue={renderCellValue} inMemory={{ level: 'sorting' }} sorting={{ columns: [], @@ -1715,6 +1724,9 @@ describe('EuiDataGrid', () => { }); it('uses schema information to sort', () => { + const renderCellValue: renderCellValue = ({ rowIndex, columnId }) => + // render A 0->4 and B 12->8 + columnId === 'A' ? rowIndex : 12 - rowIndex; const component = mount( { setVisibleColumns: () => {}, }} rowCount={5} - renderCellValue={({ rowIndex, columnId }) => - // render A 0->4 and B 12->8 - columnId === 'A' ? rowIndex : 12 - rowIndex - } + renderCellValue={renderCellValue} inMemory={{ level: 'sorting' }} sorting={{ columns: [{ id: 'B', direction: 'asc' }], @@ -1758,16 +1767,14 @@ describe('EuiDataGrid', () => { setVisibleColumns: () => {}, }} rowCount={2} - renderCellValue={({ rowIndex, columnId }) => - `${rowIndex}-${columnId}` - } + renderCellValue={renderCellValueRowAndColumnCount} /> ); expect(extractGridData(component)).toEqual([ ['A', 'B'], - ['0-A', '0-B'], - ['1-A', '1-B'], + ['0, A', '0, B'], + ['1, A', '1, B'], ]); component.setProps({ @@ -1780,8 +1787,8 @@ describe('EuiDataGrid', () => { expect(extractGridData(component)).toEqual([ ['A', 'C'], - ['0-A', '0-C'], - ['1-A', '1-C'], + ['0, A', '0, C'], + ['1, A', '1, C'], ]); }); @@ -1795,9 +1802,7 @@ describe('EuiDataGrid', () => { setVisibleColumns: () => {}, }} rowCount={2} - renderCellValue={({ rowIndex, columnId }) => - `${rowIndex}-${columnId}` - } + renderCellValue={renderCellValueRowAndColumnCount} /> ); @@ -1843,9 +1848,7 @@ describe('EuiDataGrid', () => { columns: [], }} rowCount={2} - renderCellValue={({ rowIndex, columnId }) => - `${rowIndex}-${columnId}` - } + renderCellValue={renderCellValueRowAndColumnCount} /> ); @@ -1895,9 +1898,7 @@ describe('EuiDataGrid', () => { columns: [], }} rowCount={2} - renderCellValue={({ rowIndex, columnId }) => - `${rowIndex}-${columnId}` - } + renderCellValue={renderCellValueRowAndColumnCount} /> ); @@ -1984,9 +1985,7 @@ describe('EuiDataGrid', () => { setVisibleColumns: () => {}, }} rowCount={2} - renderCellValue={({ rowIndex, columnId }) => - `${rowIndex}-${columnId}` - } + renderCellValue={renderCellValueRowAndColumnCount} /> ); @@ -2033,9 +2032,7 @@ describe('EuiDataGrid', () => { setVisibleColumns: () => {}, }} rowCount={2} - renderCellValue={({ rowIndex, columnId }) => - `${rowIndex}-${columnId}` - } + renderCellValue={renderCellValueRowAndColumnCount} /> ); const arrowA = findTestSubject( @@ -2078,9 +2075,7 @@ describe('EuiDataGrid', () => { setVisibleColumns: () => {}, }} rowCount={2} - renderCellValue={({ rowIndex, columnId }) => - `${rowIndex}-${columnId}` - } + renderCellValue={renderCellValueRowAndColumnCount} /> ); const arrowC = findTestSubject( @@ -2104,9 +2099,7 @@ describe('EuiDataGrid', () => { setVisibleColumns: () => {}, }} rowCount={2} - renderCellValue={({ rowIndex, columnId }) => - `${rowIndex}-${columnId}` - } + renderCellValue={renderCellValueRowAndColumnCount} /> ); const arrowD = findTestSubject( @@ -2169,9 +2162,7 @@ describe('EuiDataGrid', () => { setVisibleColumns: () => {}, }} rowCount={2} - renderCellValue={({ rowIndex, columnId }) => - `${rowIndex}-${columnId}` - } + renderCellValue={renderCellValueRowAndColumnCount} /> ); @@ -2248,7 +2239,7 @@ describe('EuiDataGrid', () => { setVisibleColumns: () => {}, }} rowCount={8} - renderCellValue={({ rowIndex }) => rowIndex} + renderCellValue={renderCellRowAsValue} rowHeightsOptions={{ defaultHeight: 50, rowHeights: { @@ -2328,9 +2319,7 @@ describe('EuiDataGrid', () => { setVisibleColumns: () => {}, }} rowCount={8} - renderCellValue={({ rowIndex, columnId }) => - `${rowIndex}, ${columnId}` - } + renderCellValue={renderCellValueRowAndColumnCount} pagination={pagination} /> ); @@ -2527,9 +2516,7 @@ describe('EuiDataGrid', () => { setVisibleColumns: () => {}, }} rowCount={3} - renderCellValue={({ rowIndex, columnId }) => - `${rowIndex}, ${columnId}` - } + renderCellValue={renderCellValueRowAndColumnCount} /> ); @@ -2560,6 +2547,33 @@ describe('EuiDataGrid', () => { ).toEqual('1, B'); }); it.skip('supports arrow navigation through grids with different interactive cells', () => { + const renderCellValue: renderCellValue = ({ rowIndex, columnId }) => { + if (columnId === 'A') { + return `${rowIndex}, A`; + } + + if (columnId === 'B') { + return ; + } + + if (columnId === 'C') { + return ( + <> + , + + ); + } + + if (columnId === 'D') { + return ( +
+ {rowIndex}, +
+ ); + } + + return 'error'; + }; const component = mount( { setVisibleColumns: () => {}, }} rowCount={2} - renderCellValue={({ rowIndex, columnId }) => { - if (columnId === 'A') { - return `${rowIndex}, A`; - } - - if (columnId === 'B') { - return ; - } - - if (columnId === 'C') { - return ( - <> - , - - ); - } - - if (columnId === 'D') { - return ( -
- {rowIndex}, -
- ); - } - - return 'error'; - }} + renderCellValue={renderCellValue} /> ); @@ -2646,6 +2634,11 @@ describe('EuiDataGrid', () => { expect(focusableCell.getDOMNode()).toBe(document.activeElement); }); it.skip('allows user to enter and exit grid navigation', async () => { + const renderCellValue: renderCellValue = ({ rowIndex, columnId }) => ( + <> + , + + ); const component = mount( { setVisibleColumns: () => {}, }} rowCount={3} - renderCellValue={({ rowIndex, columnId }) => ( - <> - , - - )} + renderCellValue={renderCellValue} /> ); diff --git a/src/components/datagrid/data_grid_types.ts b/src/components/datagrid/data_grid_types.ts index 25c17f44569..dc4daddb25a 100644 --- a/src/components/datagrid/data_grid_types.ts +++ b/src/components/datagrid/data_grid_types.ts @@ -595,18 +595,23 @@ export interface EuiDataGridCellPopoverElementProps ) => void; } -type RenderCellContext = (args?: unknown) => T; +type RenderCellContext> = () => T; -export type EuiDataGridCellValueElementPropsWithContext = - EuiDataGridCellValueElementProps & ReturnType>; +export type EuiDataGridCellValueElementPropsWithContext< + T extends Record +> = EuiDataGridCellValueElementProps & ReturnType>; export type renderCellValue = | ((props: EuiDataGridCellValueElementProps) => ReactNode) | ComponentClass; export type renderCellValueWithContext = - | ((props: EuiDataGridCellValueElementPropsWithContext) => ReactNode) - | ComponentClass; + | (( + props: EuiDataGridCellValueElementPropsWithContext> + ) => ReactNode) + | ComponentClass< + EuiDataGridCellValueElementPropsWithContext> + >; export interface EuiDataGridCellProps { rowIndex: number; @@ -621,7 +626,7 @@ export interface EuiDataGridCellProps { className?: string; popoverContext: DataGridCellPopoverContextShape; renderCellValue: renderCellValue | renderCellValueWithContext; - renderCellContext?: RenderCellContext; + renderCellContext?: RenderCellContext>; renderCellPopover?: | JSXElementConstructor | ((props: EuiDataGridCellPopoverElementProps) => ReactNode); From 3261a145f3e1c824e491ba88cac0147645dd097e Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Tue, 21 Nov 2023 06:37:38 +0000 Subject: [PATCH 03/17] Fix unneeded re-renders --- .../__snapshots__/data_grid.test.tsx.snap | 8 +- .../data_grid_body_virtualized.test.tsx.snap | 2 +- .../datagrid/body/data_grid_body_custom.tsx | 25 +- .../body/data_grid_body_virtualized.tsx | 552 ++++++++++-------- .../datagrid/body/data_grid_cell.tsx | 323 +++++----- .../body/data_grid_cell_actions.test.tsx | 372 +++++++++++- .../datagrid/body/data_grid_cell_actions.tsx | 64 +- .../datagrid/body/data_grid_cell_popover.tsx | 101 ++-- .../datagrid/body/data_grid_cell_wrapper.tsx | 287 +++++---- .../datagrid/body/data_grid_row_manager.ts | 6 +- .../body/footer/use_data_grid_footer.tsx | 4 +- .../body/header/data_grid_header_cell.tsx | 311 +++++----- .../body/header/data_grid_header_row.tsx | 131 +++-- .../body/header/use_data_grid_header.tsx | 8 +- .../datagrid/controls/column_selector.tsx | 74 ++- .../datagrid/controls/display_selector.tsx | 24 +- src/components/datagrid/data_grid.tsx | 30 +- src/components/datagrid/utils/focus.ts | 13 +- src/components/datagrid/utils/sorting.ts | 12 +- 19 files changed, 1459 insertions(+), 888 deletions(-) diff --git a/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap b/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap index b6dc51f0c29..c344ae0caa5 100644 --- a/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap +++ b/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap @@ -579,7 +579,7 @@ exports[`EuiDataGrid rendering renders additional toolbar controls 1`] = ` style="position: relative; height: 9007199254740991px; width: 9007199254740991px; overflow: auto; will-change: transform; direction: ltr;" >
{ + return { + schema, + schemaDetectors, + pagination, + columns, + leadingControlColumns, + trailingControlColumns, + visibleColCount, + columnWidths, + defaultColumnWidth, + renderCellValue, + renderCellContext, + renderCellPopover, + interactiveCellId, + setRowHeight, + rowHeightsOptions, + rowHeightUtils, + }; + }, [ schema, schemaDetectors, pagination, @@ -141,7 +160,7 @@ export const EuiDataGridBodyCustomRender: FunctionComponent< setRowHeight, rowHeightsOptions, rowHeightUtils, - }; + ]); const _Cell = useCallback( ({ colIndex, visibleRowIndex, ...rest }) => { @@ -158,7 +177,7 @@ export const EuiDataGridBodyCustomRender: FunctionComponent< }; return ; }, - [...Object.values(cellProps), getRowHeight] // eslint-disable-line react-hooks/exhaustive-deps + [cellProps, getRowHeight, rowHeightUtils, rowHeightsOptions] ); // Allow consumers to pass custom props/attributes/listeners etc. to the wrapping div diff --git a/src/components/datagrid/body/data_grid_body_virtualized.tsx b/src/components/datagrid/body/data_grid_body_virtualized.tsx index 212c2b858bf..edae90be965 100644 --- a/src/components/datagrid/body/data_grid_body_virtualized.tsx +++ b/src/components/datagrid/body/data_grid_body_virtualized.tsx @@ -11,15 +11,19 @@ import React, { forwardRef, FunctionComponent, createContext, + useCallback, useContext, useEffect, useRef, + useMemo, PropsWithChildren, + memo, } from 'react'; import { GridChildComponentProps, VariableSizeGrid as Grid, VariableSizeGridProps, + GridOnItemsRenderedProps, } from 'react-window'; import { useResizeObserver } from '../../observer/resize_observer'; import { useDataGridHeader } from './header'; @@ -40,25 +44,27 @@ import { useRowHeightUtils, useDefaultRowHeight } from '../utils/row_heights'; import { useScrollBars, useScroll } from '../utils/scrolling'; import { IS_JEST_ENVIRONMENT } from '../../../utils'; -export const _Cell: FunctionComponent = ({ - columnIndex, - rowIndex, - style, - data, -}) => { - const { headerRowHeight } = useContext(DataGridWrapperRowsContext); - return ( - = memo( + ({ columnIndex, rowIndex, style, data }) => { + const cellStyles = useMemo(() => { + const { headerRowHeight } = data; + return { ...style, top: `${parseFloat(style.top as string) + headerRowHeight}px`, - }} - {...data} - /> - ); -}; + }; + }, [style, data]); + return ( + + ); + } +); + +_Cell.displayName = 'Cell'; // Context is required to pass props to react-window's innerElementType // @see https://github.com/bvaughn/react-window/issues/404 @@ -72,255 +78,237 @@ export const DataGridWrapperRowsContext = type InnerElementProps = PropsWithChildren & { style: { height: number; + width: number; + pointerEvents: 'none' | undefined; }; }; -const InnerElement: VariableSizeGridProps['innerElementType'] = forwardRef< - HTMLDivElement, - InnerElementProps ->(({ children, style, ...rest }, ref) => { - const { headerRowHeight, headerRow, footerRow } = useContext( - DataGridWrapperRowsContext - ); - return ( - <> -
( + ({ children, style, ...rest }, ref) => { + const { headerRowHeight, headerRow, footerRow } = useContext( + DataGridWrapperRowsContext + ); + const innerElementStyles = useMemo(() => { + return { + width: style.width, + pointerEvents: style.pointerEvents, height: style.height + headerRowHeight, - }} - {...rest} - > - {headerRow} - {children} -
- {footerRow} - - ); -}); + }; + }, [headerRowHeight, style]); + return ( + <> +
+ {headerRow} + {children} +
+ {footerRow} + + ); + } + ) +); InnerElement.displayName = 'EuiDataGridInnerElement'; -export const EuiDataGridBodyVirtualized: FunctionComponent< - EuiDataGridBodyProps -> = ({ - leadingControlColumns, - trailingControlColumns, - columns, - visibleColCount, - schema, - schemaDetectors, - rowCount, - visibleRows: { startRow, endRow, visibleRowCount }, - renderCellValue, - renderCellContext, - renderCellPopover, - renderFooterCellValue, - interactiveCellId, - pagination, - headerIsInteractive, - handleHeaderMutation, - setVisibleColumns, - switchColumnPos, - onColumnResize, - rowHeightsOptions, - virtualizationOptions, - isFullScreen, - gridStyles, - gridWidth, - gridRef, - gridItemsRendered, - wrapperRef, -}) => { - /** - * Grid refs & observers - */ - const wrapperDimensions = useResizeObserver(wrapperRef.current); - const outerGridRef = useRef(null); // container that becomes scrollable - const innerGridRef = useRef(null); // container sized to fit all content +export const EuiDataGridBodyVirtualized: FunctionComponent = + memo( + ({ + leadingControlColumns, + trailingControlColumns, + columns, + visibleColCount, + schema, + schemaDetectors, + rowCount, + visibleRows: { startRow, endRow, visibleRowCount }, + renderCellValue, + renderCellContext, + renderCellPopover, + renderFooterCellValue, + interactiveCellId, + pagination, + headerIsInteractive, + handleHeaderMutation, + setVisibleColumns, + switchColumnPos, + onColumnResize, + rowHeightsOptions, + virtualizationOptions, + isFullScreen, + gridStyles, + gridWidth, + gridRef, + gridItemsRendered, + wrapperRef, + }) => { + /** + * Grid refs & observers + */ + const wrapperDimensions = useResizeObserver(wrapperRef.current); + const outerGridRef = useRef(null); // container that becomes scrollable + const innerGridRef = useRef(null); // container sized to fit all content - /** - * Scroll bars - */ - const { - scrollBarHeight, - hasVerticalScroll, - hasHorizontalScroll, - scrollBorderOverlay, - } = useScrollBars(outerGridRef, gridStyles.border); + /** + * Scroll bars + */ + const { + scrollBarHeight, + hasVerticalScroll, + hasHorizontalScroll, + scrollBorderOverlay, + } = useScrollBars(outerGridRef, gridStyles.border); - /** - * Widths - */ - const virtualizeContainerWidth = useVirtualizeContainerWidth( - outerGridRef.current, - gridWidth, - pagination?.pageSize - ); + /** + * Widths + */ + const virtualizeContainerWidth = useVirtualizeContainerWidth( + outerGridRef.current, + gridWidth, + pagination?.pageSize + ); - // compute the default column width from the container's width and count of visible columns - const defaultColumnWidth = useDefaultColumnWidth( - virtualizeContainerWidth, - leadingControlColumns, - trailingControlColumns, - columns - ); + // compute the default column width from the container's width and count of visible columns + const defaultColumnWidth = useDefaultColumnWidth( + virtualizeContainerWidth, + leadingControlColumns, + trailingControlColumns, + columns + ); - const { columnWidths, setColumnWidth, getColumnWidth } = useColumnWidths({ - columns, - leadingControlColumns, - trailingControlColumns, - defaultColumnWidth, - onColumnResize, - }); + const { columnWidths, setColumnWidth, getColumnWidth } = useColumnWidths({ + columns, + leadingControlColumns, + trailingControlColumns, + defaultColumnWidth, + onColumnResize, + }); - /** - * Header & footer - */ - const { headerRow, headerRowHeight } = useDataGridHeader({ - headerIsInteractive, - handleHeaderMutation, - switchColumnPos, - setVisibleColumns, - leadingControlColumns, - trailingControlColumns, - columns, - columnWidths, - defaultColumnWidth, - setColumnWidth, - schema, - schemaDetectors, - }); + /** + * Header & footer + */ + const { headerRow, headerRowHeight } = useDataGridHeader({ + headerIsInteractive, + handleHeaderMutation, + switchColumnPos, + setVisibleColumns, + leadingControlColumns, + trailingControlColumns, + columns, + columnWidths, + defaultColumnWidth, + setColumnWidth, + schema, + schemaDetectors, + }); - const { footerRow, footerRowHeight } = useDataGridFooter({ - renderFooterCellValue, - renderCellPopover, - rowIndex: visibleRowCount, - visibleRowIndex: visibleRowCount, - interactiveCellId, - leadingControlColumns, - trailingControlColumns, - columns, - columnWidths, - defaultColumnWidth, - schema, - }); + const { footerRow, footerRowHeight } = useDataGridFooter({ + renderFooterCellValue, + renderCellPopover, + rowIndex: visibleRowCount, + visibleRowIndex: visibleRowCount, + interactiveCellId, + leadingControlColumns, + trailingControlColumns, + columns, + columnWidths, + defaultColumnWidth, + schema, + }); - /** - * Handle scrolling cells fully into view - */ - useScroll({ - gridRef, - outerGridRef, - hasGridScrolling: hasVerticalScroll || hasHorizontalScroll, - headerRowHeight, - footerRowHeight, - visibleRowCount, - hasStickyFooter: !!(renderFooterCellValue && gridStyles.stickyFooter), - }); + /** + * Handle scrolling cells fully into view + */ + useScroll({ + gridRef, + outerGridRef, + hasGridScrolling: hasVerticalScroll || hasHorizontalScroll, + headerRowHeight, + footerRowHeight, + visibleRowCount, + hasStickyFooter: !!(renderFooterCellValue && gridStyles.stickyFooter), + }); - /** - * Row manager - */ - const rowManager = useRowManager({ - innerGridRef, - rowClasses: gridStyles.rowClasses, - }); + /** + * Row manager + */ + const rowManager = useRowManager({ + innerGridRef, + rowClasses: gridStyles.rowClasses, + }); - /** - * Heights - */ - const rowHeightUtils = useRowHeightUtils({ - virtualization: { - gridRef, - outerGridElementRef: outerGridRef, - gridItemsRenderedRef: gridItemsRendered, - }, - rowHeightsOptions, - gridStyles, - columns, - }); + /** + * Heights + */ + const rowHeightUtils = useRowHeightUtils({ + virtualization: { + gridRef, + outerGridElementRef: outerGridRef, + gridItemsRenderedRef: gridItemsRendered, + }, + rowHeightsOptions, + gridStyles, + columns, + }); - const { defaultRowHeight, setRowHeight, getRowHeight } = useDefaultRowHeight({ - rowHeightsOptions, - rowHeightUtils, - }); + const { defaultRowHeight, setRowHeight, getRowHeight } = + useDefaultRowHeight({ + rowHeightsOptions, + rowHeightUtils, + }); - const unconstrainedHeight = useUnconstrainedHeight({ - rowHeightUtils, - startRow, - endRow, - rowHeightsOptions, - defaultRowHeight, - headerRowHeight, - footerRowHeight, - scrollBarHeight, - innerGridRef, - }); + const unconstrainedHeight = useUnconstrainedHeight({ + rowHeightUtils, + startRow, + endRow, + rowHeightsOptions, + defaultRowHeight, + headerRowHeight, + footerRowHeight, + scrollBarHeight, + innerGridRef, + }); - /** - * Final grid height & width - */ - const { finalWidth, finalHeight } = useFinalGridDimensions({ - unconstrainedHeight, - unconstrainedWidth: 0, // unable to determine this until the container's size is known - wrapperDimensions, - wrapperRef, - isFullScreen, - rowCount, - }); + /** + * Final grid height & width + */ + const { finalWidth, finalHeight } = useFinalGridDimensions({ + unconstrainedHeight, + unconstrainedWidth: 0, // unable to determine this until the container's size is known + wrapperDimensions, + wrapperRef, + isFullScreen, + rowCount, + }); - /** - * Grid resets - */ - useEffect(() => { - if (gridRef.current) { - gridRef.current.resetAfterColumnIndex(0); - } - }, [gridRef, columns, columnWidths, defaultColumnWidth]); + /** + * Grid resets + */ + useEffect(() => { + if (gridRef.current) { + gridRef.current.resetAfterColumnIndex(0); + } + }, [gridRef, columns, columnWidths, defaultColumnWidth]); - useEffect(() => { - if (gridRef.current && rowHeightsOptions) { - gridRef.current.resetAfterRowIndex(0); - } - }, [ - gridRef, - pagination?.pageIndex, - rowHeightsOptions, - gridStyles?.cellPadding, - gridStyles?.fontSize, - ]); + useEffect(() => { + if (gridRef.current && rowHeightsOptions) { + gridRef.current.resetAfterRowIndex(0); + } + }, [ + gridRef, + pagination?.pageIndex, + rowHeightsOptions, + gridStyles?.cellPadding, + gridStyles?.fontSize, + ]); - useEffect(() => { - if (gridRef.current) { - gridRef.current.resetAfterRowIndex(0); - } - }, [gridRef, getRowHeight]); + useEffect(() => { + if (gridRef.current) { + gridRef.current.resetAfterRowIndex(0); + } + }, [gridRef, getRowHeight]); - return IS_JEST_ENVIRONMENT || finalWidth > 0 ? ( - - { - gridItemsRendered.current = itemsRendered; - virtualizationOptions?.onItemsRendered?.(itemsRendered); - }} - innerElementType={InnerElement} - outerRef={outerGridRef} - innerRef={innerGridRef} - columnCount={visibleColCount} - width={finalWidth} - columnWidth={getColumnWidth} - height={finalHeight} - rowHeight={getRowHeight} - itemData={{ + const itemData = useMemo(() => { + return { schemaDetectors, setRowHeight, leadingControlColumns, @@ -338,14 +326,68 @@ export const EuiDataGridBodyVirtualized: FunctionComponent< rowHeightUtils, rowManager, pagination, - }} - rowCount={ - IS_JEST_ENVIRONMENT || headerRowHeight > 0 ? visibleRowCount : 0 - } - > - {_Cell} - - {scrollBorderOverlay} - - ) : null; -}; + headerRowHeight, + }; + }, [ + schemaDetectors, + setRowHeight, + leadingControlColumns, + trailingControlColumns, + columns, + visibleColCount, + schema, + columnWidths, + defaultColumnWidth, + renderCellValue, + renderCellContext, + renderCellPopover, + interactiveCellId, + rowHeightsOptions, + rowHeightUtils, + rowManager, + pagination, + headerRowHeight, + ]); + + const onItemsRendered = useCallback( + (itemsRendered: GridOnItemsRenderedProps) => { + gridItemsRendered.current = itemsRendered; + virtualizationOptions?.onItemsRendered?.(itemsRendered); + }, + [gridItemsRendered, virtualizationOptions] + ); + + const rowWrapperContextValue = useMemo(() => { + return { headerRowHeight, headerRow, footerRow }; + }, [headerRowHeight, headerRow, footerRow]); + + return IS_JEST_ENVIRONMENT || finalWidth > 0 ? ( + + 0 ? visibleRowCount : 0 + } + > + {_Cell} + + {scrollBorderOverlay} + + ) : null; + } + ); diff --git a/src/components/datagrid/body/data_grid_cell.tsx b/src/components/datagrid/body/data_grid_cell.tsx index e9d0fd5915f..314f85b9dc0 100644 --- a/src/components/datagrid/body/data_grid_cell.tsx +++ b/src/components/datagrid/body/data_grid_cell.tsx @@ -107,63 +107,82 @@ const EuiDataGridCellContent: FunctionComponent< } }, [rest, renderCellContext]); - let cellContent = ( -
{ - setCellContentsRef(el); - setPopoverAnchorRef.current = - cellHeightType === 'default' - ? // Default height cells need the popover to be anchored on the wrapper, - // in order for the popover to centered on the full cell width (as content - // width is affected by the width of cell actions) - (el?.parentElement as HTMLDivElement) - : // Numerical height cells need the popover anchor to be below the wrapper - // class, in order to set height: 100% on the portalled popover div wrappers - el; - }} - data-datagrid-cellcontent - className={classes} - > - -
- ); - if (cellHeightType === 'lineCount' && !isControlColumn) { - const lines = rowHeightUtils!.getLineCount(rowHeight)!; - cellContent = ( - - {cellContent} - - ); - } - - const screenReaderText = ( - -
{ + setCellContentsRef(el); + setPopoverAnchorRef.current = + cellHeightType === 'default' + ? // Default height cells need the popover to be anchored on the wrapper, + // in order for the popover to centered on the full cell width (as content + // width is affected by the width of cell actions) + (el?.parentElement as HTMLDivElement) + : // Numerical height cells need the popover anchor to be below the wrapper + // class, in order to set height: 100% on the portalled popover div wrappers + el; + }} + data-datagrid-cellcontent + className={classes} + > + -

- - ); +
+ ); + }, [ + CellElement, + classes, + column, + colIndex, + mergedProps, + rowIndex, + cellHeightType, + setCellContentsRef, + setPopoverAnchorRef, + rest.columnType, + ]); + const truncatedCellContent = useMemo(() => { + if (cellHeightType === 'lineCount' && !isControlColumn) { + const lines = rowHeightUtils!.getLineCount(rowHeight)!; + return ( + + {cellContent} + + ); + } else { + return cellContent; + } + }, [ + cellContent, + cellHeightType, + isControlColumn, + rowHeightUtils, + rowHeight, + ]); return (
- {cellContent} - {screenReaderText} + {truncatedCellContent} + + + {cellActions}
); @@ -410,6 +429,8 @@ export class EuiDataGridCell extends Component< if (nextProps.rowHeightsOptions !== this.props.rowHeightsOptions) return true; if (nextProps.renderCellValue !== this.props.renderCellValue) return true; + if (nextProps.renderCellContext !== this.props.renderCellContext) + return true; if (nextProps.renderCellPopover !== this.props.renderCellPopover) return true; if (nextProps.interactiveCellId !== this.props.interactiveCellId) @@ -593,6 +614,71 @@ export class EuiDataGridCell extends Component< } }; + handleCellKeyDown = (event: KeyboardEvent) => { + const { + popoverContext: { openCellPopover }, + visibleRowIndex, + colIndex, + } = this.props; + const isExpandable = this.isExpandable(); + const popoverIsOpen = this.isPopoverOpen(); + + if (isExpandable) { + if (popoverIsOpen) { + return; + } + switch (event.key) { + case keys.ENTER: + case keys.F2: + event.preventDefault(); + openCellPopover({ rowIndex: visibleRowIndex, colIndex }); + break; + } + } else { + if ( + event.key === keys.ENTER || + event.key === keys.F2 || + event.key === keys.ESCAPE + ) { + const interactables = this.getInteractables(); + if (interactables.length >= 2) { + switch (event.key) { + case keys.ENTER: + // `Enter` only activates the trap + if (this.state.isEntered === false) { + this.enableTabbing(); + this.setState({ isEntered: true }); + + // result of this keypress is focus shifts to the first interactive element + // and then the browser fires the onClick event because that's how [Enter] works + // so we need to prevent that default action otherwise entering the trap triggers the first element + event.preventDefault(); + } + break; + case keys.F2: + // toggle interactives' focus trap + this.setState(({ isEntered }) => { + if (isEntered) { + this.preventTabbing(); + } else { + this.enableTabbing(); + } + return { isEntered: !isEntered }; + }); + break; + case keys.ESCAPE: + // `Escape` only de-activates the trap + this.preventTabbing(); + if (this.state.isEntered === true) { + this.setState({ isEntered: false }); + } + break; + } + } + } + } + }; + render() { const { width, @@ -606,9 +692,11 @@ export class EuiDataGridCell extends Component< rowHeightsOptions, rowManager, pagination, + rowIndex, + visibleRowIndex, + colIndex, ...rest } = this.props; - const { rowIndex, visibleRowIndex, colIndex } = rest; const isExpandable = this.isExpandable(); const popoverIsOpen = this.isPopoverOpen(); @@ -653,63 +741,6 @@ export class EuiDataGridCell extends Component< ...cellPropsStyle, // apply anything from setCellProps({ style }) }; - const handleCellKeyDown = (event: KeyboardEvent) => { - if (isExpandable) { - if (popoverIsOpen) { - return; - } - switch (event.key) { - case keys.ENTER: - case keys.F2: - event.preventDefault(); - openCellPopover({ rowIndex: visibleRowIndex, colIndex }); - break; - } - } else { - if ( - event.key === keys.ENTER || - event.key === keys.F2 || - event.key === keys.ESCAPE - ) { - const interactables = this.getInteractables(); - if (interactables.length >= 2) { - switch (event.key) { - case keys.ENTER: - // `Enter` only activates the trap - if (this.state.isEntered === false) { - this.enableTabbing(); - this.setState({ isEntered: true }); - - // result of this keypress is focus shifts to the first interactive element - // and then the browser fires the onClick event because that's how [Enter] works - // so we need to prevent that default action otherwise entering the trap triggers the first element - event.preventDefault(); - } - break; - case keys.F2: - // toggle interactives' focus trap - this.setState(({ isEntered }) => { - if (isEntered) { - this.preventTabbing(); - } else { - this.enableTabbing(); - } - return { isEntered: !isEntered }; - }); - break; - case keys.ESCAPE: - // `Escape` only de-activates the trap - this.preventTabbing(); - if (this.state.isEntered === true) { - this.setState({ isEntered: false }); - } - break; - } - } - } - } - }; - const rowHeight = rowHeightUtils?.getRowHeightOption( rowIndex, rowHeightsOptions @@ -734,40 +765,12 @@ export class EuiDataGridCell extends Component< isControlColumn: cellClasses.includes( 'euiDataGridRowCell--controlColumn' ), + rowIndex, + visibleRowIndex, + colIndex, ariaRowIndex, }; - const cellActions = showCellActions && ( - { - if (popoverIsOpen) { - closeCellPopover(); - } else { - openCellPopover({ rowIndex: visibleRowIndex, colIndex }); - } - }} - /> - ); - - const cellContent = isExpandable ? ( - - ) : ( - { - this.setState({ isEntered: false }, this.preventTabbing); - }} - clickOutsideDisables={true} - > - - - ); - const cell = (
{ this.setState({ enableInteractions: true }); @@ -793,7 +796,39 @@ export class EuiDataGridCell extends Component< }} onBlur={this.onBlur} > - {cellContent} + {isExpandable ? ( + { + if (popoverIsOpen) { + closeCellPopover(); + } else { + openCellPopover({ rowIndex: visibleRowIndex, colIndex }); + } + }} + /> + ) + } + /> + ) : ( + { + this.setState({ isEntered: false }, this.preventTabbing); + }} + clickOutsideDisables={true} + > + + + )}
); diff --git a/src/components/datagrid/body/data_grid_cell_actions.test.tsx b/src/components/datagrid/body/data_grid_cell_actions.test.tsx index 151f3003814..e803de23d07 100644 --- a/src/components/datagrid/body/data_grid_cell_actions.test.tsx +++ b/src/components/datagrid/body/data_grid_cell_actions.test.tsx @@ -7,7 +7,7 @@ */ import React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, mount } from 'enzyme'; import { render } from '../../../test/rtl'; import { EuiDataGridColumnCellAction } from '../data_grid_types'; @@ -29,35 +29,351 @@ describe('EuiDataGridCellActions', () => { }; it('renders an expand button', () => { - const component = shallow(); + const component = mount(); expect(component).toMatchInlineSnapshot(` -
- - - -
- `); + + + + + + + + +
+ + `); const button: Function = component.find('EuiI18n').renderProp('children'); expect(button('expandButtonTitle')).toMatchInlineSnapshot(` - + + + + + + `); }); @@ -100,13 +416,9 @@ describe('EuiDataGridCellActions', () => { key="0" rowIndex={0} /> - - - +
`); }); diff --git a/src/components/datagrid/body/data_grid_cell_actions.tsx b/src/components/datagrid/body/data_grid_cell_actions.tsx index bc3ed204dfe..01e452633cb 100644 --- a/src/components/datagrid/body/data_grid_cell_actions.tsx +++ b/src/components/datagrid/body/data_grid_cell_actions.tsx @@ -20,24 +20,17 @@ import { EuiFlexGroup, EuiFlexItem } from '../../flex'; import { EuiPopoverFooter } from '../../popover'; import classNames from 'classnames'; -export const EuiDataGridCellActions = ({ - onExpandClick, - column, - rowIndex, - colIndex, - cellHeightType, -}: { - onExpandClick: () => void; - column?: EuiDataGridColumn; - rowIndex: number; - colIndex: number; - cellHeightType: string; -}) => { - // Note: The cell expand button/expansion popover is *always* rendered if - // column.cellActions is present (regardless of column.isExpandable). - // This is because cell actions are not otherwise accessible to keyboard - // or screen reader users - const expandButton = ( +const ButtonComponent = (props: EuiButtonIconProps) => ( + +); + +const ExpandButton = ({ onExpandClick }: { onExpandClick: () => void }) => { + return ( ); +}; + +export const EuiDataGridCellActions = ({ + onExpandClick, + column, + rowIndex, + colIndex, + cellHeightType, +}: { + onExpandClick: () => void; + column?: EuiDataGridColumn; + rowIndex: number; + colIndex: number; + cellHeightType: string; +}) => { + // Note: The cell expand button/expansion popover is *always* rendered if + // column.cellActions is present (regardless of column.isExpandable). + // This is because cell actions are not otherwise accessible to keyboard + // or screen reader users const additionalButtons = useMemo(() => { if (!column || !Array.isArray(column?.cellActions)) return []; - const ButtonComponent = (props: EuiButtonIconProps) => ( - - ); - const [visibleCellActions] = getVisibleCellActions( column?.cellActions, column?.visibleCellActions @@ -98,7 +101,12 @@ export const EuiDataGridCellActions = ({ 'euiDataGridRowCell__actions--overlay': cellHeightType !== 'default', }); - return
{[...additionalButtons, expandButton]}
; + return ( +
+ {additionalButtons.map((button) => button)} + +
+ ); }; export const EuiDataGridCellPopoverActions = ({ diff --git a/src/components/datagrid/body/data_grid_cell_popover.tsx b/src/components/datagrid/body/data_grid_cell_popover.tsx index a64a894e121..cd025bb9e39 100644 --- a/src/components/datagrid/body/data_grid_cell_popover.tsx +++ b/src/components/datagrid/body/data_grid_cell_popover.tsx @@ -6,7 +6,13 @@ * Side Public License, v 1. */ -import React, { createContext, useState, useCallback, ReactNode } from 'react'; +import React, { + createContext, + useState, + useCallback, + ReactNode, + useMemo, +} from 'react'; import classNames from 'classnames'; import { keys } from '../../../services'; @@ -68,50 +74,61 @@ export const useCellPopover = (): { [popoverIsOpen, cellLocation] ); - const cellPopoverContext = { - popoverIsOpen, - closeCellPopover, - openCellPopover, - cellLocation, - setPopoverAnchor, - setPopoverContent, - setCellPopoverProps, - }; + const cellPopoverContext = useMemo(() => { + return { + popoverIsOpen, + closeCellPopover, + openCellPopover, + cellLocation, + setPopoverAnchor, + setPopoverContent, + setCellPopoverProps, + }; + }, [popoverIsOpen, closeCellPopover, openCellPopover, cellLocation]); // Note that this popover is rendered once at the top grid level, rather than one popover per cell - const cellPopover = popoverIsOpen && popoverAnchor && ( - { - if (event.key === keys.F2 || event.key === keys.ESCAPE) { - event.preventDefault(); - event.stopPropagation(); - closeCellPopover(); - // Ensure focus is returned to the parent cell - requestAnimationFrame(() => popoverAnchor.parentElement!.focus()); - } - }} - button={popoverAnchor} - closePopover={closeCellPopover} - > - {popoverContent} - - ); - return { cellPopoverContext, cellPopover }; + return useMemo(() => { + const cellPopover = popoverIsOpen && popoverAnchor && ( + { + if (event.key === keys.F2 || event.key === keys.ESCAPE) { + event.preventDefault(); + event.stopPropagation(); + closeCellPopover(); + // Ensure focus is returned to the parent cell + requestAnimationFrame(() => popoverAnchor.parentElement!.focus()); + } + }} + button={popoverAnchor} + closePopover={closeCellPopover} + > + {popoverContent} + + ); + return { cellPopoverContext, cellPopover }; + }, [ + cellPopoverProps, + cellPopoverContext, + closeCellPopover, + popoverAnchor, + popoverContent, + popoverIsOpen, + ]); }; /** diff --git a/src/components/datagrid/body/data_grid_cell_wrapper.tsx b/src/components/datagrid/body/data_grid_cell_wrapper.tsx index dca2e9b4319..e931808d3bb 100644 --- a/src/components/datagrid/body/data_grid_cell_wrapper.tsx +++ b/src/components/datagrid/body/data_grid_cell_wrapper.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { FunctionComponent, useContext, useMemo } from 'react'; +import React, { FunctionComponent, useContext, useMemo, memo } from 'react'; import classNames from 'classnames'; import { @@ -52,133 +52,172 @@ type CellProps = Pick< * It grabs context, determines the type of cell being rendered * (e.g. control vs data cell), & sets shared props between all cells */ -export const Cell: FunctionComponent = ({ - colIndex, - visibleRowIndex, - style, - schema, - schemaDetectors, - pagination, - columns, - leadingControlColumns, - trailingControlColumns, - visibleColCount, - columnWidths, - defaultColumnWidth, - renderCellValue, - renderCellContext, - renderCellPopover, - interactiveCellId, - setRowHeight, - rowHeightsOptions, - rowHeightUtils, - rowManager, - ...rest -}) => { - const popoverContext = useContext(DataGridCellPopoverContext); - const { getCorrectRowIndex } = useContext(DataGridSortingContext); - - let cellContent; - - const isFirstColumn = colIndex === 0; - const isLastColumn = colIndex === visibleColCount - 1; - - const isLeadingControlColumn = colIndex < leadingControlColumns.length; - const isTrailingControlColumn = - colIndex >= leadingControlColumns.length + columns.length; - - const datacolIndex = colIndex - leadingControlColumns.length; - const column = columns[datacolIndex]; - const columnId = column?.id; - - const textTransform = useMemo( - () => - schemaDetectors.filter((row: EuiDataGridSchemaDetector) => { - return column?.schema - ? column?.schema === row.type - : columnId === row.type; - })[0]?.textTransform, - [columnId, column?.schema, schemaDetectors] - ); - - const classes = classNames({ - 'euiDataGridRowCell--firstColumn': isFirstColumn, - 'euiDataGridRowCell--lastColumn': isLastColumn, - 'euiDataGridRowCell--controlColumn': - isLeadingControlColumn || isTrailingControlColumn, - [`euiDataGridRowCell--${textTransform}`]: textTransform, - }); - - const sharedCellProps = { - rowIndex: getCorrectRowIndex(visibleRowIndex), - visibleRowIndex, +export const Cell: FunctionComponent = memo( + ({ colIndex, - interactiveCellId, - className: classes, + visibleRowIndex, style, + schema, + schemaDetectors, + pagination, + columns, + leadingControlColumns, + trailingControlColumns, + visibleColCount, + columnWidths, + defaultColumnWidth, + renderCellValue, + renderCellContext, + renderCellPopover, + interactiveCellId, + setRowHeight, rowHeightsOptions, rowHeightUtils, - setRowHeight: isFirstColumn ? setRowHeight : undefined, rowManager, - popoverContext, - pagination, - renderCellContext, - }; - - if (isLeadingControlColumn) { - const leadingColumn = leadingControlColumns[colIndex]; - const { id, rowCellRender } = leadingColumn; - - cellContent = ( - - ); - } else if (isTrailingControlColumn) { - const columnOffset = columns.length + leadingControlColumns.length; - const trailingcolIndex = colIndex - columnOffset; - const trailingColumn = trailingControlColumns[trailingcolIndex]; - const { id, rowCellRender } = trailingColumn; - - cellContent = ( - + ...rest + }) => { + const popoverContext = useContext(DataGridCellPopoverContext); + const { getCorrectRowIndex } = useContext(DataGridSortingContext); + + const isFirstColumn = colIndex === 0; + const isLastColumn = colIndex === visibleColCount - 1; + + const isLeadingControlColumn = colIndex < leadingControlColumns.length; + const isTrailingControlColumn = + colIndex >= leadingControlColumns.length + columns.length; + + const datacolIndex = colIndex - leadingControlColumns.length; + const column = columns[datacolIndex]; + const columnId = column?.id; + + const textTransform = useMemo( + () => + schemaDetectors.filter((row: EuiDataGridSchemaDetector) => { + return column?.schema + ? column?.schema === row.type + : columnId === row.type; + })[0]?.textTransform, + [columnId, column?.schema, schemaDetectors] ); - } else { - // this is a normal data cell - const columnType = schema[columnId] ? schema[columnId].columnType : null; - - const isExpandable = - column.isExpandable !== undefined ? column.isExpandable : true; - - const width = columnWidths[columnId] || defaultColumnWidth; - - cellContent = ( - - ); - } - return cellContent; -}; + const sharedCellProps = useMemo(() => { + const classes = classNames({ + 'euiDataGridRowCell--firstColumn': isFirstColumn, + 'euiDataGridRowCell--lastColumn': isLastColumn, + 'euiDataGridRowCell--controlColumn': + isLeadingControlColumn || isTrailingControlColumn, + [`euiDataGridRowCell--${textTransform}`]: textTransform, + }); + return { + rowIndex: getCorrectRowIndex(visibleRowIndex), + visibleRowIndex, + colIndex, + interactiveCellId, + className: classes, + style, + rowHeightsOptions, + rowHeightUtils, + setRowHeight: isFirstColumn ? setRowHeight : undefined, + rowManager, + popoverContext, + pagination, + renderCellContext, + }; + }, [ + colIndex, + setRowHeight, + visibleRowIndex, + getCorrectRowIndex, + interactiveCellId, + style, + rowHeightsOptions, + rowHeightUtils, + rowManager, + popoverContext, + pagination, + renderCellContext, + isFirstColumn, + isLastColumn, + isLeadingControlColumn, + isTrailingControlColumn, + textTransform, + ]); + + const cellContent = useMemo(() => { + if (isLeadingControlColumn) { + const leadingColumn = leadingControlColumns[colIndex]; + const { id, rowCellRender } = leadingColumn; + + return ( + + ); + } else if (isTrailingControlColumn) { + const columnOffset = columns.length + leadingControlColumns.length; + const trailingcolIndex = colIndex - columnOffset; + const trailingColumn = trailingControlColumns[trailingcolIndex]; + const { id, rowCellRender } = trailingColumn; + + return ( + + ); + } else { + // this is a normal data cell + const columnType = schema[columnId] + ? schema[columnId].columnType + : null; + + const isExpandable = + column.isExpandable !== undefined ? column.isExpandable : true; + + const width = columnWidths[columnId] || defaultColumnWidth; + + return ( + + ); + } + }, [ + colIndex, + isLeadingControlColumn, + isTrailingControlColumn, + rest, + columnId, + column, + columnWidths, + defaultColumnWidth, + renderCellValue, + renderCellPopover, + interactiveCellId, + sharedCellProps, + schema, + columns, + leadingControlColumns, + trailingControlColumns, + ]); + return cellContent; + } +); diff --git a/src/components/datagrid/body/data_grid_row_manager.ts b/src/components/datagrid/body/data_grid_row_manager.ts index 05984951216..f378aa5853f 100644 --- a/src/components/datagrid/body/data_grid_row_manager.ts +++ b/src/components/datagrid/body/data_grid_row_manager.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { useRef, useCallback, RefObject } from 'react'; +import { useRef, useCallback, RefObject, useMemo } from 'react'; import { useUpdateEffect } from '../../../services'; import { EuiDataGridRowManager, EuiDataGridStyle } from '../data_grid_types'; @@ -94,5 +94,7 @@ export const useRowManager = ({ } }, [rowClasses]); - return { getRow }; + return useMemo(() => { + return { getRow }; + }, [getRow]); }; diff --git a/src/components/datagrid/body/footer/use_data_grid_footer.tsx b/src/components/datagrid/body/footer/use_data_grid_footer.tsx index 03873087592..80a18223a0f 100644 --- a/src/components/datagrid/body/footer/use_data_grid_footer.tsx +++ b/src/components/datagrid/body/footer/use_data_grid_footer.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useState, useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; import { useResizeObserver } from '../../../observer/resize_observer'; @@ -35,7 +35,7 @@ export const useDataGridFooter = (props: Props) => { {...footerProps} /> ); - }, Object.values(props)); // eslint-disable-line react-hooks/exhaustive-deps + }, [props]); return { footerRow, footerRowHeight }; }; diff --git a/src/components/datagrid/body/header/data_grid_header_cell.tsx b/src/components/datagrid/body/header/data_grid_header_cell.tsx index 2626bc45e36..8d0c7467c13 100644 --- a/src/components/datagrid/body/header/data_grid_header_cell.tsx +++ b/src/components/datagrid/body/header/data_grid_header_cell.tsx @@ -15,6 +15,7 @@ import React, { useRef, useCallback, useMemo, + memo, } from 'react'; import { tabbable, FocusableElement } from 'tabbable'; import { keys } from '../../../../services'; @@ -35,156 +36,172 @@ import { getColumnActions } from './column_actions'; import { EuiDataGridColumnResizer } from './data_grid_column_resizer'; import { EuiDataGridHeaderCellWrapper } from './data_grid_header_cell_wrapper'; -export const EuiDataGridHeaderCell: FunctionComponent< - EuiDataGridHeaderCellProps -> = ({ - column, - index, - columns, - columnWidths, - schema, - schemaDetectors, - defaultColumnWidth, - setColumnWidth, - setVisibleColumns, - switchColumnPos, - headerIsInteractive, -}) => { - const { id, display, displayAsText, displayHeaderCellProps } = column; - const width = columnWidths[id] || defaultColumnWidth; +export const EuiDataGridHeaderCell: FunctionComponent = + memo( + ({ + column, + index, + columns, + columnWidths, + schema, + schemaDetectors, + defaultColumnWidth, + setColumnWidth, + setVisibleColumns, + switchColumnPos, + headerIsInteractive, + }) => { + const { id, display, displayAsText, displayHeaderCellProps } = column; + const width = columnWidths[id] || defaultColumnWidth; - const columnType = schema[id] ? schema[id].columnType : null; - const classes = classnames( - { [`euiDataGridHeaderCell--${columnType}`]: columnType }, - displayHeaderCellProps?.className - ); + const columnType = schema[id] ? schema[id].columnType : null; + const classes = classnames( + { [`euiDataGridHeaderCell--${columnType}`]: columnType }, + displayHeaderCellProps?.className + ); - const { setFocusedCell, focusFirstVisibleInteractiveCell } = - useContext(DataGridFocusContext); - const { sorting } = useContext(DataGridSortingContext); + const { setFocusedCell, focusFirstVisibleInteractiveCell } = + useContext(DataGridFocusContext); + const { sorting } = useContext(DataGridSortingContext); - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const popoverArrowNavigationProps = usePopoverArrowNavigation(); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const popoverArrowNavigationProps = usePopoverArrowNavigation(); - const columnActions = getColumnActions({ - column, - columns, - schema, - schemaDetectors, - setVisibleColumns, - focusFirstVisibleInteractiveCell, - setIsPopoverOpen, - sorting, - switchColumnPos, - setFocusedCell, - }); + const columnActions = useMemo(() => { + return getColumnActions({ + column, + columns, + schema, + schemaDetectors, + setVisibleColumns, + focusFirstVisibleInteractiveCell, + setIsPopoverOpen, + sorting, + switchColumnPos, + setFocusedCell, + }); + }, [ + column, + columns, + schema, + schemaDetectors, + setVisibleColumns, + focusFirstVisibleInteractiveCell, + setIsPopoverOpen, + sorting, + switchColumnPos, + setFocusedCell, + ]); - const showColumnActions = columnActions && columnActions.length > 0; + const showColumnActions = columnActions && columnActions.length > 0; - const { sortingArrow, ariaSort, sortingScreenReaderText } = useSortingUtils({ - sorting, - id, - showColumnActions, - }); - const sortingAriaId = useGeneratedHtmlId({ - prefix: 'euiDataGridCellHeader', - suffix: 'sorting', - }); - const actionsAriaId = useGeneratedHtmlId({ - prefix: 'euiDataGridCellHeader', - suffix: 'actions', - }); + const { sortingArrow, ariaSort, sortingScreenReaderText } = + useSortingUtils({ + sorting, + id, + showColumnActions, + }); + const sortingAriaId = useGeneratedHtmlId({ + prefix: 'euiDataGridCellHeader', + suffix: 'sorting', + }); + const actionsAriaId = useGeneratedHtmlId({ + prefix: 'euiDataGridCellHeader', + suffix: 'actions', + }); - return ( - - {column.isResizable !== false && width != null ? ( - - ) : null} + return ( + + {column.isResizable !== false && width != null ? ( + + ) : null} - {!showColumnActions ? ( - <> - {sortingArrow} -
- {display || displayAsText || id} -
- {sortingScreenReaderText && ( - -

{sortingScreenReaderText}

-
- )} - - ) : ( - <> - { - setFocusedCell([index, -1]); - setIsPopoverOpen((isPopoverOpen) => !isPopoverOpen); - }} - aria-describedby={`${sortingAriaId} ${actionsAriaId}`} + {!showColumnActions ? ( + <> + {sortingArrow} +
- {sortingArrow} -
- {display || displayAsText || id} -
- + {sortingScreenReaderText && ( + +

{sortingScreenReaderText}

+
+ )} + + ) : ( + <> + { + setFocusedCell([index, -1]); + setIsPopoverOpen((isPopoverOpen) => !isPopoverOpen); + }} + aria-describedby={`${sortingAriaId} ${actionsAriaId}`} + > + {sortingArrow} +
+ {display || displayAsText || id} +
+ + + } + isOpen={isPopoverOpen} + closePopover={() => setIsPopoverOpen(false)} + {...popoverArrowNavigationProps} + > + - - } - isOpen={isPopoverOpen} - closePopover={() => setIsPopoverOpen(false)} - {...popoverArrowNavigationProps} - > - -
+ - - - - )} - + + + + )} + + ); + } ); -}; +EuiDataGridHeaderCell.displayName = 'EuiDataGridHeaderCell'; /** * Column sorting utility helpers @@ -208,14 +225,16 @@ export const useSortingUtils = ({ /** * Arrow icon */ - const sortingArrow = isColumnSorted ? ( - - ) : null; + const sortingArrow = useMemo(() => { + return isColumnSorted ? ( + + ) : null; + }, [id, isColumnSorted, sortedColumn]); /** * aria-sort attribute - should only be used when a single column is being sorted diff --git a/src/components/datagrid/body/header/data_grid_header_row.tsx b/src/components/datagrid/body/header/data_grid_header_row.tsx index 9b5fe058909..a7e37129665 100644 --- a/src/components/datagrid/body/header/data_grid_header_row.tsx +++ b/src/components/datagrid/body/header/data_grid_header_row.tsx @@ -7,7 +7,7 @@ */ import classnames from 'classnames'; -import React, { forwardRef } from 'react'; +import React, { forwardRef, memo } from 'react'; import { EuiDataGridControlHeaderCell } from './data_grid_control_header_cell'; import { EuiDataGridHeaderCell } from './data_grid_header_cell'; import { @@ -15,73 +15,72 @@ import { EuiDataGridHeaderRowProps, } from '../../data_grid_types'; -const EuiDataGridHeaderRow = forwardRef< - HTMLDivElement, - EuiDataGridHeaderRowProps ->((props, ref) => { - const { - leadingControlColumns = emptyControlColumns, - trailingControlColumns = emptyControlColumns, - columns, - schema, - schemaDetectors, - columnWidths, - defaultColumnWidth, - className, - setColumnWidth, - setVisibleColumns, - switchColumnPos, - headerIsInteractive, - 'data-test-subj': _dataTestSubj, - ...rest - } = props; +const EuiDataGridHeaderRow = memo( + forwardRef((props, ref) => { + const { + leadingControlColumns = emptyControlColumns, + trailingControlColumns = emptyControlColumns, + columns, + schema, + schemaDetectors, + columnWidths, + defaultColumnWidth, + className, + setColumnWidth, + setVisibleColumns, + switchColumnPos, + headerIsInteractive, + 'data-test-subj': _dataTestSubj, + ...rest + } = props; - const classes = classnames('euiDataGridHeader', className); - const dataTestSubj = classnames('dataGridHeader', _dataTestSubj); + const classes = classnames('euiDataGridHeader', className); + const dataTestSubj = classnames('dataGridHeader', _dataTestSubj); - return ( -
- {leadingControlColumns.map((controlColumn, index) => ( - - ))} - {columns.map((column, index) => ( - - ))} - {trailingControlColumns.map((controlColumn, index) => ( - - ))} -
- ); -}); + return ( +
+ {leadingControlColumns.map((controlColumn, index) => ( + + ))} + {columns.map((column, index) => ( + + ))} + {trailingControlColumns.map((controlColumn, index) => ( + + ))} +
+ ); + }) +); EuiDataGridHeaderRow.displayName = 'EuiDataGridHeaderRow'; diff --git a/src/components/datagrid/body/header/use_data_grid_header.tsx b/src/components/datagrid/body/header/use_data_grid_header.tsx index b709fe829e3..1e59d6891c7 100644 --- a/src/components/datagrid/body/header/use_data_grid_header.tsx +++ b/src/components/datagrid/body/header/use_data_grid_header.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { useState, useMemo } from 'react'; +import React, { useMemo, useState } from 'react'; import { useMutationObserver } from '../../../observer/mutation_observer'; import { useResizeObserver } from '../../../observer/resize_observer'; @@ -35,9 +35,11 @@ export const useDataGridHeader = ({ const headerRow = useMemo(() => { return ; - }, Object.values(props)); // eslint-disable-line react-hooks/exhaustive-deps + }, [props]); useHeaderFocusWorkaround(props.headerIsInteractive); - return { headerRow, headerRowHeight }; + return useMemo(() => { + return { headerRow, headerRowHeight }; + }, [headerRow, headerRowHeight]); }; diff --git a/src/components/datagrid/controls/column_selector.tsx b/src/components/datagrid/controls/column_selector.tsx index 0218f1f1560..12feb7ecd22 100644 --- a/src/components/datagrid/controls/column_selector.tsx +++ b/src/components/datagrid/controls/column_selector.tsx @@ -122,30 +122,30 @@ export const useDataGridColumnSelector = ( 'Drag handle' ); - let buttonText = ( - - ); - - if (numberOfHiddenFields === 1) { - buttonText = ( - - ); - } else if (numberOfHiddenFields > 1) { - buttonText = ( - - ); - } + const buttonText = useMemo(() => { + if (numberOfHiddenFields === 1) { + return ( + + ); + } else if (numberOfHiddenFields > 1) { + return ( + + ); + } else { + return ; + } + }, [numberOfHiddenFields]); - const columnSelector = - allowColumnHiding || allowColumnReorder ? ( + const columnSelector = useMemo(() => { + return allowColumnHiding || allowColumnReorder ? ( ) : null; + }, [ + allowColumnHiding, + allowColumnReorder, + buttonText, + isOpen, + columnSearchText, + displayValues, + visibleColumnIds, + controlBtnClasses, + sortedColumns, + setVisibleColumns, + setIsOpen, + onDragEnd, + isDragEnabled, + dragHandleAriaLabel, + filteredColumns, + ]); const orderedVisibleColumns = useMemo( () => @@ -343,10 +360,17 @@ export const useDataGridColumnSelector = ( [setColumns, sortedColumns] ); - return [ + return useMemo(() => { + return [ + columnSelector, + orderedVisibleColumns, + setVisibleColumns, + switchColumnPos, + ]; + }, [ columnSelector, orderedVisibleColumns, setVisibleColumns, switchColumnPos, - ]; + ]); }; diff --git a/src/components/datagrid/controls/display_selector.tsx b/src/components/datagrid/controls/display_selector.tsx index 55b1c6f0984..cb97f5bf26d 100644 --- a/src/components/datagrid/controls/display_selector.tsx +++ b/src/components/datagrid/controls/display_selector.tsx @@ -232,10 +232,10 @@ export const useDataGridDisplaySelector = ( 'Reset to default' ); - const displaySelector = - showDensityControls || - showRowHeightControls || - additionalDisplaySettings ? ( + const displaySelector = useMemo(() => { + return showDensityControls || + showRowHeightControls || + additionalDisplaySettings ? ( ) : null; + }, [ + additionalDisplaySettings, + buttonLabel, + isOpen, + resetButtonLabel, + showDensityControls, + showResetButton, + showRowHeightControls, + gridDensity, + rowHeightSelection, + lineCountInput, + setGridStyles, + setRowHeight, + setLineCountHeight, + resetToInitialState, + ]); return [displaySelector, gridStyles, rowHeightsOptions]; }; diff --git a/src/components/datagrid/data_grid.tsx b/src/components/datagrid/data_grid.tsx index a8698e03f75..c412e956609 100644 --- a/src/components/datagrid/data_grid.tsx +++ b/src/components/datagrid/data_grid.tsx @@ -104,6 +104,8 @@ const cellPaddingsToClassMap: { l: 'euiDataGrid--paddingLarge', }; +const emptyVirtualizationOptions = {}; + export const EuiDataGrid = memo( forwardRef((props, ref) => { const { @@ -272,11 +274,33 @@ export const EuiDataGrid = memo( */ const { headerIsInteractive, handleHeaderMutation } = useHeaderIsInteractive(contentRef.current); - const { focusProps: wrappingDivFocusProps, ...focusContext } = useFocus({ + const { + focusProps: wrappingDivFocusProps, + onFocusUpdate, + focusedCell, + setFocusedCell, + setIsFocusedCellInView, + focusFirstVisibleInteractiveCell, + } = useFocus({ headerIsInteractive, gridItemsRendered, }); + const focusContext = useMemo(() => { + return { + onFocusUpdate, + focusedCell, + setFocusedCell, + setIsFocusedCellInView, + focusFirstVisibleInteractiveCell, + }; + }, [ + onFocusUpdate, + focusedCell, + setFocusedCell, + setIsFocusedCellInView, + focusFirstVisibleInteractiveCell, + ]); /** * Cell popover */ @@ -463,7 +487,9 @@ export const EuiDataGrid = memo( visibleRows={visibleRows} interactiveCellId={interactiveCellId} rowHeightsOptions={rowHeightsOptions} - virtualizationOptions={virtualizationOptions || {}} + virtualizationOptions={ + virtualizationOptions || emptyVirtualizationOptions + } isFullScreen={isFullScreen} gridStyles={gridStyles} gridWidth={gridWidth} diff --git a/src/components/datagrid/utils/focus.ts b/src/components/datagrid/utils/focus.ts index 0dd226faa33..9c3d64fe96b 100644 --- a/src/components/datagrid/utils/focus.ts +++ b/src/components/datagrid/utils/focus.ts @@ -140,14 +140,23 @@ export const useFocus = ({ [isFocusedCellInView, focusFirstVisibleInteractiveCell] ); - return { + return useMemo(() => { + return { + onFocusUpdate, + focusedCell, + setFocusedCell, + setIsFocusedCellInView, + focusFirstVisibleInteractiveCell, + focusProps, + }; + }, [ onFocusUpdate, focusedCell, setFocusedCell, setIsFocusedCellInView, focusFirstVisibleInteractiveCell, focusProps, - }; + ]); }; export const notifyCellOfFocusState = ( diff --git a/src/components/datagrid/utils/sorting.ts b/src/components/datagrid/utils/sorting.ts index c697b30045c..b183ef629aa 100644 --- a/src/components/datagrid/utils/sorting.ts +++ b/src/components/datagrid/utils/sorting.ts @@ -121,9 +121,11 @@ export const useSorting = ({ [startRow, sortedRowMap] ); - return { - sorting, - sortedRowMap, - getCorrectRowIndex, - }; + return useMemo(() => { + return { + sorting, + sortedRowMap, + getCorrectRowIndex, + }; + }, [sorting, sortedRowMap, getCorrectRowIndex]); }; From b7eb3a7aaf1cbaea6fc52a3ff04570345f4e3deb Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Tue, 21 Nov 2023 16:03:14 +0000 Subject: [PATCH 04/17] Cleanup types --- .../datagrid/advanced/custom_renderer.tsx | 10 ++--- src-docs/src/views/datagrid/advanced/ref.tsx | 4 +- .../cell_popover_is_expandable.tsx | 4 +- .../cell_popover_rendercellpopover.tsx | 4 +- .../cells_popovers/visible_cell_actions.tsx | 4 +- .../datagrid/styling/row_height_auto.tsx | 4 +- .../datagrid/styling/row_height_fixed.tsx | 4 +- .../datagrid/styling/row_line_height.tsx | 4 +- .../datagrid/toolbar/additional_controls.tsx | 4 +- .../toolbar/render_custom_toolbar.tsx | 4 +- .../body/data_grid_body_custom.test.tsx | 7 +++- src/components/datagrid/data_grid.a11y.tsx | 39 +++++++++++++------ src/components/datagrid/data_grid.test.tsx | 30 +++++++------- src/components/datagrid/data_grid_types.ts | 6 +-- 14 files changed, 74 insertions(+), 54 deletions(-) diff --git a/src-docs/src/views/datagrid/advanced/custom_renderer.tsx b/src-docs/src/views/datagrid/advanced/custom_renderer.tsx index 381f99160c7..dc6f6924f07 100644 --- a/src-docs/src/views/datagrid/advanced/custom_renderer.tsx +++ b/src-docs/src/views/datagrid/advanced/custom_renderer.tsx @@ -19,7 +19,7 @@ import { EuiDataGridPaginationProps, EuiDataGridSorting, EuiDataGridColumnSortingConfig, - renderCellValue, + RenderCellValue, } from '../../../../../src'; const raw_data: Array<{ [key: string]: string }> = []; @@ -68,7 +68,7 @@ const columns = [ }, ]; -const checkboxRowCellRender: renderCellValue = ({ rowIndex }) => ( +const checkboxRowCellRender: RenderCellValue = ({ rowIndex }) => ( { +const RowCellRender: RenderCellValue = ({ setCellProps, rowIndex }) => { setCellProps({ style: { width: '100%', height: 'auto' } }); const firstName = raw_data[rowIndex].name.split(', ')[1]; @@ -149,10 +149,10 @@ const footerCellValues: { [key: string]: string } = { .toLocaleString('en-US', { style: 'currency', currency: 'USD' })}`, }; -const renderCellValue: renderCellValue = ({ rowIndex, columnId }) => +const renderCellValue: RenderCellValue = ({ rowIndex, columnId }) => raw_data[rowIndex][columnId]; -const RenderFooterCellValue: renderCellValue = ({ columnId, setCellProps }) => { +const RenderFooterCellValue: RenderCellValue = ({ columnId, setCellProps }) => { const value = footerCellValues[columnId]; useEffect(() => { diff --git a/src-docs/src/views/datagrid/advanced/ref.tsx b/src-docs/src/views/datagrid/advanced/ref.tsx index fb4400539e8..89bca493b91 100644 --- a/src-docs/src/views/datagrid/advanced/ref.tsx +++ b/src-docs/src/views/datagrid/advanced/ref.tsx @@ -20,7 +20,7 @@ import { EuiDataGridColumnSortingConfig, EuiDataGridPaginationProps, EuiDataGridSorting, - renderCellValue, + RenderCellValue, } from '../../../../../src'; const raw_data: Array<{ [key: string]: string }> = []; @@ -34,7 +34,7 @@ for (let i = 1; i < 100; i++) { }); } -const renderCellValue: renderCellValue = ({ rowIndex, columnId }) => +const renderCellValue: RenderCellValue = ({ rowIndex, columnId }) => raw_data[rowIndex][columnId]; export default () => { diff --git a/src-docs/src/views/datagrid/cells_popovers/cell_popover_is_expandable.tsx b/src-docs/src/views/datagrid/cells_popovers/cell_popover_is_expandable.tsx index 4a56ba7a4e9..6f4a8623822 100644 --- a/src-docs/src/views/datagrid/cells_popovers/cell_popover_is_expandable.tsx +++ b/src-docs/src/views/datagrid/cells_popovers/cell_popover_is_expandable.tsx @@ -5,7 +5,7 @@ import { EuiDataGrid, EuiDataGridColumnCellAction, EuiDataGridColumn, - renderCellValue, + RenderCellValue as RenderCellValueType, } from '../../../../../src'; const cellActions: EuiDataGridColumnCellAction[] = [ @@ -51,7 +51,7 @@ for (let i = 1; i < 5; i++) { }); } -const RenderCellValue: renderCellValue = ({ +const RenderCellValue: RenderCellValueType = ({ rowIndex, columnId, setCellProps, diff --git a/src-docs/src/views/datagrid/cells_popovers/cell_popover_rendercellpopover.tsx b/src-docs/src/views/datagrid/cells_popovers/cell_popover_rendercellpopover.tsx index 51faf46e290..a16fa0a34be 100644 --- a/src-docs/src/views/datagrid/cells_popovers/cell_popover_rendercellpopover.tsx +++ b/src-docs/src/views/datagrid/cells_popovers/cell_popover_rendercellpopover.tsx @@ -14,7 +14,7 @@ import { EuiCopy, EuiText, EuiImage, - renderCellValue, + RenderCellValue as RenderCellValueType, } from '../../../../../src'; const cellActions: EuiDataGridColumnCellAction[] = [ @@ -164,7 +164,7 @@ const RenderCellPopover = (props: EuiDataGridCellPopoverElementProps) => { ); }; -const renderCellValue: renderCellValue = ({ rowIndex, columnId }) => +const renderCellValue: RenderCellValueType = ({ rowIndex, columnId }) => data[rowIndex][columnId]; export default () => { diff --git a/src-docs/src/views/datagrid/cells_popovers/visible_cell_actions.tsx b/src-docs/src/views/datagrid/cells_popovers/visible_cell_actions.tsx index def02bd6a4e..c469e65a345 100644 --- a/src-docs/src/views/datagrid/cells_popovers/visible_cell_actions.tsx +++ b/src-docs/src/views/datagrid/cells_popovers/visible_cell_actions.tsx @@ -5,7 +5,7 @@ import { EuiDataGrid, EuiDataGridColumnCellAction, EuiDataGridColumn, - renderCellValue, + RenderCellValue as RenderCellValueType, } from '../../../../../src/components'; const cellActions1: EuiDataGridColumnCellAction[] = [ @@ -80,7 +80,7 @@ for (let i = 1; i < 5; i++) { }); } -const renderCellValue: renderCellValue = ({ rowIndex, columnId }) => +const renderCellValue: RenderCellValueType = ({ rowIndex, columnId }) => data[rowIndex][columnId]; export default () => { diff --git a/src-docs/src/views/datagrid/styling/row_height_auto.tsx b/src-docs/src/views/datagrid/styling/row_height_auto.tsx index a72b95c8950..304668576a7 100644 --- a/src-docs/src/views/datagrid/styling/row_height_auto.tsx +++ b/src-docs/src/views/datagrid/styling/row_height_auto.tsx @@ -10,7 +10,7 @@ import githubData from '../_row_auto_height_data.json'; import { EuiDataGrid, - renderCellValue, + RenderCellValue as RenderCellValueType, EuiLink, EuiAvatar, EuiBadge, @@ -68,7 +68,7 @@ const columns = [ // instead of loading up front, generate entries on the fly const raw_data: DataShape[] = githubData; -const RenderCellValue: renderCellValue = ({ +const RenderCellValue: RenderCellValueType = ({ rowIndex, columnId, isDetails, diff --git a/src-docs/src/views/datagrid/styling/row_height_fixed.tsx b/src-docs/src/views/datagrid/styling/row_height_fixed.tsx index bde39af0a4c..10eb3f20e6e 100644 --- a/src-docs/src/views/datagrid/styling/row_height_fixed.tsx +++ b/src-docs/src/views/datagrid/styling/row_height_fixed.tsx @@ -10,7 +10,7 @@ import githubData from '../_row_auto_height_data.json'; import { EuiDataGrid, - renderCellValue, + RenderCellValue as RenderCellValueType, EuiLink, EuiAvatar, EuiBadge, @@ -68,7 +68,7 @@ const columns = [ // instead of loading up front, generate entries on the fly const raw_data: DataShape[] = githubData; -const RenderCellValue: renderCellValue = ({ +const RenderCellValue: RenderCellValueType = ({ rowIndex, columnId, isDetails, diff --git a/src-docs/src/views/datagrid/styling/row_line_height.tsx b/src-docs/src/views/datagrid/styling/row_line_height.tsx index 69f42b894a6..917973d3188 100644 --- a/src-docs/src/views/datagrid/styling/row_line_height.tsx +++ b/src-docs/src/views/datagrid/styling/row_line_height.tsx @@ -12,7 +12,7 @@ import { EuiDataGrid, EuiDataGridColumnSortingConfig, EuiDataGridPaginationProps, - renderCellValue, + RenderCellValue as RenderCellValueType, EuiDataGridSorting, formatDate, } from '../../../../../src'; @@ -62,7 +62,7 @@ const columns = [ // instead of loading up front, generate entries on the fly const raw_data: DataShape[] = githubData; -const RenderCellValue: renderCellValue = ({ +const RenderCellValue: RenderCellValueType = ({ rowIndex, columnId, isDetails, diff --git a/src-docs/src/views/datagrid/toolbar/additional_controls.tsx b/src-docs/src/views/datagrid/toolbar/additional_controls.tsx index bb3a30b275a..926d41d4637 100644 --- a/src-docs/src/views/datagrid/toolbar/additional_controls.tsx +++ b/src-docs/src/views/datagrid/toolbar/additional_controls.tsx @@ -17,7 +17,7 @@ import { EuiContextMenuPanel, EuiPopover, EuiDataGridPaginationProps, - renderCellValue, + RenderCellValue, } from '../../../../../src'; const columns = [ @@ -50,7 +50,7 @@ for (let i = 1; i < 20; i++) { }); } -const renderCellValue: renderCellValue = ({ rowIndex, columnId }) => +const renderCellValue: RenderCellValue = ({ rowIndex, columnId }) => data[rowIndex][columnId]; export default () => { diff --git a/src-docs/src/views/datagrid/toolbar/render_custom_toolbar.tsx b/src-docs/src/views/datagrid/toolbar/render_custom_toolbar.tsx index 16ae3856a56..198aa6a70b6 100644 --- a/src-docs/src/views/datagrid/toolbar/render_custom_toolbar.tsx +++ b/src-docs/src/views/datagrid/toolbar/render_custom_toolbar.tsx @@ -13,7 +13,7 @@ import { EuiFlexGroup, EuiFlexItem, euiScreenReaderOnly, - renderCellValue, + RenderCellValue, } from '../../../../../src'; const raw_data: Array<{ [key: string]: string }> = []; @@ -79,7 +79,7 @@ const renderCustomToolbar: EuiDataGridToolbarProps['renderCustomToolbar'] = ({ ); }; -const renderCellValue: renderCellValue = ({ rowIndex, columnId }) => +const renderCellValue: RenderCellValue = ({ rowIndex, columnId }) => raw_data[rowIndex][columnId]; // Some additional custom settings to show in the Display popover diff --git a/src/components/datagrid/body/data_grid_body_custom.test.tsx b/src/components/datagrid/body/data_grid_body_custom.test.tsx index b8f33eca654..3a8756b81f5 100644 --- a/src/components/datagrid/body/data_grid_body_custom.test.tsx +++ b/src/components/datagrid/body/data_grid_body_custom.test.tsx @@ -10,7 +10,10 @@ import React, { useEffect } from 'react'; import { fireEvent } from '@testing-library/react'; import { render } from '../../../test/rtl'; -import { EuiDataGridProps, renderCellValue } from '../data_grid_types'; +import { + EuiDataGridProps, + RenderCellValue as RenderCellValueType, +} from '../data_grid_types'; import { dataGridBodyProps } from './data_grid_body.test'; import { EuiDataGridBodyCustomRender } from './data_grid_body_custom'; @@ -21,7 +24,7 @@ describe('EuiDataGridBodyCustomRender', () => { { columnA: 'hello', columnB: 'world' }, { columnA: 'lorem', columnB: 'ipsum' }, ]; - const RenderCellValue: renderCellValue = ({ rowIndex, columnId }) => + const RenderCellValue: RenderCellValueType = ({ rowIndex, columnId }) => raw_data[rowIndex][columnId as keyof DataType]; const bodyProps = { diff --git a/src/components/datagrid/data_grid.a11y.tsx b/src/components/datagrid/data_grid.a11y.tsx index d735acc9281..14e22d7c5d4 100644 --- a/src/components/datagrid/data_grid.a11y.tsx +++ b/src/components/datagrid/data_grid.a11y.tsx @@ -10,8 +10,13 @@ /// /// -import React, { useState } from 'react'; -import { EuiDataGrid, EuiDataGridColumn, EuiDataGridSorting } from './index'; +import React, { useState, useCallback } from 'react'; +import { + EuiDataGrid, + EuiDataGridColumn, + EuiDataGridSorting, + RenderCellValueWithContext, +} from './index'; import { faker } from '@faker-js/faker'; const columns: EuiDataGridColumn[] = [ @@ -73,6 +78,21 @@ const commaSeparateNumbers = (numberString: string) => { ); }; +const renderCellValue: RenderCellValueWithContext = ({ + rowIndex, + columnId, + schema, + data, +}) => { + let value = data[rowIndex][columnId]; + + if (schema === 'numeric') { + value = commaSeparateNumbers(value); + } + + return value; +}; + const DataGrid = () => { const [visibleColumns, setVisibleColumns] = useState( columns.map(({ id }) => id) @@ -101,6 +121,10 @@ const DataGrid = () => { setSortingColumns(sortingColumns); }; + const context = useCallback(() => { + return { data }; + }, [data]); + return ( { columnVisibility={{ visibleColumns, setVisibleColumns }} rowCount={data.length} inMemory={{ level: 'sorting' }} - renderCellValue={({ rowIndex, columnId, schema }) => { - let value = data[rowIndex][columnId]; - - if (schema === 'numeric') { - value = commaSeparateNumbers(value); - } - - return value; - }} + renderCellValue={renderCellValue} + renderCellContext={context} sorting={{ columns: sortingColumns, onSort: setSorting }} schemaDetectors={[ { diff --git a/src/components/datagrid/data_grid.test.tsx b/src/components/datagrid/data_grid.test.tsx index 40b7708a34a..ceb4dad1c20 100644 --- a/src/components/datagrid/data_grid.test.tsx +++ b/src/components/datagrid/data_grid.test.tsx @@ -8,7 +8,7 @@ import React, { useEffect, useState } from 'react'; import { mount, ReactWrapper } from 'enzyme'; -import { EuiDataGrid, renderCellValue } from './'; +import { EuiDataGrid, RenderCellValue } from './'; import { EuiDataGridProps } from './data_grid_types'; import { findTestSubject, requiredProps } from '../../test'; import { render } from '../../test/rtl'; @@ -386,12 +386,12 @@ function setColumnVisibility( closeColumnSelector(datagrid); } -const renderCellValueRowAndColumnCount: renderCellValue = ({ +const renderCellValueRowAndColumnCount: RenderCellValue = ({ rowIndex, columnId, }) => `${rowIndex}, ${columnId}`; -const RenderCellValueSetCellProps: renderCellValue = ({ +const RenderCellValueSetCellProps: RenderCellValue = ({ rowIndex, columnId, setCellProps, @@ -407,7 +407,7 @@ const RenderCellValueSetCellProps: renderCellValue = ({ return `${rowIndex}, ${columnId}`; }; -const renderCellBasedOnColumnId: renderCellValue = ({ columnId }) => { +const renderCellBasedOnColumnId: RenderCellValue = ({ columnId }) => { if (columnId === 'A') { return 5.5; } else if (columnId === 'B') { @@ -417,9 +417,9 @@ const renderCellBasedOnColumnId: renderCellValue = ({ columnId }) => { } }; -const renderCellRowAsValue: renderCellValue = ({ rowIndex }) => rowIndex; +const renderCellRowAsValue: RenderCellValue = ({ rowIndex }) => rowIndex; -const renderCellValueALowBHigh: renderCellValue = ({ rowIndex, columnId }) => +const renderCellValueALowBHigh: RenderCellValue = ({ rowIndex, columnId }) => // render A as 0, 1, 0, 1, 0 and B as 9->5 columnId === 'A' ? rowIndex % 2 : 9 - rowIndex; @@ -878,7 +878,7 @@ describe('EuiDataGrid', () => { G: '2019-09-18T12:31:28.234', H: '2019-09-18T12:31:28.234+0300', }; - const renderCellValue: renderCellValue = ({ columnId }) => + const renderCellValue: RenderCellValue = ({ columnId }) => values[columnId]; const component = mount( { A: '-5.80', B: '127.0.0.1', }; - const renderCellValue: renderCellValue = ({ columnId }) => + const renderCellValue: RenderCellValue = ({ columnId }) => values[columnId]; const component = mount( { describe('cell rendering', () => { it('supports hooks', () => { - const RenderCellValueWithHooks: renderCellValue = ({ + const RenderCellValueWithHooks: RenderCellValue = ({ rowIndex, columnId, }) => { @@ -1449,7 +1449,7 @@ describe('EuiDataGrid', () => { }, }; - const RenderCellValue: renderCellValue = ({ + const RenderCellValue: RenderCellValue = ({ rowIndex, columnId, setCellProps, @@ -1557,7 +1557,7 @@ describe('EuiDataGrid', () => { describe('in-memory sorting', () => { it('sorts on initial render', () => { - const renderCellValue: renderCellValue = ({ rowIndex, columnId }) => + const renderCellValue: RenderCellValue = ({ rowIndex, columnId }) => // render A 0->4 and B 9->5 columnId === 'A' ? rowIndex : 9 - rowIndex; const component = mount( @@ -1679,7 +1679,7 @@ describe('EuiDataGrid', () => { component.setProps({ sorting: { columns, onSort } }); component.update(); }); - const renderCellValue: renderCellValue = ({ rowIndex }) => + const renderCellValue: RenderCellValue = ({ rowIndex }) => `1.0.${(rowIndex % 3) + rowIndex}`; // computes as 0,2,4,3,5 const component = mount( @@ -1724,7 +1724,7 @@ describe('EuiDataGrid', () => { }); it('uses schema information to sort', () => { - const renderCellValue: renderCellValue = ({ rowIndex, columnId }) => + const renderCellValue: RenderCellValue = ({ rowIndex, columnId }) => // render A 0->4 and B 12->8 columnId === 'A' ? rowIndex : 12 - rowIndex; const component = mount( @@ -2547,7 +2547,7 @@ describe('EuiDataGrid', () => { ).toEqual('1, B'); }); it.skip('supports arrow navigation through grids with different interactive cells', () => { - const renderCellValue: renderCellValue = ({ rowIndex, columnId }) => { + const renderCellValue: RenderCellValue = ({ rowIndex, columnId }) => { if (columnId === 'A') { return `${rowIndex}, A`; } @@ -2634,7 +2634,7 @@ describe('EuiDataGrid', () => { expect(focusableCell.getDOMNode()).toBe(document.activeElement); }); it.skip('allows user to enter and exit grid navigation', async () => { - const renderCellValue: renderCellValue = ({ rowIndex, columnId }) => ( + const renderCellValue: RenderCellValue = ({ rowIndex, columnId }) => ( <> , diff --git a/src/components/datagrid/data_grid_types.ts b/src/components/datagrid/data_grid_types.ts index dc4daddb25a..1df01fbe057 100644 --- a/src/components/datagrid/data_grid_types.ts +++ b/src/components/datagrid/data_grid_types.ts @@ -601,11 +601,11 @@ export type EuiDataGridCellValueElementPropsWithContext< T extends Record > = EuiDataGridCellValueElementProps & ReturnType>; -export type renderCellValue = +export type RenderCellValue = | ((props: EuiDataGridCellValueElementProps) => ReactNode) | ComponentClass; -export type renderCellValueWithContext = +export type RenderCellValueWithContext = | (( props: EuiDataGridCellValueElementPropsWithContext> ) => ReactNode) @@ -625,7 +625,7 @@ export interface EuiDataGridCellProps { isExpandable: boolean; className?: string; popoverContext: DataGridCellPopoverContextShape; - renderCellValue: renderCellValue | renderCellValueWithContext; + renderCellValue: RenderCellValue | RenderCellValueWithContext; renderCellContext?: RenderCellContext>; renderCellPopover?: | JSXElementConstructor From d15c9354343f73eca3e014b581ee4dfeb6a60e3c Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Tue, 21 Nov 2023 18:49:14 +0000 Subject: [PATCH 05/17] Fix skeleton text performance --- src/components/skeleton/skeleton_text.tsx | 80 ++++++++++++----------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/src/components/skeleton/skeleton_text.tsx b/src/components/skeleton/skeleton_text.tsx index c4f046c34c1..1e9f08c12a9 100644 --- a/src/components/skeleton/skeleton_text.tsx +++ b/src/components/skeleton/skeleton_text.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { FunctionComponent, HTMLAttributes } from 'react'; +import React, { FunctionComponent, HTMLAttributes, useMemo, memo } from 'react'; import classNames from 'classnames'; import { CommonProps } from '../common'; @@ -32,42 +32,48 @@ export type EuiSkeletonTextProps = CommonProps & size?: TextSize; }; -export const EuiSkeletonText: FunctionComponent = ({ - isLoading = true, - lines = 3, - size = 'm', - className, - contentAriaLabel, - announceLoadingStatus, - announceLoadedStatus, - ariaLiveProps, - ariaWrapperProps, - children, - ...rest -}) => { - const euiTheme = useEuiTheme(); - const styles = euiSkeletonTextStyles(euiTheme); - const lineCssStyles = [styles.euiSkeletonText, styles[size]]; +export const EuiSkeletonText: FunctionComponent = memo( + ({ + isLoading = true, + lines = 3, + size = 'm', + className, + contentAriaLabel, + announceLoadingStatus, + announceLoadedStatus, + ariaLiveProps, + ariaWrapperProps, + children, + ...rest + }) => { + const euiTheme = useEuiTheme(); - const lineElements = []; - for (let i = 0; i < lines; i++) { - lineElements.push(); - } + const lineElements = useMemo(() => { + const styles = euiSkeletonTextStyles(euiTheme); + const lineCssStyles = [styles.euiSkeletonText, styles[size]]; - return ( - - {lineElements} - + const lineElements = []; + for (let i = 0; i < lines; i++) { + lineElements.push(); } - loadedContent={children || ''} - contentAriaLabel={contentAriaLabel} - announceLoadingStatus={announceLoadingStatus} - announceLoadedStatus={announceLoadedStatus} - ariaLiveProps={ariaLiveProps} - {...ariaWrapperProps} - /> - ); -}; + return lineElements; + }, [lines, size, euiTheme]); + + return ( + + {lineElements} + + } + loadedContent={children || ''} + contentAriaLabel={contentAriaLabel} + announceLoadingStatus={announceLoadingStatus} + announceLoadedStatus={announceLoadedStatus} + ariaLiveProps={ariaLiveProps} + {...ariaWrapperProps} + /> + ); + } +); From 150662e3441fbba0635ec460916d6c1d650b57b1 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Tue, 21 Nov 2023 20:47:18 +0000 Subject: [PATCH 06/17] Delete React version specific snapshot test --- .../body/data_grid_cell_actions.test.tsx | 163 ------------------ src/components/datagrid/data_grid.a11y.tsx | 10 +- 2 files changed, 2 insertions(+), 171 deletions(-) diff --git a/src/components/datagrid/body/data_grid_cell_actions.test.tsx b/src/components/datagrid/body/data_grid_cell_actions.test.tsx index e803de23d07..b6b683c7497 100644 --- a/src/components/datagrid/body/data_grid_cell_actions.test.tsx +++ b/src/components/datagrid/body/data_grid_cell_actions.test.tsx @@ -212,169 +212,6 @@ describe('EuiDataGridCellActions', () => {
`); - const button: Function = component.find('EuiI18n').renderProp('children'); - expect(button('expandButtonTitle')).toMatchInlineSnapshot(` - - - - - - - `); }); it('renders cell actions as `EuiButtonIcon`s', () => { diff --git a/src/components/datagrid/data_grid.a11y.tsx b/src/components/datagrid/data_grid.a11y.tsx index 14e22d7c5d4..732728245b1 100644 --- a/src/components/datagrid/data_grid.a11y.tsx +++ b/src/components/datagrid/data_grid.a11y.tsx @@ -10,7 +10,7 @@ /// /// -import React, { useState, useCallback } from 'react'; +import React, { useState } from 'react'; import { EuiDataGrid, EuiDataGridColumn, @@ -82,9 +82,8 @@ const renderCellValue: RenderCellValueWithContext = ({ rowIndex, columnId, schema, - data, }) => { - let value = data[rowIndex][columnId]; + let value = storeData[rowIndex][columnId]; if (schema === 'numeric') { value = commaSeparateNumbers(value); @@ -121,10 +120,6 @@ const DataGrid = () => { setSortingColumns(sortingColumns); }; - const context = useCallback(() => { - return { data }; - }, [data]); - return ( { rowCount={data.length} inMemory={{ level: 'sorting' }} renderCellValue={renderCellValue} - renderCellContext={context} sorting={{ columns: sortingColumns, onSort: setSorting }} schemaDetectors={[ { From b20463dac015981fbb21c37791b9197351e745ab Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Wed, 7 Feb 2024 07:43:13 +0000 Subject: [PATCH 07/17] Merge upstream WIP --- .../body/cell/data_grid_cell.test.tsx | 10 +- .../datagrid/body/cell/data_grid_cell.tsx | 203 ++++++-- .../body/cell/data_grid_cell_actions.test.tsx | 24 +- .../header/data_grid_control_header_cell.tsx | 53 +- .../body/header/data_grid_header_row.test.tsx | 4 +- .../datagrid/controls/column_sorting.tsx | 484 ++++++++++-------- .../controls/column_sorting_draggable.tsx | 43 +- src/components/datagrid/data_grid.tsx | 45 +- .../datagrid/utils/grid_height_width.ts | 87 ++-- 9 files changed, 561 insertions(+), 392 deletions(-) diff --git a/src/components/datagrid/body/cell/data_grid_cell.test.tsx b/src/components/datagrid/body/cell/data_grid_cell.test.tsx index 2e02393b49c..06f3932409c 100644 --- a/src/components/datagrid/body/cell/data_grid_cell.test.tsx +++ b/src/components/datagrid/body/cell/data_grid_cell.test.tsx @@ -85,12 +85,14 @@ describe('EuiDataGridCell', () => { act(() => { component.setState({ isHovered: true }); }); - - const getCellActions = () => component.find('EuiDataGridCellActions'); + // given how nested this component ends up being, too nested for enzyme to render, this should probably be moved to + // data_grid_cell_actions.test.tsx I think. + const getCellActions = () => + component.find('[ariaRowIndex=1][colIndex=0][rowIndex=0][onMouseEnter]'); expect(getCellActions()).toHaveLength(1); // Should handle opening the popover - (getCellActions().prop('onExpandClick') as Function)(); + (getCellActions().prop('handleCellExpansionClick') as Function)(); expect(mockPopoverContext.openCellPopover).toHaveBeenCalled(); // Should handle closing the popover @@ -98,7 +100,7 @@ describe('EuiDataGridCell', () => { isExpandable: true, popoverContext: { ...mockPopoverContext, popoverIsOpen: true }, }); - (getCellActions().prop('onExpandClick') as Function)(); + (getCellActions().prop('handleCellExpansionClick') as Function)(); expect(mockPopoverContext.closeCellPopover).toHaveBeenCalledTimes(1); }); diff --git a/src/components/datagrid/body/cell/data_grid_cell.tsx b/src/components/datagrid/body/cell/data_grid_cell.tsx index 62c0e76a8aa..d8f90833165 100644 --- a/src/components/datagrid/body/cell/data_grid_cell.tsx +++ b/src/components/datagrid/body/cell/data_grid_cell.tsx @@ -38,6 +38,7 @@ import { EuiDataGridCellValueProps, EuiDataGridCellPopoverElementProps, EuiDataGridRowHeightOption, + EuiDataGridColumn, } from '../../data_grid_types'; import { EuiDataGridCellActions, @@ -46,6 +47,103 @@ import { import { DefaultCellPopover } from './data_grid_cell_popover'; import { HandleInteractiveChildren } from './focus_utils'; +const Cell: React.FunctionComponent<{ + ariaRowIndex: number; + isFocused: boolean; + cellRef: React.MutableRefObject; + cellProps: EuiDataGridSetCellProps; + columnId: string; + colIndex: number; + rowIndex: number; + visibleRowIndex: number; + handleCellKeyDown: (event: React.KeyboardEvent) => void; + onMouseEnter: (event: React.MouseEvent) => void; + onMouseLeave: (event: React.MouseEvent) => void; + updateCellFocusContext: () => void; + isExpandable: boolean; + cellContentProps: EuiDataGridCellValueProps & { + setCellProps: EuiDataGridCellValueElementProps['setCellProps']; + setCellContentsRef: EuiDataGridCell['setCellContentsRef']; + isExpanded: boolean; + isControlColumn: boolean; + isFocused: boolean; + ariaRowIndex: number; + }; + showCellActions: boolean; + column?: EuiDataGridColumn; + handleCellExpansionClick: () => void; + popoverAnchorRef: React.MutableRefObject; +}> = memo( + ({ + ariaRowIndex, + isFocused, + cellRef, + cellProps, + columnId, + colIndex, + rowIndex, + visibleRowIndex, + handleCellKeyDown, + onMouseEnter, + onMouseLeave, + updateCellFocusContext, + isExpandable, + cellContentProps, + showCellActions, + column, + handleCellExpansionClick, + popoverAnchorRef, + }) => { + return ( +
+ + + + {/* Give the cell expansion popover a separate div/ref - otherwise the + extra popover wrappers mess up the absolute positioning and cause + animation stuttering */} +
+ + ) + } + /> + +
+ ); + } +); + const EuiDataGridCellContent: FunctionComponent< EuiDataGridCellValueProps & { setCellProps: EuiDataGridCellValueElementProps['setCellProps']; @@ -548,6 +646,7 @@ export class EuiDataGridCell extends Component< }; handleCellExpansionClick = () => { + console.log('fire'); const { popoverContext: { openCellPopover, closeCellPopover }, visibleRowIndex, @@ -646,64 +745,56 @@ export class EuiDataGridCell extends Component< ariaRowIndex, }; - const cell = ( -
, + rowManager.getRow({ + rowIndex, + visibleRowIndex, + top: style!.top as string, // comes in as a `{float}px` string from react-window + height: style!.height as number, // comes in as an integer from react-window + }) + ) + ) : ( + - - - - {/* Give the cell expansion popover a separate div/ref - otherwise the - extra popover wrappers mess up the absolute positioning and cause - animation stuttering */} -
- - ) - } - /> - -
+ updateCellFocusContext={this.updateCellFocusContext} + isExpandable={isExpandable} + cellContentProps={cellContentProps} + showCellActions={showCellActions} + column={column} + handleCellExpansionClick={this.handleCellExpansionClick} + popoverAnchorRef={this.popoverAnchorRef} + /> ); - - return rowManager && !IS_JEST_ENVIRONMENT - ? createPortal( - cell, - rowManager.getRow({ - rowIndex, - visibleRowIndex, - top: style!.top as string, // comes in as a `{float}px` string from react-window - height: style!.height as number, // comes in as an integer from react-window - }) - ) - : cell; } } diff --git a/src/components/datagrid/body/cell/data_grid_cell_actions.test.tsx b/src/components/datagrid/body/cell/data_grid_cell_actions.test.tsx index fe16e085dd9..ac88bd41bb3 100644 --- a/src/components/datagrid/body/cell/data_grid_cell_actions.test.tsx +++ b/src/components/datagrid/body/cell/data_grid_cell_actions.test.tsx @@ -34,17 +34,15 @@ describe('EuiDataGridCellActions', () => {
- - - +
`); - const button: Function = component.find('EuiI18n').renderProp('children'); + const button: Function = component + .find('ExpandButton') + .renderProp('children'); expect(button('expandButtonTitle')).toMatchInlineSnapshot(` { key="0" rowIndex={0} /> - - - +
`); }); diff --git a/src/components/datagrid/body/header/data_grid_control_header_cell.tsx b/src/components/datagrid/body/header/data_grid_control_header_cell.tsx index b765185c04a..f61e560b23e 100644 --- a/src/components/datagrid/body/header/data_grid_control_header_cell.tsx +++ b/src/components/datagrid/body/header/data_grid_control_header_cell.tsx @@ -6,36 +6,35 @@ * Side Public License, v 1. */ -import React, { FunctionComponent } from 'react'; +import React, { FunctionComponent, memo } from 'react'; import classNames from 'classnames'; import { EuiDataGridControlHeaderCellProps } from '../../data_grid_types'; import { EuiDataGridHeaderCellWrapper } from './data_grid_header_cell_wrapper'; -export const EuiDataGridControlHeaderCell: FunctionComponent< - EuiDataGridControlHeaderCellProps -> = ({ controlColumn, index }) => { - const { - headerCellRender: HeaderCellRender, - headerCellProps, - width, - id, - } = controlColumn; +export const EuiDataGridControlHeaderCell: FunctionComponent = + memo(({ controlColumn, index }) => { + const { + headerCellRender: HeaderCellRender, + headerCellProps, + width, + id, + } = controlColumn; - return ( - -
- -
-
- ); -}; + return ( + +
+ +
+
+ ); + }); diff --git a/src/components/datagrid/body/header/data_grid_header_row.test.tsx b/src/components/datagrid/body/header/data_grid_header_row.test.tsx index 1ab65f1a453..a976b53a211 100644 --- a/src/components/datagrid/body/header/data_grid_header_row.test.tsx +++ b/src/components/datagrid/body/header/data_grid_header_row.test.tsx @@ -108,7 +108,7 @@ describe('EuiDataGridHeaderRow', () => { data-test-subj="dataGridHeader" role="row" > - { data-test-subj="dataGridHeader" role="row" > - { + return ({ source: { index: sourceIndex }, destination }: DropResult) => { + if (destination) { + const destinationIndex = destination.index; + const nextColumns = euiDragDropReorder( + sorting!.columns, + sourceIndex, + destinationIndex + ); + sorting!.onSort(nextColumns); + } + }; + }, [sorting]); + + const onSort = useCallback(() => sorting && sorting.onSort([]), [sorting]); - const activeColumnIds = new Set(sorting.columns.map(({ id }) => id)); - const { inactiveColumns } = columns.reduce<{ - activeColumns: EuiDataGridColumn[]; - inactiveColumns: EuiDataGridColumn[]; - }>( - (acc, column) => { - if (activeColumnIds.has(column.id)) { - acc.activeColumns.push(column); - } else { - acc.inactiveColumns.push(column); + const schemaDetails = useCallback( + (id: string | number) => { + if (sorting) { + return schema.hasOwnProperty(id) && schema[id].columnType != null + ? getDetailsForSchema(schemaDetectors, schema[id].columnType) + : null; } - return acc; }, - { - activeColumns: [], - inactiveColumns: [], - } + [schema, schemaDetectors, sorting] ); - const onDragEnd = ({ - source: { index: sourceIndex }, - destination, - }: DropResult) => { - if (destination) { - const destinationIndex = destination.index; - const nextColumns = euiDragDropReorder( - sorting!.columns, - sourceIndex, - destinationIndex - ); - sorting!.onSort(nextColumns); - } - }; - - const schemaDetails = (id: string | number) => - schema.hasOwnProperty(id) && schema[id].columnType != null - ? getDetailsForSchema(schemaDetectors, schema[id].columnType) - : null; - - const inactiveSortableColumns = inactiveColumns.filter( - ({ id, isSortable }) => { - const schemaDetail = schemaDetails(id); - let sortable = true; - if (isSortable != null) { - sortable = isSortable; - } else if (schemaDetail != null) { - sortable = schemaDetail.hasOwnProperty('isSortable') - ? schemaDetail.isSortable! - : true; + const onInactiveClick = useCallback( + (id: string, defaultSortDirection?: 'asc' | 'desc') => { + if (sorting) { + const schemaDetails = (id: string | number) => + schema.hasOwnProperty(id) && schema[id].columnType != null + ? getDetailsForSchema(schemaDetectors, schema[id].columnType) + : null; + const nextColumns = [...sorting.columns]; + nextColumns.push({ + id, + direction: + defaultSortDirection || + schemaDetails(id)?.defaultSortDirection || + 'asc', + }); + sorting.onSort(nextColumns); } - return sortable; - } + }, + [sorting, schema, schemaDetectors] ); - const columnSorting = ( - setIsOpen(false)} - anchorPosition="downLeft" - panelPaddingSize="s" - hasDragDrop - button={ - setIsOpen(!isOpen)} + const columnSorting = useMemo(() => { + if (sorting == null) { + return null; + } else { + const activeColumnIds = new Set(sorting.columns.map(({ id }) => id)); + const { inactiveColumns } = columns.reduce<{ + activeColumns: EuiDataGridColumn[]; + inactiveColumns: EuiDataGridColumn[]; + }>( + (acc, column) => { + if (activeColumnIds.has(column.id)) { + acc.activeColumns.push(column); + } else { + acc.inactiveColumns.push(column); + } + return acc; + }, + { + activeColumns: [], + inactiveColumns: [], + } + ); + const inactiveSortableColumns = inactiveColumns.filter( + ({ id, isSortable }) => { + const schemaDetail = schemaDetails(id); + let sortable = true; + if (isSortable != null) { + sortable = isSortable; + } else if (schemaDetail != null) { + sortable = schemaDetail.hasOwnProperty('isSortable') + ? schemaDetail.isSortable! + : true; + } + return sortable; + } + ); + return ( + setIsOpen(false)} + anchorPosition="downLeft" + panelPaddingSize="s" + hasDragDrop + button={ + setIsOpen(!isOpen)} + > + {sortingButtonText} + + } > - {sortingButtonText} - - } - > - {sorting.columns.length > 0 ? ( - - - <> - {sorting.columns.map(({ id, direction }, index) => { - return ( - - ); - })} - - - - ) : ( - -

- -

-
- )} - {(inactiveSortableColumns.length > 0 || sorting.columns.length > 0) && ( - - - - {inactiveSortableColumns.length > 0 && ( - setAvailableColumnsIsOpen(false)} - anchorPosition="downLeft" - panelPaddingSize="none" - button={ + {sorting.columns.length > 0 ? ( + + + <> + {sorting.columns.map(({ id, direction }, index) => { + return ( + + ); + })} + + + + ) : ( + +

+ +

+
+ )} + {(inactiveSortableColumns.length > 0 || + sorting.columns.length > 0) && ( + + + + {inactiveSortableColumns.length > 0 && ( + setAvailableColumnsIsOpen(false)} + anchorPosition="downLeft" + panelPaddingSize="none" + button={ + + setAvailableColumnsIsOpen(!availableColumnsIsOpen) + } + > + + + } + > + + {(sortFieldAriaLabel: string) => ( +
+ {inactiveSortableColumns.map( + ({ id, defaultSortDirection }) => { + return ( + + ); + } + )} +
+ )} +
+
+ )} +
+ {sorting.columns.length > 0 ? ( + - setAvailableColumnsIsOpen(!availableColumnsIsOpen) - } + flush="right" + onClick={onSort} + data-test-subj="dataGridColumnSortingClearButton" > - } - > - - {(sortFieldAriaLabel: string) => ( -
- {inactiveSortableColumns.map( - ({ id, defaultSortDirection }) => { - return ( - - ); - } - )} -
- )} -
-
- )} -
- {sorting.columns.length > 0 ? ( - - sorting.onSort([])} - data-test-subj="dataGridColumnSortingClearButton" - > - - - - ) : null} -
-
- )} -
- ); + + ) : null} + + + )} + + ); + } + }, [ + availableColumnsIsOpen, + displayValues, + isOpen, + onSort, + onDragEnd, + schema, + schemaDetectors, + sorting, + sortingButtonText, + columns, + onInactiveClick, + schemaDetails, + ]); return columnSorting; }; diff --git a/src/components/datagrid/controls/column_sorting_draggable.tsx b/src/components/datagrid/controls/column_sorting_draggable.tsx index 2fa7f1264b3..4776d3bfd8e 100644 --- a/src/components/datagrid/controls/column_sorting_draggable.tsx +++ b/src/components/datagrid/controls/column_sorting_draggable.tsx @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import React, { FunctionComponent } from 'react'; +import React, { FunctionComponent, useCallback } from 'react'; import classNames from 'classnames'; import { EuiScreenReaderOnly } from '../../accessibility'; @@ -70,6 +70,26 @@ export const EuiDataGridColumnSortingDraggable: FunctionComponent< 'Drag handle' ); + const removeSort = useCallback(() => { + const nextColumns = [...sorting.columns]; + const columnIndex = nextColumns.map(({ id }) => id).indexOf(id); + nextColumns.splice(columnIndex, 1); + sorting.onSort(nextColumns); + }, [id, sorting]); + + const toggleLegendHandler = useCallback<(id: string, value?: any) => void>( + (_, direction) => { + const nextColumns = [...sorting.columns]; + const columnIndex = nextColumns.map(({ id }) => id).indexOf(id); + nextColumns.splice(columnIndex, 1, { + id, + direction, + }); + sorting.onSort(nextColumns); + }, + [id, sorting] + ); + return ( { - const nextColumns = [...sorting.columns]; - const columnIndex = nextColumns - .map(({ id }) => id) - .indexOf(id); - nextColumns.splice(columnIndex, 1); - sorting.onSort(nextColumns); - }} + onClick={removeSort} /> )} @@ -172,17 +185,7 @@ export const EuiDataGridColumnSortingDraggable: FunctionComponent< buttonSize="compressed" className="euiDataGridColumnSorting__order" idSelected={direction === 'asc' ? `${id}Asc` : `${id}Desc`} - onChange={(_, direction) => { - const nextColumns = [...sorting.columns]; - const columnIndex = nextColumns - .map(({ id }) => id) - .indexOf(id); - nextColumns.splice(columnIndex, 1, { - id, - direction, - }); - sorting.onSort(nextColumns); - }} + onChange={toggleLegendHandler} /> )} diff --git a/src/components/datagrid/data_grid.tsx b/src/components/datagrid/data_grid.tsx index fe3cccd9874..585991504f9 100644 --- a/src/components/datagrid/data_grid.tsx +++ b/src/components/datagrid/data_grid.tsx @@ -7,7 +7,14 @@ */ import classNames from 'classnames'; -import React, { forwardRef, useMemo, useRef, useState, memo } from 'react'; +import React, { + forwardRef, + useMemo, + useRef, + useState, + memo, + useCallback, +} from 'react'; import { VariableSizeGrid as Grid, GridOnItemsRenderedProps, @@ -369,6 +376,30 @@ export const EuiDataGrid = memo( delete rest['aria-labelledby']; } + const onKeyDown = useCallback( + (event: React.KeyboardEvent) => { + createKeyDownHandler({ + gridElement: contentRef.current, + visibleColCount, + visibleRowCount, + visibleRowStartIndex: + gridItemsRendered.current?.visibleRowStartIndex || 0, + rowCount, + pagination, + hasFooter: !!renderFooterCellValue, + focusContext, + })(event); + }, + [ + focusContext, + visibleColCount, + visibleRowCount, + rowCount, + pagination, + renderFooterCellValue, + ] + ); + return ( @@ -415,17 +446,7 @@ export const EuiDataGrid = memo( ) : null}
; }) => { const { getCorrectRowIndex } = useContext(DataGridSortingContext); - - let knownHeight = 0; // tracks the pixel height of rows we know the size of - let knownRowCount = 0; // how many rows we know the size of - for (let i = startRow; i < endRow; i++) { - const correctRowIndex = getCorrectRowIndex(i); // map visible row to logical row - - // lookup the height configuration of this row - const rowHeightOption = rowHeightUtils.getRowHeightOption( - correctRowIndex, - rowHeightsOptions - ); - - if (rowHeightOption) { - // this row's height is known - knownRowCount++; - knownHeight += rowHeightUtils.getCalculatedHeight( - rowHeightOption, - defaultRowHeight, - correctRowIndex, - rowHeightUtils.isRowHeightOverride(correctRowIndex, rowHeightsOptions) - ); - } - } - - // how many rows to provide space for on the screen - const rowCountToAffordFor = endRow - startRow; - // watch the inner element for a change to its width // which may cause the horizontal scrollbar to be added or removed const { width: innerWidth } = useResizeObserver( @@ -143,14 +122,52 @@ export const useUnconstrainedHeight = ({ const forceRender = useForceRender(); useUpdateEffect(forceRender, [innerWidth]); - const unconstrainedHeight = - defaultRowHeight * (rowCountToAffordFor - knownRowCount) + // guess how much space is required for unknown rows - knownHeight + // computed pixel height of the known rows - headerRowHeight + // account for header - footerRowHeight + // account for footer - scrollBarHeight; // account for horizontal scrollbar + return useMemo(() => { + let knownHeight = 0; // tracks the pixel height of rows we know the size of + let knownRowCount = 0; // how many rows we know the size of + for (let i = startRow; i < endRow; i++) { + const correctRowIndex = getCorrectRowIndex(i); // map visible row to logical row + + // lookup the height configuration of this row + const rowHeightOption = rowHeightUtils.getRowHeightOption( + correctRowIndex, + rowHeightsOptions + ); + + if (rowHeightOption) { + // this row's height is known + knownRowCount++; + knownHeight += rowHeightUtils.getCalculatedHeight( + rowHeightOption, + defaultRowHeight, + correctRowIndex, + rowHeightUtils.isRowHeightOverride(correctRowIndex, rowHeightsOptions) + ); + } + } + + // how many rows to provide space for on the screen + const rowCountToAffordFor = endRow - startRow; + + const unconstrainedHeight = + defaultRowHeight * (rowCountToAffordFor - knownRowCount) + // guess how much space is required for unknown rows + knownHeight + // computed pixel height of the known rows + headerRowHeight + // account for header + footerRowHeight + // account for footer + scrollBarHeight; // account for horizontal scrollbar - return unconstrainedHeight; + return unconstrainedHeight; + }, [ + defaultRowHeight, + endRow, + footerRowHeight, + headerRowHeight, + startRow, + getCorrectRowIndex, + rowHeightUtils, + rowHeightsOptions, + scrollBarHeight, + ]); }; /** From 9ed36d931d100327820f992c8f81dc3e2d9078f3 Mon Sep 17 00:00:00 2001 From: Kevin Qualters Date: Wed, 7 Feb 2024 20:24:14 +0000 Subject: [PATCH 08/17] Fix upstream merge, tests, and types --- .../datagrid/body/cell/data_grid_cell.tsx | 254 +++++---- .../body/cell/data_grid_cell_actions.test.tsx | 24 +- .../body/cell/data_grid_cell_actions.tsx | 70 ++- .../body/cell/data_grid_cell_popover.tsx | 34 +- .../body/cell/data_grid_cell_wrapper.test.tsx | 12 +- .../body/cell/data_grid_cell_wrapper.tsx | 137 +++-- src/components/datagrid/body/cell/index.ts | 2 +- .../datagrid/body/data_grid_body_custom.tsx | 4 +- .../body/data_grid_body_virtualized.test.tsx | 2 +- .../body/data_grid_body_virtualized.tsx | 49 +- .../datagrid/controls/column_sorting.tsx | 484 ++++++++---------- 11 files changed, 502 insertions(+), 570 deletions(-) diff --git a/src/components/datagrid/body/cell/data_grid_cell.tsx b/src/components/datagrid/body/cell/data_grid_cell.tsx index 9e1fdcd32e3..e1ead508adf 100644 --- a/src/components/datagrid/body/cell/data_grid_cell.tsx +++ b/src/components/datagrid/body/cell/data_grid_cell.tsx @@ -47,7 +47,123 @@ import { import { DefaultCellPopover } from './data_grid_cell_popover'; import { HandleInteractiveChildren } from './focus_utils'; -const Cell: React.FunctionComponent<{ +const EuiDataGridCellContent: FunctionComponent< + EuiDataGridCellValueProps & { + setCellProps: EuiDataGridCellValueElementProps['setCellProps']; + setCellContentsRef: EuiDataGridCell['setCellContentsRef']; + isExpanded: boolean; + isControlColumn: boolean; + isFocused: boolean; + ariaRowIndex: number; + rowHeight?: EuiDataGridRowHeightOption; + cellActions?: ReactNode; + } +> = memo( + ({ + renderCellValue, + renderCellContext, + column, + setCellContentsRef, + rowIndex, + colIndex, + ariaRowIndex, + rowHeight, + rowHeightUtils, + isControlColumn, + isFocused, + cellActions, + ...rest + }) => { + // React is more permissible than the TS types indicate + const CellElement = + renderCellValue as JSXElementConstructor; + + const cellHeightType = + rowHeightUtils?.getHeightType(rowHeight) || 'default'; + + const classes = classNames( + 'euiDataGridRowCell__content', + `euiDataGridRowCell__content--${cellHeightType}Height`, + !isControlColumn && { + 'eui-textBreakWord': cellHeightType !== 'default', + 'eui-textTruncate': cellHeightType === 'default', + } + ); + + const mergedProps = useMemo(() => { + if (renderCellContext) { + return { + ...renderCellContext(), + ...rest, + }; + } else { + return rest; + } + }, [rest, renderCellContext]); + + let cellContent = ( +
+ +
+ ); + if (cellHeightType === 'lineCount' && !isControlColumn) { + const lines = rowHeightUtils!.getLineCount(rowHeight)!; + cellContent = ( + + {cellContent} + + ); + } + + const screenReaderText = ( + + + + ); + + return ( + <> + {cellContent} + {screenReaderText} + {cellActions} + + ); + } +); +EuiDataGridCellContent.displayName = 'EuiDataGridCellContent'; + +export const Cell: React.FunctionComponent<{ ariaRowIndex: number; isFocused: boolean; cellRef: React.MutableRefObject; @@ -143,132 +259,7 @@ const Cell: React.FunctionComponent<{ ); } ); - -const EuiDataGridCellContent: FunctionComponent< - EuiDataGridCellValueProps & { - setCellProps: EuiDataGridCellValueElementProps['setCellProps']; - setCellContentsRef: EuiDataGridCell['setCellContentsRef']; - isExpanded: boolean; - isControlColumn: boolean; - isFocused: boolean; - ariaRowIndex: number; - rowHeight?: EuiDataGridRowHeightOption; - cellActions?: ReactNode; - } -> = memo( - ({ - renderCellValue, - renderCellContext, - column, - setCellContentsRef, - rowIndex, - colIndex, - ariaRowIndex, - rowHeight, - rowHeightUtils, - isControlColumn, - isFocused, - cellActions, - ...rest - }) => { - // React is more permissible than the TS types indicate - const CellElement = - renderCellValue as JSXElementConstructor; - - const cellHeightType = - rowHeightUtils?.getHeightType(rowHeight) || 'default'; - - const classes = classNames( - 'euiDataGridRowCell__content', - `euiDataGridRowCell__content--${cellHeightType}Height`, - !isControlColumn && { - 'eui-textBreakWord': cellHeightType !== 'default', - 'eui-textTruncate': cellHeightType === 'default', - } - ); - - const mergedProps = useMemo(() => { - if (renderCellContext) { - return { - ...rest, - ...renderCellContext(), - }; - } else { - return { - ...rest, - }; - } - }, [rest, renderCellContext]); - - const cellContent = useMemo(() => { - return ( -
- -
- ); - }, [ - CellElement, - classes, - column, - colIndex, - mergedProps, - rowIndex, - setCellContentsRef, - rest.columnType, - ]); - - const truncatedCellContent = useMemo(() => { - if (cellHeightType === 'lineCount' && !isControlColumn) { - const lines = rowHeightUtils!.getLineCount(rowHeight)!; - return ( - - {cellContent} - - ); - } else { - return cellContent; - } - }, [ - cellContent, - cellHeightType, - isControlColumn, - rowHeightUtils, - rowHeight, - ]); - - return ( - <> - {truncatedCellContent} - - - - - ); - } -); -EuiDataGridCellContent.displayName = 'EuiDataGridCellContent'; +Cell.displayName = 'Cell'; export class EuiDataGridCell extends Component< EuiDataGridCellProps, @@ -647,7 +638,6 @@ export class EuiDataGridCell extends Component< }; handleCellExpansionClick = () => { - console.log('fire'); const { popoverContext: { openCellPopover, closeCellPopover }, visibleRowIndex, @@ -661,8 +651,12 @@ export class EuiDataGridCell extends Component< } }; - onMouseEnter = () => this.setState({ isHovered: true }); - onMouseLeave = () => this.setState({ isHovered: false }); + onMouseEnter = () => { + this.setState({ isHovered: true }); + }; + onMouseLeave = () => { + this.setState({ isHovered: false }); + }; render() { const { diff --git a/src/components/datagrid/body/cell/data_grid_cell_actions.test.tsx b/src/components/datagrid/body/cell/data_grid_cell_actions.test.tsx index ac88bd41bb3..fe16e085dd9 100644 --- a/src/components/datagrid/body/cell/data_grid_cell_actions.test.tsx +++ b/src/components/datagrid/body/cell/data_grid_cell_actions.test.tsx @@ -34,15 +34,17 @@ describe('EuiDataGridCellActions', () => {
- + + +
`); - const button: Function = component - .find('ExpandButton') - .renderProp('children'); + const button: Function = component.find('EuiI18n').renderProp('children'); expect(button('expandButtonTitle')).toMatchInlineSnapshot(` { key="0" rowIndex={0} /> - + + +
`); }); diff --git a/src/components/datagrid/body/cell/data_grid_cell_actions.tsx b/src/components/datagrid/body/cell/data_grid_cell_actions.tsx index 26602b79902..701b25655f2 100644 --- a/src/components/datagrid/body/cell/data_grid_cell_actions.tsx +++ b/src/components/datagrid/body/cell/data_grid_cell_actions.tsx @@ -6,13 +6,7 @@ * Side Public License, v 1. */ -import React, { - JSXElementConstructor, - useMemo, - useCallback, - type FunctionComponent, - type MouseEventHandler, -} from 'react'; +import React, { JSXElementConstructor, useMemo, useCallback } from 'react'; import { EuiDataGridColumn, EuiDataGridColumnCellAction, @@ -28,23 +22,22 @@ import { import { EuiFlexGroup, EuiFlexItem } from '../../../flex'; import { EuiPopoverFooter } from '../../../popover'; -const ButtonComponent = (props: EuiButtonIconProps) => ( - -); - -const ExpandButton: FunctionComponent<{ onExpandClick: MouseEventHandler }> = ({ +export const EuiDataGridCellActions = ({ onExpandClick, + column, + rowIndex, + colIndex, +}: { + onExpandClick: () => void; + column?: EuiDataGridColumn; + rowIndex: number; + colIndex: number; }) => { - return ( + // Note: The cell expand button/expansion popover is *always* rendered if + // column.cellActions is present (regardless of column.isExpandable). + // This is because cell actions are not otherwise accessible to keyboard + // or screen reader users + const expandButton = ( = ({ )} ); -}; - -export const EuiDataGridCellActions = ({ - onExpandClick, - column, - rowIndex, - colIndex, -}: { - onExpandClick: () => void; - column?: EuiDataGridColumn; - rowIndex: number; - colIndex: number; -}) => { - // Note: The cell expand button/expansion popover is *always* rendered if - // column.cellActions is present (regardless of column.isExpandable). - // This is because cell actions are not otherwise accessible to keyboard - // or screen reader users const additionalButtons = useMemo(() => { if (!column || !Array.isArray(column?.cellActions)) return []; + const ButtonComponent = (props: EuiButtonIconProps) => ( + + ); + const [visibleCellActions] = getVisibleCellActions( column?.cellActions, column?.visibleCellActions @@ -111,8 +100,7 @@ export const EuiDataGridCellActions = ({ return (
- {[...additionalButtons]} - + {[...additionalButtons, expandButton]}
); }; diff --git a/src/components/datagrid/body/cell/data_grid_cell_popover.tsx b/src/components/datagrid/body/cell/data_grid_cell_popover.tsx index a5d294399b1..13c1785656f 100644 --- a/src/components/datagrid/body/cell/data_grid_cell_popover.tsx +++ b/src/components/datagrid/body/cell/data_grid_cell_popover.tsx @@ -78,19 +78,6 @@ export const useCellPopover = (): { [popoverIsOpen, cellLocation] ); - const cellPopoverContext = useMemo(() => { - return { - popoverIsOpen, - closeCellPopover, - openCellPopover, - cellLocation, - setPopoverAnchorPosition, - setPopoverAnchor, - setPopoverContent, - setCellPopoverProps, - }; - }, [popoverIsOpen, closeCellPopover, openCellPopover, cellLocation]); - // Override the default EuiPopover `onClickOutside` behavior, since the toggling // popover button isn't actually the DOM node we pass to `button`. Otherwise, // clicking the expansion cell action triggers an outside click @@ -126,6 +113,16 @@ export const useCellPopover = (): { ); return useMemo(() => { + const cellPopoverContext = { + popoverIsOpen, + closeCellPopover, + openCellPopover, + cellLocation, + setPopoverAnchorPosition, + setPopoverAnchor, + setPopoverContent, + setCellPopoverProps, + }; // Note that this popover is rendered once at the top grid level, rather than one popover per cell const cellPopover = popoverIsOpen && popoverAnchor && ( { const requiredProps = { @@ -33,13 +33,13 @@ describe('Cell', () => { }; it('is a light wrapper around EuiDataGridCell', () => { - const component = shallow(); + const component = shallow(); expect(component.find('EuiDataGridCell').exists()).toBe(true); }); it('renders leading control column cells', () => { const component = shallow( - { it('renders trailing control column cells', () => { const component = shallow( - { it('renders text transform classes based on schema', () => { const component = shallow( - { it('allows passing optional EuiDataGridCellProps overrides', () => { const component = shallow( - & Pick< EuiDataGridBodyProps, @@ -52,7 +53,7 @@ type CellProps = Pick< * It grabs context, determines the type of cell being rendered * (e.g. control vs data cell), & sets shared props between all cells */ -export const Cell: FunctionComponent = memo( +export const CellWrapper: FunctionComponent = memo( ({ colIndex, visibleRowIndex, @@ -143,81 +144,61 @@ export const Cell: FunctionComponent = memo( textTransform, ]); - const cellContent = useMemo(() => { - if (isLeadingControlColumn) { - const leadingColumn = leadingControlColumns[colIndex]; - const { id, rowCellRender } = leadingColumn; - - return ( - - ); - } else if (isTrailingControlColumn) { - const columnOffset = columns.length + leadingControlColumns.length; - const trailingcolIndex = colIndex - columnOffset; - const trailingColumn = trailingControlColumns[trailingcolIndex]; - const { id, rowCellRender } = trailingColumn; - - return ( - - ); - } else { - // this is a normal data cell - const columnType = schema[columnId] - ? schema[columnId].columnType - : null; - - const isExpandable = - column.isExpandable !== undefined ? column.isExpandable : true; - - const width = columnWidths[columnId] || defaultColumnWidth; - - return ( - - ); - } - }, [ - colIndex, - isLeadingControlColumn, - isTrailingControlColumn, - rest, - columnId, - column, - columnWidths, - defaultColumnWidth, - renderCellValue, - renderCellPopover, - interactiveCellId, - sharedCellProps, - schema, - columns, - leadingControlColumns, - trailingControlColumns, - ]); - return cellContent; + if (isLeadingControlColumn) { + const leadingColumn = leadingControlColumns[colIndex]; + const { id, rowCellRender } = leadingColumn; + + return ( + + ); + } else if (isTrailingControlColumn) { + const columnOffset = columns.length + leadingControlColumns.length; + const trailingcolIndex = colIndex - columnOffset; + const trailingColumn = trailingControlColumns[trailingcolIndex]; + const { id, rowCellRender } = trailingColumn; + + return ( + + ); + } else { + // this is a normal data cell + const columnType = schema[columnId] ? schema[columnId].columnType : null; + + const isExpandable = + column?.isExpandable !== undefined ? column?.isExpandable : true; + + const width = columnWidths[columnId] || defaultColumnWidth; + + return ( + + ); + } } ); + +CellWrapper.displayName = 'CellWrapper'; diff --git a/src/components/datagrid/body/cell/index.ts b/src/components/datagrid/body/cell/index.ts index ed2338e8de2..97cbb9d8106 100644 --- a/src/components/datagrid/body/cell/index.ts +++ b/src/components/datagrid/body/cell/index.ts @@ -8,7 +8,7 @@ export { EuiDataGridCell } from './data_grid_cell'; -export { Cell } from './data_grid_cell_wrapper'; +export { CellWrapper } from './data_grid_cell_wrapper'; export { DataGridCellPopoverContext, diff --git a/src/components/datagrid/body/data_grid_body_custom.tsx b/src/components/datagrid/body/data_grid_body_custom.tsx index f2819d0a557..062449f1bfb 100644 --- a/src/components/datagrid/body/data_grid_body_custom.tsx +++ b/src/components/datagrid/body/data_grid_body_custom.tsx @@ -24,7 +24,7 @@ import { } from '../data_grid_types'; import { useDataGridHeader } from './header'; import { useDataGridFooter } from './footer'; -import { Cell } from './cell'; +import { CellWrapper } from './cell'; export const EuiDataGridBodyCustomRender: FunctionComponent< EuiDataGridBodyProps @@ -171,7 +171,7 @@ export const EuiDataGridBodyCustomRender: FunctionComponent< style, ...cellProps, }; - return ; + return ; }, [cellProps, getRowHeight, rowHeightUtils, rowHeightsOptions] ); diff --git a/src/components/datagrid/body/data_grid_body_virtualized.test.tsx b/src/components/datagrid/body/data_grid_body_virtualized.test.tsx index 8359f01414b..4e90776b7a4 100644 --- a/src/components/datagrid/body/data_grid_body_virtualized.test.tsx +++ b/src/components/datagrid/body/data_grid_body_virtualized.test.tsx @@ -56,7 +56,7 @@ describe('EuiDataGridBodyVirtualized', () => { renderFooterCellValue={() =>