diff --git a/src-docs/src/views/datagrid/datagrid.js b/src-docs/src/views/datagrid/datagrid.js index 6f5ed5e6c67..611c8fb11a7 100644 --- a/src-docs/src/views/datagrid/datagrid.js +++ b/src-docs/src/views/datagrid/datagrid.js @@ -77,6 +77,8 @@ export default class DataGridContainer extends Component { super(props); this.state = { + sortingColumns: [], + data, pagination: { pageIndex: 0, pageSize: 50, @@ -84,6 +86,22 @@ export default class DataGridContainer extends Component { }; } + setSorting = sortingColumns => { + const sortedData = [...data].sort((a, b) => { + for (let i = 0; i < sortingColumns.length; i++) { + const column = sortingColumns[i]; + const aValue = a[column.id]; + const bValue = b[column.id]; + + if (aValue < bValue) return column.direction === 'asc' ? -1 : 1; + if (aValue > bValue) return column.direction === 'asc' ? 1 : -1; + } + + return 0; + }); + this.setState({ sortingColumns, data: sortedData }); + }; + setPageIndex = pageIndex => this.setState(({ pagination }) => ({ pagination: { ...pagination, pageIndex }, @@ -102,7 +120,7 @@ export default class DataGridContainer extends Component { ); render() { - const { pagination } = this.state; + const { data, pagination, sortingColumns } = this.state; return ( data[rowIndex][columnId]} + sorting={{ columns: sortingColumns, onSort: this.setSorting }} pagination={{ ...pagination, pageSizeOptions: [5, 10, 25], diff --git a/src-docs/src/views/datagrid/datagrid_example.js b/src-docs/src/views/datagrid/datagrid_example.js index 9738b0e5418..2735a39f96f 100644 --- a/src-docs/src/views/datagrid/datagrid_example.js +++ b/src-docs/src/views/datagrid/datagrid_example.js @@ -17,6 +17,10 @@ import DataGridStyling from './styling'; const dataGridStylingSource = require('!!raw-loader!./styling'); const dataGridStylingHtml = renderToHtml(DataGridStyling); +import InMemoryDataGrid from './in_memory'; +const inMemoryDataGridSource = require('!!raw-loader!./in_memory'); +const inMemoryDataGridHtml = renderToHtml(DataGridStyling); + export const DataGridExample = { title: 'Data grid', sections: [ @@ -84,5 +88,21 @@ export const DataGridExample = { demo: , props: { EuiDataGrid }, }, + { + source: [ + { + type: GuideSectionTypes.JS, + code: inMemoryDataGridSource, + }, + { + type: GuideSectionTypes.HTML, + code: inMemoryDataGridHtml, + }, + ], + title: 'In Memory', + text:

In memory description

, + components: { InMemoryDataGrid }, + demo: , + }, ], }; diff --git a/src-docs/src/views/datagrid/in_memory.js b/src-docs/src/views/datagrid/in_memory.js new file mode 100644 index 00000000000..e171e9d167d --- /dev/null +++ b/src-docs/src/views/datagrid/in_memory.js @@ -0,0 +1,455 @@ +import React, { Component } from 'react'; + +import { + EuiDataGrid, + EuiButtonGroup, + EuiSpacer, + EuiFormRow, + EuiPopover, + EuiButton, + EuiButtonIcon, + EuiLink, +} from '../../../../src/components/'; +import { iconTypes } from '../icon/icons'; + +const columns = [ + { + id: 'name', + }, + { + id: 'avatar_url', + }, + { + id: 'url', + }, + { + id: 'contributions', + }, + { + id: 'actions', + }, +]; + +const data = [ + { + name: 'cjcenizal', + avatar_url: 'https://avatars2.githubusercontent.com/u/1238659?v=4', + url: 'https://api.github.com/users/cjcenizal', + contributions: 392, + }, + { + name: 'snide', + avatar_url: 'https://avatars3.githubusercontent.com/u/324519?v=4', + url: 'https://api.github.com/users/snide', + contributions: 361, + }, + { + name: 'chandlerprall', + avatar_url: 'https://avatars3.githubusercontent.com/u/313125?v=4', + url: 'https://api.github.com/users/chandlerprall', + contributions: 274, + }, + { + name: 'cchaos', + avatar_url: 'https://avatars3.githubusercontent.com/u/549577?v=4', + url: 'https://api.github.com/users/cchaos', + contributions: 156, + }, + { + name: 'bevacqua', + avatar_url: 'https://avatars3.githubusercontent.com/u/934293?v=4', + url: 'https://api.github.com/users/bevacqua', + contributions: 128, + }, + { + name: 'thompsongl', + avatar_url: 'https://avatars0.githubusercontent.com/u/2728212?v=4', + url: 'https://api.github.com/users/thompsongl', + contributions: 106, + }, + { + name: 'pugnascotia', + avatar_url: 'https://avatars1.githubusercontent.com/u/8696382?v=4', + url: 'https://api.github.com/users/pugnascotia', + contributions: 82, + }, + { + name: 'nreese', + avatar_url: 'https://avatars0.githubusercontent.com/u/373691?v=4', + url: 'https://api.github.com/users/nreese', + contributions: 58, + }, + { + name: 'dmeiss', + avatar_url: 'https://avatars3.githubusercontent.com/u/45879454?v=4', + url: 'https://api.github.com/users/dmeiss', + contributions: 52, + }, + { + name: 'ryankeairns', + avatar_url: 'https://avatars2.githubusercontent.com/u/446285?v=4', + url: 'https://api.github.com/users/ryankeairns', + contributions: 32, + }, + { + name: 'stacey-gammon', + avatar_url: 'https://avatars3.githubusercontent.com/u/16563603?v=4', + url: 'https://api.github.com/users/stacey-gammon', + contributions: 24, + }, + { + name: 'theodesp', + avatar_url: 'https://avatars0.githubusercontent.com/u/328805?v=4', + url: 'https://api.github.com/users/theodesp', + contributions: 22, + }, + { + name: 'uboness', + avatar_url: 'https://avatars3.githubusercontent.com/u/211019?v=4', + url: 'https://api.github.com/users/uboness', + contributions: 17, + }, + { + name: 'weltenwort', + avatar_url: 'https://avatars3.githubusercontent.com/u/973741?v=4', + url: 'https://api.github.com/users/weltenwort', + contributions: 16, + }, + { + name: 'jen-huang', + avatar_url: 'https://avatars0.githubusercontent.com/u/1965714?v=4', + url: 'https://api.github.com/users/jen-huang', + contributions: 13, + }, + { + name: 'PopradiArpad', + avatar_url: 'https://avatars3.githubusercontent.com/u/4144816?v=4', + url: 'https://api.github.com/users/PopradiArpad', + contributions: 11, + }, + { + name: 'chrisronline', + avatar_url: 'https://avatars1.githubusercontent.com/u/56682?v=4', + url: 'https://api.github.com/users/chrisronline', + contributions: 10, + }, + { + name: 'timroes', + avatar_url: 'https://avatars0.githubusercontent.com/u/877229?v=4', + url: 'https://api.github.com/users/timroes', + contributions: 10, + }, + { + name: 'daveyholler', + avatar_url: 'https://avatars2.githubusercontent.com/u/739960?v=4', + url: 'https://api.github.com/users/daveyholler', + contributions: 9, + }, + { + name: 'sqren', + avatar_url: 'https://avatars3.githubusercontent.com/u/209966?v=4', + url: 'https://api.github.com/users/sqren', + contributions: 9, + }, +]; + +export default class InMemoryDataGrid extends Component { + constructor(props) { + super(props); + this.borderOptions = [ + { + id: 'all', + label: 'All', + }, + { + id: 'horizontal', + label: 'Horizontal only', + }, + { + id: 'none', + label: 'None', + }, + ]; + + this.fontSizeOptions = [ + { + id: 's', + label: 'Small', + }, + { + id: 'm', + label: 'Medium', + }, + { + id: 'l', + label: 'Large', + }, + ]; + + this.cellPaddingOptions = [ + { + id: 's', + label: 'Small', + }, + { + id: 'm', + label: 'Medium', + }, + { + id: 'l', + label: 'Large', + }, + ]; + + this.stripeOptions = [ + { + id: 'true', + label: 'Stripes on', + }, + { + id: 'false', + label: 'Stripes off', + }, + ]; + + this.rowHoverOptions = [ + { + id: 'none', + label: 'None', + }, + { + id: 'highlight', + label: 'Highlight', + }, + ]; + + this.headerOptions = [ + { + id: 'shade', + label: 'Shade', + }, + { + id: 'underline', + label: 'Underline', + }, + ]; + + this.state = { + borderSelected: 'all', + fontSizeSelected: 'm', + cellPaddingSelected: 'm', + stripes: false, + stripesSelected: 'false', + rowHoverSelected: 'highlight', + isPopoverOpen: false, + headerSelected: 'shade', + + data, + sortingColumns: [{ id: 'contributions', direction: 'asc' }], + + pagination: { + pageIndex: 0, + pageSize: 10, + }, + }; + } + + onBorderChange = optionId => { + this.setState({ + borderSelected: optionId, + }); + }; + + onFontSizeChange = optionId => { + this.setState({ + fontSizeSelected: optionId, + }); + }; + + onCellPaddingChange = optionId => { + this.setState({ + cellPaddingSelected: optionId, + }); + }; + + onStripesChange = optionId => { + this.setState({ + stripesSelected: optionId, + stripes: !this.state.stripes, + }); + }; + + onRowHoverChange = optionId => { + this.setState({ + rowHoverSelected: optionId, + }); + }; + + onHeaderChange = optionId => { + this.setState({ + headerSelected: optionId, + }); + }; + + onPopoverButtonClick() { + this.setState({ + isPopoverOpen: !this.state.isPopoverOpen, + }); + } + + closePopover() { + this.setState({ + isPopoverOpen: false, + }); + } + + setSorting = sortingColumns => this.setState({ sortingColumns }); + + setPageIndex = pageIndex => + this.setState(({ pagination }) => ({ + pagination: { ...pagination, pageIndex }, + })); + + setPageSize = pageSize => + this.setState(({ pagination }) => ({ + pagination: { ...pagination, pageSize }, + })); + + dummyIcon = () => ( + + ); + + render() { + const { data, pagination, sortingColumns } = this.state; + + const button = ( + + Table styling + + ); + + return ( +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + { + const value = data[rowIndex][columnId]; + + if (columnId === 'actions') { + return ( + <> + {this.dummyIcon()} + {this.dummyIcon()} + + ); + } + + if (columnId === 'url') { + return {value}; + } + + if (columnId === 'avatar_url') { + return ( +

+ Avatar: {value} +

+ ); + } + + return value; + }} + inMemory="sorting" + sorting={{ columns: sortingColumns, onSort: this.setSorting }} + pagination={{ + ...pagination, + pageSizeOptions: [5, 10, 25], + onChangeItemsPerPage: this.setPageSize, + onChangePage: this.setPageIndex, + }} + /> +
+ ); + } +} diff --git a/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap b/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap index 8d093658514..71b200daef1 100644 --- a/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap +++ b/src/components/datagrid/__snapshots__/data_grid.test.tsx.snap @@ -335,11 +335,13 @@ Array [
-
- 0, A +
+
+ 0, A +
-
- 0, B +
+
+ 0, B +
-
- 1, A +
+
+ 1, A +
-
- 1, B +
+
+ 1, B +
-
- 2, A +
+
+ 2, A +
-
- 2, B +
+
+ 2, B +
[^-]+)$/)!.groups!.direction; + + return [columnSorter, sortDirection]; +} + +function sortByColumn( + datagrid: ReactWrapper, + columnId: string, + direction: 'asc' | 'desc' | 'off' +) { + let [columnSorter, currentSortDirection] = getColumnSortDirection( + datagrid, + columnId + ); + while (currentSortDirection !== direction) { + /* eslint-disable no-loop-func */ + act(() => { + columnSorter.simulate('click'); + }); + [columnSorter, currentSortDirection] = getColumnSortDirection( + datagrid, + columnId + ); + } +} + expect.extend({ toBeEuiPopover(received: ReactWrapper) { const pass = received.name() === 'EuiPopover'; @@ -617,6 +654,155 @@ Array [ }); }); + describe('column sorting', () => { + it('calls the onSort callback', () => { + const onSort = jest.fn(columns => { + component.setProps({ sorting: { columns, onSort } }); + component.update(); + }); + + const component = mount( + 'hello'} + /> + ); + + sortByColumn(component, 'ColumnA', 'desc'); + + expect(onSort).toHaveBeenCalledTimes(2); + expect(onSort).toHaveBeenCalledWith([ + { id: 'ColumnA', direction: 'asc' }, + ]); + expect(onSort).toHaveBeenCalledWith([ + { id: 'ColumnA', direction: 'desc' }, + ]); + + const [, sortDirection] = getColumnSortDirection(component, 'ColumnA'); + expect(sortDirection).toBe('desc'); + }); + + describe('in-memory sorting', () => { + it('sorts on initial render', () => { + const component = mount( + + // render A 0->4 and B 9->5 + columnId === 'A' ? rowIndex : 9 - rowIndex + } + inMemory="sorting" + sorting={{ + columns: [{ id: 'A', direction: 'desc' }], + onSort: () => {}, + }} + /> + ); + + expect(extractGridData(component)).toEqual([ + ['A', 'B'], + ['4', '5'], + ['3', '6'], + ['2', '7'], + ['1', '8'], + ['0', '9'], + ]); + }); + + it('sorts on multiple columns', () => { + const component = mount( + + // render A as 0, 1, 0, 1, 0 and B as 9->5 + columnId === 'A' ? rowIndex % 2 : 9 - rowIndex + } + inMemory="sorting" + sorting={{ + columns: [ + { id: 'A', direction: 'desc' }, + { id: 'B', direction: 'asc' }, + ], + onSort: () => {}, + }} + /> + ); + + expect(extractGridData(component)).toEqual([ + ['A', 'B'], + ['1', '6'], + ['1', '8'], + ['0', '5'], + ['0', '7'], + ['0', '9'], + ]); + }); + + it('sorts in response to user interaction', () => { + const onSort = jest.fn(columns => { + component.setProps({ sorting: { columns, onSort } }); + component.update(); + }); + + const component = mount( + + // render A as 0, 1, 0, 1, 0 and B as 9->5 + columnId === 'A' ? rowIndex % 2 : 9 - rowIndex + } + inMemory="sorting" + sorting={{ + columns: [], + onSort, + }} + /> + ); + + expect(extractGridData(component)).toEqual([ + ['A', 'B'], + ['0', '9'], + ['1', '8'], + ['0', '7'], + ['1', '6'], + ['0', '5'], + ]); + + sortByColumn(component, 'A', 'desc'); + expect(extractGridData(component)).toEqual([ + ['A', 'B'], + ['1', '8'], + ['1', '6'], + ['0', '9'], + ['0', '7'], + ['0', '5'], + ]); + + sortByColumn(component, 'B', 'asc'); + expect(extractGridData(component)).toEqual([ + ['A', 'B'], + ['1', '6'], + ['1', '8'], + ['0', '5'], + ['0', '7'], + ['0', '9'], + ]); + }); + }); + }); + describe('keyboard controls', () => { it('supports simple arrow navigation', () => { const component = mount( diff --git a/src/components/datagrid/data_grid.tsx b/src/components/datagrid/data_grid.tsx index 21f776ea975..607e6c1b99b 100644 --- a/src/components/datagrid/data_grid.tsx +++ b/src/components/datagrid/data_grid.tsx @@ -16,7 +16,10 @@ import { CommonProps, Omit } from '../common'; import { EuiDataGridColumn, EuiDataGridColumnWidths, + EuiDataGridInMemory, EuiDataGridPaginationProps, + EuiDataGridInMemoryValues, + EuiDataGridSorting, EuiDataGridStyle, EuiDataGridStyleBorders, EuiDataGridStyleCellPaddings, @@ -37,6 +40,7 @@ import { EuiTablePagination } from '../table/table_pagination'; import { EuiFocusTrap } from '../focus_trap'; import { EuiResizeObserver } from '../observer/resize_observer'; import { CELL_CONTENTS_ATTR } from './utils'; +import { EuiDataGridInMemoryRenderer } from './data_grid_inmemory_renderer'; // When below this number the grid only shows the full screen button const MINIMUM_WIDTH_FOR_GRID_CONTROLS = 479; @@ -47,7 +51,15 @@ type CommonGridProps = CommonProps & rowCount: number; renderCellValue: EuiDataGridCellProps['renderCellValue']; gridStyle?: EuiDataGridStyle; + inMemory?: EuiDataGridInMemory; + /** + * Set to `null` to disable pagination + */ pagination?: EuiDataGridPaginationProps; + /** + * Set to `null` to disable sorting + */ + sorting?: EuiDataGridSorting; }; // This structure forces either aria-label or aria-labelledby to be defined @@ -134,6 +146,73 @@ function renderPagination(props: EuiDataGridProps) { ); } +function renderSorting(props: EuiDataGridProps) { + const { columns, sorting } = props; + + if (sorting == null) return null; + + const sortedColumns = sorting.columns.reduce<{ + [key: string]: 'asc' | 'desc'; + }>((sortedColumns, { id, direction }) => { + sortedColumns[id] = direction; + return sortedColumns; + }, {}); + + return ( +
+ {columns.map(column => { + let sortIcon = '☐'; + let nextSortDir: 'asc' | 'desc' | null = 'asc'; + if (sortedColumns.hasOwnProperty(column.id)) { + sortIcon = sortedColumns[column.id] === 'asc' ? '⬆️' : '⬇️'; + nextSortDir = sortedColumns[column.id] === 'asc' ? 'desc' : null; + } + + return ( +
{ + const nextColumnOrder = [...sorting.columns]; + let foundColumn = false; + + for (let i = 0; i < nextColumnOrder.length; i++) { + if (nextColumnOrder[i].id === column.id) { + foundColumn = true; + + if (nextSortDir === null) { + nextColumnOrder.splice(i--, 1); + } else { + nextColumnOrder[i] = { + id: column.id, + direction: nextSortDir, + }; + } + } + } + + if (foundColumn === false) { + nextColumnOrder.push({ + id: column.id, + direction: 'asc', + }); + } + + sorting.onSort(nextColumnOrder); + }}> + {sortIcon} + {column.id} +
+ ); + })} +
+ ); +} + export const EuiDataGrid: FunctionComponent = props => { const [isFullScreen, setIsFullScreen] = useState(false); const [showGridControls, setShowGridControls] = useState(true); @@ -246,6 +325,8 @@ export const EuiDataGrid: FunctionComponent = props => { className, gridStyle = startingStyles, pagination, + sorting, + inMemory = false, ...rest } = props; @@ -294,6 +375,21 @@ export const EuiDataGrid: FunctionComponent = props => { className ); + const [inMemoryValues, setInMemoryValues] = useState< + EuiDataGridInMemoryValues + >({}); + + const onCellRender = useCallback( + (rowIndex, column, value) => { + setInMemoryValues(inMemoryValues => { + const nextInMemoryVaues = { ...inMemoryValues }; + nextInMemoryVaues[rowIndex] = nextInMemoryVaues[rowIndex] || {}; + nextInMemoryVaues[rowIndex][column.id] = value; + return nextInMemoryVaues; + }); + }, + [inMemoryValues, setInMemoryValues] + ); // These grid controls will only show when there is room. Check the resize observer callback const gridControls = ( @@ -359,6 +455,14 @@ export const EuiDataGrid: FunctionComponent = props => { ref={resizeRef} {...rest}>
+ {inMemory ? ( + + ) : null}
= props => { /> = props => { {renderPagination(props)} + {renderSorting(props)}
- -
+ { + this.updateFocus(); + this.setTabbablesTabIndex(); + }} + observerOptions={{ + childList: true, + subtree: true, + }}> + {ref => ( +
+
+ +
+
+ )} +
); diff --git a/src/components/datagrid/data_grid_data_row.tsx b/src/components/datagrid/data_grid_data_row.tsx index 3336e21abc2..c388b16fe36 100644 --- a/src/components/datagrid/data_grid_data_row.tsx +++ b/src/components/datagrid/data_grid_data_row.tsx @@ -15,6 +15,7 @@ export type EuiDataGridDataRowProps = CommonProps & isGridNavigationEnabled: EuiDataGridCellProps['isGridNavigationEnabled']; onCellFocus: Function; interactiveCellId: EuiDataGridCellProps['interactiveCellId']; + visibleRowIndex: number; }; const EuiDataGridDataRow: FunctionComponent< @@ -31,6 +32,7 @@ const EuiDataGridDataRow: FunctionComponent< isGridNavigationEnabled, interactiveCellId, 'data-test-subj': _dataTestSubj, + visibleRowIndex, ...rest } = props; @@ -44,10 +46,12 @@ const EuiDataGridDataRow: FunctionComponent< const width = columnWidths[id]; - const isFocusable = focusedCell[0] === i && focusedCell[1] === rowIndex; + const isFocusable = + focusedCell[0] === i && focusedCell[1] === visibleRowIndex; + return ( & { columns: EuiDataGridColumn[]; columnWidths: EuiDataGridColumnWidths; setColumnWidth: (columnId: string, width: number) => void; + sorting?: EuiDataGridSorting; }; const EuiDataGridHeaderRow: FunctionComponent< @@ -19,6 +26,7 @@ const EuiDataGridHeaderRow: FunctionComponent< columnWidths, className, setColumnWidth, + sorting, 'data-test-subj': _dataTestSubj, ...rest } = props; @@ -33,9 +41,44 @@ const EuiDataGridHeaderRow: FunctionComponent< const width = columnWidths[id]; + const ariaProps: { + 'aria-sort'?: HTMLAttributes['aria-sort']; + 'aria-describedby'?: HTMLAttributes< + HTMLDivElement + >['aria-describedby']; + } = {}; + + let screenReaderId; + let sortString; + + if (sorting) { + const sortedColumnIds = new Set(sorting.columns.map(({ id }) => id)); + + if (sorting.columns.length === 1 && sortedColumnIds.has(id)) { + const sortDirection = sorting.columns[0].direction; + + let sortValue: HTMLAttributes['aria-sort'] = + 'other'; + if (sortDirection === 'asc') { + sortValue = 'ascending'; + } else if (sortDirection === 'desc') { + sortValue = 'descending'; + } + + ariaProps['aria-sort'] = sortValue; + } else if (sorting.columns.length >= 2 && sortedColumnIds.has(id)) { + sortString = sorting.columns + .map(col => `Sorted by ${col.id} ${col.direction}`) + .join(' then '); + screenReaderId = htmlIdGenerator()(); + ariaProps['aria-describedby'] = screenReaderId; + } + } + return (
{id}
+ {sorting && sorting.columns.length >= 2 && ( + +
{sortString}
+
+ )}
); })} diff --git a/src/components/datagrid/data_grid_inmemory_renderer.tsx b/src/components/datagrid/data_grid_inmemory_renderer.tsx new file mode 100644 index 00000000000..a666c7747a3 --- /dev/null +++ b/src/components/datagrid/data_grid_inmemory_renderer.tsx @@ -0,0 +1,69 @@ +import React, { + Fragment, + FunctionComponent, + JSXElementConstructor, + useEffect, + useMemo, + useState, +} from 'react'; +import { createPortal } from 'react-dom'; +import { CellValueElementProps, EuiDataGridCellProps } from './data_grid_cell'; +import { EuiDataGridColumn } from './data_grid_types'; +import { EuiInnerText } from '../inner_text'; + +interface EuiDataGridInMemoryRendererProps { + columns: EuiDataGridColumn[]; + rowCount: number; + renderCellValue: EuiDataGridCellProps['renderCellValue']; + onCellRender: ( + rowIndex: number, + column: EuiDataGridColumn, + value: string + ) => void; +} + +export const EuiDataGridInMemoryRenderer: FunctionComponent< + EuiDataGridInMemoryRendererProps +> = ({ columns, rowCount, renderCellValue, onCellRender }) => { + const [documentFragment] = useState(() => document.createDocumentFragment()); + + const rows = useMemo(() => { + const rows = []; + + const CellElement = renderCellValue as JSXElementConstructor< + CellValueElementProps + >; + + for (let i = 0; i < rowCount; i++) { + rows.push( + + {columns.map(column => ( + + + {(ref, text) => { + useEffect(() => { + if (text != null) { + onCellRender(i, column, text); + } + }, [text]); + return ( +
+ +
+ ); + }} +
+
+ ))} +
+ ); + } + + return rows; + }, [columns, rowCount, renderCellValue]); + + return createPortal( + {rows}, + (documentFragment as unknown) as Element + ); +}; diff --git a/src/components/datagrid/data_grid_types.ts b/src/components/datagrid/data_grid_types.ts index 63d8aa10dca..d52a213d609 100644 --- a/src/components/datagrid/data_grid_types.ts +++ b/src/components/datagrid/data_grid_types.ts @@ -5,17 +5,6 @@ export interface EuiDataGridColumn { export interface EuiDataGridColumnWidths { [key: string]: number; } - -// ideally this would use a generic to enforce `pageSize` exists in `pageSizeOptions`, -// but TypeScript's default understanding of an array is number[] unless `as const` is used -// which defeats the generic's purpose & functionality as it would check for `number` in `number[]` -export interface EuiDataGridPaginationProps { - pageIndex: number; - pageSize: number; - pageSizeOptions: number[]; - onChangeItemsPerPage: (itemsPerPage: number) => void; - onChangePage: (pageIndex: number) => void; -} // Types for styling options, passed down through the `gridStyle` prop export type EuiDataGridStyleFontSizes = 's' | 'm' | 'l'; export type EuiDataGridStyleBorders = 'all' | 'horizontal' | 'none'; @@ -31,3 +20,41 @@ export interface EuiDataGridStyle { rowHover?: EuiDataGridStyleRowHover; cellPadding?: EuiDataGridStyleCellPaddings; } + +// ideally this would use a generic to enforce `pageSize` exists in `pageSizeOptions`, +// but TypeScript's default understanding of an array is number[] unless `as const` is used +// which defeats the generic's purpose & functionality as it would check for `number` in `number[]` +export interface EuiDataGridPaginationProps { + pageIndex: number; + pageSize: number; + pageSizeOptions: number[]; + onChangeItemsPerPage: (itemsPerPage: number) => void; + onChangePage: (pageIndex: number) => void; +} + +export interface EuiDataGridSorting { + onSort: (columns: EuiDataGridSorting['columns']) => void; + columns: Array<{ id: string; direction: 'asc' | 'desc' }>; +} + +/* +Given the data flow Filtering->Sorting->Pagination: +Each step can be performed by service calls or in-memory by the grid. +However, we cannot allow any service calls after an in-memory operation. +E.g. if Pagination requires a service call the grid cannot perform +in-memory Filtering or Sorting. This means a single value representing the +service / in-memory boundary can be used. Thus there are four states for in-memory: +* false - all service calls +* "pagination" - only pagination is performed in-memory +* "sorting" - sorting & pagination is performed in-memory +* "filtering" - all operations are performed in-memory, no service calls + */ +export type EuiDataGridInMemory = + | false + | 'pagination' + | 'sorting' + | 'filtering'; + +export interface EuiDataGridInMemoryValues { + [key: string]: { [key: string]: string }; +}