From 59461c7e068bbd6b61fa85d8479516fbd9070a82 Mon Sep 17 00:00:00 2001
From: Constance
Date: Tue, 11 Jan 2022 13:30:32 -0800
Subject: [PATCH 1/9] [EuiDataGrid] Set up `ref` that exposes focus/popover
internal APIs (#5499)
* Set up types
* Set up forwardRef
* Add setFocusedCell API to returned grid ref obj
* Add colIndex prop to cell actions
- so that cell actions that trigger modals or flyouts can re-focus into the correct cell using the new ref API
* Add documentation + example + props
* Add changelog
* [PR feedback] Types
Co-authored-by: Chandler Prall
* [PR feedback] Clean up unit test
* [Rebase] Tweak useImperativeHandle location
- Moving it below fullscreen logic, as we're oging to expose setIsFullScreen as an API shortly
Co-authored-by: Chandler Prall
---
CHANGELOG.md | 2 +
src-docs/src/routes.js | 2 +
.../src/views/datagrid/datagrid_example.js | 17 +
.../views/datagrid/datagrid_ref_example.js | 67 ++
src-docs/src/views/datagrid/ref.js | 196 +++++
.../datagrid/body/data_grid_cell.tsx | 4 +-
.../body/data_grid_cell_buttons.test.tsx | 2 +
.../datagrid/body/data_grid_cell_buttons.tsx | 5 +-
.../body/data_grid_cell_popover.test.tsx | 3 +-
.../datagrid/body/data_grid_cell_popover.tsx | 2 +
src/components/datagrid/data_grid.test.tsx | 27 +-
src/components/datagrid/data_grid.tsx | 746 +++++++++---------
src/components/datagrid/data_grid_types.ts | 18 +
13 files changed, 725 insertions(+), 366 deletions(-)
create mode 100644 src-docs/src/views/datagrid/datagrid_ref_example.js
create mode 100644 src-docs/src/views/datagrid/ref.js
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3a6728405d0..4b13ae8a11c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,7 @@
## [`main`](https://github.com/elastic/eui/tree/main)
+- Added the ability to access certain `EuiDataGrid` internal methods via the `ref` prop ([#5499](https://github.com/elastic/eui/pull/5499))
+
**Breaking changes**
- Removed `data-test-subj="dataGridWrapper"` from `EuiDataGrid` in favor of `data-test-subj="euiDataGridBody"` ([#5506](https://github.com/elastic/eui/pull/5506))
diff --git a/src-docs/src/routes.js b/src-docs/src/routes.js
index 11d3ff15f0a..23c7d8366e5 100644
--- a/src-docs/src/routes.js
+++ b/src-docs/src/routes.js
@@ -89,6 +89,7 @@ import { DataGridControlColumnsExample } from './views/datagrid/datagrid_control
import { DataGridFooterRowExample } from './views/datagrid/datagrid_footer_row_example';
import { DataGridVirtualizationExample } from './views/datagrid/datagrid_virtualization_example';
import { DataGridRowHeightOptionsExample } from './views/datagrid/datagrid_height_options_example';
+import { DataGridRefExample } from './views/datagrid/datagrid_ref_example';
import { DatePickerExample } from './views/date_picker/date_picker_example';
@@ -489,6 +490,7 @@ const navigation = [
DataGridFooterRowExample,
DataGridVirtualizationExample,
DataGridRowHeightOptionsExample,
+ DataGridRefExample,
TableExample,
TableInMemoryExample,
].map((example) => createExample(example)),
diff --git a/src-docs/src/views/datagrid/datagrid_example.js b/src-docs/src/views/datagrid/datagrid_example.js
index 47a606c471c..3ecd3c20e5b 100644
--- a/src-docs/src/views/datagrid/datagrid_example.js
+++ b/src-docs/src/views/datagrid/datagrid_example.js
@@ -33,6 +33,7 @@ import {
EuiDataGridRowHeightsOptions,
EuiDataGridCellValueElementProps,
EuiDataGridSchemaDetector,
+ EuiDataGridRefProps,
} from '!!prop-loader!../../../../src/components/datagrid/data_grid_types';
const gridSnippet = `
@@ -164,6 +165,8 @@ const gridSnippet = `
);
},
}}
+ // Optional. For advanced control of internal data grid popover/focus state, passes back an object of API methods
+ ref={dataGridRef}
/>
`;
@@ -323,6 +326,19 @@ const gridConcepts = [
),
},
+ {
+ title: 'ref',
+ description: (
+
+ Passes back an object of internal EuiDataGridRefProps{' '}
+ methods for advanced control of data grid popover/focus state. See{' '}
+
+ Data grid ref methods
+ {' '}
+ for more details and examples.
+
+ ),
+ },
];
export const DataGridExample = {
@@ -414,6 +430,7 @@ export const DataGridExample = {
EuiDataGridToolBarAdditionalControlsLeftOptions,
EuiDataGridPopoverContentProps,
EuiDataGridRowHeightsOptions,
+ EuiDataGridRefProps,
},
demo: (
diff --git a/src-docs/src/views/datagrid/datagrid_ref_example.js b/src-docs/src/views/datagrid/datagrid_ref_example.js
new file mode 100644
index 00000000000..9345f4d75c0
--- /dev/null
+++ b/src-docs/src/views/datagrid/datagrid_ref_example.js
@@ -0,0 +1,67 @@
+import React from 'react';
+
+import { GuideSectionTypes } from '../../components';
+import {
+ EuiCode,
+ EuiCodeBlock,
+ EuiSpacer,
+ EuiCallOut,
+} from '../../../../src/components';
+
+import { EuiDataGridRefProps } from '!!prop-loader!../../../../src/components/datagrid/data_grid_types';
+import DataGridRef from './ref';
+const dataGridRefSource = require('!!raw-loader!./ref');
+const dataGridRefSnippet = `const dataGridRef = useRef();
+
+
+// Mnaually focus a specific cell within the data grid
+dataGridRef.current.setFocusedCell({ rowIndex, colIndex });
+`;
+
+export const DataGridRefExample = {
+ title: 'Data grid ref methods',
+ sections: [
+ {
+ source: [
+ {
+ type: GuideSectionTypes.JS,
+ code: dataGridRefSource,
+ },
+ ],
+ text: (
+ <>
+
+ For advanced use cases, and particularly for data grids that manage
+ associated modals/flyouts and need to manually control their grid
+ cell popovers & focus states, we expose certain internal methods via
+ the ref prop of EuiDataGrid. These methods are:
+
+
+
+ setFocusedCell({'{ rowIndex, colIndex }'}) -
+ focuses the specified cell in the grid.
+
+
+ Your modal or flyout should restore focus into the grid on close
+ to prevent keyboard or screen reader users from being stranded.
+
+
+
+ {dataGridRefSnippet}
+
+ The below example shows how to use the internal APIs for a data grid
+ that opens a modal via cell actions.
+
+
+ {/* TODO: if no keyboard shortcuts panel gets built, add keyboard shortcut info here */}
- )}
- {pagination && (
-
- )}
-
-
- {/* TODO: if no keyboard shortcuts panel gets built, add keyboard shortcut info here */}
-
-
-
-
-
- );
-};
+
+
+
+
+ );
+ }
+);
+
+EuiDataGrid.displayName = 'EuiDataGrid';
diff --git a/src/components/datagrid/data_grid_types.ts b/src/components/datagrid/data_grid_types.ts
index 4e06beba1c0..ba23040dba9 100644
--- a/src/components/datagrid/data_grid_types.ts
+++ b/src/components/datagrid/data_grid_types.ts
@@ -283,6 +283,19 @@ export type EuiDataGridProps = OneOf<
'aria-label' | 'aria-labelledby'
>;
+export interface EuiDataGridRefProps {
+ /**
+ * Allows manually focusing the specified cell in the grid.
+ *
+ * Using this method is an accessibility requirement if your EuiDataGrid
+ * toggles a modal or flyout - focus must be restored to the grid on close
+ * to prevent keyboard or screen reader users from being stranded.
+ */
+ setFocusedCell(targetCell: EuiDataGridCellLocation): void;
+}
+
+export type EuiDataGridCellLocation = { rowIndex: number; colIndex: number };
+
export interface EuiDataGridColumnResizerProps {
columnId: string;
columnWidth: number;
@@ -307,6 +320,7 @@ export interface EuiDataGridCellPopoverProps {
| JSXElementConstructor
| ((props: EuiDataGridCellValueElementProps) => ReactNode);
rowIndex: number;
+ colIndex: number;
}
export interface EuiDataGridColumnSortingDraggableProps {
id: string;
@@ -516,6 +530,10 @@ export interface EuiDataGridColumnCellActionProps {
* The index of the row that contains cell's data
*/
rowIndex: number;
+ /**
+ * The index of the column that contains cell's data
+ */
+ colIndex: number;
/**
* The id of the column that contains the cell's data
*/
From e5def1c73045810f0022be1bfeb48472ec07d2d6 Mon Sep 17 00:00:00 2001
From: Constance
Date: Wed, 12 Jan 2022 09:00:59 -0800
Subject: [PATCH 2/9] [EuiDataGrid] Add `setIsFullScreen` to ref API (#5531)
* Expose `setIsFullScreen` to ref API
* Update documentation/examples
---
src-docs/src/views/datagrid/datagrid_ref_example.js | 10 ++++++++++
src-docs/src/views/datagrid/ref.js | 8 ++++++++
src/components/datagrid/data_grid.test.tsx | 1 +
src/components/datagrid/data_grid.tsx | 1 +
src/components/datagrid/data_grid_types.ts | 4 ++++
5 files changed, 24 insertions(+)
diff --git a/src-docs/src/views/datagrid/datagrid_ref_example.js b/src-docs/src/views/datagrid/datagrid_ref_example.js
index 9345f4d75c0..d3ab45bf471 100644
--- a/src-docs/src/views/datagrid/datagrid_ref_example.js
+++ b/src-docs/src/views/datagrid/datagrid_ref_example.js
@@ -14,6 +14,9 @@ const dataGridRefSource = require('!!raw-loader!./ref');
const dataGridRefSnippet = `const dataGridRef = useRef();
+// Mnaually toggle the data grid's full screen state
+dataGridRef.current.setIsFullScreen(true);
+
// Mnaually focus a specific cell within the data grid
dataGridRef.current.setFocusedCell({ rowIndex, colIndex });
`;
@@ -37,6 +40,13 @@ export const DataGridRefExample = {
the ref prop of EuiDataGrid. These methods are:
+
+
+ setIsFullScreen(isFullScreen) - controls the
+ full screen state of the data grid. Accepts a true/false boolean
+ flag.
+
+
setFocusedCell({'{ rowIndex, colIndex }'}) -
focuses the specified cell in the grid.
diff --git a/src-docs/src/views/datagrid/ref.js b/src-docs/src/views/datagrid/ref.js
index e043abcedc9..62a0f64b807 100644
--- a/src-docs/src/views/datagrid/ref.js
+++ b/src-docs/src/views/datagrid/ref.js
@@ -148,6 +148,14 @@ export default () => {
Set cell focus
+
+ dataGridRef.current.setIsFullScreen(true)}
+ >
+ Set grid to full screen
+
+
diff --git a/src/components/datagrid/data_grid.test.tsx b/src/components/datagrid/data_grid.test.tsx
index a4200aab0ad..66bd8cba928 100644
--- a/src/components/datagrid/data_grid.test.tsx
+++ b/src/components/datagrid/data_grid.test.tsx
@@ -2744,6 +2744,7 @@ describe('EuiDataGrid', () => {
);
expect(gridRef.current).toEqual({
+ setIsFullScreen: expect.any(Function),
setFocusedCell: expect.any(Function),
});
});
diff --git a/src/components/datagrid/data_grid.tsx b/src/components/datagrid/data_grid.tsx
index f5688219b83..d350c50c919 100644
--- a/src/components/datagrid/data_grid.tsx
+++ b/src/components/datagrid/data_grid.tsx
@@ -290,6 +290,7 @@ export const EuiDataGrid = forwardRef(
useImperativeHandle(
ref,
() => ({
+ setIsFullScreen,
setFocusedCell: ({ rowIndex, colIndex }) => {
focusContext.setFocusedCell([colIndex, rowIndex]);
},
diff --git a/src/components/datagrid/data_grid_types.ts b/src/components/datagrid/data_grid_types.ts
index ba23040dba9..cf788d29b88 100644
--- a/src/components/datagrid/data_grid_types.ts
+++ b/src/components/datagrid/data_grid_types.ts
@@ -284,6 +284,10 @@ export type EuiDataGridProps = OneOf<
>;
export interface EuiDataGridRefProps {
+ /**
+ * Allows manually controlling the full-screen state of the grid.
+ */
+ setIsFullScreen: (isFullScreen: boolean) => void;
/**
* Allows manually focusing the specified cell in the grid.
*
From f8a25bca3ee27eee7eed541d97543b363f0bed96 Mon Sep 17 00:00:00 2001
From: Constance
Date: Wed, 26 Jan 2022 15:46:23 -0800
Subject: [PATCH 3/9] [EuiDataGrid] Add `openCellPopover` and
`closeCellPopover` to ref APIs (#5550)
* [setup] Update testCustomHook to expose fn that allows accessing most recent state/value
- without this callback, the initial returned hook values will be stale/not properly return most recent values
- see next commit for example usage within useCellPopover
* Set up cell popover context
- set up initial open/location state, + open/close popover APIs returned to consumers
- improve auto props documentation - remove EuiDataGridCellLocation in favor of specifying rowIndex and colIndex (it's less DRY but it's easier for devs to not have to look up EuiDataGridCellLocation from our docs)
* Pass down popoverContext to cells as a prop
- I'm not using context here because we're already using this.context for focus, and unfortunately class components can only initialize one context at time using `static contextType` (see https://reactjs.org/docs/context.html#classcontexttype)
* Remove internal cell popoverIsOpen state
- This should now be handled by the overarching context state, and the cell should simply react to it or update it (similar to how focusContext works)
+ add new var for hasCellButtons
+ add unit tests for isFocusedCell alongside isPopoverOpen (since both methods perform similar functions)
* Update cell popovers to set the popover anchor & content
- content is TODO, will likely be easier to compare when cleaning it up/moving it all at once
* Refactor EuiDataGridCellPopover
- It should no longer exist as a reusable component that belongs to every single cell, but instead as a single popover that exists at the top grid level and moves from cell to cell
- I cleaned and split up the JSX for the popover (e.g. moving popover actions to data_grid_cell_buttons, where it feels like it belongs more) and think it's significantly more DRY now - note the entire `anchorContent` branch removed from EuiDataGridCell that is no longer necessary
- Note that due to this change, we now have to mock EuiWrappingPopover in EuiDataGrid tests, as we see failures otherwise
* [bugfix] Handle cells with open popover being scrolled out of view
- this is the same behavior as in prod
- causes weird DOM issues if we don't close the cell popover automatically
* [bugfix] Workaround for popover DOM stuttering issues
* [enhancement] Account for openCellPopover being called on cells out of view
+ write bonus Cypress test for useScroll's focus effect now that we have access to the imperative ref
* Update documentation example
+ remove code snippet - it was starting to get redundant with the API bullet points, and is already available as tab if needed
+ fix control button widths
* [PR feedback] Be more specific about useImperativeHandle dependencies
+ add a few explanatory comments
* [PR feedback] Rename openCellLocation to cellLocation
- to make it sound less like a verb/method
* [PR feedback] Ignore edge case of `openCellPopover` being called on an `isExpandable: false` cell
---
.../views/datagrid/datagrid_ref_example.js | 27 +-
src-docs/src/views/datagrid/ref.js | 24 +-
.../data_grid_cell.test.tsx.snap | 47 +++
.../datagrid/body/data_grid_body.tsx | 5 +-
.../datagrid/body/data_grid_cell.test.tsx | 202 ++++++++++-
.../datagrid/body/data_grid_cell.tsx | 161 ++++++---
.../body/data_grid_cell_buttons.test.tsx | 63 +++-
.../datagrid/body/data_grid_cell_buttons.tsx | 44 +++
.../body/data_grid_cell_popover.test.tsx | 322 ++++++++++--------
.../datagrid/body/data_grid_cell_popover.tsx | 139 ++++----
.../body/data_grid_footer_row.test.tsx | 52 +++
.../datagrid/body/data_grid_footer_row.tsx | 5 +-
.../controls/display_selector.test.tsx | 4 +-
src/components/datagrid/data_grid.test.tsx | 10 +
src/components/datagrid/data_grid.tsx | 247 +++++++-------
src/components/datagrid/data_grid_types.ts | 46 +--
.../datagrid/utils/scrolling.spec.tsx | 35 +-
.../datagrid/utils/scrolling.test.ts | 68 ++--
src/components/datagrid/utils/scrolling.ts | 14 +
src/test/test_custom_hook.tsx | 15 +-
20 files changed, 1069 insertions(+), 461 deletions(-)
diff --git a/src-docs/src/views/datagrid/datagrid_ref_example.js b/src-docs/src/views/datagrid/datagrid_ref_example.js
index d3ab45bf471..ee550b19479 100644
--- a/src-docs/src/views/datagrid/datagrid_ref_example.js
+++ b/src-docs/src/views/datagrid/datagrid_ref_example.js
@@ -1,12 +1,7 @@
import React from 'react';
import { GuideSectionTypes } from '../../components';
-import {
- EuiCode,
- EuiCodeBlock,
- EuiSpacer,
- EuiCallOut,
-} from '../../../../src/components';
+import { EuiCode, EuiSpacer, EuiCallOut } from '../../../../src/components';
import { EuiDataGridRefProps } from '!!prop-loader!../../../../src/components/datagrid/data_grid_types';
import DataGridRef from './ref';
@@ -19,6 +14,12 @@ dataGridRef.current.setIsFullScreen(true);
// Mnaually focus a specific cell within the data grid
dataGridRef.current.setFocusedCell({ rowIndex, colIndex });
+
+// Manually opens the popover of a specified cell within the data grid
+dataGridRef.current.openCellPopover({ rowIndex, colIndex });
+
+// Close any open cell popover
+dataGridRef.current.closeCellPopover();
`;
export const DataGridRefExample = {
@@ -59,9 +60,21 @@ export const DataGridRefExample = {
Your modal or flyout should restore focus into the grid on close
to prevent keyboard or screen reader users from being stranded.
+
+
+ openCellPopover({'{ rowIndex, colIndex }'}) -
+ opens the specified cell's popover contents.
+
+
+ When using setFocusedCell or{' '}
+ openCellPopover, keep in mind:
+
+
+ colIndex is affected by the user reordering or hiding
+ columns.
+
+
+ If the passed cell indices are outside the data grid's
+ total row count or visible column count, an error will be
+ thrown.
+
+
+ If the data grid is paginated or sorted, the grid will
+ handle automatically finding specified row index's
+ correct location for you.
+
+
+
+
diff --git a/src-docs/src/views/datagrid/ref.js b/src-docs/src/views/datagrid/ref.tsx
similarity index 78%
rename from src-docs/src/views/datagrid/ref.js
rename to src-docs/src/views/datagrid/ref.tsx
index 2ce537c6884..267892df41e 100644
--- a/src-docs/src/views/datagrid/ref.js
+++ b/src-docs/src/views/datagrid/ref.tsx
@@ -1,4 +1,5 @@
import React, { useCallback, useMemo, useState, useRef } from 'react';
+// @ts-ignore - faker does not have type declarations
import { fake } from 'faker';
import {
@@ -9,15 +10,16 @@ import {
EuiFieldNumber,
EuiButton,
EuiDataGrid,
+ EuiDataGridRefProps,
EuiModal,
EuiModalBody,
EuiModalFooter,
EuiModalHeader,
EuiModalHeaderTitle,
EuiText,
-} from '../../../../src/components/';
+} from '../../../../src/components';
-const raw_data = [];
+const raw_data: Array<{ [key: string]: string }> = [];
for (let i = 1; i < 100; i++) {
raw_data.push({
name: fake('{{name.lastName}}, {{name.firstName}}'),
@@ -29,20 +31,23 @@ for (let i = 1; i < 100; i++) {
}
export default () => {
- const dataGridRef = useRef();
+ const dataGridRef = useRef(null);
// Modal
const [isModalVisible, setIsModalVisible] = useState(false);
- const [lastFocusedCell, setLastFocusedCell] = useState({});
+ const [lastFocusedCell, setLastFocusedCell] = useState({
+ rowIndex: 0,
+ colIndex: 0,
+ });
const closeModal = useCallback(() => {
setIsModalVisible(false);
- dataGridRef.current.setFocusedCell(lastFocusedCell); // Set the data grid focus back to the cell that opened the modal
+ dataGridRef.current!.setFocusedCell(lastFocusedCell); // Set the data grid focus back to the cell that opened the modal
}, [lastFocusedCell]);
const showModal = useCallback(({ rowIndex, colIndex }) => {
setIsModalVisible(true);
- dataGridRef.current.closeCellPopover(); // Close any open cell popovers
+ dataGridRef.current!.closeCellPopover(); // Close any open cell popovers
setLastFocusedCell({ rowIndex, colIndex }); // Store the cell that opened this modal
}, []);
@@ -101,11 +106,18 @@ export default () => {
// Pagination
const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 25 });
- const onChangePage = useCallback(
- (pageIndex) =>
- setPagination((pagination) => ({ ...pagination, pageIndex })),
- []
- );
+ const onChangePage = useCallback((pageIndex) => {
+ setPagination((pagination) => ({ ...pagination, pageIndex }));
+ }, []);
+ const onChangePageSize = useCallback((pageSize) => {
+ setPagination((pagination) => ({ ...pagination, pageSize }));
+ }, []);
+
+ // Sorting
+ const [sortingColumns, setSortingColumns] = useState([]);
+ const onSort = useCallback((sortingColumns) => {
+ setSortingColumns(sortingColumns);
+ }, []);
// Manual cell focus
const [rowIndexAction, setRowIndexAction] = useState(0);
@@ -118,7 +130,7 @@ export default () => {
setRowIndexAction(Number(e.target.value))}
compressed
@@ -129,7 +141,7 @@ export default () => {
setColIndexAction(Number(e.target.value))}
compressed
@@ -140,7 +152,7 @@ export default () => {
- dataGridRef.current.setFocusedCell({
+ dataGridRef.current!.setFocusedCell({
rowIndex: rowIndexAction,
colIndex: colIndexAction,
})
@@ -153,7 +165,7 @@ export default () => {
- dataGridRef.current.openCellPopover({
+ dataGridRef.current!.openCellPopover({
rowIndex: rowIndexAction,
colIndex: colIndexAction,
})
@@ -165,7 +177,7 @@ export default () => {
dataGridRef.current.setIsFullScreen(true)}
+ onClick={() => dataGridRef.current!.setIsFullScreen(true)}
>
Set grid to full screen
@@ -177,14 +189,17 @@ export default () => {
aria-label="Data grid demo"
columns={columns}
columnVisibility={{ visibleColumns, setVisibleColumns }}
+ sorting={{ columns: sortingColumns, onSort }}
+ inMemory={{ level: 'sorting' }}
rowCount={raw_data.length}
renderCellValue={({ rowIndex, columnId }) =>
raw_data[rowIndex][columnId]
}
pagination={{
...pagination,
- pageSizeOptions: [25],
+ pageSizeOptions: [25, 50],
onChangePage: onChangePage,
+ onChangeItemsPerPage: onChangePageSize,
}}
height={400}
ref={dataGridRef}
diff --git a/src/components/datagrid/body/data_grid_cell.tsx b/src/components/datagrid/body/data_grid_cell.tsx
index 7413dfc58e8..b6041330b0f 100644
--- a/src/components/datagrid/body/data_grid_cell.tsx
+++ b/src/components/datagrid/body/data_grid_cell.tsx
@@ -490,7 +490,7 @@ export class EuiDataGridCell extends Component<
rowManager,
...rest
} = this.props;
- const { rowIndex, colIndex } = rest;
+ const { rowIndex, visibleRowIndex, colIndex } = rest;
const popoverIsOpen = this.isPopoverOpen();
const hasCellButtons = isExpandable || column?.cellActions;
@@ -534,7 +534,7 @@ export class EuiDataGridCell extends Component<
case keys.ENTER:
case keys.F2:
event.preventDefault();
- openCellPopover({ rowIndex, colIndex });
+ openCellPopover({ rowIndex: visibleRowIndex, colIndex });
break;
}
} else {
@@ -641,7 +641,7 @@ export class EuiDataGridCell extends Component<
if (popoverIsOpen) {
closeCellPopover();
} else {
- openCellPopover({ rowIndex, colIndex });
+ openCellPopover({ rowIndex: visibleRowIndex, colIndex });
}
}}
/>
diff --git a/src/components/datagrid/body/header/data_grid_header_cell.test.tsx b/src/components/datagrid/body/header/data_grid_header_cell.test.tsx
index e17c2f65a82..2e26839e72f 100644
--- a/src/components/datagrid/body/header/data_grid_header_cell.test.tsx
+++ b/src/components/datagrid/body/header/data_grid_header_cell.test.tsx
@@ -49,7 +49,7 @@ describe('EuiDataGridHeaderCell', () => {
diff --git a/src/components/datagrid/data_grid.test.tsx b/src/components/datagrid/data_grid.test.tsx
index 019b20824b7..846d8f27d73 100644
--- a/src/components/datagrid/data_grid.test.tsx
+++ b/src/components/datagrid/data_grid.test.tsx
@@ -6,10 +6,10 @@
* Side Public License, v 1.
*/
-import React, { useEffect, useState, createRef } from 'react';
+import React, { useEffect, useState } from 'react';
import { mount, ReactWrapper, render } from 'enzyme';
import { EuiDataGrid } from './';
-import { EuiDataGridProps, EuiDataGridRefProps } from './data_grid_types';
+import { EuiDataGridProps } from './data_grid_types';
import {
findTestSubject,
requiredProps,
@@ -2749,29 +2749,4 @@ describe('EuiDataGrid', () => {
expect(takeMountedSnapshot(component)).toMatchSnapshot();
});
});
-
- it('returns a ref which exposes internal imperative APIs', () => {
- const gridRef = createRef();
-
- mount(
- {},
- }}
- rowCount={1}
- renderCellValue={() => 'value'}
- ref={gridRef}
- />
- );
-
- expect(gridRef.current).toEqual({
- setIsFullScreen: expect.any(Function),
- setFocusedCell: expect.any(Function),
- openCellPopover: expect.any(Function),
- closeCellPopover: expect.any(Function),
- });
- });
});
diff --git a/src/components/datagrid/data_grid.tsx b/src/components/datagrid/data_grid.tsx
index e84c0caa00a..bed2f94fb14 100644
--- a/src/components/datagrid/data_grid.tsx
+++ b/src/components/datagrid/data_grid.tsx
@@ -13,7 +13,6 @@ import React, {
useMemo,
useRef,
useState,
- useImperativeHandle,
} from 'react';
import {
VariableSizeGrid as Grid,
@@ -56,6 +55,7 @@ import {
schemaDetectors as providedSchemaDetectors,
useMergedSchema,
} from './utils/data_grid_schema';
+import { useImperativeGridRef } from './utils/ref';
import {
EuiDataGridColumn,
EuiDataGridProps,
@@ -302,23 +302,18 @@ export const EuiDataGrid = forwardRef(
};
/**
- * Expose internal APIs as ref to consumer
+ * Expose certain internal APIs as ref to consumer
*/
- const { setFocusedCell } = focusContext; // eslint complains about the dependency array otherwise
- const { openCellPopover, closeCellPopover } = cellPopoverContext;
-
- useImperativeHandle(
+ useImperativeGridRef({
ref,
- () => ({
- setIsFullScreen,
- setFocusedCell: ({ rowIndex, colIndex }) => {
- setFocusedCell([colIndex, rowIndex]); // Transmog args from obj to array
- },
- openCellPopover,
- closeCellPopover,
- }),
- [setFocusedCell, openCellPopover, closeCellPopover]
- );
+ setIsFullScreen,
+ focusContext,
+ cellPopoverContext,
+ sortingContext,
+ pagination,
+ rowCount,
+ visibleColCount,
+ });
/**
* Classes
diff --git a/src/components/datagrid/data_grid_types.ts b/src/components/datagrid/data_grid_types.ts
index d3d8abdbb1e..48618ab813d 100644
--- a/src/components/datagrid/data_grid_types.ts
+++ b/src/components/datagrid/data_grid_types.ts
@@ -179,10 +179,13 @@ export interface EuiDataGridVisibleRows {
export interface DataGridSortingContextShape {
sorting?: EuiDataGridSorting;
- sortedRowMap: { [key: number]: number };
- getCorrectRowIndex: (rowIndex: number) => number;
+ sortedRowMap: number[];
+ getCorrectRowIndex: (visibleRowIndex: number) => number;
}
+// An array of [x,y] coordinates. Note that the `y` value expected internally is a `visibleRowIndex`
+export type EuiDataGridFocusedCell = [number, number];
+
export interface DataGridFocusContextShape {
focusedCell?: EuiDataGridFocusedCell;
setFocusedCell: (cell: EuiDataGridFocusedCell) => void;
@@ -196,6 +199,7 @@ export interface DataGridFocusContextShape {
export interface DataGridCellPopoverContextShape {
popoverIsOpen: boolean;
+ // Note that the rowIndex used to locate cells internally is a `visibleRowIndex`
cellLocation: { rowIndex: number; colIndex: number };
openCellPopover(args: { rowIndex: number; colIndex: number }): void;
closeCellPopover(): void;
@@ -764,8 +768,6 @@ export interface EuiDataGridInMemory {
skipColumns?: string[];
}
-export type EuiDataGridFocusedCell = [number, number];
-
export interface EuiDataGridInMemoryValues {
[rowIndex: string]: { [columnId: string]: string };
}
diff --git a/src/components/datagrid/utils/ref.spec.tsx b/src/components/datagrid/utils/ref.spec.tsx
new file mode 100644
index 00000000000..59f8937ddd3
--- /dev/null
+++ b/src/components/datagrid/utils/ref.spec.tsx
@@ -0,0 +1,198 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import React, { useState, createRef, forwardRef } from 'react';
+import { EuiDataGrid } from '../';
+import { EuiDataGridRefProps } from '../data_grid_types';
+
+// We need to set up a test component here for sorting/pagination state to work
+// The underlying imperative ref should still be forwarded and work as normal
+const GridTest = forwardRef((_, ref) => {
+ // Pagination
+ const [pageIndex, setPageIndex] = useState(0);
+ const onChangePage = (pageIndex) => setPageIndex(pageIndex);
+
+ // Sorting
+ const [sortingColumns, setSortingColumns] = useState([]);
+ const onSort = (sortingColumns) => setSortingColumns(sortingColumns);
+
+ return (
+ {},
+ }}
+ rowCount={100}
+ renderCellValue={({ rowIndex, columnId }) => `${columnId}, ${rowIndex}`}
+ pagination={{
+ pageIndex,
+ pageSize: 25,
+ onChangePage,
+ onChangeItemsPerPage: () => {},
+ }}
+ sorting={{ columns: sortingColumns, onSort }}
+ inMemory={{ level: 'sorting' }}
+ />
+ );
+});
+GridTest.displayName = 'GridTest';
+
+describe('useImperativeGridRef', () => {
+ const ref = createRef();
+
+ beforeEach(() => {
+ cy.mount();
+ cy.get('[data-test-subj="euiDataGridBody"]'); // Wait for the grid to finish rendering and pass back the ref
+ });
+
+ describe('setIsFullScreen', () => {
+ it('allows the consumer to manually toggle full screen mode', () => {
+ ref.current.setIsFullScreen(true);
+ cy.get('[data-test-subj="euiDataGrid"]').should(
+ 'have.class',
+ 'euiDataGrid--fullScreen'
+ );
+ // Has to be a separate .then() block from the above for some Cypress-y reason
+ cy.then(() => {
+ ref.current.setIsFullScreen(false);
+ cy.get('[data-test-subj="euiDataGrid"]').should(
+ 'not.have.class',
+ 'euiDataGrid--fullScreen'
+ );
+ });
+ });
+ });
+
+ describe('setFocusedCell', () => {
+ it('allows the consumer to manually focus into a specific grid cell', () => {
+ ref.current.setFocusedCell({ rowIndex: 1, colIndex: 1 });
+ cy.focused()
+ .should('have.attr', 'data-gridcell-visible-row-index', '1')
+ .should('have.attr', 'data-gridcell-column-index', '1');
+ });
+
+ it('should scroll to cells that are not in view', () => {
+ ref.current.setFocusedCell({ rowIndex: 24, colIndex: 5 });
+ cy.focused()
+ .should('have.attr', 'data-gridcell-visible-row-index', '24')
+ .should('have.attr', 'data-gridcell-column-index', '5');
+ });
+
+ it('should paginate to cells that are not on the current page', () => {
+ ref.current.setFocusedCell({ rowIndex: 50, colIndex: 0 });
+ cy.get('.euiPagination .euiScreenReaderOnly').should(
+ 'have.text',
+ 'Page 3 of 4'
+ );
+ cy.focused()
+ .should('have.attr', 'data-gridcell-visible-row-index', '0')
+ .should('have.attr', 'data-gridcell-column-index', '0');
+ });
+
+ it('should correctly find the specified rowIndex when sorted', () => {
+ cy.get('[data-test-subj="dataGridHeaderCell-A"]').click();
+ cy.contains('Sort High-Low').click();
+ cy.then(() => {
+ ref.current.setFocusedCell({ rowIndex: 95, colIndex: 0 });
+ cy.focused()
+ .should('have.attr', 'data-gridcell-visible-row-index', '4')
+ .should('have.attr', 'data-gridcell-column-index', '0');
+ });
+ });
+
+ it('should throw an error if the passed cell indices are invalid', () => {
+ cy.on('fail', (err) => {
+ expect(err.message).to.equal(
+ 'Row 150 is not a valid row. The maximum visible row index is 99.'
+ );
+ });
+ ref.current.setFocusedCell({ rowIndex: 150, colIndex: 0 });
+ });
+ });
+
+ describe('openCellPopover', () => {
+ it("allows the consumer to manually open a specific grid cell's popover", () => {
+ ref.current.openCellPopover({ rowIndex: 2, colIndex: 2 });
+ cy.focused()
+ .should('have.attr', 'data-test-subj', 'euiDataGridExpansionPopover')
+ .find('.euiText')
+ .should('have.text', 'C, 2');
+ });
+
+ it('should scroll to cells that are not in view', () => {
+ ref.current.openCellPopover({ rowIndex: 23, colIndex: 0 });
+ cy.focused()
+ .should('have.attr', 'data-test-subj', 'euiDataGridExpansionPopover')
+ .find('.euiText')
+ .should('have.text', 'A, 23');
+ });
+
+ it('should paginate to cells that are not on the current page', () => {
+ ref.current.openCellPopover({ rowIndex: 99, colIndex: 5 });
+ cy.get('.euiPagination .euiScreenReaderOnly').should(
+ 'have.text',
+ 'Page 4 of 4'
+ );
+ cy.focused()
+ .should('have.attr', 'data-test-subj', 'euiDataGridExpansionPopover')
+ .find('.euiText')
+ .should('have.text', 'F, 99');
+ });
+
+ it('should correctly find the specified rowIndex when sorted', () => {
+ cy.get('[data-test-subj="dataGridHeaderCell-A"]').click();
+ cy.contains('Sort High-Low').click();
+ cy.then(() => {
+ ref.current.openCellPopover({ rowIndex: 98, colIndex: 1 });
+ cy.focused()
+ .should('have.attr', 'data-test-subj', 'euiDataGridExpansionPopover')
+ .find('.euiText')
+ .should('have.text', 'B, 98');
+ });
+ });
+
+ it('should throw an error if the passed cell indices are invalid', () => {
+ cy.on('fail', (err) => {
+ expect(err.message).to.equal(
+ 'Column 10 is not a valid column. The maximum visible column index is 5.'
+ );
+ });
+ ref.current.openCellPopover({ rowIndex: 0, colIndex: 10 });
+ });
+ });
+
+ describe('closeCellPopover', () => {
+ it('allows the consumer to manually close any open popovers', () => {
+ ref.current.setFocusedCell({ colIndex: 0, rowIndex: 0 });
+ cy.realPress('Enter');
+ cy.get('[data-test-subj="euiDataGridExpansionPopover"]').should(
+ 'have.length',
+ 1
+ );
+ cy.then(() => {
+ ref.current.closeCellPopover();
+ cy.get('[data-test-subj="euiDataGridExpansionPopover"]').should(
+ 'have.length',
+ 0
+ );
+ });
+ });
+ });
+});
diff --git a/src/components/datagrid/utils/ref.test.ts b/src/components/datagrid/utils/ref.test.ts
new file mode 100644
index 00000000000..bf49b5fcd87
--- /dev/null
+++ b/src/components/datagrid/utils/ref.test.ts
@@ -0,0 +1,105 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { testCustomHook } from '../../../test/test_custom_hook.test_helper';
+import { useCellLocationCheck, useSortPageCheck } from './ref';
+
+// see ref.spec.tsx for E2E useImperativeGridRef tests
+
+describe('useCellLocationCheck', () => {
+ const {
+ return: { checkCellExists },
+ } = testCustomHook(() => useCellLocationCheck(10, 5));
+
+ it("throws an error if the passed rowIndex is higher than the grid's rowCount", () => {
+ expect(() => {
+ checkCellExists({ rowIndex: 12, colIndex: 0 });
+ }).toThrow(
+ 'Row 12 is not a valid row. The maximum visible row index is 9.'
+ );
+ });
+
+ it("throws an error if the passed colIndex is higher than the grid's visibleColCount", () => {
+ expect(() => {
+ checkCellExists({ rowIndex: 1, colIndex: 5 });
+ }).toThrow(
+ 'Column 5 is not a valid column. The maximum visible column index is 4.'
+ );
+ });
+
+ it('does not throw if the rowIndex and colIndex are within grid bounds', () => {
+ expect(() => {
+ checkCellExists({ rowIndex: 0, colIndex: 0 });
+ }).not.toThrow();
+ });
+});
+
+describe('useSortPageCheck', () => {
+ describe('if the grid is not sorted or paginated', () => {
+ const pagination = undefined;
+ const sortedRowMap: number[] = [];
+
+ it('returns the passed rowIndex as-is', () => {
+ const {
+ return: { findVisibleRowIndex },
+ } = testCustomHook(() => useSortPageCheck(pagination, sortedRowMap));
+
+ expect(findVisibleRowIndex(5)).toEqual(5);
+ });
+ });
+
+ describe('if the grid is sorted', () => {
+ const pagination = undefined;
+ const sortedRowMap = [3, 4, 1, 2, 0];
+
+ it('returns the visibleRowIndex of the passed rowIndex (which is the index of the sortedRowMap)', () => {
+ const {
+ return: { findVisibleRowIndex },
+ } = testCustomHook(() => useSortPageCheck(pagination, sortedRowMap));
+
+ expect(findVisibleRowIndex(0)).toEqual(4);
+ expect(findVisibleRowIndex(1)).toEqual(2);
+ expect(findVisibleRowIndex(2)).toEqual(3);
+ expect(findVisibleRowIndex(3)).toEqual(0);
+ expect(findVisibleRowIndex(4)).toEqual(1);
+ });
+ });
+
+ describe('if the grid is paginated', () => {
+ const pagination = {
+ pageSize: 20,
+ pageIndex: 0,
+ onChangePage: jest.fn(),
+ onChangeItemsPerPage: jest.fn(),
+ };
+ const sortedRowMap: number[] = [];
+
+ beforeEach(() => jest.clearAllMocks());
+
+ it('calculates what page the row should be on, paginates to that page, and returns the index of the row on that page', () => {
+ const {
+ return: { findVisibleRowIndex },
+ } = testCustomHook(() => useSortPageCheck(pagination, sortedRowMap));
+
+ expect(findVisibleRowIndex(20)).toEqual(0); // First item on 2nd page
+ expect(pagination.onChangePage).toHaveBeenLastCalledWith(1);
+
+ expect(findVisibleRowIndex(75)).toEqual(15); // 16th item on 4th page
+ expect(pagination.onChangePage).toHaveBeenLastCalledWith(3);
+ });
+
+ it('does not paginate if the user is already on the correct page', () => {
+ const {
+ return: { findVisibleRowIndex },
+ } = testCustomHook(() => useSortPageCheck(pagination, sortedRowMap));
+
+ expect(findVisibleRowIndex(5)).toEqual(5);
+ expect(pagination.onChangePage).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/src/components/datagrid/utils/ref.ts b/src/components/datagrid/utils/ref.ts
new file mode 100644
index 00000000000..88aebb8eb8d
--- /dev/null
+++ b/src/components/datagrid/utils/ref.ts
@@ -0,0 +1,154 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+import { useImperativeHandle, useCallback, Ref } from 'react';
+import {
+ EuiDataGridRefProps,
+ EuiDataGridProps,
+ DataGridFocusContextShape,
+ DataGridCellPopoverContextShape,
+ DataGridSortingContextShape,
+} from '../data_grid_types';
+
+interface Dependencies {
+ ref: Ref;
+ setIsFullScreen: EuiDataGridRefProps['setIsFullScreen'];
+ focusContext: DataGridFocusContextShape;
+ cellPopoverContext: DataGridCellPopoverContextShape;
+ sortingContext: DataGridSortingContextShape;
+ pagination: EuiDataGridProps['pagination'];
+ rowCount: number;
+ visibleColCount: number;
+}
+
+export const useImperativeGridRef = ({
+ ref,
+ setIsFullScreen,
+ focusContext,
+ cellPopoverContext,
+ sortingContext: { sortedRowMap },
+ pagination,
+ rowCount,
+ visibleColCount,
+}: Dependencies) => {
+ // Cell location helpers
+ const { checkCellExists } = useCellLocationCheck(rowCount, visibleColCount);
+ const { findVisibleRowIndex } = useSortPageCheck(pagination, sortedRowMap);
+
+ // Focus APIs
+ const { setFocusedCell: _setFocusedCell } = focusContext; // eslint complains about the dependency array otherwise
+
+ // When we pass this API to the consumer, we can't know for sure that
+ // the targeted cell is valid or in view (unlike our internal state, where
+ // both of those states can be guaranteed), so we need to do some extra
+ // checks here to make sure the grid automatically handles all cells
+ const setFocusedCell = useCallback(
+ ({ rowIndex, colIndex }) => {
+ checkCellExists({ rowIndex, colIndex });
+ const visibleRowIndex = findVisibleRowIndex(rowIndex);
+ _setFocusedCell([colIndex, visibleRowIndex]); // Transmog args from obj to array
+ },
+ [_setFocusedCell, checkCellExists, findVisibleRowIndex]
+ );
+
+ // Popover APIs
+ const {
+ openCellPopover: _openCellPopover,
+ closeCellPopover,
+ } = cellPopoverContext;
+
+ // When we pass this API to the consumer, we can't know for sure that
+ // the targeted cell is valid or in view (unlike our internal state, where
+ // both of those states can be guaranteed), so we need to do some extra
+ // checks here to make sure the grid automatically handles all cells
+ const openCellPopover = useCallback(
+ ({ rowIndex, colIndex }) => {
+ checkCellExists({ rowIndex, colIndex });
+ const visibleRowIndex = findVisibleRowIndex(rowIndex);
+ _openCellPopover({ rowIndex: visibleRowIndex, colIndex });
+ },
+ [_openCellPopover, checkCellExists, findVisibleRowIndex]
+ );
+
+ // Set the ref APIs
+ useImperativeHandle(
+ ref,
+ () => ({
+ setIsFullScreen,
+ setFocusedCell,
+ openCellPopover,
+ closeCellPopover,
+ }),
+ [setIsFullScreen, setFocusedCell, openCellPopover, closeCellPopover]
+ );
+};
+
+/**
+ * Throw a digestible error if the consumer attempts to focus into an invalid
+ * cell range, which should also stop the APIs from continuing
+ */
+export const useCellLocationCheck = (rowCount: number, colCount: number) => {
+ const checkCellExists = useCallback(
+ ({ rowIndex, colIndex }) => {
+ if (rowIndex >= rowCount || rowIndex < 0) {
+ throw new Error(
+ `Row ${rowIndex} is not a valid row. The maximum visible row index is ${
+ rowCount - 1
+ }.`
+ );
+ }
+ if (colIndex >= colCount) {
+ throw new Error(
+ `Column ${colIndex} is not a valid column. The maximum visible column index is ${
+ colCount - 1
+ }.`
+ );
+ }
+ },
+ [rowCount, colCount]
+ );
+
+ return { checkCellExists };
+};
+
+/**
+ * The rowIndex passed from the consumer is the unsorted and unpaginated
+ * index derived from their original data. We need to convert that rowIndex
+ * into a visibleRowIndex (which is what our internal cell APIs use) and, if
+ * the row is not on the current page, the grid should automatically handle
+ * paginating to that row.
+ */
+export const useSortPageCheck = (
+ pagination: EuiDataGridProps['pagination'],
+ sortedRowMap: DataGridSortingContextShape['sortedRowMap']
+) => {
+ const findVisibleRowIndex = useCallback(
+ (rowIndex: number): number => {
+ // Account for sorting
+ const visibleRowIndex = sortedRowMap.length
+ ? sortedRowMap.findIndex((mappedIndex) => mappedIndex === rowIndex)
+ : rowIndex;
+
+ // Account for pagination
+ if (pagination) {
+ const pageIndex = Math.floor(visibleRowIndex / pagination.pageSize);
+ // If the targeted row is on a different page than the current page,
+ // we should automatically navigate the user to the correct page
+ if (pageIndex !== pagination.pageIndex) {
+ pagination.onChangePage(pageIndex);
+ }
+ // Get the row's visible row index on that page
+ return visibleRowIndex % pagination.pageSize;
+ }
+ return visibleRowIndex;
+ },
+ [pagination, sortedRowMap]
+ );
+
+ return { findVisibleRowIndex };
+};
diff --git a/src/components/datagrid/utils/scrolling.ts b/src/components/datagrid/utils/scrolling.ts
index 4aaf3df4ee3..486cecfc983 100644
--- a/src/components/datagrid/utils/scrolling.ts
+++ b/src/components/datagrid/utils/scrolling.ts
@@ -74,6 +74,9 @@ export const useScrollCellIntoView = ({
hasStickyFooter,
}: Dependencies) => {
const scrollCellIntoView = useCallback(
+ // Note: in order for this UX to work correctly with react-window's APIs,
+ // the `rowIndex` arg expected is actually our internal `visibleRowIndex`,
+ // not the `rowIndex` from the raw unsorted/unpaginated user data
async ({ rowIndex, colIndex }: ScrollCellIntoView) => {
if (!gridRef.current || !outerGridRef.current || !innerGridRef.current) {
return; // Grid isn't rendered yet or is empty
diff --git a/src/components/datagrid/utils/sorting.ts b/src/components/datagrid/utils/sorting.ts
index 9a4f17e6eed..59a9d11fce7 100644
--- a/src/components/datagrid/utils/sorting.ts
+++ b/src/components/datagrid/utils/sorting.ts
@@ -21,7 +21,7 @@ export const DataGridSortingContext = createContext<
DataGridSortingContextShape
>({
sorting: undefined,
- sortedRowMap: {},
+ sortedRowMap: [],
getCorrectRowIndex: (number) => number,
});
@@ -43,7 +43,7 @@ export const useSorting = ({
const sortingColumns = sorting?.columns;
const sortedRowMap = useMemo(() => {
- const rowMap: DataGridSortingContextShape['sortedRowMap'] = {};
+ const rowMap: DataGridSortingContextShape['sortedRowMap'] = [];
if (
inMemory?.level === 'sorting' &&
@@ -103,19 +103,21 @@ export const useSorting = ({
schemaDetectors,
]);
+ // Given a visible row index, obtain the unpaginated & unsorted
+ // row index from the passed cell data
const getCorrectRowIndex = useCallback(
- (rowIndex: number) => {
- let rowIndexWithOffset = rowIndex;
-
- if (rowIndex - startRow < 0) {
- rowIndexWithOffset = rowIndex + startRow;
- }
-
- const correctRowIndex = sortedRowMap.hasOwnProperty(rowIndexWithOffset)
- ? sortedRowMap[rowIndexWithOffset]
- : rowIndexWithOffset;
-
- return correctRowIndex;
+ (visibleRowIndex: number) => {
+ const isPaginated = visibleRowIndex - startRow < 0;
+ const unpaginatedRowIndex = isPaginated
+ ? visibleRowIndex + startRow
+ : visibleRowIndex;
+
+ const unsortedRowIndex =
+ unpaginatedRowIndex in sortedRowMap
+ ? sortedRowMap[unpaginatedRowIndex]
+ : unpaginatedRowIndex;
+
+ return unsortedRowIndex;
},
[startRow, sortedRowMap]
);
From 36201c6078c1ad66706b24667208808d7c209fa8 Mon Sep 17 00:00:00 2001
From: Constance Chen
Date: Tue, 1 Feb 2022 15:23:05 -0800
Subject: [PATCH 5/9] Improve changelog
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e3905dcedc..d793605e3e5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,6 @@
## [`main`](https://github.com/elastic/eui/tree/main)
-- Added the ability to access certain `EuiDataGrid` internal methods via the `ref` prop ([#5499](https://github.com/elastic/eui/pull/5499))
+- Added the ability to control internal `EuiDataGrid` fullscreen, cell focus, and cell popover state via the `ref` prop ([#5590](https://github.com/elastic/eui/pull/5590))
## [`46.2.0`](https://github.com/elastic/eui/tree/v46.2.0)
From 938e2d82ee9dbd0aaf92007a245465ec58e6cbe9 Mon Sep 17 00:00:00 2001
From: Elizabet Oliveira
Date: Wed, 2 Feb 2022 17:47:33 +0000
Subject: [PATCH 6/9] Data grid ref methods docs review
---
.../views/datagrid/datagrid_ref_example.js | 51 +++++++++++--------
1 file changed, 29 insertions(+), 22 deletions(-)
diff --git a/src-docs/src/views/datagrid/datagrid_ref_example.js b/src-docs/src/views/datagrid/datagrid_ref_example.js
index 40a2c47f54b..66795c4e25b 100644
--- a/src-docs/src/views/datagrid/datagrid_ref_example.js
+++ b/src-docs/src/views/datagrid/datagrid_ref_example.js
@@ -38,7 +38,8 @@ export const DataGridRefExample = {
For advanced use cases, and particularly for data grids that manage
associated modals/flyouts and need to manually control their grid
cell popovers & focus states, we expose certain internal methods via
- the ref prop of EuiDataGrid. These methods are:
+ the ref prop of EuiDataGrid.
+ These methods are:
@@ -56,6 +57,7 @@ export const DataGridRefExample = {
iconType="accessibility"
title="Using this method is an accessibility requirement if your data
grid toggles a modal or flyout."
+ color="warning"
>
Your modal or flyout should restore focus into the grid on close
to prevent keyboard or screen reader users from being stranded.
@@ -65,27 +67,6 @@ export const DataGridRefExample = {
openCellPopover({'{ rowIndex, colIndex }'}) -
opens the specified cell's popover contents.
-
-
- When using setFocusedCell or{' '}
- openCellPopover, keep in mind:
-
-
- colIndex is affected by the user reordering or hiding
- columns.
-
-
- If the passed cell indices are outside the data grid's
- total row count or visible column count, an error will be
- thrown.
-
-
- If the data grid is paginated or sorted, the grid will
- handle automatically finding specified row index's
- correct location for you.
-
+
+
+
+
+ When using setFocusedCell or{' '}
+ openCellPopover, keep in mind:
+
+
+ colIndex is affected by the user reordering
+ or hiding columns.
+
+
+ If the passed cell indices are outside the data grid's
+ total row count or visible column count, an error will be
+ thrown.
+
+
+ If the data grid is paginated or sorted, the grid will handle
+ automatically finding specified row index's correct
+ location for you.
+
+
+
+
+
+
The below example shows how to use the internal APIs for a data grid
that opens a modal via cell actions.
From 2056498e15b033439bdcb781413df8abc0d25471 Mon Sep 17 00:00:00 2001
From: Constance Chen
Date: Wed, 2 Feb 2022 10:41:42 -0800
Subject: [PATCH 7/9] Fix colIndex to be available in renderCellValue as well
as cell actions
- primarily for use within trailing/leading cells and custom actions
- see https://github.com/elastic/eui/pull/5499/commits/1609e45d93f3b960b29070761bb39b57b133dfdd
---
.../body/__snapshots__/data_grid_cell.test.tsx.snap | 1 +
src/components/datagrid/body/data_grid_cell.tsx | 2 ++
src/components/datagrid/data_grid_types.ts | 6 ++++++
src/components/datagrid/utils/in_memory.tsx | 3 ++-
4 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/src/components/datagrid/body/__snapshots__/data_grid_cell.test.tsx.snap b/src/components/datagrid/body/__snapshots__/data_grid_cell.test.tsx.snap
index 371eb036f0f..c8489a5a6ee 100644
--- a/src/components/datagrid/body/__snapshots__/data_grid_cell.test.tsx.snap
+++ b/src/components/datagrid/body/__snapshots__/data_grid_cell.test.tsx.snap
@@ -131,6 +131,7 @@ exports[`EuiDataGridCell renders 1`] = `
style={Object {}}
>