From 8ac98fd49c6b76d380662c53ee827350dc673c15 Mon Sep 17 00:00:00 2001 From: Rodrigo Rodrigues Date: Wed, 14 Apr 2021 15:38:38 +0100 Subject: [PATCH] Improvements over the types/props Removed debouncing on recomputing and made it instantly Added a debugger for column grid --- src/ApolloSpreadsheet.tsx | 11 +- src/ApolloSpreadsheetProps.tsx | 119 ++++++++++++++++--- src/columnGrid/ColumnGrid.tsx | 46 +++---- src/columnGrid/__tests__/ColumnGrid.spec.tsx | 5 +- src/columnGrid/column-grid-props.ts | 46 +++---- src/gridContainer/GridContainerProps.ts | 20 ++-- src/gridWrapper/GridWrapper.tsx | 35 ++---- src/gridWrapper/gridWrapperCommonProps.tsx | 65 ---------- src/gridWrapper/gridWrapperProps.ts | 48 ++++---- src/gridWrapper/index.ts | 1 - src/types/row.type.ts | 2 +- 11 files changed, 200 insertions(+), 198 deletions(-) delete mode 100644 src/gridWrapper/gridWrapperCommonProps.tsx diff --git a/src/ApolloSpreadsheet.tsx b/src/ApolloSpreadsheet.tsx index 91f46d1..d2ea2c1 100644 --- a/src/ApolloSpreadsheet.tsx +++ b/src/ApolloSpreadsheet.tsx @@ -1,8 +1,8 @@ -import React, { forwardRef, useCallback, useEffect, useRef, useState } from 'react' +import React, { forwardRef, useCallback, useRef, useState } from 'react' import GridWrapper from './gridWrapper/GridWrapper' import ColumnGrid from './columnGrid/ColumnGrid' -import { NavigationCoords, useKeyboard } from './keyboard' -import { Row, StretchMode } from './types' +import { useKeyboard } from './keyboard' +import { StretchMode } from './types' import { useMergeCells } from './mergeCells' import { useHeaders } from './columnGrid' import { useData } from './data' @@ -36,7 +36,6 @@ const useStyles = makeStyles(() => ({ height: '100%', width: '100%', }, - fixedBottomContainer: {}, })) export const ApolloSpreadSheet: React.FC = forwardRef( @@ -179,7 +178,7 @@ export const ApolloSpreadSheet: React.FC = forwardRef( coords={coords} columns={columns} width={width} - defaultColumnWidth={minColumnWidth} + minColumnWidth={minColumnWidth} getColumnWidth={getColumnWidth} minRowHeight={props.minColumnHeight ?? 50} scrollLeft={scrollLeft} @@ -192,7 +191,7 @@ export const ApolloSpreadSheet: React.FC = forwardRef( rows={rows} data={cells} coords={coords} - defaultColumnWidth={minColumnWidth} + minColumnWidth={minColumnWidth} width={width} getColumnWidth={getColumnWidth} minRowHeight={props.minRowHeight ?? 50} diff --git a/src/ApolloSpreadsheetProps.tsx b/src/ApolloSpreadsheetProps.tsx index 961e5cb..528b52e 100644 --- a/src/ApolloSpreadsheetProps.tsx +++ b/src/ApolloSpreadsheetProps.tsx @@ -1,40 +1,123 @@ -import { DisableSortFilterParam, GridWrapperCommonProps } from './gridWrapper' +import { DisableSortFilter, ICellChangeEvent, OutsideClickDeselect } from './gridWrapper' import { GridContainerCommonProps } from './gridContainer' import { GridTheme, Row, StretchMode } from './types' import { KeyDownEventParams, NavigationCoords } from './keyboard' import { SelectionProps } from './rowSelection' import { ApiRef } from './api' import { NestedRowsProps } from './nestedRows' +import { Column, NestedHeader } from './columnGrid' +import { MergeCell } from './mergeCells' +import { Alignment } from 'react-virtualized' -export interface ApolloSpreadsheetProps - extends GridWrapperCommonProps, - GridContainerCommonProps, - NestedRowsProps { - theme?: GridTheme - /** @default { rowIndex: 0, colIndex: 0} **/ - defaultCoords?: NavigationCoords - /** - * Main grid body (rows and cells) class name - */ - className?: string - rows: Row[] +export interface ApolloCrudProps { + onCreateRow?: (coords: NavigationCoords) => void + onCellChange?: ICellChangeEvent +} + +export interface ApolloColumnRowSizeProps { /** @default 50 **/ minRowHeight?: number /** @default 50 **/ minColumnHeight?: number /** @default 30 **/ minColumnWidth?: number - /** @default StretchMode.None */ - stretchMode?: StretchMode + /** + * Whether CellMeasurer will set a fixed or dynamic width + * By enabling this, CellMeasurer will be ignored therefore it results in faster performance + * when you can predict a fixed size + * @default true + */ + fixedRowWidth?: boolean + /** + * Whether CellMeasurer will set a fixed or dynamic row height + * By enabling this, CellMeasurer will be ignored therefore it results in faster performance + * when you can predict a fixed size + * @default false + */ + fixedRowHeight?: boolean + /** + * Provides a constant row height or conditionally + * @description This requires fixedRowHeight to be enabled and set to true. This is preferable when you can predict + * the size therefore it would result in faster measurements + * @default dynamic + */ + rowHeight?: number +} + +export interface ApolloDataProps { + rows: Row[] + columns: Column[] + nestedHeaders?: Array + mergeCells?: MergeCell[] +} + +export interface ApolloNavigationProps { + /** @default { rowIndex: 0, colIndex: 0} **/ + defaultCoords?: NavigationCoords onKeyDown?: (params: KeyDownEventParams) => void - selection?: SelectionProps - onCreateRow?: (coords: NavigationCoords) => void + /** @default false **/ + suppressNavigation?: boolean +} + +export interface ApolloSortProps { /** * Indicates if the sort is disabled globally or on a specific column * @default true **/ - disableSort?: boolean | DisableSortFilterParam + disableSort?: DisableSortFilter +} + +export interface ApolloLayoutProps { + /** @default StretchMode.None */ + stretchMode?: StretchMode + theme?: GridTheme + /** + * Border for highlighted cell + */ + highlightBorderColor?: string + /** + * Main grid body (rows and cells) class name + */ + className?: string + selection?: SelectionProps + /** @default false **/ + outsideClickDeselects?: OutsideClickDeselect + /** + * Controls scroll-to-cell behavior of the Grid. + * The default ("auto") scrolls the least amount possible to ensure that the specified cell is fully visible. + * Use "start" to align cells to the top/left of the Grid and "end" to align bottom/right. + */ + scrollToAlignment?: Alignment +} + +export interface ApolloCoreProps { /** * Providing a custom ApiRef will override internal ref by allowing the exposure of grid methods */ apiRef?: ApiRef } + +export interface ApolloVirtualizedProps { + /** + * Overscan count buffer for react-virtualized + * @description Keep in mind a lower value + * @default 2 + */ + overscanRowCount?: number + /** + * Overscan count buffer for react-virtualized + * @description Keep in mind a lower value + * @default 2 + */ + overscanColumnCount?: number +} + +export type ApolloSpreadsheetProps = ApolloCoreProps & + GridContainerCommonProps & + NestedRowsProps & + ApolloCrudProps & + ApolloColumnRowSizeProps & + ApolloSortProps & + ApolloNavigationProps & + ApolloLayoutProps & + ApolloDataProps & + ApolloVirtualizedProps diff --git a/src/columnGrid/ColumnGrid.tsx b/src/columnGrid/ColumnGrid.tsx index 9d7119d..5402c3e 100644 --- a/src/columnGrid/ColumnGrid.tsx +++ b/src/columnGrid/ColumnGrid.tsx @@ -1,5 +1,5 @@ import React, { useRef, useEffect, useMemo, useCallback, CSSProperties } from 'react' -import { Grid, CellMeasurerCache } from 'react-virtualized' +import { Grid, CellMeasurerCache, CellMeasurerCacheParams } from 'react-virtualized' import CellMeasurer from '../cellMeasurer/CellMeasureWrapper' import { GridHeader } from './types' import clsx from 'clsx' @@ -13,6 +13,7 @@ import { makeStyles } from '@material-ui/core/styles' import { isFunctionType } from '../helpers' import flattenDeep from 'lodash/flattenDeep' import { createCellQueryProperties } from '../keyboard/utils' +import { useLogger } from '../logger' type SortDisabled = boolean const useStyles = makeStyles(() => ({ @@ -53,9 +54,10 @@ const useStyles = makeStyles(() => ({ })) export const ColumnGrid: React.FC = React.memo(props => { const classes = useStyles() - const cache = useRef( - new CellMeasurerCache({ - defaultWidth: props.defaultColumnWidth, + const logger = useLogger('ColumnGrid') + const cache: CellMeasurerCache = useMemo(() => { + const options: CellMeasurerCacheParams = { + defaultWidth: props.minColumnWidth, defaultHeight: props.minRowHeight, //Width and height are fixed //Width is calculated on useHeaders hook @@ -63,11 +65,22 @@ export const ColumnGrid: React.FC = React.memo(props => { fixedWidth: true, fixedHeight: true, minHeight: props.minRowHeight, - minWidth: props.defaultColumnWidth, - }), - ).current - const recomputingTimeout = useRef(undefined) + minWidth: props.minColumnWidth, + } + return new CellMeasurerCache(options) + }, [props.minColumnWidth, props.minRowHeight]) + const gridRef = useRef(null) + const cacheRef = useRef(cache) + const loggerRef = useRef(logger) + + useEffect(() => { + loggerRef.current = logger + }, [logger]) + + useEffect(() => { + cacheRef.current = cache + }, [cache]) //Stores the headers sort configuration (whether they have sort disabled or not) const headersSortDisabledMap = useMemo(() => { @@ -89,23 +102,14 @@ export const ColumnGrid: React.FC = React.memo(props => { // clear cache and recompute when data changes OR when the container width changes const recomputeSizes = useCallback(() => { - cache.clearAll() + loggerRef.current.debug('Recomputing Sizes') + cacheRef.current?.clearAll() gridRef.current?.recomputeGridSize() - }, [cache]) - - function recomputingCleanup() { - if (recomputingTimeout.current) { - clearTimeout(recomputingTimeout.current) - } - } + }, []) // clear cache and recompute when data changes useEffect(() => { - if (recomputingTimeout.current) { - clearTimeout(recomputingTimeout.current) - } - recomputingTimeout.current = setTimeout(recomputeSizes, 100) - return recomputingCleanup + recomputeSizes() }, [props.data, props.width, recomputeSizes]) function getSortIndicatorComponent(order: string | undefined) { diff --git a/src/columnGrid/__tests__/ColumnGrid.spec.tsx b/src/columnGrid/__tests__/ColumnGrid.spec.tsx index 818e3e7..535115e 100644 --- a/src/columnGrid/__tests__/ColumnGrid.spec.tsx +++ b/src/columnGrid/__tests__/ColumnGrid.spec.tsx @@ -16,12 +16,13 @@ describe('', () => { ], data: [], minRowHeight: 10, - defaultColumnWidth: 10, + minColumnWidth: 10, getColumnWidth: ({ index }: { index: number }) => 0, width: 100, scrollLeft: 0, coords: { rowIndex: 0, colIndex: 0 }, - apiRef: apiRefMock + apiRef: apiRefMock, + nestedRowsEnabled: false } const grid = shallow() diff --git a/src/columnGrid/column-grid-props.ts b/src/columnGrid/column-grid-props.ts index 516c413..eee7206 100644 --- a/src/columnGrid/column-grid-props.ts +++ b/src/columnGrid/column-grid-props.ts @@ -1,39 +1,27 @@ -import { StretchMode } from '../types' -import { GridHeader, Column, NestedHeader } from './types' -import { NavigationCoords } from '../keyboard/types' -import { DisableSortFilterParam } from '../gridWrapper' -import { ApiRef } from '../api' +import { GridHeader } from './types' +import { NavigationCoords } from '../keyboard' import { SortState } from '../sort/useSort' +import { + ApolloColumnRowSizeProps, + ApolloCoreProps, + ApolloDataProps, + ApolloLayoutProps, + ApolloSortProps, + ApolloVirtualizedProps, +} from '../ApolloSpreadsheetProps' -export interface ColumnGridProps { - /** - * Indicates if the sort is disabled globally or on a specific column - * @default true **/ - disableSort?: boolean | DisableSortFilterParam - apiRef: ApiRef +export interface ColumnGridProps + extends ApolloVirtualizedProps, + Pick, + Pick, + ApolloSortProps, + Required, + Pick { data: Array - columns: Column[] - nestedHeaders?: Array - minRowHeight: number coords: NavigationCoords - defaultColumnWidth: number getColumnWidth: ({ index }: { index: number }) => number sort: SortState | null width: number scrollLeft: number - /** @default StretchMode.None */ - stretchMode?: StretchMode - /** - * Overscan count buffer for react-virtualized - * @description Keep in mind a lower value - * @default 2 - */ - overscanRowCount?: number - /** - * Overscan count buffer for react-virtualized - * @description Keep in mind a lower value - * @default 2 - */ - overscanColumnCount?: number nestedRowsEnabled: boolean } diff --git a/src/gridContainer/GridContainerProps.ts b/src/gridContainer/GridContainerProps.ts index e5f6f7a..50872bd 100644 --- a/src/gridContainer/GridContainerProps.ts +++ b/src/gridContainer/GridContainerProps.ts @@ -1,8 +1,11 @@ import React from 'react' import { OnScrollParams } from 'react-virtualized' -import { Column } from '../columnGrid/types' -import { StretchMode } from '../types' -import { ApiRef } from '../api/types' +import { + ApolloColumnRowSizeProps, + ApolloCoreProps, + ApolloDataProps, + ApolloLayoutProps, +} from '../ApolloSpreadsheetProps' export interface GridContainerChildrenProps { width: number @@ -18,10 +21,11 @@ export interface GridContainerCommonProps { containerClassName?: string } -export interface GridContainerProps extends GridContainerCommonProps { - columns: Column[] - minColumnWidth: number - stretchMode: StretchMode +export interface GridContainerProps + extends GridContainerCommonProps, + Required, + Required>, + Required>, + Pick { children: (props: GridContainerChildrenProps) => unknown - apiRef: ApiRef } diff --git a/src/gridWrapper/GridWrapper.tsx b/src/gridWrapper/GridWrapper.tsx index 8b5ea1a..25c8710 100644 --- a/src/gridWrapper/GridWrapper.tsx +++ b/src/gridWrapper/GridWrapper.tsx @@ -54,12 +54,12 @@ const GridWrapper: React.FC = React.memo( const cache: CellMeasurerCache = useMemo(() => { const isFixedCellHeight = props.fixedRowHeight && props.rowHeight const options: CellMeasurerCacheParams = { - defaultWidth: props.defaultColumnWidth, + defaultWidth: props.minColumnWidth, defaultHeight: isFixedCellHeight ? props.rowHeight : props.minRowHeight, fixedWidth: props.fixedRowWidth ?? true, fixedHeight: props.fixedRowHeight, minHeight: isFixedCellHeight ? undefined : props.minRowHeight, - minWidth: props.defaultColumnWidth, + minWidth: props.minColumnWidth, } /** * Used to skip calculations in a faster way when we have fixed height @@ -71,7 +71,7 @@ const GridWrapper: React.FC = React.memo( return new CellMeasurerCache(options) }, [ - props.defaultColumnWidth, + props.minColumnWidth, props.fixedRowHeight, props.fixedRowWidth, props.minRowHeight, @@ -79,15 +79,13 @@ const GridWrapper: React.FC = React.memo( ]) const cacheRef = useRef(cache) - const classes = useStyles() const gridRef = useRef(null) - const recomputingTimeout = useRef(undefined) const loggerRef = useRef(logger) - const currentCoordsRef = useRef(coords) + const coordsRef = useRef(coords) useEffect(() => { - currentCoordsRef.current = coords + coordsRef.current = coords }, [coords]) useEffect(() => { @@ -99,36 +97,25 @@ const GridWrapper: React.FC = React.memo( cacheRef.current.clearAll() gridRef.current?.recomputeGridSize() - //Ensure we do have a valid index range - if (currentCoordsRef.current.rowIndex !== -1 && currentCoordsRef.current.colIndex !== -1) { + //Ensure we do have a valid index range (and if so we can scroll to that cell) + if (coordsRef.current.rowIndex !== -1 && coordsRef.current.colIndex !== -1) { //When the re-computation happens the scroll position is affected and gets reset gridRef.current?.scrollToCell({ - columnIndex: currentCoordsRef.current.colIndex, - rowIndex: currentCoordsRef.current.rowIndex, + columnIndex: coordsRef.current.colIndex, + rowIndex: coordsRef.current.rowIndex, }) } }, []) - function recomputingCleanup() { - if (recomputingTimeout.current) { - clearTimeout(recomputingTimeout.current) - } - } - /** @todo We might need to perform some benchmark tests and ensure its not spamming **/ - // clear cache and recompute when any dependency change useEffect(() => { - if (recomputingTimeout.current) { - clearTimeout(recomputingTimeout.current) - } - recomputingTimeout.current = setTimeout(recomputeSizes, 100) - return recomputingCleanup + recomputeSizes() }, [ //If any of those dependencies change we might need to recompute the sizes data, props.width, props.height, - props.defaultColumnWidth, + props.minColumnWidth, props.fixedRowHeight, props.fixedRowWidth, props.minRowHeight, diff --git a/src/gridWrapper/gridWrapperCommonProps.tsx b/src/gridWrapper/gridWrapperCommonProps.tsx deleted file mode 100644 index 729bed9..0000000 --- a/src/gridWrapper/gridWrapperCommonProps.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { Column, NestedHeader } from '../columnGrid' -import { MergeCell, MergePosition } from '../mergeCells' -import { Alignment } from 'react-virtualized' -import { OutsideClickDeselectCallback } from './gridWrapperProps' -import { NavigationCoords } from '../keyboard' -import { ICellChangeEvent } from './interfaces' -import { DynamicCallback } from '../types' - -export interface GridWrapperCommonProps { - columns: Column[] - nestedHeaders?: Array - /** @default false **/ - suppressNavigation?: boolean - /** @default false **/ - outsideClickDeselects?: boolean | OutsideClickDeselectCallback - mergeCells?: MergeCell[] - mergedPositions?: MergePosition[] - isMerged?: (coords: NavigationCoords) => boolean - /** - * Overscan count buffer for react-virtualized - * @description Keep in mind a lower value - * @default 2 - */ - overscanRowCount?: number - /** - * Overscan count buffer for react-virtualized - * @description Keep in mind a lower value - * @default 2 - */ - overscanColumnCount?: number - onCellChange?: ICellChangeEvent - /** - * Controls scroll-to-cell behavior of the Grid. - * The default ("auto") scrolls the least amount possible to ensure that the specified cell is fully visible. - * Use "start" to align cells to the top/left of the Grid and "end" to align bottom/right. - */ - scrollToAlignment?: Alignment - - /** - * Border for highlighted cell - */ - highlightBorderColor?: string - - /** - * Whether CellMeasurer will set a fixed or dynamic width - * By enabling this, CellMeasurer will be ignored therefore it results in faster performance - * when you can predict a fixed size - * @default true - */ - fixedRowWidth?: boolean - /** - * Whether CellMeasurer will set a fixed or dynamic row height - * By enabling this, CellMeasurer will be ignored therefore it results in faster performance - * when you can predict a fixed size - * @default false - */ - fixedRowHeight?: boolean - /** - * Provides a constant row height or conditionally - * @description This requires fixedRowHeight to be enabled and set to true. This is preferable when you can predict - * the size therefore it would result in faster measurements - * @default dynamic - */ - rowHeight?: number -} diff --git a/src/gridWrapper/gridWrapperProps.ts b/src/gridWrapper/gridWrapperProps.ts index a3e2064..05db379 100644 --- a/src/gridWrapper/gridWrapperProps.ts +++ b/src/gridWrapper/gridWrapperProps.ts @@ -1,38 +1,40 @@ import { Column } from '../columnGrid' import { NavigationCoords } from '../keyboard' import { GridCell } from './interfaces' -import { OnScrollParams } from 'react-virtualized' -import { StretchMode } from '../types' -import { ApiRef } from '../api' -import { SelectionProps } from '../rowSelection' -import { GridWrapperCommonProps } from './gridWrapperCommonProps' +import { Index, OnScrollParams } from 'react-virtualized' import { NestedRowsProps } from '../nestedRows' +import { + ApolloColumnRowSizeProps, + ApolloCoreProps, + ApolloCrudProps, + ApolloDataProps, + ApolloLayoutProps, + ApolloVirtualizedProps, +} from '../ApolloSpreadsheetProps' +import { MergePosition } from '../mergeCells' -export interface DisableSortFilterParam { - (column: Column): boolean -} - -export interface OutsideClickDeselectCallback { - (target: HTMLElement): boolean -} +export type DisableSortFilter = (column: Column) => boolean | boolean +export type OutsideClickDeselect = (target: HTMLElement) => boolean | boolean -export interface GridWrapperProps extends GridWrapperCommonProps { - /** - * Defines the min row height but if the rowHeight property is defined this will be ignored - */ - minRowHeight: number - defaultColumnWidth: number +export interface GridWrapperProps + extends Pick, + Pick< + ApolloLayoutProps, + 'stretchMode' | 'selection' | 'highlightBorderColor' | 'scrollToAlignment' + >, + ApolloVirtualizedProps, + Pick, + Required, + ApolloColumnRowSizeProps { + mergedPositions?: MergePosition[] + isMerged?: (coords: NavigationCoords) => boolean width: number scrollLeft: number onScroll?: (params: OnScrollParams) => any height: number - rows: TRow[] coords: NavigationCoords data: GridCell[][] columnCount: number - getColumnWidth: ({ index }: { index: number }) => number - apiRef: ApiRef - stretchMode: StretchMode - selection?: SelectionProps + getColumnWidth: (index: Index) => number nestedRowsProps: NestedRowsProps } diff --git a/src/gridWrapper/index.ts b/src/gridWrapper/index.ts index 8bb634e..9f5306c 100644 --- a/src/gridWrapper/index.ts +++ b/src/gridWrapper/index.ts @@ -2,4 +2,3 @@ export * from './GridWrapper' export * from './gridWrapperProps' export * from './interfaces' export * from './utils' -export * from './gridWrapperCommonProps' diff --git a/src/types/row.type.ts b/src/types/row.type.ts index 581b6c9..b35130c 100644 --- a/src/types/row.type.ts +++ b/src/types/row.type.ts @@ -1,7 +1,7 @@ /** * Represents the Row model as an object */ -export interface Row extends Object { +export interface Row { [key: string]: any /** * Provides nested rows that might be collapsed if