Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EuiDataGrid hooks cleanup #2331

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions src/components/datagrid/__snapshots__/data_grid.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,6 @@ Array [
class="euiDataGridHeaderCell"
data-test-subj="dataGridHeaderCell-A"
role="columnheader"
style="width:undefinedpx"
>
<div
class="euiDataGridHeaderCell__content"
Expand All @@ -301,7 +300,6 @@ Array [
class="euiDataGridHeaderCell"
data-test-subj="dataGridHeaderCell-B"
role="columnheader"
style="width:undefinedpx"
>
<div
class="euiDataGridHeaderCell__content"
Expand All @@ -319,7 +317,6 @@ Array [
class="euiDataGridRowCell"
data-test-subj="dataGridRowCell"
role="gridcell"
style="width:undefinedpx"
tabindex="0"
>
<div
Expand Down Expand Up @@ -354,7 +351,6 @@ Array [
class="euiDataGridRowCell"
data-test-subj="dataGridRowCell"
role="gridcell"
style="width:undefinedpx"
tabindex="-1"
>
<div
Expand Down Expand Up @@ -395,7 +391,6 @@ Array [
class="euiDataGridRowCell"
data-test-subj="dataGridRowCell"
role="gridcell"
style="width:undefinedpx"
tabindex="-1"
>
<div
Expand Down Expand Up @@ -430,7 +425,6 @@ Array [
class="euiDataGridRowCell"
data-test-subj="dataGridRowCell"
role="gridcell"
style="width:undefinedpx"
tabindex="-1"
>
<div
Expand Down Expand Up @@ -471,7 +465,6 @@ Array [
class="euiDataGridRowCell"
data-test-subj="dataGridRowCell"
role="gridcell"
style="width:undefinedpx"
tabindex="-1"
>
<div
Expand Down Expand Up @@ -506,7 +499,6 @@ Array [
class="euiDataGridRowCell"
data-test-subj="dataGridRowCell"
role="gridcell"
style="width:undefinedpx"
tabindex="-1"
>
<div
Expand Down
214 changes: 129 additions & 85 deletions src/components/datagrid/data_grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import React, {
useCallback,
useState,
useEffect,
useRef,
Fragment,
ReactChild,
} from 'react';
Expand Down Expand Up @@ -104,6 +103,14 @@ const cellPaddingsToClassMap: {
};
const ORIGIN: [number, number] = [0, 0];

// returns whether or not this element is a gridcell with CELL_CONTENTS_ATTR
const isInteractiveCell = (element: HTMLElement) => {
if (element.getAttribute('role') !== 'gridcell') {
return false;
}
return Boolean(element.querySelector(`[${CELL_CONTENTS_ATTR}="true"]`));
};

function computeVisibleRows(props: EuiDataGridProps) {
const { pagination, rowCount } = props;

Expand Down Expand Up @@ -213,62 +220,83 @@ function renderSorting(props: EuiDataGridProps) {
);
}

export const EuiDataGrid: FunctionComponent<EuiDataGridProps> = props => {
const [isFullScreen, setIsFullScreen] = useState(false);
const [showGridControls, setShowGridControls] = useState(true);
const [focusedCell, setFocusedCell] = useState<[number, number]>(ORIGIN);
const containerRef = useRef<HTMLDivElement>(null);
const [interactiveCellId] = useState(htmlIdGenerator()());
const [columnWidths, setColumnWidths] = useState<EuiDataGridColumnWidths>({});
const setColumnWidth = (columnId: string, width: number) => {
setColumnWidths({ ...columnWidths, [columnId]: width });
};
function useDefaultColumnWidth(
container: HTMLElement | null,
columns: EuiDataGridProps['columns']
): number | null {
const [defaultColumnWidth, setDefaultColumnWidth] = useState<number | null>(
null
);

useEffect(() => {
if (containerRef.current != null) {
const gridWidth = containerRef.current.clientWidth;
const columnWidth = Math.max(gridWidth / props.columns.length, 100);
const columnWidths = props.columns.reduce(
(columnWidths: EuiDataGridColumnWidths, column) => {
columnWidths[column.id] = columnWidth;
return columnWidths;
},
{}
);
setColumnWidths(columnWidths);
if (container != null) {
const gridWidth = container.clientWidth;
const columnWidth = Math.max(gridWidth / columns.length, 100);
setDefaultColumnWidth(columnWidth);
}
// @TODO: come back to this hook lifecycle
}, []); // eslint-disable-line react-hooks/exhaustive-deps
}, [container, columns]);

return defaultColumnWidth;
}

const onResize = ({ width }: { width: number }) => {
setShowGridControls(
width > MINIMUM_WIDTH_FOR_GRID_CONTROLS || isFullScreen
);
function useColumnWidths(): [
EuiDataGridColumnWidths,
(columnId: string, width: number) => void
] {
const [columnWidths, setColumnWidths] = useState<EuiDataGridColumnWidths>({});
const setColumnWidth = (columnId: string, width: number) => {
setColumnWidths({ ...columnWidths, [columnId]: width });
};
return [columnWidths, setColumnWidth];
}

const [isGridNavigationEnabled, setIsGridNavigationEnabled] = useState<
boolean
>(true);
function useOnResize(
setShowGridControls: (showGridControls: boolean) => void,
isFullScreen: boolean
) {
return useCallback(
({ width }: { width: number }) => {
setShowGridControls(
width > MINIMUM_WIDTH_FOR_GRID_CONTROLS || isFullScreen
);
},
[setShowGridControls, isFullScreen]
);
}

const isInteractiveCell = (element: HTMLElement) => {
if (element.getAttribute('role') !== 'gridcell') {
return false;
}
function useInMemoryValues(): [
EuiDataGridInMemoryValues,
(rowIndex: number, column: EuiDataGridColumn, value: string) => void
] {
const [inMemoryValues, setInMemoryValues] = useState<
EuiDataGridInMemoryValues
>({});

return Boolean(element.querySelector(`[${CELL_CONTENTS_ATTR}="true"]`));
};
const onCellRender = useCallback(
(rowIndex, column, value) => {
setInMemoryValues(inMemoryValues => {
const nextInMemoryVaues = { ...inMemoryValues };
nextInMemoryVaues[rowIndex] = nextInMemoryVaues[rowIndex] || {};
nextInMemoryVaues[rowIndex][column.id] = value;
return nextInMemoryVaues;
});
},
[inMemoryValues, setInMemoryValues]
);

const handleGridKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
switch (e.keyCode) {
case keyCodes.ESCAPE:
e.preventDefault();
setIsFullScreen(false);
break;
}
};
return [inMemoryValues, onCellRender];
}

const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
const colCount = props.columns.length - 1;
function createKeyDownHandler(
props: EuiDataGridProps,
visibleColumns: EuiDataGridProps['columns'],
focusedCell: [number, number],
setFocusedCell: (focusedCell: [number, number]) => void,
isGridNavigationEnabled: boolean,
setIsGridNavigationEnabled: (isGridNavigationEnabled: boolean) => void
) {
return (event: KeyboardEvent<HTMLDivElement>) => {
const colCount = visibleColumns.length - 1;
const [x, y] = focusedCell;
const rowCount = computeVisibleRows(props);
const { keyCode, target } = event;
Expand Down Expand Up @@ -317,39 +345,58 @@ export const EuiDataGrid: FunctionComponent<EuiDataGridProps> = props => {
}
}
};
}

export const EuiDataGrid: FunctionComponent<EuiDataGridProps> = props => {
const [isFullScreen, setIsFullScreen] = useState(false);
const [showGridControls, setShowGridControls] = useState(true);
const [focusedCell, setFocusedCell] = useState<[number, number]>(ORIGIN);
const [containerRef, setContainerRef] = useState<HTMLDivElement | null>(null);
const [interactiveCellId] = useState(htmlIdGenerator()());

const [columnWidths, setColumnWidth] = useColumnWidths();

// enables/disables grid controls based on available width
const onResize = useOnResize(setShowGridControls, isFullScreen);

const [isGridNavigationEnabled, setIsGridNavigationEnabled] = useState<
boolean
>(true);

const handleGridKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
switch (e.keyCode) {
case keyCodes.ESCAPE:
if (isFullScreen) {
e.preventDefault();
setIsFullScreen(false);
}
break;
}
};

const {
columns,
rowCount,
renderCellValue,
className,
gridStyle = startingStyles,
gridStyle,
pagination,
sorting,
inMemory = false,
...rest
} = props;

// apply style props on top of defaults
const gridStyleWithDefaults = { ...startingStyles, ...gridStyle };

const [ColumnSelector, visibleColumns] = useColumnSelector(columns);
const [StyleSelector, gridStyles, setGridStyles] = useStyleSelector();
const [StyleSelector, gridStyles] = useStyleSelector(gridStyleWithDefaults);

useEffect(() => {
if (gridStyle) {
const oldStyles = gridStyles;
/*eslint-disable */
const mergedStyle = Object.assign(
/*eslint-enable */
{},
oldStyles,
// @ts-ignore
gridStyle
);
setGridStyles(mergedStyle);
} else {
setGridStyles(startingStyles);
}
// @TODO: come back to this hook lifecycle
}, [gridStyle]); // eslint-disable-line react-hooks/exhaustive-deps
// compute the default column width from the container's clientWidth and count of visible columns
const defaultColumnWidth = useDefaultColumnWidth(
containerRef,
visibleColumns
);

const classes = classNames(
'euiDataGrid',
Expand All @@ -375,21 +422,8 @@ export const EuiDataGrid: FunctionComponent<EuiDataGridProps> = props => {
className
);

const [inMemoryValues, setInMemoryValues] = useState<
EuiDataGridInMemoryValues
>({});
const [inMemoryValues, onCellRender] = useInMemoryValues();

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 = (
<Fragment>
Expand All @@ -405,8 +439,6 @@ export const EuiDataGrid: FunctionComponent<EuiDataGridProps> = props => {
document.body.classList.remove('euiDataGrid__restrictBody');
}

const onCellFocus = useCallback(setFocusedCell, [setFocusedCell]);

// extract aria-label and/or aria-labelledby from `rest`
const gridAriaProps: {
'aria-label'?: string;
Expand All @@ -423,7 +455,10 @@ export const EuiDataGrid: FunctionComponent<EuiDataGridProps> = props => {

return (
<EuiFocusTrap disabled={!isFullScreen} style={{ height: '100%' }}>
<div className={classes} onKeyDown={handleGridKeyDown} ref={containerRef}>
<div
className={classes}
onKeyDown={handleGridKeyDown}
ref={setContainerRef}>
<div className="euiDataGrid__controls">
{showGridControls ? gridControls : null}
<EuiI18n
Expand All @@ -450,7 +485,14 @@ export const EuiDataGrid: FunctionComponent<EuiDataGridProps> = props => {
<EuiResizeObserver onResize={onResize}>
{resizeRef => (
<div
onKeyDown={handleKeyDown}
onKeyDown={createKeyDownHandler(
props,
visibleColumns,
focusedCell,
setFocusedCell,
isGridNavigationEnabled,
setIsGridNavigationEnabled
)}
className="euiDataGrid__verticalScroll"
ref={resizeRef}
{...rest}>
Expand All @@ -470,15 +512,17 @@ export const EuiDataGrid: FunctionComponent<EuiDataGridProps> = props => {
<EuiDataGridHeaderRow
columns={visibleColumns}
columnWidths={columnWidths}
defaultColumnWidth={defaultColumnWidth}
setColumnWidth={setColumnWidth}
/>
<EuiDataGridBody
columnWidths={columnWidths}
defaultColumnWidth={defaultColumnWidth}
inMemoryValues={inMemoryValues}
inMemory={inMemory}
columns={visibleColumns}
focusedCell={focusedCell}
onCellFocus={onCellFocus}
onCellFocus={setFocusedCell}
pagination={pagination}
sorting={sorting}
renderCellValue={renderCellValue}
Expand Down
Loading