From 18894a51e12e1b2a6cee281bf28cecc5017d3972 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Sun, 25 Aug 2019 15:15:05 -0500 Subject: [PATCH 01/20] Remove unused props --- package.json | 8 ++-- packages/react-data-grid/src/Canvas.tsx | 4 +- packages/react-data-grid/src/Cell.tsx | 43 ++++++++++++------- .../react-data-grid/src/Cell/CellActions.tsx | 13 +++--- .../react-data-grid/src/Cell/CellContent.tsx | 39 +++++++++++++---- .../react-data-grid/src/Cell/CellValue.tsx | 6 +-- packages/react-data-grid/src/Row.tsx | 5 +-- packages/react-data-grid/src/common/types.ts | 3 +- 8 files changed, 74 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index 2e505b9edd..dd880f2f2c 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,8 @@ "@types/classnames": "^2.2.9", "@types/enzyme": "^3.10.1", "@types/jest": "^24.0.15", - "@types/react": "^16.8.23", - "@types/react-dom": "^16.8.3", + "@types/react": "^16.9.2", + "@types/react-dom": "^16.9.0", "@types/react-is": "^16.7.1", "@types/shallowequal": "^1.1.1", "@typescript-eslint/eslint-plugin": "^1.11.0", @@ -49,13 +49,13 @@ "less-loader": "^5.0.0", "markdown": "^0.5.0", "node-dir": "^0.1.12", - "react": "^16.8.6", + "react": "^16.9.0", "react-contextmenu": "^2.10.0", "react-data-grid": "file:packages/react-data-grid", "react-data-grid-addons": "file:packages/react-data-grid-addons", "react-dnd-test-backend": "^2.6.0", "react-docgen": "^2.21.0", - "react-dom": "^16.8.6", + "react-dom": "^16.9.0", "react-router-dom": "^5.0.1", "recharts": "^1.6.2", "style-loader": "^0.23.1", diff --git a/packages/react-data-grid/src/Canvas.tsx b/packages/react-data-grid/src/Canvas.tsx index 06b8f2a9eb..3ffad7bbfb 100644 --- a/packages/react-data-grid/src/Canvas.tsx +++ b/packages/react-data-grid/src/Canvas.tsx @@ -55,7 +55,7 @@ export interface CanvasProps extends SharedViewportProps, SharedViewportSt onScroll(position: ScrollPosition): void; } -type RendererProps = Pick, 'rowVisibleStartIdx' | 'rowVisibleEndIdx' | 'columns' | 'cellMetaData' | 'colVisibleStartIdx' | 'colVisibleEndIdx' | 'colOverscanEndIdx' | 'colOverscanStartIdx' | 'lastFrozenColumnIndex' | 'isScrolling'> & { +type RendererProps = Pick, 'columns' | 'cellMetaData' | 'colVisibleStartIdx' | 'colVisibleEndIdx' | 'colOverscanEndIdx' | 'colOverscanStartIdx' | 'lastFrozenColumnIndex' | 'isScrolling'> & { ref(row: (RowRenderer & React.Component>) | null): void; key: number; idx: number; @@ -303,8 +303,6 @@ export default class Canvas extends React.PureComponent> { } }, idx: rowIdx, - rowVisibleStartIdx: this.props.rowVisibleStartIdx, - rowVisibleEndIdx: this.props.rowVisibleEndIdx, row, height: rowHeight, columns, diff --git a/packages/react-data-grid/src/Cell.tsx b/packages/react-data-grid/src/Cell.tsx index b4087db848..b6f1391733 100644 --- a/packages/react-data-grid/src/Cell.tsx +++ b/packages/react-data-grid/src/Cell.tsx @@ -25,9 +25,9 @@ export default class Cell extends React.PureComponent> implements Ce private readonly cell = React.createRef(); - componentDidMount() { - this.checkScroll(); - } + // componentDidMount() { + // this.checkScroll(); + // } componentDidUpdate(prevProps: Props) { if (isFrozen(prevProps.column) && !isFrozen(this.props.column)) { @@ -85,27 +85,26 @@ export default class Cell extends React.PureComponent> implements Ce } getCellClass() { - const { idx, column, lastFrozenColumnIndex, isRowSelected, tooltip, expandableOptions } = this.props; + const { idx, column, lastFrozenColumnIndex, tooltip, expandableOptions } = this.props; return classNames( column.cellClass, 'react-grid-Cell', this.props.className, { 'react-grid-Cell--frozen': isFrozen(column), 'rdg-last--frozen': lastFrozenColumnIndex === idx, - 'row-selected': isRowSelected, 'has-tooltip': !!tooltip, 'rdg-child-cell': expandableOptions && expandableOptions.subRowDetails && expandableOptions.treeDepth > 0 } ); } - checkScroll() { - const { scrollLeft, column } = this.props; - const node = this.cell.current; - if (isFrozen(column) && node && node.style.transform != null) { - this.setScrollLeft(scrollLeft); - } - } + // checkScroll() { + // const { scrollLeft, column } = this.props; + // const node = this.cell.current; + // if (isFrozen(column) && node && node.style.transform != null) { + // this.setScrollLeft(scrollLeft); + // } + // } setScrollLeft(scrollLeft: number) { const node = this.cell.current; @@ -164,14 +163,28 @@ export default class Cell extends React.PureComponent> implements Ce } render() { - const { column, children, expandableOptions, cellMetaData, rowData } = this.props; + const { idx, rowIdx, column, value, tooltip, children, height, cellControls, expandableOptions, cellMetaData, rowData, isScrolling } = this.props; if (column.hidden) { return null; } const style = this.getStyle(); const className = this.getCellClass(); - const cellContent = children || {...this.props} />; + const cellContent = children || ( + + idx={idx} + rowIdx={rowIdx} + column={column} + rowData={rowData} + value={value} + tooltip={tooltip} + expandableOptions={expandableOptions} + height={height} + onDeleteSubRow={cellMetaData.onDeleteSubRow} + cellControls={cellControls} + isScrolling={isScrolling} + /> + ); const events = this.getEvents(); const cellExpander = expandableOptions && expandableOptions.canExpand && ( extends React.PureComponent> implements Ce column={column} rowData={rowData} - cellMetaData={cellMetaData} + getCellActions={cellMetaData.getCellActions} /> {cellExpander} {cellContent} diff --git a/packages/react-data-grid/src/Cell/CellActions.tsx b/packages/react-data-grid/src/Cell/CellActions.tsx index 7937723286..73e02897a9 100644 --- a/packages/react-data-grid/src/Cell/CellActions.tsx +++ b/packages/react-data-grid/src/Cell/CellActions.tsx @@ -1,17 +1,14 @@ import React from 'react'; +import { CellMetaData } from '../common/types'; import CellAction from './CellAction'; import { Props as CellProps } from '../Cell'; -type CellActionsProps = Pick, -'cellMetaData' -| 'column' -| 'rowData' ->; +type CellActionsProps = Pick, 'column' | 'rowData'> & Pick, 'getCellActions'>; -export default function CellActions({ cellMetaData, column, rowData }: CellActionsProps) { - if (cellMetaData.getCellActions) { - const cellActionButtons = cellMetaData.getCellActions(column, rowData); +export default function CellActions({ getCellActions, column, rowData }: CellActionsProps) { + if (getCellActions) { + const cellActionButtons = getCellActions(column, rowData); if (cellActionButtons && cellActionButtons.length > 0) { const actionButtons = cellActionButtons.map((action, index) => { return ; diff --git a/packages/react-data-grid/src/Cell/CellContent.tsx b/packages/react-data-grid/src/Cell/CellContent.tsx index 290928575d..9915444e51 100644 --- a/packages/react-data-grid/src/Cell/CellContent.tsx +++ b/packages/react-data-grid/src/Cell/CellContent.tsx @@ -1,6 +1,7 @@ import React from 'react'; import classNames from 'classnames'; +import { CellMetaData } from '../common/types'; import ChildRowDeleteButton from '../ChildRowDeleteButton'; import { Props as CellProps } from '../Cell'; import CellValue from './CellValue'; @@ -16,22 +17,34 @@ export type CellContentProps = Pick, | 'tooltip' | 'height' | 'cellControls' -| 'cellMetaData' +> & Pick, +'onDeleteSubRow' >; -export default function CellContent({ idx, tooltip, expandableOptions, height, cellMetaData, cellControls, ...props }: CellContentProps) { - const { column } = props; +export default function CellContent({ + idx, + rowIdx, + column, + rowData, + value, + tooltip, + expandableOptions, + height, + onDeleteSubRow, + cellControls, + isScrolling +}: CellContentProps) { const isExpandCell = expandableOptions ? expandableOptions.field === column.key : false; const treeDepth = expandableOptions ? expandableOptions.treeDepth : 0; const marginLeft = expandableOptions && isExpandCell ? expandableOptions.treeDepth * 30 : 0; function handleDeleteSubRow() { - if (cellMetaData.onDeleteSubRow) { - cellMetaData.onDeleteSubRow({ + if (onDeleteSubRow) { + onDeleteSubRow({ idx, - rowIdx: props.rowIdx, - rowData: props.rowData, + rowIdx, + rowData, expandArgs: expandableOptions }); } @@ -42,7 +55,7 @@ export default function CellContent({ idx, tooltip, expandableOptions, height treeDepth={treeDepth} cellHeight={height} onDeleteSubRow={handleDeleteSubRow} - isDeleteSubRowEnabled={!!cellMetaData.onDeleteSubRow} + isDeleteSubRowEnabled={!!onDeleteSubRow} /> ); @@ -54,7 +67,15 @@ export default function CellContent({ idx, tooltip, expandableOptions, height
{cellDeleter}
- {...props} /> + + + rowIdx={rowIdx} + rowData={rowData} + column={column} + value={value} + isScrolling={isScrolling} + /> + {cellControls}
{tooltip && {tooltip}} diff --git a/packages/react-data-grid/src/Cell/CellValue.tsx b/packages/react-data-grid/src/Cell/CellValue.tsx index edd91d3eec..3eacbb9ecb 100644 --- a/packages/react-data-grid/src/Cell/CellValue.tsx +++ b/packages/react-data-grid/src/Cell/CellValue.tsx @@ -17,9 +17,9 @@ export default function CellValue({ rowIdx, rowData, column, value, isScrolli // convention based method to get corresponding Id or Name of any Name or Id property const { getRowMetaData } = column; if (getRowMetaData) { - if (process.env.NODE_ENV === 'development') { - console.warn('getRowMetaData for formatters is deprecated and will be removed in a future version of ReactDataGrid. Instead access row prop of formatter'); - } + // if (process.env.NODE_ENV === 'development') { + // console.warn('getRowMetaData for formatters is deprecated and will be removed in a future version of ReactDataGrid. Instead access row prop of formatter'); + // } return getRowMetaData(row, column); } } diff --git a/packages/react-data-grid/src/Row.tsx b/packages/react-data-grid/src/Row.tsx index a16523d25e..e49a2cf7e2 100644 --- a/packages/react-data-grid/src/Row.tsx +++ b/packages/react-data-grid/src/Row.tsx @@ -43,7 +43,7 @@ export default class Row extends React.Component> impleme getCell(column: CalculatedColumn) { const Renderer = this.props.cellRenderer!; - const { idx, cellMetaData, isScrolling, row, isSelected, scrollLeft, lastFrozenColumnIndex } = this.props; + const { idx, cellMetaData, isScrolling, row, lastFrozenColumnIndex } = this.props; const { key } = column; const cellProps: CellRendererProps & { ref: (cell: CellRenderer | null) => void } = { @@ -55,10 +55,9 @@ export default class Row extends React.Component> impleme cellMetaData, value: this.getCellValue(key || String(column.idx) as keyof R) as R[keyof R], // FIXME: fix types rowData: row, - isRowSelected: isSelected, expandableOptions: this.getExpandableOptions(key), isScrolling, - scrollLeft, + // scrollLeft, lastFrozenColumnIndex }; diff --git a/packages/react-data-grid/src/common/types.ts b/packages/react-data-grid/src/common/types.ts index fb8bf03dd7..52ccdf3a67 100644 --- a/packages/react-data-grid/src/common/types.ts +++ b/packages/react-data-grid/src/common/types.ts @@ -159,8 +159,7 @@ export interface CellRendererProps { rowData: TRow; cellMetaData: CellMetaData; isScrolling: boolean; - scrollLeft: number; - isRowSelected?: boolean; + scrollLeft?: number; expandableOptions?: ExpandableOptions; lastFrozenColumnIndex?: number; } From 6d6626d2228fafae3ef55e187add803e391c063d Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Tue, 27 Aug 2019 15:56:54 -0500 Subject: [PATCH 02/20] Initial commit to suport position sticky --- examples/scripts/example03-frozen-cols.js | 10 +- packages/react-data-grid/src/Canvas.tsx | 12 +- packages/react-data-grid/src/Cell.tsx | 35 ++-- .../react-data-grid/src/Cell/CellActions.tsx | 2 +- .../react-data-grid/src/Cell/CellContent.tsx | 2 +- packages/react-data-grid/src/ColumnMetrics.ts | 2 +- packages/react-data-grid/src/Grid.tsx | 36 +---- packages/react-data-grid/src/Header.tsx | 2 +- packages/react-data-grid/src/HeaderRow.tsx | 2 +- .../react-data-grid/src/ReactDataGrid.tsx | 15 +- packages/react-data-grid/src/Row.tsx | 5 +- packages/react-data-grid/src/Viewport.tsx | 42 ++--- .../src/__tests__/ColumnMetrics.spec.ts | 2 +- .../src/__tests__/Header.spec.tsx | 4 +- .../domUtils.tsx} | 14 +- packages/react-data-grid/src/utils/index.tsx | 2 + .../src/utils/viewportUtils.ts | 153 +++++++++++------- packages/react-data-grid/style/rdg-cell.less | 11 ++ packages/react-data-grid/style/rdg-row.less | 6 +- 19 files changed, 201 insertions(+), 156 deletions(-) rename packages/react-data-grid/src/{getScrollbarSize.ts => utils/domUtils.tsx} (61%) create mode 100644 packages/react-data-grid/src/utils/index.tsx diff --git a/examples/scripts/example03-frozen-cols.js b/examples/scripts/example03-frozen-cols.js index 24de851b97..b22952bb99 100644 --- a/examples/scripts/example03-frozen-cols.js +++ b/examples/scripts/example03-frozen-cols.js @@ -6,9 +6,9 @@ import exampleWrapper from '../components/exampleWrapper'; class Example extends React.Component { constructor(props, context) { super(props, context); - this.createRows(); + const extraColumns = [...Array(500).keys()].map(i => ({ key: `col${i}`, name: `col${i}` })); + this.createRows(extraColumns); - const extraColumns = [...Array(50).keys()].map(i => ({ key: `col${i}`, name: `col${i}` })); const columns = [ { key: 'id', @@ -59,7 +59,7 @@ class Example extends React.Component { return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())).toLocaleDateString(); }; - createRows = () => { + createRows = (extraColumns) => { const rows = []; for (let i = 1; i < 1000; i++) { rows.push({ @@ -71,6 +71,10 @@ class Example extends React.Component { startDate: this.getRandomDate(new Date(2015, 3, 1), new Date()), completeDate: this.getRandomDate(new Date(), new Date(2016, 0, 1)) }); + + for (const extraColumn of extraColumns) { + rows[rows.length - 1][extraColumn.key] = `${extraColumn.key}`; + } } this._rows = rows; diff --git a/packages/react-data-grid/src/Canvas.tsx b/packages/react-data-grid/src/Canvas.tsx index 3ffad7bbfb..8debc52aa3 100644 --- a/packages/react-data-grid/src/Canvas.tsx +++ b/packages/react-data-grid/src/Canvas.tsx @@ -6,7 +6,7 @@ import RowsContainerDefault from './RowsContainer'; import RowGroup from './RowGroup'; import { InteractionMasks } from './masks'; import * as rowUtils from './RowUtils'; -import { getColumnScrollPosition } from './utils/canvasUtils'; +import { getColumnScrollPosition, isPositionStickySupported } from './utils'; import { EventTypes } from './common/enums'; import { CalculatedColumn, Position, ScrollPosition, SubRowDetails, RowRenderer, RowRendererProps, RowData } from './common/types'; import { ViewportProps, ViewportState } from './Viewport'; @@ -93,9 +93,10 @@ export default class Canvas extends React.PureComponent> { handleScroll = (e: React.UIEvent) => { const { scrollLeft, scrollTop } = e.currentTarget; this._scroll = { scrollTop, scrollLeft }; - if (this.props.onScroll) { - this.props.onScroll(this._scroll); + if (!isPositionStickySupported()) { + this.setScrollLeft(scrollLeft); } + this.props.onScroll(this._scroll); }; onHitBottomCanvas = () => { @@ -152,11 +153,6 @@ export default class Canvas extends React.PureComponent> { return rows; } - getScroll() { - const { scrollTop, scrollLeft } = this.canvas.current!; - return { scrollTop, scrollLeft }; - } - getClientScrollTopOffset(node: HTMLDivElement) { const { rowHeight } = this.props; const scrollVariation = node.scrollTop % rowHeight; diff --git a/packages/react-data-grid/src/Cell.tsx b/packages/react-data-grid/src/Cell.tsx index b6f1391733..fb5aaf1501 100644 --- a/packages/react-data-grid/src/Cell.tsx +++ b/packages/react-data-grid/src/Cell.tsx @@ -1,35 +1,47 @@ import React from 'react'; import classNames from 'classnames'; +import shallowEqual from 'shallowequal'; import { SubRowOptions, ColumnEventInfo, CellRenderer, CellRendererProps } from './common/types'; import CellActions from './Cell/CellActions'; import CellExpand from './Cell/CellExpander'; import CellContent from './Cell/CellContent'; import { isFrozen } from './ColumnUtils'; +import { isPositionStickySupported } from './utils'; -function getSubRowOptions({ rowIdx, idx, rowData, expandableOptions: expandArgs }: Props): SubRowOptions { +function getSubRowOptions({ rowIdx, idx, rowData, expandableOptions: expandArgs }: CellProps): SubRowOptions { return { rowIdx, idx, rowData, expandArgs }; } -export interface Props extends CellRendererProps { +export interface CellProps extends CellRendererProps { // TODO: Check if these props are required or not. These are most likely set by custom cell renderer className?: string; tooltip?: string | null; cellControls?: unknown; } -export default class Cell extends React.PureComponent> implements CellRenderer { +export default class Cell extends React.Component> implements CellRenderer { static defaultProps = { value: '' }; private readonly cell = React.createRef(); - // componentDidMount() { - // this.checkScroll(); - // } + shouldComponentUpdate(nextProps: CellProps) { + const { scrollLeft, ...rest } = this.props; + const { scrollLeft: nextScrollLeft, ...nextRest } = nextProps; - componentDidUpdate(prevProps: Props) { + return !shallowEqual(rest, nextRest); + } + + componentDidMount() { + const { scrollLeft } = this.props; + if (scrollLeft !== undefined) { + this.setScrollLeft(scrollLeft); + } + } + + componentDidUpdate(prevProps: CellProps) { if (isFrozen(prevProps.column) && !isFrozen(this.props.column)) { this.removeScroll(); } @@ -90,6 +102,7 @@ export default class Cell extends React.PureComponent> implements Ce column.cellClass, 'react-grid-Cell', this.props.className, { + 'react-grid-Cell--sticky': isPositionStickySupported(), 'react-grid-Cell--frozen': isFrozen(column), 'rdg-last--frozen': lastFrozenColumnIndex === idx, 'has-tooltip': !!tooltip, @@ -98,14 +111,6 @@ export default class Cell extends React.PureComponent> implements Ce ); } - // checkScroll() { - // const { scrollLeft, column } = this.props; - // const node = this.cell.current; - // if (isFrozen(column) && node && node.style.transform != null) { - // this.setScrollLeft(scrollLeft); - // } - // } - setScrollLeft(scrollLeft: number) { const node = this.cell.current; if (node) { diff --git a/packages/react-data-grid/src/Cell/CellActions.tsx b/packages/react-data-grid/src/Cell/CellActions.tsx index 73e02897a9..7e2ef32619 100644 --- a/packages/react-data-grid/src/Cell/CellActions.tsx +++ b/packages/react-data-grid/src/Cell/CellActions.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { CellMetaData } from '../common/types'; import CellAction from './CellAction'; -import { Props as CellProps } from '../Cell'; +import { CellProps } from '../Cell'; type CellActionsProps = Pick, 'column' | 'rowData'> & Pick, 'getCellActions'>; diff --git a/packages/react-data-grid/src/Cell/CellContent.tsx b/packages/react-data-grid/src/Cell/CellContent.tsx index 9915444e51..232dd50dba 100644 --- a/packages/react-data-grid/src/Cell/CellContent.tsx +++ b/packages/react-data-grid/src/Cell/CellContent.tsx @@ -3,7 +3,7 @@ import classNames from 'classnames'; import { CellMetaData } from '../common/types'; import ChildRowDeleteButton from '../ChildRowDeleteButton'; -import { Props as CellProps } from '../Cell'; +import { CellProps } from '../Cell'; import CellValue from './CellValue'; export type CellContentProps = Pick, diff --git a/packages/react-data-grid/src/ColumnMetrics.ts b/packages/react-data-grid/src/ColumnMetrics.ts index 8beb010077..3429cee814 100644 --- a/packages/react-data-grid/src/ColumnMetrics.ts +++ b/packages/react-data-grid/src/ColumnMetrics.ts @@ -1,6 +1,6 @@ export { sameColumn } from './ColumnComparer'; import { getSize, isFrozen } from './ColumnUtils'; -import getScrollbarSize from './getScrollbarSize'; +import { getScrollbarSize } from './utils'; import { isColumnsImmutable } from './common/utils'; import { Column, CalculatedColumn, ColumnList, ColumnMetrics } from './common/types'; diff --git a/packages/react-data-grid/src/Grid.tsx b/packages/react-data-grid/src/Grid.tsx index e4bc05dc2c..b8605fc993 100644 --- a/packages/react-data-grid/src/Grid.tsx +++ b/packages/react-data-grid/src/Grid.tsx @@ -3,7 +3,6 @@ import { isValidElementType } from 'react-is'; import Header from './Header'; import Viewport, { ScrollState } from './Viewport'; -import { isFrozen } from './ColumnUtils'; import { HeaderRowData, CellMetaData, RowSelection, InteractionMasksMetaData, SelectedRow } from './common/types'; import { DEFINE_SORT } from './common/enums'; import { DataGridProps, DataGridState } from './ReactDataGrid'; @@ -30,6 +29,9 @@ type SharedDataGridProps = Pick, | 'onHeaderDrop' | 'getSubRowDetails' | 'editorPortalTarget' +| 'overscanRowCount' +| 'overscanColumnCount' +| 'useIsScrolling' >; type SharedDataGridState = Pick, @@ -58,41 +60,14 @@ export default class Grid extends React.Component> { private readonly header = React.createRef>(); private readonly viewport = React.createRef>(); - private _scrollLeft?: number = undefined; - - _onScroll() { - if (this._scrollLeft !== undefined) { - this.header.current!.setScrollLeft(this._scrollLeft); - if (this.viewport.current) { - this.viewport.current.setScrollLeft(this._scrollLeft); - } - } - } - - areFrozenColumnsScrolledLeft(scrollLeft: number) { - return scrollLeft > 0 && this.props.columnMetrics.columns.some(c => isFrozen(c)); - } onScroll = (scrollState: ScrollState) => { + this.header.current!.setScrollLeft(scrollState.scrollLeft); if (this.props.onScroll) { this.props.onScroll(scrollState); } - const { scrollLeft } = scrollState; - if (this._scrollLeft !== scrollLeft || this.areFrozenColumnsScrolledLeft(scrollLeft)) { - this._scrollLeft = scrollLeft; - this._onScroll(); - } }; - componentDidMount() { - this._scrollLeft = this.viewport.current ? this.viewport.current.getScroll().scrollLeft : 0; - this._onScroll(); - } - - componentDidUpdate() { - this._onScroll(); - } - render() { const { headerRows } = this.props; const EmptyRowsView = this.props.emptyRowsView; @@ -149,6 +124,9 @@ export default class Grid extends React.Component> { interactionMasksMetaData={this.props.interactionMasksMetaData} RowsContainer={this.props.RowsContainer} editorPortalTarget={this.props.editorPortalTarget} + overscanRowCount={this.props.overscanRowCount} + overscanColumnCount={this.props.overscanColumnCount} + useIsScrolling={this.props.useIsScrolling} />
)} diff --git a/packages/react-data-grid/src/Header.tsx b/packages/react-data-grid/src/Header.tsx index 7662d7ea44..4f9f2048fa 100644 --- a/packages/react-data-grid/src/Header.tsx +++ b/packages/react-data-grid/src/Header.tsx @@ -4,7 +4,7 @@ import classNames from 'classnames'; import HeaderRow from './HeaderRow'; import { resizeColumn } from './ColumnMetrics'; -import getScrollbarSize from './getScrollbarSize'; +import { getScrollbarSize } from './utils'; import { HeaderRowType } from './common/enums'; import { CalculatedColumn, ColumnMetrics } from './common/types'; import { GridProps } from './Grid'; diff --git a/packages/react-data-grid/src/HeaderRow.tsx b/packages/react-data-grid/src/HeaderRow.tsx index e90c0188b6..841d6e4769 100644 --- a/packages/react-data-grid/src/HeaderRow.tsx +++ b/packages/react-data-grid/src/HeaderRow.tsx @@ -4,7 +4,7 @@ import shallowEqual from 'shallowequal'; import HeaderCell from './HeaderCell'; import SortableHeaderCell from './common/cells/headerCells/SortableHeaderCell'; import FilterableHeaderCell from './common/cells/headerCells/FilterableHeaderCell'; -import getScrollbarSize from './getScrollbarSize'; +import { getScrollbarSize } from './utils'; import { isFrozen } from './ColumnUtils'; import { HeaderRowType, HeaderCellType, DEFINE_SORT } from './common/enums'; import { CalculatedColumn, AddFilterEvent } from './common/types'; diff --git a/packages/react-data-grid/src/ReactDataGrid.tsx b/packages/react-data-grid/src/ReactDataGrid.tsx index a9e1516332..0e9880d4dd 100644 --- a/packages/react-data-grid/src/ReactDataGrid.tsx +++ b/packages/react-data-grid/src/ReactDataGrid.tsx @@ -156,6 +156,10 @@ export interface DataGridProps { onCellDeSelected?(position: Position): void; /** called before cell is set active, returns a boolean to determine whether cell is editable */ onCheckCellIsEditable?(event: CheckCellIsEditableEvent): boolean; + + overscanRowCount?: number; + overscanColumnCount?: number; + useIsScrolling?: boolean; } type DefaultProps = Pick, @@ -171,6 +175,9 @@ type DefaultProps = Pick, | 'minColumnWidth' | 'columnEquality' | 'editorPortalTarget' +| 'overscanRowCount' +| 'overscanColumnCount' +| 'useIsScrolling' >; export interface DataGridState { @@ -208,7 +215,10 @@ export default class ReactDataGrid extends React.Component(); @@ -728,6 +738,9 @@ export default class ReactDataGrid extends React.Component ); diff --git a/packages/react-data-grid/src/Row.tsx b/packages/react-data-grid/src/Row.tsx index e49a2cf7e2..2cc7366314 100644 --- a/packages/react-data-grid/src/Row.tsx +++ b/packages/react-data-grid/src/Row.tsx @@ -5,6 +5,7 @@ import rowComparer from './common/utils/RowComparer'; import Cell from './Cell'; import { isFrozen } from './ColumnUtils'; import * as rowUtils from './RowUtils'; +import { isPositionStickySupported } from './utils'; import { RowRenderer, RowRendererProps, CellRenderer, CellRendererProps, CalculatedColumn } from './common/types'; export default class Row extends React.Component> implements RowRenderer { @@ -43,7 +44,7 @@ export default class Row extends React.Component> impleme getCell(column: CalculatedColumn) { const Renderer = this.props.cellRenderer!; - const { idx, cellMetaData, isScrolling, row, lastFrozenColumnIndex } = this.props; + const { idx, cellMetaData, isScrolling, row, lastFrozenColumnIndex, scrollLeft } = this.props; const { key } = column; const cellProps: CellRendererProps & { ref: (cell: CellRenderer | null) => void } = { @@ -57,7 +58,7 @@ export default class Row extends React.Component> impleme rowData: row, expandableOptions: this.getExpandableOptions(key), isScrolling, - // scrollLeft, + scrollLeft: isFrozen(column) && !isPositionStickySupported() ? scrollLeft : undefined, lastFrozenColumnIndex }; diff --git a/packages/react-data-grid/src/Viewport.tsx b/packages/react-data-grid/src/Viewport.tsx index 7d251d2364..8ec048afe4 100644 --- a/packages/react-data-grid/src/Viewport.tsx +++ b/packages/react-data-grid/src/Viewport.tsx @@ -3,15 +3,9 @@ import React from 'react'; import Canvas from './Canvas'; import { getGridState, - getColOverscanEndIdx, - getVisibleBoundaries, - getScrollDirection, - getRowOverscanStartIdx, - getRowOverscanEndIdx, - getColOverscanStartIdx, - getNonFrozenVisibleColStartIdx, - getNonFrozenRenderedColumnCount, - findLastFrozenColumnIndex + getVerticalRangeToRender, + getHorizontalRangeToRender, + getScrollDirection } from './utils/viewportUtils'; import { GridProps } from './Grid'; import { ScrollPosition } from './common/types'; @@ -66,6 +60,9 @@ type SharedGridProps = Pick, | 'interactionMasksMetaData' | 'RowsContainer' | 'editorPortalTarget' +| 'overscanRowCount' +| 'overscanColumnCount' +| 'useIsScrolling' >; export interface ViewportProps extends SharedGridProps { @@ -109,14 +106,6 @@ export default class Viewport extends React.Component, Viewp onScroll(nextScrollState); }; - getScroll() { - return this.canvas.current!.getScroll(); - } - - setScrollLeft(scrollLeft: number) { - this.canvas.current!.setScrollLeft(scrollLeft); - } - getDOMNodeOffsetWidth() { return this.viewport.current ? this.viewport.current.offsetWidth : 0; } @@ -128,19 +117,12 @@ export default class Viewport extends React.Component, Viewp } getNextScrollState({ scrollTop, scrollLeft, height, rowHeight, rowsCount }: ScrollParams): ScrollState { - const isScrolling = true; + // const isScrolling = true; const { columns } = this.props.columnMetrics; const scrollDirection = getScrollDirection(this.state, scrollTop, scrollLeft); - const { rowVisibleStartIdx, rowVisibleEndIdx } = getVisibleBoundaries(height, rowHeight, scrollTop, rowsCount); - const rowOverscanStartIdx = getRowOverscanStartIdx(scrollDirection, rowVisibleStartIdx); - const rowOverscanEndIdx = getRowOverscanEndIdx(scrollDirection, rowVisibleEndIdx, rowsCount); - const totalNumberColumns = columns.length; - const lastFrozenColumnIndex = findLastFrozenColumnIndex(columns); - const nonFrozenColVisibleStartIdx = getNonFrozenVisibleColStartIdx(columns, scrollLeft); - const nonFrozenRenderedColumnCount = getNonFrozenRenderedColumnCount(this.props.columnMetrics, this.getDOMNodeOffsetWidth(), scrollLeft); - const colVisibleEndIdx = Math.min(nonFrozenColVisibleStartIdx + nonFrozenRenderedColumnCount, totalNumberColumns); - const colOverscanStartIdx = getColOverscanStartIdx(scrollDirection, nonFrozenColVisibleStartIdx, lastFrozenColumnIndex); - const colOverscanEndIdx = getColOverscanEndIdx(scrollDirection, colVisibleEndIdx, totalNumberColumns); + const { rowVisibleStartIdx, rowVisibleEndIdx, rowOverscanStartIdx, rowOverscanEndIdx } = getVerticalRangeToRender(height, rowHeight, scrollTop, rowsCount, scrollDirection); + const { colVisibleStartIdx, colVisibleEndIdx, lastFrozenColumnIndex, colOverscanStartIdx, colOverscanEndIdx } = getHorizontalRangeToRender(columns, scrollLeft, this.getDOMNodeOffsetWidth(), this.props.columnMetrics.totalColumnWidth, scrollDirection); + return { height, scrollTop, @@ -149,13 +131,13 @@ export default class Viewport extends React.Component, Viewp rowVisibleEndIdx, rowOverscanStartIdx, rowOverscanEndIdx, - colVisibleStartIdx: nonFrozenColVisibleStartIdx, + colVisibleStartIdx, colVisibleEndIdx, colOverscanStartIdx, colOverscanEndIdx, scrollDirection, lastFrozenColumnIndex, - isScrolling + isScrolling: true }; } diff --git a/packages/react-data-grid/src/__tests__/ColumnMetrics.spec.ts b/packages/react-data-grid/src/__tests__/ColumnMetrics.spec.ts index 902c3d40ff..ae52889b0c 100644 --- a/packages/react-data-grid/src/__tests__/ColumnMetrics.spec.ts +++ b/packages/react-data-grid/src/__tests__/ColumnMetrics.spec.ts @@ -1,4 +1,4 @@ -import getScrollbarSize from '../getScrollbarSize'; +import { getScrollbarSize } from '../utils'; import * as ColumnMetrics from '../ColumnMetrics'; import { Column } from '../common/types'; diff --git a/packages/react-data-grid/src/__tests__/Header.spec.tsx b/packages/react-data-grid/src/__tests__/Header.spec.tsx index 10321d6fbb..30037feb78 100644 --- a/packages/react-data-grid/src/__tests__/Header.spec.tsx +++ b/packages/react-data-grid/src/__tests__/Header.spec.tsx @@ -4,7 +4,7 @@ import { shallow } from 'enzyme'; import Header, { HeaderProps } from '../Header'; import HeaderRow from '../HeaderRow'; import helpers, { fakeCellMetaData, Row } from '../helpers/test/GridPropHelpers'; -import * as GetScrollbarSize from '../getScrollbarSize'; +import * as GetScrollbarSize from '../utils'; import { HeaderRowType, DEFINE_SORT } from '../common/enums'; const SCROLL_BAR_SIZE = 17; @@ -35,7 +35,7 @@ describe('Header Unit Tests', () => { } beforeEach(() => { - jest.spyOn(GetScrollbarSize, 'default').mockReturnValue(SCROLL_BAR_SIZE); + jest.spyOn(GetScrollbarSize, 'getScrollbarSize').mockReturnValue(SCROLL_BAR_SIZE); }); it('should render a default header row', () => { diff --git a/packages/react-data-grid/src/getScrollbarSize.ts b/packages/react-data-grid/src/utils/domUtils.tsx similarity index 61% rename from packages/react-data-grid/src/getScrollbarSize.ts rename to packages/react-data-grid/src/utils/domUtils.tsx index 239e3fff6d..5cdad34914 100644 --- a/packages/react-data-grid/src/getScrollbarSize.ts +++ b/packages/react-data-grid/src/utils/domUtils.tsx @@ -1,6 +1,7 @@ let size: number | undefined; +let positionSticky: boolean | undefined; -export default function getScrollbarSize(): number { +export function getScrollbarSize(): number { if (size === undefined) { const outer = document.createElement('div'); outer.style.width = '50px'; @@ -27,3 +28,14 @@ export default function getScrollbarSize(): number { return size; } + +export function isPositionStickySupported() { + if (positionSticky === undefined) { + const el = document.createElement('a'); + const mStyle = el.style; + mStyle.cssText = 'position:-webkit-sticky;position:sticky'; + + positionSticky = mStyle.position ? mStyle.position.includes('sticky') : false; + } + return positionSticky; +} diff --git a/packages/react-data-grid/src/utils/index.tsx b/packages/react-data-grid/src/utils/index.tsx new file mode 100644 index 0000000000..67f8cabce4 --- /dev/null +++ b/packages/react-data-grid/src/utils/index.tsx @@ -0,0 +1,2 @@ +export * from './domUtils'; +export * from './canvasUtils'; diff --git a/packages/react-data-grid/src/utils/viewportUtils.ts b/packages/react-data-grid/src/utils/viewportUtils.ts index d6d02ce366..eda0cd8723 100644 --- a/packages/react-data-grid/src/utils/viewportUtils.ts +++ b/packages/react-data-grid/src/utils/viewportUtils.ts @@ -2,7 +2,8 @@ import { isFrozen } from '../ColumnUtils'; import { SCROLL_DIRECTION } from '../common/enums'; import { CalculatedColumn, ColumnMetrics } from '../common/types'; -export const OVERSCAN_ROWS = 2; +export const OVERSCAN_ROWS = 6; +export const OVERSCAN_COLUMNS = 6; const { min, max, ceil, round } = Math; @@ -29,8 +30,17 @@ export function getGridState(props: { columnMetrics: ColumnMetrics; rowsCo }; } +function findLastIndex(items: T[], predicate: (item: T) => boolean) { + for (let i = items.length - 1; i >= 0; i--) { + if (predicate(items[i])) { + return i; + } + } + return -1; +} + export function findLastFrozenColumnIndex(columns: CalculatedColumn[]): number { - return columns.findIndex(c => isFrozen(c)); + return findLastIndex(columns, c => isFrozen(c)); } function getTotalFrozenColumnWidth(columns: CalculatedColumn[]): number { @@ -58,45 +68,76 @@ function getColumnCountForWidth(columns: CalculatedColumn[], initialWidth: return count; } -export function getNonFrozenVisibleColStartIdx(columns: CalculatedColumn[], scrollLeft: number): number { +// export function getNonFrozenVisibleColStartIdx(columns: CalculatedColumn[], scrollLeft: number): number { +// let remainingScroll = scrollLeft; +// const lastFrozenColumnIndex = findLastFrozenColumnIndex(columns); +// const nonFrozenColumns = columns.slice(lastFrozenColumnIndex + 1); +// let columnIndex = lastFrozenColumnIndex; +// while (remainingScroll >= 0 && columnIndex < nonFrozenColumns.length) { +// columnIndex++; +// const column = columns[columnIndex]; +// remainingScroll -= column ? column.width : 0; +// } +// return max(columnIndex, 0); +// } + +// export function getNonFrozenRenderedColumnCount(columnMetrics: ColumnMetrics, viewportDomWidth: number, scrollLeft: number): number { +// const { columns, totalColumnWidth } = columnMetrics; +// if (columns.length === 0) { +// return 0; +// } +// const colVisibleStartIdx = getNonFrozenVisibleColStartIdx(columns, scrollLeft); +// const totalFrozenColumnWidth = getTotalFrozenColumnWidth(columns); +// const viewportWidth = viewportDomWidth > 0 ? viewportDomWidth : totalColumnWidth; +// const firstColumn = columns[colVisibleStartIdx]; +// // calculate the portion width of first column hidden behind frozen columns +// const scrolledFrozenWidth = totalFrozenColumnWidth + scrollLeft; +// const firstColumnHiddenWidth = scrolledFrozenWidth > firstColumn.left ? scrolledFrozenWidth - firstColumn.left : 0; +// const initialWidth = viewportWidth - totalFrozenColumnWidth + firstColumnHiddenWidth; +// return getColumnCountForWidth(columns, initialWidth, colVisibleStartIdx); +// } + +// export interface VisibleBoundaries { +// rowVisibleStartIdx: number; +// rowVisibleEndIdx: number; +// rowOverscanStartIdx: number; +// rowOverscanEndIdx: number; +// } + +export function getVerticalRangeToRender(gridHeight: number, rowHeight: number, scrollTop: number, rowsCount: number, scrollDirection: SCROLL_DIRECTION) { + const renderedRowsCount = ceil(gridHeight / rowHeight); + const rowVisibleStartIdx = max(0, round(scrollTop / rowHeight)); + const rowVisibleEndIdx = min(rowsCount, rowVisibleStartIdx + renderedRowsCount); + const rowOverscanStartIdx = max(0, rowVisibleStartIdx - (scrollDirection === SCROLL_DIRECTION.UP ? OVERSCAN_ROWS : 1)); + const rowOverscanEndIdx = min(rowsCount, rowVisibleEndIdx + (scrollDirection === SCROLL_DIRECTION.DOWN ? OVERSCAN_ROWS : 1)); + + return { rowVisibleStartIdx, rowVisibleEndIdx, rowOverscanStartIdx, rowOverscanEndIdx }; +} + +export function getHorizontalRangeToRender(columns: CalculatedColumn[], scrollLeft: number, viewportDomWidth: number, totalColumnWidth: number, scrollDirection: SCROLL_DIRECTION) { let remainingScroll = scrollLeft; const lastFrozenColumnIndex = findLastFrozenColumnIndex(columns); - const nonFrozenColumns = columns.slice(lastFrozenColumnIndex + 1); + // const nonFrozenColumns = columns.slice(lastFrozenColumnIndex + 1); let columnIndex = lastFrozenColumnIndex; - while (remainingScroll >= 0 && columnIndex < nonFrozenColumns.length) { + while (remainingScroll >= 0 && columnIndex < columns.length) { columnIndex++; const column = columns[columnIndex]; remainingScroll -= column ? column.width : 0; } - return max(columnIndex, 0); -} + const colVisibleStartIdx = max(columnIndex, 0); -export function getNonFrozenRenderedColumnCount(columnMetrics: ColumnMetrics, viewportDomWidth: number, scrollLeft: number): number { - const { columns, totalColumnWidth } = columnMetrics; - if (columns.length === 0) { - return 0; - } - const colVisibleStartIdx = getNonFrozenVisibleColStartIdx(columns, scrollLeft); const totalFrozenColumnWidth = getTotalFrozenColumnWidth(columns); const viewportWidth = viewportDomWidth > 0 ? viewportDomWidth : totalColumnWidth; - const firstColumn = columns[colVisibleStartIdx]; - // calculate the portion width of first column hidden behind frozen columns - const scrolledFrozenWidth = totalFrozenColumnWidth + scrollLeft; - const firstColumnHiddenWidth = scrolledFrozenWidth > firstColumn.left ? scrolledFrozenWidth - firstColumn.left : 0; - const initialWidth = viewportWidth - totalFrozenColumnWidth + firstColumnHiddenWidth; - return getColumnCountForWidth(columns, initialWidth, colVisibleStartIdx); -} - -export interface VisibleBoundaries { - rowVisibleStartIdx: number; - rowVisibleEndIdx: number; -} - -export function getVisibleBoundaries(gridHeight: number, rowHeight: number, scrollTop: number, rowsCount: number): VisibleBoundaries { - const renderedRowsCount = ceil(gridHeight / rowHeight); - const rowVisibleStartIdx = max(0, round(scrollTop / rowHeight)); - const rowVisibleEndIdx = min(rowVisibleStartIdx + renderedRowsCount, rowsCount); - return { rowVisibleStartIdx, rowVisibleEndIdx }; + // const firstColumn = columns[colVisibleStartIdx]; + // const scrolledFrozenWidth = totalFrozenColumnWidth + scrollLeft; + // const firstColumnHiddenWidth = scrolledFrozenWidth > firstColumn.left ? scrolledFrozenWidth - firstColumn.left : 0; + const availableWidth = viewportWidth - totalFrozenColumnWidth; // + firstColumnHiddenWidth; + const nonFrozenRenderedColumnCount = getColumnCountForWidth(columns, availableWidth, colVisibleStartIdx); + const colVisibleEndIdx = min(columns.length, colVisibleStartIdx + nonFrozenRenderedColumnCount); + const colOverscanStartIdx = max(0, colVisibleStartIdx - (scrollDirection === SCROLL_DIRECTION.LEFT ? OVERSCAN_COLUMNS : 1)); + const colOverscanEndIdx = min(columns.length, colVisibleEndIdx + (scrollDirection === SCROLL_DIRECTION.RIGHT ? OVERSCAN_COLUMNS : 1)); + + return { colVisibleStartIdx, colVisibleEndIdx, lastFrozenColumnIndex, colOverscanStartIdx, colOverscanEndIdx }; } interface ScrollState { @@ -114,28 +155,28 @@ export function getScrollDirection(lastScroll: ScrollState, scrollTop: number, s return SCROLL_DIRECTION.NONE; } -export function getRowOverscanStartIdx(scrollDirection: SCROLL_DIRECTION, rowVisibleStartIdx: number): number { - return scrollDirection === SCROLL_DIRECTION.UP ? max(0, rowVisibleStartIdx - OVERSCAN_ROWS) : max(0, rowVisibleStartIdx); -} - -export function getRowOverscanEndIdx(scrollDirection: SCROLL_DIRECTION, rowVisibleEndIdx: number, rowsCount: number): number { - if (scrollDirection === SCROLL_DIRECTION.DOWN) { - const overscanBoundaryIdx = rowVisibleEndIdx + OVERSCAN_ROWS; - return min(overscanBoundaryIdx, rowsCount); - } - return rowVisibleEndIdx; -} - -export function getColOverscanStartIdx(scrollDirection: SCROLL_DIRECTION, colVisibleStartIdx: number, lastFrozenColumnIdx: number): number { - if (scrollDirection === SCROLL_DIRECTION.LEFT || scrollDirection === SCROLL_DIRECTION.RIGHT) { - return lastFrozenColumnIdx > -1 ? lastFrozenColumnIdx + 1 : 0; - } - return colVisibleStartIdx; -} - -export function getColOverscanEndIdx(scrollDirection: SCROLL_DIRECTION, colVisibleEndIdx: number, totalNumberColumns: number): number { - if (scrollDirection === SCROLL_DIRECTION.DOWN || scrollDirection === SCROLL_DIRECTION.UP) { - return colVisibleEndIdx; - } - return totalNumberColumns; -} +// export function getRowOverscanStartIdx(scrollDirection: SCROLL_DIRECTION, rowVisibleStartIdx: number): number { +// return scrollDirection === SCROLL_DIRECTION.UP ? max(0, rowVisibleStartIdx - OVERSCAN_ROWS) : max(0, rowVisibleStartIdx); +// } + +// export function getRowOverscanEndIdx(scrollDirection: SCROLL_DIRECTION, rowVisibleEndIdx: number, rowsCount: number): number { +// if (scrollDirection === SCROLL_DIRECTION.DOWN) { +// const overscanBoundaryIdx = rowVisibleEndIdx + OVERSCAN_ROWS; +// return min(overscanBoundaryIdx, rowsCount); +// } +// return rowVisibleEndIdx; +// } + +// export function getColOverscanStartIdx(scrollDirection: SCROLL_DIRECTION, colVisibleStartIdx: number, lastFrozenColumnIdx: number): number { +// if (scrollDirection === SCROLL_DIRECTION.LEFT || scrollDirection === SCROLL_DIRECTION.RIGHT) { +// return lastFrozenColumnIdx > -1 ? lastFrozenColumnIdx + 1 : 0; +// } +// return colVisibleStartIdx; +// } + +// export function getColOverscanEndIdx(scrollDirection: SCROLL_DIRECTION, colVisibleEndIdx: number, totalNumberColumns: number): number { +// if (scrollDirection === SCROLL_DIRECTION.DOWN || scrollDirection === SCROLL_DIRECTION.UP) { +// return colVisibleEndIdx; +// } +// return totalNumberColumns; +// } diff --git a/packages/react-data-grid/style/rdg-cell.less b/packages/react-data-grid/style/rdg-cell.less index 7cf26833b9..9802a77ddd 100644 --- a/packages/react-data-grid/style/rdg-cell.less +++ b/packages/react-data-grid/style/rdg-cell.less @@ -34,6 +34,17 @@ z-index: 3 } +.react-grid-Cell--sticky { + &.react-grid-Cell { + display: inline-block; + } + + &.react-grid-Cell--frozen { + position: -webkit-sticky; + position: sticky; + } +} + .react-contextmenu--visible { z-index: 1000; } diff --git a/packages/react-data-grid/style/rdg-row.less b/packages/react-data-grid/style/rdg-row.less index e7eafe6d65..5cf63ff135 100644 --- a/packages/react-data-grid/style/rdg-row.less +++ b/packages/react-data-grid/style/rdg-row.less @@ -1,6 +1,6 @@ -.react-grid-Row { - overflow: hidden; -} +// .react-grid-Row { +// overflow: hidden; +// } .react-grid-Row:hover .react-grid-Cell, .react-grid-Row.row-context-menu .react-grid-Cell { From 3fbcf71a5239f4125b76b1bc474859eba983ca9c Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Wed, 28 Aug 2019 10:19:24 -0500 Subject: [PATCH 03/20] Cleanup scroll logic and fix update logic --- examples/scripts/example03-frozen-cols.js | 2 +- examples/scripts/example31-isScrolling.js | 1 + packages/react-data-grid/src/Canvas.tsx | 22 +- packages/react-data-grid/src/Grid.tsx | 148 ++++--- .../react-data-grid/src/ReactDataGrid.tsx | 10 +- packages/react-data-grid/src/RowGroup.tsx | 2 +- packages/react-data-grid/src/Viewport.tsx | 369 ++++++++---------- packages/react-data-grid/src/common/types.ts | 6 +- .../src/utils/viewportUtils.ts | 158 +++----- 9 files changed, 299 insertions(+), 419 deletions(-) diff --git a/examples/scripts/example03-frozen-cols.js b/examples/scripts/example03-frozen-cols.js index b22952bb99..290a6f9f23 100644 --- a/examples/scripts/example03-frozen-cols.js +++ b/examples/scripts/example03-frozen-cols.js @@ -6,7 +6,7 @@ import exampleWrapper from '../components/exampleWrapper'; class Example extends React.Component { constructor(props, context) { super(props, context); - const extraColumns = [...Array(500).keys()].map(i => ({ key: `col${i}`, name: `col${i}` })); + const extraColumns = [...Array(50).keys()].map(i => ({ key: `col${i}`, name: `col${i}` })); this.createRows(extraColumns); const columns = [ diff --git a/examples/scripts/example31-isScrolling.js b/examples/scripts/example31-isScrolling.js index a3e3d4c278..972be13717 100644 --- a/examples/scripts/example31-isScrolling.js +++ b/examples/scripts/example31-isScrolling.js @@ -61,6 +61,7 @@ class Example extends React.Component { rowsCount={this.state.rows.length} minHeight={800} onScroll={this.onScroll} + useIsScrolling /> ); diff --git a/packages/react-data-grid/src/Canvas.tsx b/packages/react-data-grid/src/Canvas.tsx index 8debc52aa3..a118e3bdb7 100644 --- a/packages/react-data-grid/src/Canvas.tsx +++ b/packages/react-data-grid/src/Canvas.tsx @@ -9,7 +9,7 @@ import * as rowUtils from './RowUtils'; import { getColumnScrollPosition, isPositionStickySupported } from './utils'; import { EventTypes } from './common/enums'; import { CalculatedColumn, Position, ScrollPosition, SubRowDetails, RowRenderer, RowRendererProps, RowData } from './common/types'; -import { ViewportProps, ViewportState } from './Viewport'; +import { ViewportProps, ScrollState } from './Viewport'; type SharedViewportProps = Pick, 'rowKey' @@ -34,8 +34,10 @@ type SharedViewportProps = Pick, | 'interactionMasksMetaData' >; -type SharedViewportState = Pick; export interface CanvasProps extends SharedViewportProps, SharedViewportState { columns: CalculatedColumn[]; + height: number; width: number; totalColumnWidth: number; + isScrolling?: boolean; onScroll(position: ScrollPosition): void; } @@ -73,7 +75,6 @@ export default class Canvas extends React.PureComponent> { private readonly interactionMasks = React.createRef>(); private readonly rows = new Map & React.Component>>(); private unsubscribeScrollToColumn?(): void; - private _scroll = { scrollTop: 0, scrollLeft: 0 }; componentDidMount() { this.unsubscribeScrollToColumn = this.props.eventBus.subscribe(EventTypes.SCROLL_TO_COLUMN, this.scrollToColumn); @@ -92,11 +93,10 @@ export default class Canvas extends React.PureComponent> { handleScroll = (e: React.UIEvent) => { const { scrollLeft, scrollTop } = e.currentTarget; - this._scroll = { scrollTop, scrollLeft }; if (!isPositionStickySupported()) { this.setScrollLeft(scrollLeft); } - this.props.onScroll(this._scroll); + this.props.onScroll({ scrollLeft, scrollTop }); }; onHitBottomCanvas = () => { @@ -311,7 +311,7 @@ export default class Canvas extends React.PureComponent> { colOverscanEndIdx, lastFrozenColumnIndex, isScrolling: this.props.isScrolling, - scrollLeft: this._scroll.scrollLeft + scrollLeft: this.props.scrollLeft }); }); @@ -349,8 +349,8 @@ export default class Canvas extends React.PureComponent> { onHitTopBoundary={this.onHitTopCanvas} onHitLeftBoundary={this.handleHitColummBoundary} onHitRightBoundary={this.handleHitColummBoundary} - scrollLeft={this._scroll.scrollLeft} - scrollTop={this._scroll.scrollTop} + scrollLeft={this.props.scrollLeft} + scrollTop={this.props.scrollTop} getRowHeight={this.getRowHeight} getRowTop={this.getRowTop} getRowColumns={this.getRowColumns} diff --git a/packages/react-data-grid/src/Grid.tsx b/packages/react-data-grid/src/Grid.tsx index b8605fc993..d88d1ea577 100644 --- a/packages/react-data-grid/src/Grid.tsx +++ b/packages/react-data-grid/src/Grid.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useRef, createElement } from 'react'; import { isValidElementType } from 'react-is'; import Header from './Header'; @@ -46,91 +46,83 @@ export interface GridProps extends SharedDataGridProps, SharedDataGridStat selectedRows?: SelectedRow[]; rowSelection?: RowSelection; rowOffsetHeight: number; - onSort(columnKey: keyof R, sortDirection: DEFINE_SORT): void; + eventBus: EventBus; totalWidth: number | string; + interactionMasksMetaData: InteractionMasksMetaData; + onSort(columnKey: keyof R, sortDirection: DEFINE_SORT): void; onViewportKeydown(e: React.KeyboardEvent): void; onViewportKeyup(e: React.KeyboardEvent): void; onColumnResize(idx: number, width: number): void; - eventBus: EventBus; - interactionMasksMetaData: InteractionMasksMetaData; } -export default class Grid extends React.Component> { - static displayName = 'Grid'; - - private readonly header = React.createRef>(); - private readonly viewport = React.createRef>(); +export default function Grid({ emptyRowsView, headerRows, ...props }: GridProps) { + const header = useRef>(null); - onScroll = (scrollState: ScrollState) => { - this.header.current!.setScrollLeft(scrollState.scrollLeft); - if (this.props.onScroll) { - this.props.onScroll(scrollState); + function onScroll(scrollState: ScrollState) { + header.current!.setScrollLeft(scrollState.scrollLeft); + if (props.onScroll) { + props.onScroll(scrollState); } - }; + } - render() { - const { headerRows } = this.props; - const EmptyRowsView = this.props.emptyRowsView; - return ( -
- - ref={this.header} - columnMetrics={this.props.columnMetrics} - onColumnResize={this.props.onColumnResize} - rowHeight={this.props.rowHeight} - totalWidth={this.props.totalWidth} - headerRows={headerRows} - sortColumn={this.props.sortColumn} - sortDirection={this.props.sortDirection} - draggableHeaderCell={this.props.draggableHeaderCell} - onSort={this.props.onSort} - onHeaderDrop={this.props.onHeaderDrop} - getValidFilterValues={this.props.getValidFilterValues} - cellMetaData={this.props.cellMetaData} - /> - {this.props.rowsCount === 0 && isValidElementType(EmptyRowsView) ? ( -
- -
- ) : ( -
- - ref={this.viewport} - rowKey={this.props.rowKey} - rowHeight={this.props.rowHeight} - rowRenderer={this.props.rowRenderer} - rowGetter={this.props.rowGetter} - rowsCount={this.props.rowsCount} - selectedRows={this.props.selectedRows} - columnMetrics={this.props.columnMetrics} - totalWidth={this.props.totalWidth} - onScroll={this.onScroll} - cellMetaData={this.props.cellMetaData} - rowOffsetHeight={this.props.rowOffsetHeight || this.props.rowHeight * headerRows.length} - minHeight={this.props.minHeight} - scrollToRowIndex={this.props.scrollToRowIndex} - contextMenu={this.props.contextMenu} - rowSelection={this.props.rowSelection} - getSubRowDetails={this.props.getSubRowDetails} - rowGroupRenderer={this.props.rowGroupRenderer} - enableCellSelect={this.props.enableCellSelect} - enableCellAutoFocus={this.props.enableCellAutoFocus} - cellNavigationMode={this.props.cellNavigationMode} - eventBus={this.props.eventBus} - interactionMasksMetaData={this.props.interactionMasksMetaData} - RowsContainer={this.props.RowsContainer} - editorPortalTarget={this.props.editorPortalTarget} - overscanRowCount={this.props.overscanRowCount} - overscanColumnCount={this.props.overscanColumnCount} - useIsScrolling={this.props.useIsScrolling} - /> -
- )} -
- ); - } + return ( +
+ + ref={header} + columnMetrics={props.columnMetrics} + onColumnResize={props.onColumnResize} + rowHeight={props.rowHeight} + totalWidth={props.totalWidth} + headerRows={headerRows} + sortColumn={props.sortColumn} + sortDirection={props.sortDirection} + draggableHeaderCell={props.draggableHeaderCell} + onSort={props.onSort} + onHeaderDrop={props.onHeaderDrop} + getValidFilterValues={props.getValidFilterValues} + cellMetaData={props.cellMetaData} + /> + {props.rowsCount === 0 && isValidElementType(emptyRowsView) ? ( +
+ {createElement(emptyRowsView)} +
+ ) : ( +
+ + rowKey={props.rowKey} + rowHeight={props.rowHeight} + rowRenderer={props.rowRenderer} + rowGetter={props.rowGetter} + rowsCount={props.rowsCount} + selectedRows={props.selectedRows} + columnMetrics={props.columnMetrics} + totalWidth={props.totalWidth} + onScroll={onScroll} + cellMetaData={props.cellMetaData} + rowOffsetHeight={props.rowOffsetHeight || props.rowHeight * headerRows.length} + minHeight={props.minHeight} + scrollToRowIndex={props.scrollToRowIndex} + contextMenu={props.contextMenu} + rowSelection={props.rowSelection} + getSubRowDetails={props.getSubRowDetails} + rowGroupRenderer={props.rowGroupRenderer} + enableCellSelect={props.enableCellSelect} + enableCellAutoFocus={props.enableCellAutoFocus} + cellNavigationMode={props.cellNavigationMode} + eventBus={props.eventBus} + interactionMasksMetaData={props.interactionMasksMetaData} + RowsContainer={props.RowsContainer} + editorPortalTarget={props.editorPortalTarget} + overscanRowCount={props.overscanRowCount} + overscanColumnCount={props.overscanColumnCount} + useIsScrolling={props.useIsScrolling} + /> +
+ )} +
+ ); } diff --git a/packages/react-data-grid/src/ReactDataGrid.tsx b/packages/react-data-grid/src/ReactDataGrid.tsx index 0e9880d4dd..9ec9c6da1a 100644 --- a/packages/react-data-grid/src/ReactDataGrid.tsx +++ b/packages/react-data-grid/src/ReactDataGrid.tsx @@ -175,9 +175,6 @@ type DefaultProps = Pick, | 'minColumnWidth' | 'columnEquality' | 'editorPortalTarget' -| 'overscanRowCount' -| 'overscanColumnCount' -| 'useIsScrolling' >; export interface DataGridState { @@ -215,14 +212,10 @@ export default class ReactDataGrid extends React.Component(); - private readonly base = React.createRef>(); private readonly selectAllCheckbox = React.createRef(); private readonly eventBus = new EventBus(); private readonly _keysDown = new Set(); @@ -702,7 +695,6 @@ export default class ReactDataGrid extends React.Component - ref={this.base} rowKey={this.props.rowKey} headerRows={this.getHeaderRows()} draggableHeaderCell={this.props.draggableHeaderCell} diff --git a/packages/react-data-grid/src/RowGroup.tsx b/packages/react-data-grid/src/RowGroup.tsx index d092de587f..ac72b14ffb 100644 --- a/packages/react-data-grid/src/RowGroup.tsx +++ b/packages/react-data-grid/src/RowGroup.tsx @@ -20,7 +20,7 @@ interface Props { colVisibleEndIdx: number; colOverscanStartIdx: number; colOverscanEndIdx: number; - isScrolling: boolean; + isScrolling?: boolean; columnGroupDisplayName: string; columnGroupName: string; isExpanded: boolean; diff --git a/packages/react-data-grid/src/Viewport.tsx b/packages/react-data-grid/src/Viewport.tsx index 8ec048afe4..fe0a77ed62 100644 --- a/packages/react-data-grid/src/Viewport.tsx +++ b/packages/react-data-grid/src/Viewport.tsx @@ -1,26 +1,12 @@ -import React from 'react'; +import React, { useRef, useState, useEffect } from 'react'; import Canvas from './Canvas'; -import { - getGridState, - getVerticalRangeToRender, - getHorizontalRangeToRender, - getScrollDirection -} from './utils/viewportUtils'; +import { getVerticalRangeToRender, getHorizontalRangeToRender, getScrollDirection } from './utils/viewportUtils'; import { GridProps } from './Grid'; import { ScrollPosition } from './common/types'; import { SCROLL_DIRECTION } from './common/enums'; -interface ScrollParams { - height: number; - scrollTop: number; - scrollLeft: number; - rowsCount: number; - rowHeight: number; -} - export interface ScrollState { - height: number; scrollTop: number; scrollLeft: number; rowVisibleStartIdx: number; @@ -31,9 +17,8 @@ export interface ScrollState { colVisibleEndIdx: number; colOverscanStartIdx: number; colOverscanEndIdx: number; - scrollDirection: SCROLL_DIRECTION; lastFrozenColumnIndex: number; - isScrolling: boolean; + scrollDirection: SCROLL_DIRECTION; } type SharedGridProps = Pick, @@ -69,212 +54,182 @@ export interface ViewportProps extends SharedGridProps { onScroll(scrollState: ScrollState): void; } -export interface ViewportState { - rowOverscanStartIdx: number; - rowOverscanEndIdx: number; - rowVisibleStartIdx: number; - rowVisibleEndIdx: number; - height: number; - scrollTop: number; - scrollLeft: number; - colVisibleStartIdx: number; - colVisibleEndIdx: number; - colOverscanStartIdx: number; - colOverscanEndIdx: number; - isScrolling: boolean; - lastFrozenColumnIndex: number; -} - -export default class Viewport extends React.Component, ViewportState> { - static displayName = 'Viewport'; - - readonly state: Readonly = getGridState(this.props); - private readonly canvas = React.createRef>(); - private readonly viewport = React.createRef(); - private resetScrollStateTimeoutId: number | null = null; - - onScroll = ({ scrollTop, scrollLeft }: ScrollPosition) => { - const { rowHeight, rowsCount, onScroll } = this.props; - const nextScrollState = this.updateScroll({ - scrollTop, - scrollLeft, - height: this.state.height, - rowHeight, - rowsCount - }); - - onScroll(nextScrollState); - }; +export default function Viewport({ + minHeight, + rowHeight, + rowOffsetHeight, + rowsCount, + onScroll: handleScroll, + columnMetrics, + overscanRowCount, + overscanColumnCount, + useIsScrolling, + ...props +}: ViewportProps) { + const canvas = useRef>(null); + const viewport = useRef(null); + const resetScrollStateTimeoutId = useRef(null); + const firstRender = useRef(true); + + const canvasHeight = minHeight - rowOffsetHeight; + + const [scrollState, setScrollState] = useState(() => { + return { + scrollLeft: 0, + scrollTop: 0, + scrollDirection: SCROLL_DIRECTION.NONE, + ...getVerticalRangeToRender({ + height: canvasHeight, + rowHeight, + scrollTop: 0, + rowsCount, + scrollDirection: SCROLL_DIRECTION.NONE, + overscanRowCount + }), + ...getHorizontalRangeToRender({ + columns: columnMetrics.columns, + scrollLeft: 0, + viewportWidth: getDOMNodeOffsetWidth(), + totalColumnWidth: columnMetrics.totalColumnWidth, + scrollDirection: SCROLL_DIRECTION.NONE, + overscanColumnCount + }) + }; + }); + const [isScrolling, setIsScrolling] = useState(undefined); - getDOMNodeOffsetWidth() { - return this.viewport.current ? this.viewport.current.offsetWidth : 0; + function getDOMNodeOffsetWidth() { + return viewport.current ? viewport.current.offsetWidth : 0; } - clearScrollTimer() { - if (this.resetScrollStateTimeoutId !== null) { - window.clearTimeout(this.resetScrollStateTimeoutId); + function clearScrollTimer() { + if (resetScrollStateTimeoutId.current !== null) { + window.clearTimeout(resetScrollStateTimeoutId.current); + resetScrollStateTimeoutId.current = null; } } - getNextScrollState({ scrollTop, scrollLeft, height, rowHeight, rowsCount }: ScrollParams): ScrollState { - // const isScrolling = true; - const { columns } = this.props.columnMetrics; - const scrollDirection = getScrollDirection(this.state, scrollTop, scrollLeft); - const { rowVisibleStartIdx, rowVisibleEndIdx, rowOverscanStartIdx, rowOverscanEndIdx } = getVerticalRangeToRender(height, rowHeight, scrollTop, rowsCount, scrollDirection); - const { colVisibleStartIdx, colVisibleEndIdx, lastFrozenColumnIndex, colOverscanStartIdx, colOverscanEndIdx } = getHorizontalRangeToRender(columns, scrollLeft, this.getDOMNodeOffsetWidth(), this.props.columnMetrics.totalColumnWidth, scrollDirection); - - return { - height, - scrollTop, - scrollLeft, - rowVisibleStartIdx, - rowVisibleEndIdx, - rowOverscanStartIdx, - rowOverscanEndIdx, - colVisibleStartIdx, - colVisibleEndIdx, - colOverscanStartIdx, - colOverscanEndIdx, - scrollDirection, - lastFrozenColumnIndex, - isScrolling: true - }; - } - - resetScrollStateAfterDelay() { - this.clearScrollTimer(); - this.resetScrollStateTimeoutId = window.setTimeout( - this.resetScrollStateAfterDelayCallback, - 500 + function resetScrollStateAfterDelay() { + clearScrollTimer(); + resetScrollStateTimeoutId.current = window.setTimeout( + resetScrollStateAfterDelayCallback, + 150 ); } - resetScrollStateAfterDelayCallback = () => { - this.resetScrollStateTimeoutId = null; - this.setState({ isScrolling: false }); - }; - - updateScroll(scrollParams: ScrollParams) { - this.resetScrollStateAfterDelay(); - const nextScrollState = this.getNextScrollState(scrollParams); - this.setState(nextScrollState); - return nextScrollState; + function resetScrollStateAfterDelayCallback() { + resetScrollStateTimeoutId.current = null; + setIsScrolling(false); } - metricsUpdated = () => { - if (!this.viewport.current) { - return; - } - - const { height } = this.viewport.current.getBoundingClientRect(); - - if (height) { - const { scrollTop, scrollLeft } = this.state; - const { rowHeight, rowsCount } = this.props; - this.updateScroll({ - scrollTop, - scrollLeft, - height, - rowHeight, - rowsCount - }); + function onScroll({ scrollLeft, scrollTop }: ScrollPosition) { + if (useIsScrolling) { + setIsScrolling(true); + resetScrollStateAfterDelay(); } - }; - componentWillReceiveProps(nextProps: ViewportProps) { - const { rowHeight, rowsCount } = nextProps; - if (this.props.rowHeight !== nextProps.rowHeight - || this.props.minHeight !== nextProps.minHeight) { - const { scrollTop, scrollLeft, height } = getGridState(nextProps); - this.updateScroll({ - scrollTop, - scrollLeft, - height, - rowHeight, - rowsCount - }); - } else if (this.props.columnMetrics.columns.length !== nextProps.columnMetrics.columns.length) { - this.setState(getGridState(nextProps)); - } else if (this.props.rowsCount !== nextProps.rowsCount) { - const { scrollTop, scrollLeft, height } = this.state; - this.updateScroll({ - scrollTop, - scrollLeft, - height, + const scrollDirection = getScrollDirection(scrollState, { scrollLeft, scrollTop }); + const nextScrollState = { + scrollLeft, + scrollTop, + scrollDirection, + ...getVerticalRangeToRender({ + height: canvasHeight, rowHeight, - rowsCount - }); - // Added to fix the hiding of the bottom scrollbar when showing the filters. - } else if (this.props.rowOffsetHeight !== nextProps.rowOffsetHeight) { - const { scrollTop, scrollLeft } = this.state; - // The value of height can be positive or negative and will be added to the current height to cater for changes in the header height (due to the filer) - const height = this.state.height + this.props.rowOffsetHeight - nextProps.rowOffsetHeight; - this.updateScroll({ scrollTop, + rowsCount, + scrollDirection, + overscanRowCount + }), + ...getHorizontalRangeToRender({ + columns: columnMetrics.columns, scrollLeft, - height, - rowHeight, - rowsCount - }); - } - } - - componentDidMount() { - window.addEventListener('resize', this.metricsUpdated); - this.metricsUpdated(); - } + viewportWidth: getDOMNodeOffsetWidth(), + totalColumnWidth: columnMetrics.totalColumnWidth, + scrollDirection, + overscanColumnCount + }) + }; - componentWillUnmount() { - window.removeEventListener('resize', this.metricsUpdated); - this.clearScrollTimer(); + setScrollState(nextScrollState); + handleScroll(nextScrollState); } - render() { - return ( -
- - ref={this.canvas} - rowKey={this.props.rowKey} - totalWidth={this.props.totalWidth} - width={this.props.columnMetrics.width} - totalColumnWidth={this.props.columnMetrics.totalColumnWidth} - rowGetter={this.props.rowGetter} - rowsCount={this.props.rowsCount} - selectedRows={this.props.selectedRows} - columns={this.props.columnMetrics.columns} - rowRenderer={this.props.rowRenderer} - rowOverscanStartIdx={this.state.rowOverscanStartIdx} - rowOverscanEndIdx={this.state.rowOverscanEndIdx} - rowVisibleStartIdx={this.state.rowVisibleStartIdx} - rowVisibleEndIdx={this.state.rowVisibleEndIdx} - colVisibleStartIdx={this.state.colVisibleStartIdx} - colVisibleEndIdx={this.state.colVisibleEndIdx} - colOverscanStartIdx={this.state.colOverscanStartIdx} - colOverscanEndIdx={this.state.colOverscanEndIdx} - lastFrozenColumnIndex={this.state.lastFrozenColumnIndex} - cellMetaData={this.props.cellMetaData} - height={this.state.height} - rowHeight={this.props.rowHeight} - onScroll={this.onScroll} - scrollToRowIndex={this.props.scrollToRowIndex} - contextMenu={this.props.contextMenu} - rowSelection={this.props.rowSelection} - getSubRowDetails={this.props.getSubRowDetails} - rowGroupRenderer={this.props.rowGroupRenderer} - isScrolling={this.state.isScrolling} - enableCellSelect={this.props.enableCellSelect} - enableCellAutoFocus={this.props.enableCellAutoFocus} - cellNavigationMode={this.props.cellNavigationMode} - eventBus={this.props.eventBus} - RowsContainer={this.props.RowsContainer} - editorPortalTarget={this.props.editorPortalTarget} - interactionMasksMetaData={this.props.interactionMasksMetaData} - /> -
- ); - } + useEffect(() => { + if (firstRender.current) { + firstRender.current = false; + return; + } + const scrollDirection = SCROLL_DIRECTION.NONE; + setScrollState(({ scrollTop, scrollLeft }) => ({ + scrollTop, + scrollLeft, + scrollDirection, + ...getVerticalRangeToRender({ + height: canvasHeight, + rowHeight, + scrollTop, + rowsCount, + scrollDirection, + overscanRowCount + }), + ...getHorizontalRangeToRender({ + columns: columnMetrics.columns, + scrollLeft, + viewportWidth: getDOMNodeOffsetWidth(), + totalColumnWidth: columnMetrics.totalColumnWidth, + scrollDirection, + overscanColumnCount + }) + })); + }, [canvasHeight, columnMetrics.columns, columnMetrics.totalColumnWidth, overscanColumnCount, overscanRowCount, rowHeight, rowsCount]); + + return ( +
+ + ref={canvas} + rowKey={props.rowKey} + totalWidth={props.totalWidth} + width={columnMetrics.width} + totalColumnWidth={columnMetrics.totalColumnWidth} + rowGetter={props.rowGetter} + rowsCount={rowsCount} + selectedRows={props.selectedRows} + columns={columnMetrics.columns} + rowRenderer={props.rowRenderer} + scrollTop={scrollState.scrollTop} + scrollLeft={scrollState.scrollLeft} + rowOverscanStartIdx={scrollState.rowOverscanStartIdx} + rowOverscanEndIdx={scrollState.rowOverscanEndIdx} + rowVisibleStartIdx={scrollState.rowVisibleStartIdx} + rowVisibleEndIdx={scrollState.rowVisibleEndIdx} + colVisibleStartIdx={scrollState.colVisibleStartIdx} + colVisibleEndIdx={scrollState.colVisibleEndIdx} + colOverscanStartIdx={scrollState.colOverscanStartIdx} + colOverscanEndIdx={scrollState.colOverscanEndIdx} + lastFrozenColumnIndex={scrollState.lastFrozenColumnIndex} + cellMetaData={props.cellMetaData} + height={canvasHeight} + rowHeight={rowHeight} + onScroll={onScroll} + scrollToRowIndex={props.scrollToRowIndex} + contextMenu={props.contextMenu} + rowSelection={props.rowSelection} + getSubRowDetails={props.getSubRowDetails} + rowGroupRenderer={props.rowGroupRenderer} + isScrolling={isScrolling} + enableCellSelect={props.enableCellSelect} + enableCellAutoFocus={props.enableCellAutoFocus} + cellNavigationMode={props.cellNavigationMode} + eventBus={props.eventBus} + RowsContainer={props.RowsContainer} + editorPortalTarget={props.editorPortalTarget} + interactionMasksMetaData={props.interactionMasksMetaData} + /> +
+ ); } diff --git a/packages/react-data-grid/src/common/types.ts b/packages/react-data-grid/src/common/types.ts index 52ccdf3a67..7daa94a1e4 100644 --- a/packages/react-data-grid/src/common/types.ts +++ b/packages/react-data-grid/src/common/types.ts @@ -129,7 +129,7 @@ export interface FormatterProps { value: TValue; column: CalculatedColumn; row: TRow; - isScrolling: boolean; + isScrolling?: boolean; dependentValues?: TDependentValue; } @@ -158,7 +158,7 @@ export interface CellRendererProps { column: CalculatedColumn; rowData: TRow; cellMetaData: CellMetaData; - isScrolling: boolean; + isScrolling?: boolean; scrollLeft?: number; expandableOptions?: ExpandableOptions; lastFrozenColumnIndex?: number; @@ -176,7 +176,7 @@ export interface RowRendererProps { subRowDetails?: SubRowDetails; colOverscanStartIdx: number; colOverscanEndIdx: number; - isScrolling: boolean; + isScrolling?: boolean; scrollLeft: number; lastFrozenColumnIndex?: number; } diff --git a/packages/react-data-grid/src/utils/viewportUtils.ts b/packages/react-data-grid/src/utils/viewportUtils.ts index eda0cd8723..03587ee972 100644 --- a/packages/react-data-grid/src/utils/viewportUtils.ts +++ b/packages/react-data-grid/src/utils/viewportUtils.ts @@ -1,35 +1,9 @@ import { isFrozen } from '../ColumnUtils'; import { SCROLL_DIRECTION } from '../common/enums'; -import { CalculatedColumn, ColumnMetrics } from '../common/types'; - -export const OVERSCAN_ROWS = 6; -export const OVERSCAN_COLUMNS = 6; +import { CalculatedColumn } from '../common/types'; const { min, max, ceil, round } = Math; -export function getGridState(props: { columnMetrics: ColumnMetrics; rowsCount: number; minHeight: number; rowHeight: number; rowOffsetHeight: number }) { - const totalNumberColumns = props.columnMetrics.columns.length; - const canvasHeight = props.minHeight - props.rowOffsetHeight; - const renderedRowsCount = ceil((props.minHeight - props.rowHeight) / props.rowHeight); - const rowOverscanEndIdx = min(props.rowsCount, renderedRowsCount * 2); - - return { - rowOverscanStartIdx: 0, - rowOverscanEndIdx, - rowVisibleStartIdx: 0, - rowVisibleEndIdx: renderedRowsCount, - height: canvasHeight, - scrollTop: 0, - scrollLeft: 0, - colVisibleStartIdx: 0, - colVisibleEndIdx: totalNumberColumns, - colOverscanStartIdx: 0, - colOverscanEndIdx: totalNumberColumns, - isScrolling: false, - lastFrozenColumnIndex: 0 - }; -} - function findLastIndex(items: T[], predicate: (item: T) => boolean) { for (let i = items.length - 1; i >= 0; i--) { if (predicate(items[i])) { @@ -68,56 +42,51 @@ function getColumnCountForWidth(columns: CalculatedColumn[], initialWidth: return count; } -// export function getNonFrozenVisibleColStartIdx(columns: CalculatedColumn[], scrollLeft: number): number { -// let remainingScroll = scrollLeft; -// const lastFrozenColumnIndex = findLastFrozenColumnIndex(columns); -// const nonFrozenColumns = columns.slice(lastFrozenColumnIndex + 1); -// let columnIndex = lastFrozenColumnIndex; -// while (remainingScroll >= 0 && columnIndex < nonFrozenColumns.length) { -// columnIndex++; -// const column = columns[columnIndex]; -// remainingScroll -= column ? column.width : 0; -// } -// return max(columnIndex, 0); -// } - -// export function getNonFrozenRenderedColumnCount(columnMetrics: ColumnMetrics, viewportDomWidth: number, scrollLeft: number): number { -// const { columns, totalColumnWidth } = columnMetrics; -// if (columns.length === 0) { -// return 0; -// } -// const colVisibleStartIdx = getNonFrozenVisibleColStartIdx(columns, scrollLeft); -// const totalFrozenColumnWidth = getTotalFrozenColumnWidth(columns); -// const viewportWidth = viewportDomWidth > 0 ? viewportDomWidth : totalColumnWidth; -// const firstColumn = columns[colVisibleStartIdx]; -// // calculate the portion width of first column hidden behind frozen columns -// const scrolledFrozenWidth = totalFrozenColumnWidth + scrollLeft; -// const firstColumnHiddenWidth = scrolledFrozenWidth > firstColumn.left ? scrolledFrozenWidth - firstColumn.left : 0; -// const initialWidth = viewportWidth - totalFrozenColumnWidth + firstColumnHiddenWidth; -// return getColumnCountForWidth(columns, initialWidth, colVisibleStartIdx); -// } - -// export interface VisibleBoundaries { -// rowVisibleStartIdx: number; -// rowVisibleEndIdx: number; -// rowOverscanStartIdx: number; -// rowOverscanEndIdx: number; -// } - -export function getVerticalRangeToRender(gridHeight: number, rowHeight: number, scrollTop: number, rowsCount: number, scrollDirection: SCROLL_DIRECTION) { - const renderedRowsCount = ceil(gridHeight / rowHeight); +interface VerticalRangeToRenderParams { + height: number; + rowHeight: number; + scrollTop: number; + rowsCount: number; + scrollDirection: SCROLL_DIRECTION; + overscanRowCount?: number; +} + +export function getVerticalRangeToRender({ + height, + rowHeight, + scrollTop, + rowsCount, + scrollDirection, + overscanRowCount = 2 +}: VerticalRangeToRenderParams) { + const renderedRowsCount = ceil(height / rowHeight); const rowVisibleStartIdx = max(0, round(scrollTop / rowHeight)); const rowVisibleEndIdx = min(rowsCount, rowVisibleStartIdx + renderedRowsCount); - const rowOverscanStartIdx = max(0, rowVisibleStartIdx - (scrollDirection === SCROLL_DIRECTION.UP ? OVERSCAN_ROWS : 1)); - const rowOverscanEndIdx = min(rowsCount, rowVisibleEndIdx + (scrollDirection === SCROLL_DIRECTION.DOWN ? OVERSCAN_ROWS : 1)); + const rowOverscanStartIdx = max(0, rowVisibleStartIdx - (scrollDirection === SCROLL_DIRECTION.UP ? overscanRowCount : 1)); + const rowOverscanEndIdx = min(rowsCount, rowVisibleEndIdx + (scrollDirection === SCROLL_DIRECTION.DOWN ? overscanRowCount : 1)); return { rowVisibleStartIdx, rowVisibleEndIdx, rowOverscanStartIdx, rowOverscanEndIdx }; } -export function getHorizontalRangeToRender(columns: CalculatedColumn[], scrollLeft: number, viewportDomWidth: number, totalColumnWidth: number, scrollDirection: SCROLL_DIRECTION) { +interface HorizontalRangeToRenderParams { + columns: CalculatedColumn[]; + viewportWidth: number; + totalColumnWidth: number; + scrollLeft: number; + scrollDirection: SCROLL_DIRECTION; + overscanColumnCount?: number; +} + +export function getHorizontalRangeToRender({ + columns, + scrollLeft, + viewportWidth, + totalColumnWidth, + scrollDirection, + overscanColumnCount = 2 +}: HorizontalRangeToRenderParams) { let remainingScroll = scrollLeft; const lastFrozenColumnIndex = findLastFrozenColumnIndex(columns); - // const nonFrozenColumns = columns.slice(lastFrozenColumnIndex + 1); let columnIndex = lastFrozenColumnIndex; while (remainingScroll >= 0 && columnIndex < columns.length) { columnIndex++; @@ -127,56 +96,27 @@ export function getHorizontalRangeToRender(columns: CalculatedColumn[], sc const colVisibleStartIdx = max(columnIndex, 0); const totalFrozenColumnWidth = getTotalFrozenColumnWidth(columns); - const viewportWidth = viewportDomWidth > 0 ? viewportDomWidth : totalColumnWidth; - // const firstColumn = columns[colVisibleStartIdx]; - // const scrolledFrozenWidth = totalFrozenColumnWidth + scrollLeft; - // const firstColumnHiddenWidth = scrolledFrozenWidth > firstColumn.left ? scrolledFrozenWidth - firstColumn.left : 0; - const availableWidth = viewportWidth - totalFrozenColumnWidth; // + firstColumnHiddenWidth; + viewportWidth = viewportWidth > 0 ? viewportWidth : totalColumnWidth; + const availableWidth = viewportWidth - totalFrozenColumnWidth; const nonFrozenRenderedColumnCount = getColumnCountForWidth(columns, availableWidth, colVisibleStartIdx); const colVisibleEndIdx = min(columns.length, colVisibleStartIdx + nonFrozenRenderedColumnCount); - const colOverscanStartIdx = max(0, colVisibleStartIdx - (scrollDirection === SCROLL_DIRECTION.LEFT ? OVERSCAN_COLUMNS : 1)); - const colOverscanEndIdx = min(columns.length, colVisibleEndIdx + (scrollDirection === SCROLL_DIRECTION.RIGHT ? OVERSCAN_COLUMNS : 1)); + const colOverscanStartIdx = max(0, colVisibleStartIdx - (scrollDirection === SCROLL_DIRECTION.LEFT ? overscanColumnCount : 1)); + const colOverscanEndIdx = min(columns.length, colVisibleEndIdx + (scrollDirection === SCROLL_DIRECTION.RIGHT ? overscanColumnCount : 1)); return { colVisibleStartIdx, colVisibleEndIdx, lastFrozenColumnIndex, colOverscanStartIdx, colOverscanEndIdx }; } interface ScrollState { - scrollTop?: number; - scrollLeft?: number; + scrollTop: number; + scrollLeft: number; } -export function getScrollDirection(lastScroll: ScrollState, scrollTop: number, scrollLeft: number): SCROLL_DIRECTION { - if (scrollTop !== lastScroll.scrollTop && lastScroll.scrollTop !== undefined) { - return scrollTop - lastScroll.scrollTop >= 0 ? SCROLL_DIRECTION.DOWN : SCROLL_DIRECTION.UP; +export function getScrollDirection(prevScroll: ScrollState, nextScroll: ScrollState): SCROLL_DIRECTION { + if (nextScroll.scrollTop !== prevScroll.scrollTop) { + return nextScroll.scrollTop - prevScroll.scrollTop >= 0 ? SCROLL_DIRECTION.DOWN : SCROLL_DIRECTION.UP; } - if (scrollLeft !== lastScroll.scrollLeft && lastScroll.scrollLeft !== undefined) { - return scrollLeft - lastScroll.scrollLeft >= 0 ? SCROLL_DIRECTION.RIGHT : SCROLL_DIRECTION.LEFT; + if (nextScroll.scrollLeft !== prevScroll.scrollLeft) { + return nextScroll.scrollLeft - prevScroll.scrollLeft >= 0 ? SCROLL_DIRECTION.RIGHT : SCROLL_DIRECTION.LEFT; } return SCROLL_DIRECTION.NONE; } - -// export function getRowOverscanStartIdx(scrollDirection: SCROLL_DIRECTION, rowVisibleStartIdx: number): number { -// return scrollDirection === SCROLL_DIRECTION.UP ? max(0, rowVisibleStartIdx - OVERSCAN_ROWS) : max(0, rowVisibleStartIdx); -// } - -// export function getRowOverscanEndIdx(scrollDirection: SCROLL_DIRECTION, rowVisibleEndIdx: number, rowsCount: number): number { -// if (scrollDirection === SCROLL_DIRECTION.DOWN) { -// const overscanBoundaryIdx = rowVisibleEndIdx + OVERSCAN_ROWS; -// return min(overscanBoundaryIdx, rowsCount); -// } -// return rowVisibleEndIdx; -// } - -// export function getColOverscanStartIdx(scrollDirection: SCROLL_DIRECTION, colVisibleStartIdx: number, lastFrozenColumnIdx: number): number { -// if (scrollDirection === SCROLL_DIRECTION.LEFT || scrollDirection === SCROLL_DIRECTION.RIGHT) { -// return lastFrozenColumnIdx > -1 ? lastFrozenColumnIdx + 1 : 0; -// } -// return colVisibleStartIdx; -// } - -// export function getColOverscanEndIdx(scrollDirection: SCROLL_DIRECTION, colVisibleEndIdx: number, totalNumberColumns: number): number { -// if (scrollDirection === SCROLL_DIRECTION.DOWN || scrollDirection === SCROLL_DIRECTION.UP) { -// return colVisibleEndIdx; -// } -// return totalNumberColumns; -// } From bb28272d5dd72d9fd066f34cc50e9ea194bd02c8 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Wed, 28 Aug 2019 10:20:15 -0500 Subject: [PATCH 04/20] Revert commented code --- packages/react-data-grid/src/Cell/CellValue.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-data-grid/src/Cell/CellValue.tsx b/packages/react-data-grid/src/Cell/CellValue.tsx index 3eacbb9ecb..edd91d3eec 100644 --- a/packages/react-data-grid/src/Cell/CellValue.tsx +++ b/packages/react-data-grid/src/Cell/CellValue.tsx @@ -17,9 +17,9 @@ export default function CellValue({ rowIdx, rowData, column, value, isScrolli // convention based method to get corresponding Id or Name of any Name or Id property const { getRowMetaData } = column; if (getRowMetaData) { - // if (process.env.NODE_ENV === 'development') { - // console.warn('getRowMetaData for formatters is deprecated and will be removed in a future version of ReactDataGrid. Instead access row prop of formatter'); - // } + if (process.env.NODE_ENV === 'development') { + console.warn('getRowMetaData for formatters is deprecated and will be removed in a future version of ReactDataGrid. Instead access row prop of formatter'); + } return getRowMetaData(row, column); } } From a9244dcad69b135b3e8f3cc63bdba8b2563b7a55 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Wed, 28 Aug 2019 15:12:12 -0500 Subject: [PATCH 05/20] Fix initial load performance --- examples/scripts/example03-frozen-cols.js | 2 +- packages/react-data-grid/src/Canvas.tsx | 2 +- packages/react-data-grid/src/ColumnMetrics.ts | 1 + packages/react-data-grid/src/Row.tsx | 4 +- packages/react-data-grid/src/Viewport.tsx | 170 ++++++++---------- packages/react-data-grid/src/common/types.ts | 5 +- .../src/utils/viewportUtils.ts | 59 +++--- 7 files changed, 105 insertions(+), 138 deletions(-) diff --git a/examples/scripts/example03-frozen-cols.js b/examples/scripts/example03-frozen-cols.js index 290a6f9f23..b22952bb99 100644 --- a/examples/scripts/example03-frozen-cols.js +++ b/examples/scripts/example03-frozen-cols.js @@ -6,7 +6,7 @@ import exampleWrapper from '../components/exampleWrapper'; class Example extends React.Component { constructor(props, context) { super(props, context); - const extraColumns = [...Array(50).keys()].map(i => ({ key: `col${i}`, name: `col${i}` })); + const extraColumns = [...Array(500).keys()].map(i => ({ key: `col${i}`, name: `col${i}` })); this.createRows(extraColumns); const columns = [ diff --git a/packages/react-data-grid/src/Canvas.tsx b/packages/react-data-grid/src/Canvas.tsx index a118e3bdb7..c5b2d17c27 100644 --- a/packages/react-data-grid/src/Canvas.tsx +++ b/packages/react-data-grid/src/Canvas.tsx @@ -45,7 +45,6 @@ type SharedViewportState = Pick; export interface CanvasProps extends SharedViewportProps, SharedViewportState { @@ -53,6 +52,7 @@ export interface CanvasProps extends SharedViewportProps, SharedViewportSt height: number; width: number; totalColumnWidth: number; + lastFrozenColumnIndex: number; isScrolling?: boolean; onScroll(position: ScrollPosition): void; } diff --git a/packages/react-data-grid/src/ColumnMetrics.ts b/packages/react-data-grid/src/ColumnMetrics.ts index 3429cee814..f9c033b483 100644 --- a/packages/react-data-grid/src/ColumnMetrics.ts +++ b/packages/react-data-grid/src/ColumnMetrics.ts @@ -72,6 +72,7 @@ export function recalculate(metrics: Metrics): ColumnMetrics { return { width, columns: calculatedColumns, + lastFrozenColumnIndex: frozenColumns.length - 1, totalWidth: metrics.totalWidth, totalColumnWidth: getTotalColumnWidth(columns), minColumnWidth: metrics.minColumnWidth diff --git a/packages/react-data-grid/src/Row.tsx b/packages/react-data-grid/src/Row.tsx index 2cc7366314..f3a0725304 100644 --- a/packages/react-data-grid/src/Row.tsx +++ b/packages/react-data-grid/src/Row.tsx @@ -66,8 +66,8 @@ export default class Row extends React.Component> impleme } getCells() { - const { colOverscanStartIdx, colOverscanEndIdx, columns } = this.props; - const frozenColumns = columns.filter(c => isFrozen(c)); + const { colOverscanStartIdx, colOverscanEndIdx, columns, lastFrozenColumnIndex } = this.props; + const frozenColumns = columns.slice(0, lastFrozenColumnIndex + 1); const nonFrozenColumn = columns.slice(colOverscanStartIdx, colOverscanEndIdx + 1).filter(c => !isFrozen(c)); return nonFrozenColumn.concat(frozenColumns).map(c => this.getCell(c)); } diff --git a/packages/react-data-grid/src/Viewport.tsx b/packages/react-data-grid/src/Viewport.tsx index fe0a77ed62..3f1c070ef6 100644 --- a/packages/react-data-grid/src/Viewport.tsx +++ b/packages/react-data-grid/src/Viewport.tsx @@ -17,7 +17,6 @@ export interface ScrollState { colVisibleEndIdx: number; colOverscanStartIdx: number; colOverscanEndIdx: number; - lastFrozenColumnIndex: number; scrollDirection: SCROLL_DIRECTION; } @@ -69,35 +68,11 @@ export default function Viewport({ const canvas = useRef>(null); const viewport = useRef(null); const resetScrollStateTimeoutId = useRef(null); - const firstRender = useRef(true); + const [scrollState, setScrollState] = useState(null); + const [isScrolling, setIsScrolling] = useState(undefined); const canvasHeight = minHeight - rowOffsetHeight; - const [scrollState, setScrollState] = useState(() => { - return { - scrollLeft: 0, - scrollTop: 0, - scrollDirection: SCROLL_DIRECTION.NONE, - ...getVerticalRangeToRender({ - height: canvasHeight, - rowHeight, - scrollTop: 0, - rowsCount, - scrollDirection: SCROLL_DIRECTION.NONE, - overscanRowCount - }), - ...getHorizontalRangeToRender({ - columns: columnMetrics.columns, - scrollLeft: 0, - viewportWidth: getDOMNodeOffsetWidth(), - totalColumnWidth: columnMetrics.totalColumnWidth, - scrollDirection: SCROLL_DIRECTION.NONE, - overscanColumnCount - }) - }; - }); - const [isScrolling, setIsScrolling] = useState(undefined); - function getDOMNodeOffsetWidth() { return viewport.current ? viewport.current.offsetWidth : 0; } @@ -128,7 +103,15 @@ export default function Viewport({ resetScrollStateAfterDelay(); } - const scrollDirection = getScrollDirection(scrollState, { scrollLeft, scrollTop }); + let previousScrollPosition: ScrollPosition | undefined; + if (scrollState !== null) { + previousScrollPosition = { + scrollLeft: scrollState.scrollLeft, + scrollTop: scrollState.scrollTop + }; + } + + const scrollDirection = getScrollDirection(previousScrollPosition, { scrollLeft, scrollTop }); const nextScrollState = { scrollLeft, scrollTop, @@ -142,10 +125,9 @@ export default function Viewport({ overscanRowCount }), ...getHorizontalRangeToRender({ - columns: columnMetrics.columns, + columnMetrics, scrollLeft, viewportWidth: getDOMNodeOffsetWidth(), - totalColumnWidth: columnMetrics.totalColumnWidth, scrollDirection, overscanColumnCount }) @@ -156,33 +138,33 @@ export default function Viewport({ } useEffect(() => { - if (firstRender.current) { - firstRender.current = false; - return; - } const scrollDirection = SCROLL_DIRECTION.NONE; - setScrollState(({ scrollTop, scrollLeft }) => ({ - scrollTop, - scrollLeft, - scrollDirection, - ...getVerticalRangeToRender({ - height: canvasHeight, - rowHeight, + setScrollState(prevScrollState => { + const scrollTop = prevScrollState ? prevScrollState.scrollTop : 0; + const scrollLeft = prevScrollState ? prevScrollState.scrollLeft : 0; + + return { scrollTop, - rowsCount, - scrollDirection, - overscanRowCount - }), - ...getHorizontalRangeToRender({ - columns: columnMetrics.columns, scrollLeft, - viewportWidth: getDOMNodeOffsetWidth(), - totalColumnWidth: columnMetrics.totalColumnWidth, scrollDirection, - overscanColumnCount - }) - })); - }, [canvasHeight, columnMetrics.columns, columnMetrics.totalColumnWidth, overscanColumnCount, overscanRowCount, rowHeight, rowsCount]); + ...getVerticalRangeToRender({ + height: canvasHeight, + rowHeight, + scrollTop, + rowsCount, + scrollDirection, + overscanRowCount + }), + ...getHorizontalRangeToRender({ + columnMetrics, + scrollLeft, + viewportWidth: getDOMNodeOffsetWidth(), + scrollDirection, + overscanColumnCount + }) + }; + }); + }, [canvasHeight, columnMetrics, overscanColumnCount, overscanRowCount, rowHeight, rowsCount]); return (
({ style={{ top: rowOffsetHeight }} ref={viewport} > - - ref={canvas} - rowKey={props.rowKey} - totalWidth={props.totalWidth} - width={columnMetrics.width} - totalColumnWidth={columnMetrics.totalColumnWidth} - rowGetter={props.rowGetter} - rowsCount={rowsCount} - selectedRows={props.selectedRows} - columns={columnMetrics.columns} - rowRenderer={props.rowRenderer} - scrollTop={scrollState.scrollTop} - scrollLeft={scrollState.scrollLeft} - rowOverscanStartIdx={scrollState.rowOverscanStartIdx} - rowOverscanEndIdx={scrollState.rowOverscanEndIdx} - rowVisibleStartIdx={scrollState.rowVisibleStartIdx} - rowVisibleEndIdx={scrollState.rowVisibleEndIdx} - colVisibleStartIdx={scrollState.colVisibleStartIdx} - colVisibleEndIdx={scrollState.colVisibleEndIdx} - colOverscanStartIdx={scrollState.colOverscanStartIdx} - colOverscanEndIdx={scrollState.colOverscanEndIdx} - lastFrozenColumnIndex={scrollState.lastFrozenColumnIndex} - cellMetaData={props.cellMetaData} - height={canvasHeight} - rowHeight={rowHeight} - onScroll={onScroll} - scrollToRowIndex={props.scrollToRowIndex} - contextMenu={props.contextMenu} - rowSelection={props.rowSelection} - getSubRowDetails={props.getSubRowDetails} - rowGroupRenderer={props.rowGroupRenderer} - isScrolling={isScrolling} - enableCellSelect={props.enableCellSelect} - enableCellAutoFocus={props.enableCellAutoFocus} - cellNavigationMode={props.cellNavigationMode} - eventBus={props.eventBus} - RowsContainer={props.RowsContainer} - editorPortalTarget={props.editorPortalTarget} - interactionMasksMetaData={props.interactionMasksMetaData} - /> + {scrollState && ( + + ref={canvas} + rowKey={props.rowKey} + totalWidth={props.totalWidth} + width={columnMetrics.width} + totalColumnWidth={columnMetrics.totalColumnWidth} + columns={columnMetrics.columns} + lastFrozenColumnIndex={columnMetrics.lastFrozenColumnIndex} + rowGetter={props.rowGetter} + rowsCount={rowsCount} + selectedRows={props.selectedRows} + rowRenderer={props.rowRenderer} + scrollTop={scrollState.scrollTop} + scrollLeft={scrollState.scrollLeft} + rowOverscanStartIdx={scrollState.rowOverscanStartIdx} + rowOverscanEndIdx={scrollState.rowOverscanEndIdx} + rowVisibleStartIdx={scrollState.rowVisibleStartIdx} + rowVisibleEndIdx={scrollState.rowVisibleEndIdx} + colVisibleStartIdx={scrollState.colVisibleStartIdx} + colVisibleEndIdx={scrollState.colVisibleEndIdx} + colOverscanStartIdx={scrollState.colOverscanStartIdx} + colOverscanEndIdx={scrollState.colOverscanEndIdx} + cellMetaData={props.cellMetaData} + height={canvasHeight} + rowHeight={rowHeight} + onScroll={onScroll} + scrollToRowIndex={props.scrollToRowIndex} + contextMenu={props.contextMenu} + rowSelection={props.rowSelection} + getSubRowDetails={props.getSubRowDetails} + rowGroupRenderer={props.rowGroupRenderer} + isScrolling={isScrolling} + enableCellSelect={props.enableCellSelect} + enableCellAutoFocus={props.enableCellAutoFocus} + cellNavigationMode={props.cellNavigationMode} + eventBus={props.eventBus} + RowsContainer={props.RowsContainer} + editorPortalTarget={props.editorPortalTarget} + interactionMasksMetaData={props.interactionMasksMetaData} + /> + )}
); } diff --git a/packages/react-data-grid/src/common/types.ts b/packages/react-data-grid/src/common/types.ts index 7daa94a1e4..a7dda61b74 100644 --- a/packages/react-data-grid/src/common/types.ts +++ b/packages/react-data-grid/src/common/types.ts @@ -62,6 +62,7 @@ export type ColumnList = Column[] | List>; export interface ColumnMetrics { columns: CalculatedColumn[]; + lastFrozenColumnIndex: number; width: number; totalColumnWidth: number; totalWidth: number; @@ -161,7 +162,7 @@ export interface CellRendererProps { isScrolling?: boolean; scrollLeft?: number; expandableOptions?: ExpandableOptions; - lastFrozenColumnIndex?: number; + lastFrozenColumnIndex: number; } export interface RowRendererProps { @@ -178,7 +179,7 @@ export interface RowRendererProps { colOverscanEndIdx: number; isScrolling?: boolean; scrollLeft: number; - lastFrozenColumnIndex?: number; + lastFrozenColumnIndex: number; } export interface FilterRendererProps { diff --git a/packages/react-data-grid/src/utils/viewportUtils.ts b/packages/react-data-grid/src/utils/viewportUtils.ts index 03587ee972..9ab6ee22b7 100644 --- a/packages/react-data-grid/src/utils/viewportUtils.ts +++ b/packages/react-data-grid/src/utils/viewportUtils.ts @@ -1,24 +1,9 @@ -import { isFrozen } from '../ColumnUtils'; import { SCROLL_DIRECTION } from '../common/enums'; -import { CalculatedColumn } from '../common/types'; +import { CalculatedColumn, ScrollPosition, ColumnMetrics } from '../common/types'; const { min, max, ceil, round } = Math; -function findLastIndex(items: T[], predicate: (item: T) => boolean) { - for (let i = items.length - 1; i >= 0; i--) { - if (predicate(items[i])) { - return i; - } - } - return -1; -} - -export function findLastFrozenColumnIndex(columns: CalculatedColumn[]): number { - return findLastIndex(columns, c => isFrozen(c)); -} - -function getTotalFrozenColumnWidth(columns: CalculatedColumn[]): number { - const lastFrozenColumnIndex = findLastFrozenColumnIndex(columns); +function getTotalFrozenColumnWidth(columns: CalculatedColumn[], lastFrozenColumnIndex: number): number { if (lastFrozenColumnIndex === -1) { return 0; } @@ -62,31 +47,30 @@ export function getVerticalRangeToRender({ const renderedRowsCount = ceil(height / rowHeight); const rowVisibleStartIdx = max(0, round(scrollTop / rowHeight)); const rowVisibleEndIdx = min(rowsCount, rowVisibleStartIdx + renderedRowsCount); - const rowOverscanStartIdx = max(0, rowVisibleStartIdx - (scrollDirection === SCROLL_DIRECTION.UP ? overscanRowCount : 1)); - const rowOverscanEndIdx = min(rowsCount, rowVisibleEndIdx + (scrollDirection === SCROLL_DIRECTION.DOWN ? overscanRowCount : 1)); + const rowOverscanStartIdx = max(0, rowVisibleStartIdx - (scrollDirection === SCROLL_DIRECTION.UP ? overscanRowCount : 2)); + const rowOverscanEndIdx = min(rowsCount, rowVisibleEndIdx + (scrollDirection === SCROLL_DIRECTION.DOWN ? overscanRowCount : 2)); return { rowVisibleStartIdx, rowVisibleEndIdx, rowOverscanStartIdx, rowOverscanEndIdx }; } interface HorizontalRangeToRenderParams { - columns: CalculatedColumn[]; + columnMetrics: ColumnMetrics; viewportWidth: number; - totalColumnWidth: number; scrollLeft: number; scrollDirection: SCROLL_DIRECTION; overscanColumnCount?: number; } export function getHorizontalRangeToRender({ - columns, + columnMetrics, scrollLeft, viewportWidth, - totalColumnWidth, scrollDirection, overscanColumnCount = 2 }: HorizontalRangeToRenderParams) { + const { columns, totalColumnWidth, lastFrozenColumnIndex } = columnMetrics; + let remainingScroll = scrollLeft; - const lastFrozenColumnIndex = findLastFrozenColumnIndex(columns); let columnIndex = lastFrozenColumnIndex; while (remainingScroll >= 0 && columnIndex < columns.length) { columnIndex++; @@ -95,28 +79,25 @@ export function getHorizontalRangeToRender({ } const colVisibleStartIdx = max(columnIndex, 0); - const totalFrozenColumnWidth = getTotalFrozenColumnWidth(columns); + const totalFrozenColumnWidth = getTotalFrozenColumnWidth(columns, lastFrozenColumnIndex); viewportWidth = viewportWidth > 0 ? viewportWidth : totalColumnWidth; const availableWidth = viewportWidth - totalFrozenColumnWidth; const nonFrozenRenderedColumnCount = getColumnCountForWidth(columns, availableWidth, colVisibleStartIdx); const colVisibleEndIdx = min(columns.length, colVisibleStartIdx + nonFrozenRenderedColumnCount); - const colOverscanStartIdx = max(0, colVisibleStartIdx - (scrollDirection === SCROLL_DIRECTION.LEFT ? overscanColumnCount : 1)); - const colOverscanEndIdx = min(columns.length, colVisibleEndIdx + (scrollDirection === SCROLL_DIRECTION.RIGHT ? overscanColumnCount : 1)); + const colOverscanStartIdx = max(0, colVisibleStartIdx - (scrollDirection === SCROLL_DIRECTION.LEFT ? overscanColumnCount : 2)); + const colOverscanEndIdx = min(columns.length, colVisibleEndIdx + (scrollDirection === SCROLL_DIRECTION.RIGHT ? overscanColumnCount : 2)); - return { colVisibleStartIdx, colVisibleEndIdx, lastFrozenColumnIndex, colOverscanStartIdx, colOverscanEndIdx }; + return { colVisibleStartIdx, colVisibleEndIdx, colOverscanStartIdx, colOverscanEndIdx }; } -interface ScrollState { - scrollTop: number; - scrollLeft: number; -} - -export function getScrollDirection(prevScroll: ScrollState, nextScroll: ScrollState): SCROLL_DIRECTION { - if (nextScroll.scrollTop !== prevScroll.scrollTop) { - return nextScroll.scrollTop - prevScroll.scrollTop >= 0 ? SCROLL_DIRECTION.DOWN : SCROLL_DIRECTION.UP; - } - if (nextScroll.scrollLeft !== prevScroll.scrollLeft) { - return nextScroll.scrollLeft - prevScroll.scrollLeft >= 0 ? SCROLL_DIRECTION.RIGHT : SCROLL_DIRECTION.LEFT; +export function getScrollDirection(prevScroll: ScrollPosition | undefined, nextScroll: ScrollPosition): SCROLL_DIRECTION { + if (prevScroll !== undefined) { + if (nextScroll.scrollTop !== prevScroll.scrollTop) { + return nextScroll.scrollTop - prevScroll.scrollTop >= 0 ? SCROLL_DIRECTION.DOWN : SCROLL_DIRECTION.UP; + } + if (nextScroll.scrollLeft !== prevScroll.scrollLeft) { + return nextScroll.scrollLeft - prevScroll.scrollLeft >= 0 ? SCROLL_DIRECTION.RIGHT : SCROLL_DIRECTION.LEFT; + } } return SCROLL_DIRECTION.NONE; } From 8d3620571002bdbafc75292404ac1227127a0b85 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Tue, 3 Sep 2019 13:45:32 -0500 Subject: [PATCH 06/20] Cleanup headers --- packages/react-data-grid/src/Canvas.tsx | 6 +- packages/react-data-grid/src/Grid.tsx | 12 ++- packages/react-data-grid/src/Header.tsx | 99 ++++++++----------- packages/react-data-grid/src/HeaderRow.tsx | 14 +-- .../react-data-grid/src/ReactDataGrid.tsx | 26 +++-- packages/react-data-grid/style/rdg-core.less | 3 - .../react-data-grid/style/rdg-header.less | 29 ++---- packages/react-data-grid/style/rdg-row.less | 6 +- 8 files changed, 80 insertions(+), 115 deletions(-) diff --git a/packages/react-data-grid/src/Canvas.tsx b/packages/react-data-grid/src/Canvas.tsx index c5b2d17c27..0adb862e06 100644 --- a/packages/react-data-grid/src/Canvas.tsx +++ b/packages/react-data-grid/src/Canvas.tsx @@ -273,12 +273,8 @@ export default class Canvas extends React.PureComponent> { } renderPlaceholder(key: string, height: number) { - // just renders empty cells - // if we wanted to show gridlines, we'd need classes and position as with renderScrollingPlaceholder return ( -
- {this.props.columns.map(column =>
)} -
+
); } diff --git a/packages/react-data-grid/src/Grid.tsx b/packages/react-data-grid/src/Grid.tsx index d88d1ea577..9ceb87a565 100644 --- a/packages/react-data-grid/src/Grid.tsx +++ b/packages/react-data-grid/src/Grid.tsx @@ -41,7 +41,7 @@ type SharedDataGridState = Pick, >; export interface GridProps extends SharedDataGridProps, SharedDataGridState { - headerRows: HeaderRowData[]; + headerRows: [HeaderRowData, HeaderRowData | undefined]; cellMetaData: CellMetaData; selectedRows?: SelectedRow[]; rowSelection?: RowSelection; @@ -57,9 +57,13 @@ export interface GridProps extends SharedDataGridProps, SharedDataGridStat export default function Grid({ emptyRowsView, headerRows, ...props }: GridProps) { const header = useRef>(null); + const scrollLeft = useRef(0); function onScroll(scrollState: ScrollState) { - header.current!.setScrollLeft(scrollState.scrollLeft); + if (scrollLeft.current !== scrollState.scrollLeft) { + scrollLeft.current = scrollState.scrollLeft; + header.current!.setScrollLeft(scrollState.scrollLeft); + } if (props.onScroll) { props.onScroll(scrollState); } @@ -72,9 +76,9 @@ export default function Grid({ emptyRowsView, headerRows, ...props }: GridPro ref={header} columnMetrics={props.columnMetrics} onColumnResize={props.onColumnResize} - rowHeight={props.rowHeight} totalWidth={props.totalWidth} headerRows={headerRows} + rowOffsetHeight={props.rowOffsetHeight} sortColumn={props.sortColumn} sortDirection={props.sortDirection} draggableHeaderCell={props.draggableHeaderCell} @@ -103,7 +107,7 @@ export default function Grid({ emptyRowsView, headerRows, ...props }: GridPro totalWidth={props.totalWidth} onScroll={onScroll} cellMetaData={props.cellMetaData} - rowOffsetHeight={props.rowOffsetHeight || props.rowHeight * headerRows.length} + rowOffsetHeight={props.rowOffsetHeight} minHeight={props.minHeight} scrollToRowIndex={props.scrollToRowIndex} contextMenu={props.contextMenu} diff --git a/packages/react-data-grid/src/Header.tsx b/packages/react-data-grid/src/Header.tsx index 4f9f2048fa..5fe9bc66b5 100644 --- a/packages/react-data-grid/src/Header.tsx +++ b/packages/react-data-grid/src/Header.tsx @@ -1,20 +1,18 @@ import React from 'react'; -import ReactDOM from 'react-dom'; import classNames from 'classnames'; import HeaderRow from './HeaderRow'; import { resizeColumn } from './ColumnMetrics'; import { getScrollbarSize } from './utils'; -import { HeaderRowType } from './common/enums'; -import { CalculatedColumn, ColumnMetrics } from './common/types'; +import { CalculatedColumn, ColumnMetrics, HeaderRowData } from './common/types'; import { GridProps } from './Grid'; type SharedGridProps = Pick, 'columnMetrics' | 'onColumnResize' -| 'rowHeight' | 'totalWidth' | 'headerRows' +| 'rowOffsetHeight' | 'sortColumn' | 'sortDirection' | 'draggableHeaderCell' @@ -67,45 +65,48 @@ export default class Header extends React.Component, State> this.props.onColumnResize(pos, width || column.width); }; - getHeaderRows() { + getHeaderRow = (row: HeaderRowData, ref: React.RefObject>) => { const columnMetrics = this.getColumnMetrics(); + const scrollbarSize = getScrollbarSize() > 0 ? getScrollbarSize() : 0; + const updatedWidth = typeof this.props.totalWidth === 'number' + ? this.props.totalWidth - scrollbarSize + : this.props.totalWidth; - return this.props.headerRows.map((row, index) => { + return ( + + key={row.rowType} + ref={ref} + rowType={row.rowType} + style={{ + width: updatedWidth, + height: row.height + }} + onColumnResize={this.onColumnResize} + onColumnResizeEnd={this.onColumnResizeEnd} + width={columnMetrics.width} + height={row.height} + columns={columnMetrics.columns} + draggableHeaderCell={this.props.draggableHeaderCell} + filterable={row.filterable} + onFilterChange={row.onFilterChange} + onHeaderDrop={this.props.onHeaderDrop} + sortColumn={this.props.sortColumn} + sortDirection={this.props.sortDirection} + onSort={this.props.onSort} + getValidFilterValues={this.props.getValidFilterValues} + /> + ); + }; + + getHeaderRows() { + const { headerRows } = this.props; + const rows = [this.getHeaderRow(headerRows[0], this.row)]; + if (headerRows[1]) { // To allow header filters to be visible - const isFilterRow = row.rowType === HeaderRowType.FILTER; - const rowHeight = isFilterRow ? '500px' : 'auto'; - const scrollbarSize = getScrollbarSize() > 0 ? getScrollbarSize() : 0; - const updatedWidth = typeof this.props.totalWidth === 'number' - ? this.props.totalWidth - scrollbarSize - : this.props.totalWidth; - const headerRowStyle: React.CSSProperties = { - top: this.getCombinedHeaderHeights(index), - width: updatedWidth, - minHeight: rowHeight - }; - - return ( - - key={row.rowType} - ref={isFilterRow ? this.filterRow : this.row} - rowType={row.rowType} - style={headerRowStyle} - onColumnResize={this.onColumnResize} - onColumnResizeEnd={this.onColumnResizeEnd} - width={columnMetrics.width} - height={row.height || this.props.rowHeight} - columns={columnMetrics.columns} - draggableHeaderCell={this.props.draggableHeaderCell} - filterable={row.filterable} - onFilterChange={row.onFilterChange} - onHeaderDrop={this.props.onHeaderDrop} - sortColumn={this.props.sortColumn} - sortDirection={this.props.sortDirection} - onSort={this.props.onSort} - getValidFilterValues={this.props.getValidFilterValues} - /> - ); - }); + rows.push(this.getHeaderRow({ ...headerRows[1], height: 500 }, this.filterRow)); + } + + return rows; } getColumnMetrics(): ColumnMetrics { @@ -121,25 +122,9 @@ export default class Header extends React.Component, State> return idx === -1 ? null : idx; } - getCombinedHeaderHeights(until?: number): number { - const stopAt = typeof until === 'number' - ? until - : this.props.headerRows.length; - - let height = 0; - for (let index = 0; index < stopAt; index++) { - height += this.props.headerRows[index].height || this.props.rowHeight; - } - return height; - } - setScrollLeft(scrollLeft: number): void { - const node = ReactDOM.findDOMNode(this.row.current) as Element; - node.scrollLeft = scrollLeft; this.row.current!.setScrollLeft(scrollLeft); if (this.filterRow.current) { - const nodeFilters = ReactDOM.findDOMNode(this.filterRow.current) as Element; - nodeFilters.scrollLeft = scrollLeft; this.filterRow.current.setScrollLeft(scrollLeft); } } @@ -156,7 +141,7 @@ export default class Header extends React.Component, State> return (
diff --git a/packages/react-data-grid/src/HeaderRow.tsx b/packages/react-data-grid/src/HeaderRow.tsx index 841d6e4769..9bd7a67bc4 100644 --- a/packages/react-data-grid/src/HeaderRow.tsx +++ b/packages/react-data-grid/src/HeaderRow.tsx @@ -4,7 +4,6 @@ import shallowEqual from 'shallowequal'; import HeaderCell from './HeaderCell'; import SortableHeaderCell from './common/cells/headerCells/SortableHeaderCell'; import FilterableHeaderCell from './common/cells/headerCells/FilterableHeaderCell'; -import { getScrollbarSize } from './utils'; import { isFrozen } from './ColumnUtils'; import { HeaderRowType, HeaderCellType, DEFINE_SORT } from './common/enums'; import { CalculatedColumn, AddFilterEvent } from './common/types'; @@ -34,6 +33,7 @@ export interface HeaderRowProps extends SharedHeaderProps { export default class HeaderRow extends React.Component> { static displayName = 'HeaderRow'; + private readonly headerRow = React.createRef(); private readonly cells = new Map>(); shouldComponentUpdate(nextProps: HeaderRowProps) { @@ -134,6 +134,7 @@ export default class HeaderRow extends React.Component> { } setScrollLeft(scrollLeft: number): void { + this.headerRow.current!.scrollLeft = scrollLeft; this.props.columns.forEach(column => { const { key } = column; if (!this.cells.has(key)) return; @@ -147,20 +148,13 @@ export default class HeaderRow extends React.Component> { } render() { - const cellsStyle: React.CSSProperties = { - width: this.props.width ? this.props.width + getScrollbarSize() : '100%', - height: this.props.height - }; - - // FIXME: do we need 2 wrapping divs? return (
-
- {this.getCells()} -
+ {this.getCells()}
); } diff --git a/packages/react-data-grid/src/ReactDataGrid.tsx b/packages/react-data-grid/src/ReactDataGrid.tsx index 9ec9c6da1a..0c66534e2d 100644 --- a/packages/react-data-grid/src/ReactDataGrid.tsx +++ b/packages/react-data-grid/src/ReactDataGrid.tsx @@ -565,22 +565,17 @@ export default class ReactDataGrid extends React.Component offsetHeight += row.height, 0); - } - - getHeaderRows() { + getHeaderRows(): [HeaderRowData, HeaderRowData | undefined] { const { headerRowHeight, rowHeight, onAddFilter, headerFiltersHeight } = this.props; - const rows: HeaderRowData[] = [{ height: headerRowHeight || rowHeight, rowType: HeaderRowType.HEADER }]; - if (this.state.canFilter === true) { - rows.push({ + return [ + { height: headerRowHeight || rowHeight, rowType: HeaderRowType.HEADER }, + this.state.canFilter ? { rowType: HeaderRowType.FILTER, filterable: true, onFilterChange: onAddFilter, - height: headerFiltersHeight - }); - } - return rows; + height: headerFiltersHeight || headerRowHeight || rowHeight + } : undefined + ]; } getRowSelectionProps() { @@ -682,6 +677,9 @@ export default class ReactDataGrid extends React.Component extends React.Component rowKey={this.props.rowKey} - headerRows={this.getHeaderRows()} + headerRows={headerRows} draggableHeaderCell={this.props.draggableHeaderCell} getValidFilterValues={this.props.getValidFilterValues} columnMetrics={this.state.columnMetrics} @@ -708,7 +706,7 @@ export default class ReactDataGrid extends React.Component div { - white-space: nowrap; - overflow: hidden; - } + position: relative; + overflow: hidden; + .user-select(); } .react-grid-HeaderCell { - display: inline-block; position: absolute; padding: 8px; margin: 0; @@ -32,10 +26,7 @@ background: #f9f9f9; border-right: 1px solid #dddddd; border-bottom: 1px solid #dddddd; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; + .user-select(); } .rdg-header-cell-resizable::after { diff --git a/packages/react-data-grid/style/rdg-row.less b/packages/react-data-grid/style/rdg-row.less index 5cf63ff135..f2b433bacf 100644 --- a/packages/react-data-grid/style/rdg-row.less +++ b/packages/react-data-grid/style/rdg-row.less @@ -1,6 +1,6 @@ -// .react-grid-Row { -// overflow: hidden; -// } +.react-grid-Row { + position: relative; +} .react-grid-Row:hover .react-grid-Cell, .react-grid-Row.row-context-menu .react-grid-Cell { From 993d2fd0a242e113c302020c3d939bf05ef35dc5 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Tue, 3 Sep 2019 18:38:00 -0500 Subject: [PATCH 07/20] Cleanup HeaderRow --- packages/react-data-grid/src/Header.tsx | 9 ++------ packages/react-data-grid/src/HeaderRow.tsx | 24 ++++++++-------------- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/packages/react-data-grid/src/Header.tsx b/packages/react-data-grid/src/Header.tsx index 5fe9bc66b5..2194b92a97 100644 --- a/packages/react-data-grid/src/Header.tsx +++ b/packages/react-data-grid/src/Header.tsx @@ -77,13 +77,9 @@ export default class Header extends React.Component, State> key={row.rowType} ref={ref} rowType={row.rowType} - style={{ - width: updatedWidth, - height: row.height - }} onColumnResize={this.onColumnResize} onColumnResizeEnd={this.onColumnResizeEnd} - width={columnMetrics.width} + width={updatedWidth} height={row.height} columns={columnMetrics.columns} draggableHeaderCell={this.props.draggableHeaderCell} @@ -102,8 +98,7 @@ export default class Header extends React.Component, State> const { headerRows } = this.props; const rows = [this.getHeaderRow(headerRows[0], this.row)]; if (headerRows[1]) { - // To allow header filters to be visible - rows.push(this.getHeaderRow({ ...headerRows[1], height: 500 }, this.filterRow)); + rows.push(this.getHeaderRow(headerRows[1], this.filterRow)); } return rows; diff --git a/packages/react-data-grid/src/HeaderRow.tsx b/packages/react-data-grid/src/HeaderRow.tsx index 9bd7a67bc4..a1656b37d9 100644 --- a/packages/react-data-grid/src/HeaderRow.tsx +++ b/packages/react-data-grid/src/HeaderRow.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import shallowEqual from 'shallowequal'; import HeaderCell from './HeaderCell'; import SortableHeaderCell from './common/cells/headerCells/SortableHeaderCell'; @@ -19,34 +18,22 @@ type SharedHeaderProps = Pick, >; export interface HeaderRowProps extends SharedHeaderProps { - width?: number; + width: number | string; height: number; columns: CalculatedColumn[]; onColumnResize(column: CalculatedColumn, width: number): void; onColumnResizeEnd(column: CalculatedColumn, width: number): void; - style?: React.CSSProperties; filterable?: boolean; onFilterChange?(args: AddFilterEvent): void; rowType: HeaderRowType; } -export default class HeaderRow extends React.Component> { +export default class HeaderRow extends React.PureComponent> { static displayName = 'HeaderRow'; private readonly headerRow = React.createRef(); private readonly cells = new Map>(); - shouldComponentUpdate(nextProps: HeaderRowProps) { - return ( - nextProps.width !== this.props.width - || nextProps.height !== this.props.height - || nextProps.columns !== this.props.columns - || !shallowEqual(nextProps.style, this.props.style) - || this.props.sortColumn !== nextProps.sortColumn - || this.props.sortDirection !== nextProps.sortDirection - ); - } - getHeaderCellType(column: CalculatedColumn): HeaderCellType { if (column.filterable && this.props.filterable) { return HeaderCellType.FILTERABLE; @@ -148,10 +135,15 @@ export default class HeaderRow extends React.Component> { } render() { + const { width, height, rowType } = this.props; + return (
{this.getCells()} From df98c3ae3653dfa8074880c42122c30b33125a05 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Mon, 23 Sep 2019 08:06:31 -0500 Subject: [PATCH 08/20] Clean up width calculation logic --- packages/react-data-grid/src/Canvas.tsx | 7 +- packages/react-data-grid/src/Cell.tsx | 2 - packages/react-data-grid/src/ColumnMetrics.ts | 3 +- packages/react-data-grid/src/Grid.tsx | 69 +++++++++---------- packages/react-data-grid/src/Header.tsx | 11 ++- packages/react-data-grid/src/HeaderRow.tsx | 4 +- .../react-data-grid/src/ReactDataGrid.tsx | 29 ++------ packages/react-data-grid/src/Viewport.tsx | 8 +-- .../react-data-grid/src/utils/domUtils.tsx | 39 ++++------- packages/react-data-grid/style/rdg-cell.less | 7 +- packages/react-data-grid/style/rdg-core.less | 17 ++--- .../react-data-grid/style/rdg-header.less | 12 ++-- .../react-data-grid/style/rdg-viewport.less | 8 --- .../style/react-data-grid.less | 1 - packages/react-data-grid/style/variables.less | 6 ++ 15 files changed, 83 insertions(+), 140 deletions(-) delete mode 100644 packages/react-data-grid/style/rdg-viewport.less create mode 100644 packages/react-data-grid/style/variables.less diff --git a/packages/react-data-grid/src/Canvas.tsx b/packages/react-data-grid/src/Canvas.tsx index 0adb862e06..154769ad76 100644 --- a/packages/react-data-grid/src/Canvas.tsx +++ b/packages/react-data-grid/src/Canvas.tsx @@ -13,7 +13,6 @@ import { ViewportProps, ScrollState } from './Viewport'; type SharedViewportProps = Pick, 'rowKey' -| 'totalWidth' | 'rowGetter' | 'rowsCount' | 'selectedRows' @@ -279,7 +278,7 @@ export default class Canvas extends React.PureComponent> { } render() { - const { rowOverscanStartIdx, rowOverscanEndIdx, cellMetaData, columns, colOverscanStartIdx, colOverscanEndIdx, colVisibleStartIdx, colVisibleEndIdx, lastFrozenColumnIndex, rowHeight, rowsCount, totalColumnWidth, totalWidth, height, rowGetter, contextMenu } = this.props; + const { rowOverscanStartIdx, rowOverscanEndIdx, cellMetaData, columns, colOverscanStartIdx, colOverscanEndIdx, colVisibleStartIdx, colVisibleEndIdx, lastFrozenColumnIndex, rowHeight, rowsCount, totalColumnWidth, height, rowGetter, contextMenu } = this.props; const RowsContainer = this.props.RowsContainer || RowsContainerDefault; const rows = this.getRows(rowOverscanStartIdx, rowOverscanEndIdx) @@ -322,7 +321,7 @@ export default class Canvas extends React.PureComponent> { return (
@@ -354,7 +353,7 @@ export default class Canvas extends React.PureComponent> { {...this.props.interactionMasksMetaData} /> -
{rows}
+
{rows}
); diff --git a/packages/react-data-grid/src/Cell.tsx b/packages/react-data-grid/src/Cell.tsx index fb5aaf1501..1d2d7dc12e 100644 --- a/packages/react-data-grid/src/Cell.tsx +++ b/packages/react-data-grid/src/Cell.tsx @@ -7,7 +7,6 @@ import CellActions from './Cell/CellActions'; import CellExpand from './Cell/CellExpander'; import CellContent from './Cell/CellContent'; import { isFrozen } from './ColumnUtils'; -import { isPositionStickySupported } from './utils'; function getSubRowOptions({ rowIdx, idx, rowData, expandableOptions: expandArgs }: CellProps): SubRowOptions { return { rowIdx, idx, rowData, expandArgs }; @@ -102,7 +101,6 @@ export default class Cell extends React.Component> implements Ce column.cellClass, 'react-grid-Cell', this.props.className, { - 'react-grid-Cell--sticky': isPositionStickySupported(), 'react-grid-Cell--frozen': isFrozen(column), 'rdg-last--frozen': lastFrozenColumnIndex === idx, 'has-tooltip': !!tooltip, diff --git a/packages/react-data-grid/src/ColumnMetrics.ts b/packages/react-data-grid/src/ColumnMetrics.ts index f9c033b483..64f57ca1d7 100644 --- a/packages/react-data-grid/src/ColumnMetrics.ts +++ b/packages/react-data-grid/src/ColumnMetrics.ts @@ -57,7 +57,8 @@ export function recalculate(metrics: Metrics): ColumnMetrics { setColumnWidths(columns, metrics.totalWidth); const width = getTotalColumnWidth(columns); - const unallocatedWidth = metrics.totalWidth - width - getScrollbarSize(); + const borderWidth = 2; + const unallocatedWidth = metrics.totalWidth - width - getScrollbarSize() - borderWidth; // compute width for columns which doesn't specify width setDefferedColumnWidths(columns, unallocatedWidth, metrics.minColumnWidth); diff --git a/packages/react-data-grid/src/Grid.tsx b/packages/react-data-grid/src/Grid.tsx index 9ceb87a565..1c133fa6c7 100644 --- a/packages/react-data-grid/src/Grid.tsx +++ b/packages/react-data-grid/src/Grid.tsx @@ -47,7 +47,6 @@ export interface GridProps extends SharedDataGridProps, SharedDataGridStat rowSelection?: RowSelection; rowOffsetHeight: number; eventBus: EventBus; - totalWidth: number | string; interactionMasksMetaData: InteractionMasksMetaData; onSort(columnKey: keyof R, sortDirection: DEFINE_SORT): void; onViewportKeydown(e: React.KeyboardEvent): void; @@ -69,14 +68,12 @@ export default function Grid({ emptyRowsView, headerRows, ...props }: GridPro } } - return ( -
+
ref={header} columnMetrics={props.columnMetrics} onColumnResize={props.onColumnResize} - totalWidth={props.totalWidth} headerRows={headerRows} rowOffsetHeight={props.rowOffsetHeight} sortColumn={props.sortColumn} @@ -92,40 +89,36 @@ export default function Grid({ emptyRowsView, headerRows, ...props }: GridPro {createElement(emptyRowsView)}
) : ( -
- - rowKey={props.rowKey} - rowHeight={props.rowHeight} - rowRenderer={props.rowRenderer} - rowGetter={props.rowGetter} - rowsCount={props.rowsCount} - selectedRows={props.selectedRows} - columnMetrics={props.columnMetrics} - totalWidth={props.totalWidth} - onScroll={onScroll} - cellMetaData={props.cellMetaData} - rowOffsetHeight={props.rowOffsetHeight} - minHeight={props.minHeight} - scrollToRowIndex={props.scrollToRowIndex} - contextMenu={props.contextMenu} - rowSelection={props.rowSelection} - getSubRowDetails={props.getSubRowDetails} - rowGroupRenderer={props.rowGroupRenderer} - enableCellSelect={props.enableCellSelect} - enableCellAutoFocus={props.enableCellAutoFocus} - cellNavigationMode={props.cellNavigationMode} - eventBus={props.eventBus} - interactionMasksMetaData={props.interactionMasksMetaData} - RowsContainer={props.RowsContainer} - editorPortalTarget={props.editorPortalTarget} - overscanRowCount={props.overscanRowCount} - overscanColumnCount={props.overscanColumnCount} - useIsScrolling={props.useIsScrolling} - /> -
+ + rowKey={props.rowKey} + rowHeight={props.rowHeight} + rowRenderer={props.rowRenderer} + rowGetter={props.rowGetter} + rowsCount={props.rowsCount} + selectedRows={props.selectedRows} + columnMetrics={props.columnMetrics} + onScroll={onScroll} + cellMetaData={props.cellMetaData} + rowOffsetHeight={props.rowOffsetHeight} + minHeight={props.minHeight} + scrollToRowIndex={props.scrollToRowIndex} + contextMenu={props.contextMenu} + rowSelection={props.rowSelection} + getSubRowDetails={props.getSubRowDetails} + rowGroupRenderer={props.rowGroupRenderer} + enableCellSelect={props.enableCellSelect} + enableCellAutoFocus={props.enableCellAutoFocus} + cellNavigationMode={props.cellNavigationMode} + eventBus={props.eventBus} + interactionMasksMetaData={props.interactionMasksMetaData} + RowsContainer={props.RowsContainer} + editorPortalTarget={props.editorPortalTarget} + overscanRowCount={props.overscanRowCount} + overscanColumnCount={props.overscanColumnCount} + useIsScrolling={props.useIsScrolling} + onViewportKeydown={props.onViewportKeydown} + onViewportKeyup={props.onViewportKeyup} + /> )}
); diff --git a/packages/react-data-grid/src/Header.tsx b/packages/react-data-grid/src/Header.tsx index 2194b92a97..05dbf923f7 100644 --- a/packages/react-data-grid/src/Header.tsx +++ b/packages/react-data-grid/src/Header.tsx @@ -10,7 +10,6 @@ import { GridProps } from './Grid'; type SharedGridProps = Pick, 'columnMetrics' | 'onColumnResize' -| 'totalWidth' | 'headerRows' | 'rowOffsetHeight' | 'sortColumn' @@ -67,10 +66,6 @@ export default class Header extends React.Component, State> getHeaderRow = (row: HeaderRowData, ref: React.RefObject>) => { const columnMetrics = this.getColumnMetrics(); - const scrollbarSize = getScrollbarSize() > 0 ? getScrollbarSize() : 0; - const updatedWidth = typeof this.props.totalWidth === 'number' - ? this.props.totalWidth - scrollbarSize - : this.props.totalWidth; return ( @@ -79,7 +74,6 @@ export default class Header extends React.Component, State> rowType={row.rowType} onColumnResize={this.onColumnResize} onColumnResizeEnd={this.onColumnResizeEnd} - width={updatedWidth} height={row.height} columns={columnMetrics.columns} draggableHeaderCell={this.props.draggableHeaderCell} @@ -136,7 +130,10 @@ export default class Header extends React.Component, State> return (
diff --git a/packages/react-data-grid/src/HeaderRow.tsx b/packages/react-data-grid/src/HeaderRow.tsx index a1656b37d9..823073b06f 100644 --- a/packages/react-data-grid/src/HeaderRow.tsx +++ b/packages/react-data-grid/src/HeaderRow.tsx @@ -18,7 +18,6 @@ type SharedHeaderProps = Pick, >; export interface HeaderRowProps extends SharedHeaderProps { - width: number | string; height: number; columns: CalculatedColumn[]; onColumnResize(column: CalculatedColumn, width: number): void; @@ -135,13 +134,12 @@ export default class HeaderRow extends React.PureComponent> } render() { - const { width, height, rowType } = this.props; + const { height, rowType } = this.props; return (
extends React.Component extends React.Component { const columnMetrics = this.createColumnMetrics(); this.setState({ columnMetrics }); @@ -666,24 +658,14 @@ export default class ReactDataGrid extends React.Component @@ -711,7 +693,6 @@ export default class ReactDataGrid extends React.Component = Pick, | 'rowsCount' | 'selectedRows' | 'columnMetrics' -| 'totalWidth' | 'cellMetaData' | 'rowOffsetHeight' | 'minHeight' @@ -47,6 +46,8 @@ type SharedGridProps = Pick, | 'overscanRowCount' | 'overscanColumnCount' | 'useIsScrolling' +| 'onViewportKeydown' +| 'onViewportKeyup' >; export interface ViewportProps extends SharedGridProps { @@ -168,15 +169,14 @@ export default function Viewport({ return (
{scrollState && ( ref={canvas} rowKey={props.rowKey} - totalWidth={props.totalWidth} width={columnMetrics.width} totalColumnWidth={columnMetrics.totalColumnWidth} columns={columnMetrics.columns} diff --git a/packages/react-data-grid/src/utils/domUtils.tsx b/packages/react-data-grid/src/utils/domUtils.tsx index 5cdad34914..798a701a80 100644 --- a/packages/react-data-grid/src/utils/domUtils.tsx +++ b/packages/react-data-grid/src/utils/domUtils.tsx @@ -1,41 +1,32 @@ let size: number | undefined; -let positionSticky: boolean | undefined; export function getScrollbarSize(): number { if (size === undefined) { - const outer = document.createElement('div'); - outer.style.width = '50px'; - outer.style.height = '50px'; - outer.style.position = 'absolute'; - outer.style.top = '-200px'; - outer.style.left = '-200px'; - - const inner = document.createElement('div'); - inner.style.height = '100px'; - inner.style.width = '100%'; - - outer.appendChild(inner); - document.body.appendChild(outer); + const scrollDiv = document.createElement('div'); - const outerWidth = outer.clientWidth; - outer.style.overflowY = 'scroll'; - const innerWidth = inner.clientWidth; + scrollDiv.style.position = 'absolute'; + scrollDiv.style.top = '-9999px'; + scrollDiv.style.width = '50px'; + scrollDiv.style.height = '50px'; + scrollDiv.style.overflow = 'scroll'; - document.body.removeChild(outer); - - size = outerWidth - innerWidth; + document.body.appendChild(scrollDiv); + size = scrollDiv.offsetWidth - scrollDiv.clientWidth; + document.body.removeChild(scrollDiv); } return size; } -export function isPositionStickySupported() { +let positionSticky: boolean | undefined; + +export function isPositionStickySupported(): boolean { if (positionSticky === undefined) { const el = document.createElement('a'); - const mStyle = el.style; - mStyle.cssText = 'position:-webkit-sticky;position:sticky'; + const { style } = el; + style.cssText = 'position:-webkit-sticky;position:sticky'; - positionSticky = mStyle.position ? mStyle.position.includes('sticky') : false; + positionSticky = style.position ? style.position.includes('sticky') : false; } return positionSticky; } diff --git a/packages/react-data-grid/style/rdg-cell.less b/packages/react-data-grid/style/rdg-cell.less index 9802a77ddd..d75f62fa55 100644 --- a/packages/react-data-grid/style/rdg-cell.less +++ b/packages/react-data-grid/style/rdg-cell.less @@ -34,12 +34,9 @@ z-index: 3 } -.react-grid-Cell--sticky { - &.react-grid-Cell { +@supports (position: sticky) { + .react-grid-Cell--frozen { display: inline-block; - } - - &.react-grid-Cell--frozen { position: -webkit-sticky; position: sticky; } diff --git a/packages/react-data-grid/style/rdg-core.less b/packages/react-data-grid/style/rdg-core.less index 963e2b0c13..dbd0022154 100644 --- a/packages/react-data-grid/style/rdg-core.less +++ b/packages/react-data-grid/style/rdg-core.less @@ -1,3 +1,5 @@ +@import './variables.less'; + .react-grid-Container { // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context // We set a stacking context so internal components don't render above external components. @@ -6,23 +8,12 @@ } .react-grid-Grid { - position: relative; - overflow: hidden; - outline: none; - clear : both; - padding: 0; - background-color: #fff; - color: inherit; border: 1px solid #ddd; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; + .user-select(); } .react-grid-Canvas { - position: absolute; - top: 0; - left: 0; + position: relative; overflow-x: auto; overflow-y: scroll; background-color: #fff; diff --git a/packages/react-data-grid/style/rdg-header.less b/packages/react-data-grid/style/rdg-header.less index fae051c0bc..9e643f5f72 100644 --- a/packages/react-data-grid/style/rdg-header.less +++ b/packages/react-data-grid/style/rdg-header.less @@ -1,21 +1,20 @@ -.user-select(@value: none) { - -webkit-user-select: @value; - -moz-user-select: @value; - -ms-user-select: @value; - user-select: @value; -} +@import './variables.less'; + .react-grid-Header { box-shadow: 0 0 4px 0 #ddd; background: #f9f9f9; } + .react-grid-Header--resizing { cursor: ew-resize; } + .react-grid-HeaderRow { position: relative; overflow: hidden; .user-select(); } + .react-grid-HeaderCell { position: absolute; padding: 8px; @@ -42,6 +41,7 @@ .react-grid-HeaderCell--frozen:last-of-type { box-shadow: 2px 0 5px -2px rgba(136, 136, 136, .3); } + .rdg-can-drop > .react-grid-HeaderCell { background: #ececec; } diff --git a/packages/react-data-grid/style/rdg-viewport.less b/packages/react-data-grid/style/rdg-viewport.less deleted file mode 100644 index f0c3da8435..0000000000 --- a/packages/react-data-grid/style/rdg-viewport.less +++ /dev/null @@ -1,8 +0,0 @@ -.rdg-viewport { - position: absolute; - left: 0; - right: 0; - bottom: 0; - padding: 0; - overflow: hidden; -} diff --git a/packages/react-data-grid/style/react-data-grid.less b/packages/react-data-grid/style/react-data-grid.less index ac08a5e0d6..ff25dee049 100644 --- a/packages/react-data-grid/style/react-data-grid.less +++ b/packages/react-data-grid/style/react-data-grid.less @@ -4,4 +4,3 @@ @import 'rdg-header.less'; @import 'rdg-interaction-masks.less'; @import 'rdg-row.less'; -@import 'rdg-viewport.less'; diff --git a/packages/react-data-grid/style/variables.less b/packages/react-data-grid/style/variables.less new file mode 100644 index 0000000000..388b69a3e8 --- /dev/null +++ b/packages/react-data-grid/style/variables.less @@ -0,0 +1,6 @@ +.user-select(@value: none) { + -webkit-user-select: @value; + -moz-user-select: @value; + -ms-user-select: @value; + user-select: @value; +} From cb8089d16f4dd1fb39de39fb63457abb1e27562e Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Mon, 23 Sep 2019 08:11:09 -0500 Subject: [PATCH 09/20] useIsScrolling -> enableIsScrolling --- examples/scripts/example31-isScrolling.js | 2 +- packages/react-data-grid/src/Grid.tsx | 4 ++-- packages/react-data-grid/src/ReactDataGrid.tsx | 4 ++-- packages/react-data-grid/src/Viewport.tsx | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/scripts/example31-isScrolling.js b/examples/scripts/example31-isScrolling.js index 972be13717..6547f7ac64 100644 --- a/examples/scripts/example31-isScrolling.js +++ b/examples/scripts/example31-isScrolling.js @@ -61,7 +61,7 @@ class Example extends React.Component { rowsCount={this.state.rows.length} minHeight={800} onScroll={this.onScroll} - useIsScrolling + enableIsScrolling />
); diff --git a/packages/react-data-grid/src/Grid.tsx b/packages/react-data-grid/src/Grid.tsx index 1c133fa6c7..8304e60068 100644 --- a/packages/react-data-grid/src/Grid.tsx +++ b/packages/react-data-grid/src/Grid.tsx @@ -31,7 +31,7 @@ type SharedDataGridProps = Pick, | 'editorPortalTarget' | 'overscanRowCount' | 'overscanColumnCount' -| 'useIsScrolling' +| 'enableIsScrolling' >; type SharedDataGridState = Pick, @@ -115,7 +115,7 @@ export default function Grid({ emptyRowsView, headerRows, ...props }: GridPro editorPortalTarget={props.editorPortalTarget} overscanRowCount={props.overscanRowCount} overscanColumnCount={props.overscanColumnCount} - useIsScrolling={props.useIsScrolling} + enableIsScrolling={props.enableIsScrolling} onViewportKeydown={props.onViewportKeydown} onViewportKeyup={props.onViewportKeyup} /> diff --git a/packages/react-data-grid/src/ReactDataGrid.tsx b/packages/react-data-grid/src/ReactDataGrid.tsx index 996957ca49..05af2435b6 100644 --- a/packages/react-data-grid/src/ReactDataGrid.tsx +++ b/packages/react-data-grid/src/ReactDataGrid.tsx @@ -159,7 +159,7 @@ export interface DataGridProps { overscanRowCount?: number; overscanColumnCount?: number; - useIsScrolling?: boolean; + enableIsScrolling?: boolean; } type DefaultProps = Pick, @@ -711,7 +711,7 @@ export default class ReactDataGrid extends React.Component
); diff --git a/packages/react-data-grid/src/Viewport.tsx b/packages/react-data-grid/src/Viewport.tsx index 40deba697a..cf41285c2b 100644 --- a/packages/react-data-grid/src/Viewport.tsx +++ b/packages/react-data-grid/src/Viewport.tsx @@ -45,7 +45,7 @@ type SharedGridProps = Pick, | 'editorPortalTarget' | 'overscanRowCount' | 'overscanColumnCount' -| 'useIsScrolling' +| 'enableIsScrolling' | 'onViewportKeydown' | 'onViewportKeyup' >; @@ -63,7 +63,7 @@ export default function Viewport({ columnMetrics, overscanRowCount, overscanColumnCount, - useIsScrolling, + enableIsScrolling, ...props }: ViewportProps) { const canvas = useRef>(null); @@ -99,7 +99,7 @@ export default function Viewport({ } function onScroll({ scrollLeft, scrollTop }: ScrollPosition) { - if (useIsScrolling) { + if (enableIsScrolling) { setIsScrolling(true); resetScrollStateAfterDelay(); } From ad93bc42229bcec885268616cd0604bbc2c28b5b Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Mon, 23 Sep 2019 10:03:01 -0500 Subject: [PATCH 10/20] Add return types --- .../src/utils/viewportUtils.ts | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/react-data-grid/src/utils/viewportUtils.ts b/packages/react-data-grid/src/utils/viewportUtils.ts index 9ab6ee22b7..83629ead23 100644 --- a/packages/react-data-grid/src/utils/viewportUtils.ts +++ b/packages/react-data-grid/src/utils/viewportUtils.ts @@ -1,8 +1,6 @@ import { SCROLL_DIRECTION } from '../common/enums'; import { CalculatedColumn, ScrollPosition, ColumnMetrics } from '../common/types'; -const { min, max, ceil, round } = Math; - function getTotalFrozenColumnWidth(columns: CalculatedColumn[], lastFrozenColumnIndex: number): number { if (lastFrozenColumnIndex === -1) { return 0; @@ -36,6 +34,13 @@ interface VerticalRangeToRenderParams { overscanRowCount?: number; } +interface VerticalRangeToRender { + rowVisibleStartIdx: number; + rowVisibleEndIdx: number; + rowOverscanStartIdx: number; + rowOverscanEndIdx: number; +} + export function getVerticalRangeToRender({ height, rowHeight, @@ -43,16 +48,23 @@ export function getVerticalRangeToRender({ rowsCount, scrollDirection, overscanRowCount = 2 -}: VerticalRangeToRenderParams) { - const renderedRowsCount = ceil(height / rowHeight); - const rowVisibleStartIdx = max(0, round(scrollTop / rowHeight)); - const rowVisibleEndIdx = min(rowsCount, rowVisibleStartIdx + renderedRowsCount); - const rowOverscanStartIdx = max(0, rowVisibleStartIdx - (scrollDirection === SCROLL_DIRECTION.UP ? overscanRowCount : 2)); - const rowOverscanEndIdx = min(rowsCount, rowVisibleEndIdx + (scrollDirection === SCROLL_DIRECTION.DOWN ? overscanRowCount : 2)); +}: VerticalRangeToRenderParams): VerticalRangeToRender { + const renderedRowsCount = Math.ceil(height / rowHeight); + const rowVisibleStartIdx = Math.max(0, Math.round(scrollTop / rowHeight)); + const rowVisibleEndIdx = Math.min(rowsCount, rowVisibleStartIdx + renderedRowsCount); + const rowOverscanStartIdx = Math.max(0, rowVisibleStartIdx - (scrollDirection === SCROLL_DIRECTION.UP ? overscanRowCount : 2)); + const rowOverscanEndIdx = Math.min(rowsCount, rowVisibleEndIdx + (scrollDirection === SCROLL_DIRECTION.DOWN ? overscanRowCount : 2)); return { rowVisibleStartIdx, rowVisibleEndIdx, rowOverscanStartIdx, rowOverscanEndIdx }; } +interface HorizontalRangeToRender { + colVisibleStartIdx: number; + colVisibleEndIdx: number; + colOverscanStartIdx: number; + colOverscanEndIdx: number; +} + interface HorizontalRangeToRenderParams { columnMetrics: ColumnMetrics; viewportWidth: number; @@ -67,7 +79,7 @@ export function getHorizontalRangeToRender({ viewportWidth, scrollDirection, overscanColumnCount = 2 -}: HorizontalRangeToRenderParams) { +}: HorizontalRangeToRenderParams): HorizontalRangeToRender { const { columns, totalColumnWidth, lastFrozenColumnIndex } = columnMetrics; let remainingScroll = scrollLeft; @@ -77,15 +89,15 @@ export function getHorizontalRangeToRender({ const column = columns[columnIndex]; remainingScroll -= column ? column.width : 0; } - const colVisibleStartIdx = max(columnIndex, 0); + const colVisibleStartIdx = Math.max(columnIndex, 0); const totalFrozenColumnWidth = getTotalFrozenColumnWidth(columns, lastFrozenColumnIndex); viewportWidth = viewportWidth > 0 ? viewportWidth : totalColumnWidth; const availableWidth = viewportWidth - totalFrozenColumnWidth; const nonFrozenRenderedColumnCount = getColumnCountForWidth(columns, availableWidth, colVisibleStartIdx); - const colVisibleEndIdx = min(columns.length, colVisibleStartIdx + nonFrozenRenderedColumnCount); - const colOverscanStartIdx = max(0, colVisibleStartIdx - (scrollDirection === SCROLL_DIRECTION.LEFT ? overscanColumnCount : 2)); - const colOverscanEndIdx = min(columns.length, colVisibleEndIdx + (scrollDirection === SCROLL_DIRECTION.RIGHT ? overscanColumnCount : 2)); + const colVisibleEndIdx = Math.min(columns.length, colVisibleStartIdx + nonFrozenRenderedColumnCount); + const colOverscanStartIdx = Math.max(0, colVisibleStartIdx - (scrollDirection === SCROLL_DIRECTION.LEFT ? overscanColumnCount : 2)); + const colOverscanEndIdx = Math.min(columns.length, colVisibleEndIdx + (scrollDirection === SCROLL_DIRECTION.RIGHT ? overscanColumnCount : 2)); return { colVisibleStartIdx, colVisibleEndIdx, colOverscanStartIdx, colOverscanEndIdx }; } From 691f39dfe224ae27f29e0215ef4078da729515bf Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Mon, 23 Sep 2019 10:07:34 -0500 Subject: [PATCH 11/20] Add some comments --- packages/react-data-grid/src/ReactDataGrid.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/react-data-grid/src/ReactDataGrid.tsx b/packages/react-data-grid/src/ReactDataGrid.tsx index 05af2435b6..abb7c17639 100644 --- a/packages/react-data-grid/src/ReactDataGrid.tsx +++ b/packages/react-data-grid/src/ReactDataGrid.tsx @@ -156,9 +156,20 @@ export interface DataGridProps { onCellDeSelected?(position: Position): void; /** called before cell is set active, returns a boolean to determine whether cell is editable */ onCheckCellIsEditable?(event: CheckCellIsEditableEvent): boolean; - + /** + * The number of rows to render outside of the visible area + * Note that overscanning too much can negatively impact performance. By default, grid overscans by two items. + */ overscanRowCount?: number; + /** + * The number of columns to render outside of the visible area + * Note that overscanning too much can negatively impact performance. By default, grid overscans by two items. + */ overscanColumnCount?: number; + /** + * Provides an additional isScrolling parameter to formatters. This parameter can be used to show a placeholder row or column while the list is being scrolled. + * Note that using this parameter will result in an additional render call after scrolling has stopped (when isScrolling changes from true to false). + */ enableIsScrolling?: boolean; } From bce8b2def888887be73faf1d663ea5d6514d60e5 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Mon, 23 Sep 2019 10:15:17 -0500 Subject: [PATCH 12/20] Fix transform style --- packages/react-data-grid/src/Cell.tsx | 2 +- packages/react-data-grid/src/HeaderCell.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-data-grid/src/Cell.tsx b/packages/react-data-grid/src/Cell.tsx index 1d2d7dc12e..09fdf41b2c 100644 --- a/packages/react-data-grid/src/Cell.tsx +++ b/packages/react-data-grid/src/Cell.tsx @@ -119,7 +119,7 @@ export default class Cell extends React.Component> implements Ce removeScroll() { const node = this.cell.current; if (node) { - node.style.transform = null; + node.style.transform = 'none'; } } diff --git a/packages/react-data-grid/src/HeaderCell.tsx b/packages/react-data-grid/src/HeaderCell.tsx index ad2c8bbda8..7d905a1df6 100644 --- a/packages/react-data-grid/src/HeaderCell.tsx +++ b/packages/react-data-grid/src/HeaderCell.tsx @@ -130,7 +130,7 @@ export default class HeaderCell extends React.Component> { removeScroll() { const node = this.cell.current; if (node) { - node.style.transform = null; + node.style.transform = 'none'; } } From da20e0e0689c3ff52795e1538d760e8724695157 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Mon, 23 Sep 2019 12:01:43 -0500 Subject: [PATCH 13/20] Check for null --- packages/react-data-grid/src/Grid.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-data-grid/src/Grid.tsx b/packages/react-data-grid/src/Grid.tsx index 8304e60068..1c9b900e78 100644 --- a/packages/react-data-grid/src/Grid.tsx +++ b/packages/react-data-grid/src/Grid.tsx @@ -59,9 +59,9 @@ export default function Grid({ emptyRowsView, headerRows, ...props }: GridPro const scrollLeft = useRef(0); function onScroll(scrollState: ScrollState) { - if (scrollLeft.current !== scrollState.scrollLeft) { + if (header.current && scrollLeft.current !== scrollState.scrollLeft) { scrollLeft.current = scrollState.scrollLeft; - header.current!.setScrollLeft(scrollState.scrollLeft); + header.current.setScrollLeft(scrollState.scrollLeft); } if (props.onScroll) { props.onScroll(scrollState); From 925afc5e335c8c584d45ea5ce2cb225a2666f913 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Tue, 24 Sep 2019 14:58:36 -0500 Subject: [PATCH 14/20] Fix unit tests --- examples/__tests__/Grid.spec.js | 35 -- package.json | 2 +- .../src/__tests__/Grid.spec.js | 51 +- .../src/__tests__/data/MockStateObject.js | 3 +- .../src/__tests__/Canvas.spec.tsx | 5 +- .../src/__tests__/Cell.spec.tsx | 21 +- .../src/__tests__/ColumnMetrics.spec.ts | 2 +- .../src/__tests__/Grid.spec.js | 5 - .../src/__tests__/Header.spec.tsx | 38 +- .../src/__tests__/HeaderRow.spec.tsx | 31 +- .../src/__tests__/Row.spec.tsx | 7 +- .../src/__tests__/Viewport.spec.tsx | 176 ------- .../utils/__tests__/RowComparer.spec.ts | 3 +- .../src/utils/__tests__/viewportUtils.spec.ts | 449 +++++------------- .../src/utils/viewportUtils.ts | 6 +- 15 files changed, 201 insertions(+), 633 deletions(-) delete mode 100644 examples/__tests__/Grid.spec.js delete mode 100644 packages/react-data-grid/src/__tests__/Viewport.spec.tsx diff --git a/examples/__tests__/Grid.spec.js b/examples/__tests__/Grid.spec.js deleted file mode 100644 index b722b58358..0000000000 --- a/examples/__tests__/Grid.spec.js +++ /dev/null @@ -1,35 +0,0 @@ -import TestUtils from 'react-dom/test-utils'; -import GridRunner from './GridRunner'; - -describe('Grid Integration', () => { - let gridRunner; - let grid; - - beforeEach(() => { - gridRunner = new GridRunner({}); - grid = gridRunner.grid; - }); - - afterEach(() => { - gridRunner.dispose(); - gridRunner = null; - grid = null; - }); - - describe('Setup', () => { - it('Creates the grid', () => { - expect(grid).toBeDefined(); - }); - - it('Renders the grid', () => { - TestUtils.isDOMComponent(grid); - }); - - it('Renders the expected number of rows', () => { - const { rowOverscanStartIdx, rowOverscanEndIdx } = gridRunner.getDisplayInfo(); - const expectedNumberOfRows = rowOverscanEndIdx - rowOverscanStartIdx; - - expect(gridRunner.getRenderedRows().length).toEqual(expectedNumberOfRows); - }); - }); -}); diff --git a/package.json b/package.json index dd880f2f2c..bfdcfabdc7 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "scripts": { "docs": "node docs/utils/docsGen.js", "test": "jest", - "test-dev": "jest --watch", + "test:watch": "jest --watch", "postinstall": "lerna bootstrap --no-ci", "prebuild": "node tools/buildStylesheets.js", "build": "lerna run build", diff --git a/packages/react-data-grid-addons/src/__tests__/Grid.spec.js b/packages/react-data-grid-addons/src/__tests__/Grid.spec.js index 8b987d373e..f9620704c2 100644 --- a/packages/react-data-grid-addons/src/__tests__/Grid.spec.js +++ b/packages/react-data-grid-addons/src/__tests__/Grid.spec.js @@ -45,7 +45,7 @@ describe('Grid', () => { }; const getBaseGrid = (wrapper) => { - return wrapper.instance().base.current; + return wrapper.find('Grid'); }; const buildFakeEvent = (addedData) => { @@ -57,7 +57,7 @@ describe('Grid', () => { }; const simulateGridKeyDownWithKeyCode = (wrapper, keyCode) => { - getBaseGrid(wrapper).props.onViewportKeydown(buildFakeEvent({ keyCode })); + getBaseGrid(wrapper).props().onViewportKeydown(buildFakeEvent({ keyCode })); }; it('should create a new instance of Grid', () => { @@ -87,18 +87,19 @@ describe('Grid', () => { beforeEach(() => { wrapper = setup({ toolbar: }).wrapper; wrapper.find(ToolBarStub).props().onToggleFilter(); + wrapper.update(); }); it('uses the appropriate default for the grid row height', () => { - expect(getBaseGrid(wrapper).props.rowHeight).toEqual(35); + expect(getBaseGrid(wrapper).props().rowHeight).toEqual(35); }); it('uses the appropriate default for the header row height', () => { - expect(getBaseGrid(wrapper).props.headerRows[0].height).toEqual(35); + expect(getBaseGrid(wrapper).props().headerRows[0].height).toEqual(35); }); it('uses the appropriate default for the header filter row height', () => { - expect(getBaseGrid(wrapper).props.headerRows[1].height).toEqual(45); + expect(getBaseGrid(wrapper).props().headerRows[1].height).toEqual(45); }); }); @@ -108,18 +109,19 @@ describe('Grid', () => { beforeEach(() => { wrapper = setup({ toolbar: , rowHeight: 40 }).wrapper; wrapper.find(ToolBarStub).props().onToggleFilter(); + wrapper.update(); }); it('passes the correct heigth to the grid rows', () => { - expect(getBaseGrid(wrapper).props.rowHeight).toEqual(40); + expect(getBaseGrid(wrapper).props().rowHeight).toEqual(40); }); it('passes the grid row heigth to the header row when no height to the specific header row is provided', () => { - expect(getBaseGrid(wrapper).props.headerRows[0].height).toEqual(40); + expect(getBaseGrid(wrapper).props().headerRows[0].height).toEqual(40); }); it('uses the default prop height for the filter row when none is provided', () => { - expect(getBaseGrid(wrapper).props.headerRows[1].height).toEqual(45); + expect(getBaseGrid(wrapper).props().headerRows[1].height).toEqual(45); }); }); @@ -134,18 +136,19 @@ describe('Grid', () => { headerFiltersHeight: 60 }).wrapper; wrapper.find(ToolBarStub).props().onToggleFilter(); + wrapper.update(); }); it('passes the correct heigth to the grid rows', () => { - expect(getBaseGrid(wrapper).props.rowHeight).toEqual(40); + expect(getBaseGrid(wrapper).props().rowHeight).toEqual(40); }); it('passes the correct heigth to the header row', () => { - expect(getBaseGrid(wrapper).props.headerRows[0].height).toEqual(50); + expect(getBaseGrid(wrapper).props().headerRows[0].height).toEqual(50); }); it('passes the correct heigth to the header filter row', () => { - expect(getBaseGrid(wrapper).props.headerRows[1].height).toEqual(60); + expect(getBaseGrid(wrapper).props().headerRows[1].height).toEqual(60); }); }); }); @@ -169,7 +172,7 @@ describe('Grid', () => { it('should set filter state of grid and render a filterable header row', () => { expect(wrapper.instance().state.canFilter).toBe(true); - expect(getBaseGrid(wrapper).props.headerRows.length).toEqual(2); + expect(getBaseGrid(wrapper).props().headerRows.length).toEqual(2); }); }); }); @@ -182,11 +185,11 @@ describe('Grid', () => { beforeEach(() => { ({ wrapper, columns, rows } = setup({ enableRowSelect: true })); - selectRowCol = getBaseGrid(wrapper).props.columnMetrics.columns[0]; + selectRowCol = getBaseGrid(wrapper).props().columnMetrics.columns[0]; }); it('should render an additional Select Row column', () => { - expect(getBaseGrid(wrapper).props.columnMetrics.columns.length).toEqual(columns.length + 1); + expect(getBaseGrid(wrapper).props().columnMetrics.columns.length).toEqual(columns.length + 1); expect(selectRowCol.key).toEqual('select-row'); expect(TestUtils.isElementOfType(selectRowCol.formatter, CheckboxEditor)).toBe(true); }); @@ -232,7 +235,7 @@ describe('Grid', () => { describe('when selected is false', () => { beforeEach(() => { wrapper.instance().setState({ selectedRows: [{ id: 0, isSelected: false }, { id: 1, isSelected: false }, { id: 2, isSelected: false }, { id: 3, isSelected: false }] }); - const selectRowCol = getBaseGrid(wrapper).props.columnMetrics.columns[0]; + const selectRowCol = getBaseGrid(wrapper).props().columnMetrics.columns[0]; selectRowCol.onCellChange(3, 'select-row', rows[3], buildFakeEvent()); }); @@ -244,7 +247,7 @@ describe('Grid', () => { describe('when selected is null', () => { beforeEach(() => { wrapper.instance().setState({ selectedRows: [{ id: 0, isSelected: null }, { id: 1, isSelected: null }, { id: 2, isSelected: null }, { id: 3, isSelected: null }] }); - const selectRowCol = getBaseGrid(wrapper).props.columnMetrics.columns[0]; + const selectRowCol = getBaseGrid(wrapper).props().columnMetrics.columns[0]; selectRowCol.onCellChange(2, 'select-row', rows[2], buildFakeEvent()); }); @@ -256,7 +259,7 @@ describe('Grid', () => { describe('when selected is true', () => { beforeEach(() => { wrapper.instance().setState({ selectedRows: [{ id: 0, isSelected: null }, { id: 1, isSelected: true }, { id: 2, isSelected: true }, { id: 3, isSelected: true }] }); - const selectRowCol = getBaseGrid(wrapper).props.columnMetrics.columns[0]; + const selectRowCol = getBaseGrid(wrapper).props().columnMetrics.columns[0]; selectRowCol.onCellChange(3, 'select-row', rows[3], buildFakeEvent()); }); @@ -292,7 +295,7 @@ describe('Grid', () => { } } }).wrapper; - selectRowCol = getBaseGrid(wrapper).props.columnMetrics.columns[0]; + selectRowCol = getBaseGrid(wrapper).props().columnMetrics.columns[0]; }); it('should call rowSelection.onRowsSelected when row selected', () => { @@ -348,7 +351,7 @@ describe('Grid', () => { } }); - const selectRowCol = getBaseGrid(wrapper).props.columnMetrics.columns[0]; + const selectRowCol = getBaseGrid(wrapper).props().columnMetrics.columns[0]; // header checkbox const checkboxWrapper = document.createElement('div'); @@ -381,7 +384,7 @@ describe('Grid', () => { } }); - const selectRowCol = getBaseGrid(wrapper).props.columnMetrics.columns[0]; + const selectRowCol = getBaseGrid(wrapper).props().columnMetrics.columns[0]; // header checkbox const checkboxWrapper = document.createElement('div'); @@ -417,12 +420,6 @@ describe('Grid', () => { }); describe('Table width', () => { - it('should generate the width based on the container size', () => { - const { wrapper } = setup(); - const tableElement = ReactDOM.findDOMNode(wrapper.instance()); - expect(tableElement.style.width).toEqual('100%'); - }); - describe('providing table width as prop', () => { it('should set the width of the table', () => { const { wrapper } = setup(); @@ -451,7 +448,7 @@ describe('Grid', () => { } }); - getBaseGrid(wrapper).props.cellMetaData.onCellClick({ idx: 1, rowIdx: 1 }); + getBaseGrid(wrapper).props().cellMetaData.onCellClick({ idx: 1, rowIdx: 1 }); expect(rowClicks).toBe(1); const { row, column } = rowClicked; expect(row).toMatchObject(rows[1]); diff --git a/packages/react-data-grid-addons/src/__tests__/data/MockStateObject.js b/packages/react-data-grid-addons/src/__tests__/data/MockStateObject.js index 89e39e7316..b26f1363bb 100644 --- a/packages/react-data-grid-addons/src/__tests__/data/MockStateObject.js +++ b/packages/react-data-grid-addons/src/__tests__/data/MockStateObject.js @@ -31,7 +31,8 @@ export default function(stateValues, events) { width: 400, totalWidth: 0, totalColumnWidth: 400, - minColumnWidth: 80 + minColumnWidth: 80, + lastFrozenColumnIndex: -1 }, selectedRows: [], canFilter: false, diff --git a/packages/react-data-grid/src/__tests__/Canvas.spec.tsx b/packages/react-data-grid/src/__tests__/Canvas.spec.tsx index 0d2c3b4470..6b45f39dd3 100644 --- a/packages/react-data-grid/src/__tests__/Canvas.spec.tsx +++ b/packages/react-data-grid/src/__tests__/Canvas.spec.tsx @@ -53,11 +53,12 @@ const testProps: CanvasProps = { eventBus: new EventBus(), editorPortalTarget: document.body, width: 1000, - totalWidth: 1000, totalColumnWidth: 1000, onScroll() { }, lastFrozenColumnIndex: 0, - RowsContainer: RowsContainer as CanvasProps['RowsContainer'] + RowsContainer: RowsContainer as CanvasProps['RowsContainer'], + scrollTop: 0, + scrollLeft: 0 }; function renderComponent(extraProps?: Partial>) { diff --git a/packages/react-data-grid/src/__tests__/Cell.spec.tsx b/packages/react-data-grid/src/__tests__/Cell.spec.tsx index 16fa658e8c..32f096838e 100644 --- a/packages/react-data-grid/src/__tests__/Cell.spec.tsx +++ b/packages/react-data-grid/src/__tests__/Cell.spec.tsx @@ -1,7 +1,7 @@ import React, { PropsWithChildren } from 'react'; import { mount } from 'enzyme'; -import Cell, { Props } from '../Cell'; +import Cell, { CellProps } from '../Cell'; import helpers, { Row } from '../helpers/test/GridPropHelpers'; import CellAction from '../Cell/CellAction'; import { SimpleCellFormatter } from '../formatters'; @@ -36,7 +36,7 @@ const expandableOptions = { const defaultColumn: CalculatedColumn = { idx: 0, key: 'description', name: 'Desciption', width: 100, left: 0 }; -const testProps: Props = { +const testProps: CellProps = { rowIdx: 0, idx: 1, column: defaultColumn, @@ -45,10 +45,11 @@ const testProps: Props = { rowData: { id: 1, description: 'Wicklow' }, height: 40, isScrolling: false, - scrollLeft: 0 + scrollLeft: 0, + lastFrozenColumnIndex: -1 }; -const renderComponent = (extraProps?: PropsWithChildren>>) => { +const renderComponent = (extraProps?: PropsWithChildren>>) => { return mount( {...testProps} {...extraProps} />); }; @@ -78,11 +79,11 @@ describe('Cell Tests', () => { }); describe('Rendering Cell component', () => { - function shallowRenderComponent(props: Props) { + function shallowRenderComponent(props: CellProps) { return mount( {...props} />); } - const requiredProperties: Props = { + const requiredProperties: CellProps = { rowIdx: 18, idx: 19, height: 60, @@ -92,7 +93,8 @@ describe('Cell Tests', () => { rowData: helpers.rowGetter(11), expandableOptions, isScrolling: false, - scrollLeft: 0 + scrollLeft: 0, + lastFrozenColumnIndex: -1 }; it('passes classname property', () => { @@ -113,8 +115,8 @@ describe('Cell Tests', () => { }); describe('CellActions', () => { - const setup = (propsOverride: Partial> = {}) => { - const props: Props = { + const setup = (propsOverride: Partial> = {}) => { + const props: CellProps = { rowIdx: 18, idx: 19, height: 60, @@ -125,6 +127,7 @@ describe('Cell Tests', () => { expandableOptions, isScrolling: false, scrollLeft: 0, + lastFrozenColumnIndex: -1, ...propsOverride }; diff --git a/packages/react-data-grid/src/__tests__/ColumnMetrics.spec.ts b/packages/react-data-grid/src/__tests__/ColumnMetrics.spec.ts index ae52889b0c..b82169bd1c 100644 --- a/packages/react-data-grid/src/__tests__/ColumnMetrics.spec.ts +++ b/packages/react-data-grid/src/__tests__/ColumnMetrics.spec.ts @@ -12,7 +12,7 @@ interface Row { } function getAvailableWidthPerColumn(totalWidth: number, consumedWidth: number, numberOfcolumns: number): number { - let availableWidth = totalWidth - getScrollbarSize() - consumedWidth; + let availableWidth = totalWidth - getScrollbarSize() - consumedWidth - 2; // 2 for border width availableWidth = availableWidth % numberOfcolumns === 0 ? availableWidth : availableWidth - 1; return availableWidth / numberOfcolumns; diff --git a/packages/react-data-grid/src/__tests__/Grid.spec.js b/packages/react-data-grid/src/__tests__/Grid.spec.js index ffa18788c9..cf189452a2 100644 --- a/packages/react-data-grid/src/__tests__/Grid.spec.js +++ b/packages/react-data-grid/src/__tests__/Grid.spec.js @@ -103,11 +103,6 @@ describe('Rendering Grid component', () => { const draggableDiv = wrapper.find('div').at(0); expect(draggableDiv.hasClass('react-grid-Grid')); }); - it('passes style property', () => { - const wrapper = renderComponent(allProperties()); - const draggableDiv = wrapper.find('div').at(0); - expect(draggableDiv.props().style).toBeDefined(); - }); it('does not pass unknown properties to the div', () => { const wrapper = renderComponent(allProperties()); const draggableDiv = wrapper.find('div').at(0); diff --git a/packages/react-data-grid/src/__tests__/Header.spec.tsx b/packages/react-data-grid/src/__tests__/Header.spec.tsx index 30037feb78..7ba678c2c7 100644 --- a/packages/react-data-grid/src/__tests__/Header.spec.tsx +++ b/packages/react-data-grid/src/__tests__/Header.spec.tsx @@ -4,7 +4,7 @@ import { shallow } from 'enzyme'; import Header, { HeaderProps } from '../Header'; import HeaderRow from '../HeaderRow'; import helpers, { fakeCellMetaData, Row } from '../helpers/test/GridPropHelpers'; -import * as GetScrollbarSize from '../utils'; +import * as utils from '../utils'; import { HeaderRowType, DEFINE_SORT } from '../common/enums'; const SCROLL_BAR_SIZE = 17; @@ -17,25 +17,25 @@ describe('Header Unit Tests', () => { minColumnWidth: 80, totalColumnWidth: 2600, totalWidth: 2600, - width: 2600 + width: 2600, + lastFrozenColumnIndex: 0 }, cellMetaData: fakeCellMetaData, - totalWidth: 1000, - rowHeight: 50, headerRows: [{ height: 50, rowType: HeaderRowType.HEADER, onFilterChange() { } - }], + }, undefined], onColumnResize: jest.fn(), onSort: () => null, onHeaderDrop() { }, - draggableHeaderCell: () => null + draggableHeaderCell: () => null, + rowOffsetHeight: 30 }; } beforeEach(() => { - jest.spyOn(GetScrollbarSize, 'getScrollbarSize').mockReturnValue(SCROLL_BAR_SIZE); + jest.spyOn(utils, 'getScrollbarSize').mockReturnValue(SCROLL_BAR_SIZE); }); it('should render a default header row', () => { @@ -68,20 +68,20 @@ describe('Header Unit Tests', () => { minColumnWidth: 81, totalColumnWidth: 2600, totalWidth: 2600, - width: 2601 + width: 2601, + lastFrozenColumnIndex: 0 }, - rowHeight: 51, - totalWidth: 2600, headerRows: [{ height: 51, rowType: HeaderRowType.HEADER, onFilterChange() { } - }], + }, undefined], onSort: jest.fn(), onHeaderDrop() { }, cellMetaData: fakeCellMetaData, draggableHeaderCell: () => null, - onColumnResize() { } + onColumnResize() { }, + rowOffsetHeight: 30 }; const testAllProps: HeaderProps = { columnMetrics: { @@ -89,15 +89,14 @@ describe('Header Unit Tests', () => { minColumnWidth: 80, totalColumnWidth: 2600, totalWidth: 2600, - width: 2600 + width: 2600, + lastFrozenColumnIndex: 0 }, - totalWidth: 1000, - rowHeight: 50, headerRows: [{ height: 50, rowType: HeaderRowType.HEADER, onFilterChange() { } - }], + }, undefined], sortColumn: 'count', sortDirection: DEFINE_SORT.DESC, onSort: jest.fn(), @@ -105,7 +104,8 @@ describe('Header Unit Tests', () => { draggableHeaderCell: jest.fn(), getValidFilterValues: jest.fn(), cellMetaData: fakeCellMetaData, - onHeaderDrop() { } + onHeaderDrop() { }, + rowOffsetHeight: 30 }; it('passes classname property', () => { const wrapper = renderComponent(testAllProps); @@ -120,8 +120,8 @@ describe('Header Unit Tests', () => { it('should account for scrollbar size in header', () => { const wrapper = renderComponent(testAllProps); - const headerRow = wrapper.find('.react-grid-Header').childAt(0); - expect(headerRow.props().style.width).toBe(testAllProps.totalWidth as number - SCROLL_BAR_SIZE); + const headerDiv = wrapper.find('div'); + expect(headerDiv.props().style!.paddingRight).toBe(SCROLL_BAR_SIZE); }); it('does not pass onScroll properties if it is not available from props', () => { const wrapper = renderComponent(testRequiredProps); diff --git a/packages/react-data-grid/src/__tests__/HeaderRow.spec.tsx b/packages/react-data-grid/src/__tests__/HeaderRow.spec.tsx index 035704a846..0a48bd62ef 100644 --- a/packages/react-data-grid/src/__tests__/HeaderRow.spec.tsx +++ b/packages/react-data-grid/src/__tests__/HeaderRow.spec.tsx @@ -139,28 +139,6 @@ describe('Header Row Unit Tests', () => { draggableHeaderCell: () =>
}; - const allProperties: HeaderRowProps = { - height: 35, - columns: helpers.columns, - onSort: jest.fn(), - rowType: HeaderRowType.HEADER, - onColumnResize: jest.fn(), - onColumnResizeEnd: jest.fn(), - width: 200, - style: { - overflow: 'scroll', - width: 201, - height: 36, - position: 'relative' - }, - sortColumn: 'count', - sortDirection: DEFINE_SORT.NONE, - filterable: true, - onFilterChange() { }, - onHeaderDrop() { }, - draggableHeaderCell: () =>
- }; - it('passes classname property', () => { const wrapper = renderComponent(requiredProps); const headerRowDiv = wrapper.find('div').at(0); @@ -171,15 +149,10 @@ describe('Header Row Unit Tests', () => { const headerRowDiv = wrapper.find('div').at(0); expect(headerRowDiv.props().width).toBeUndefined(); }); - it('passes style property, if available from props', () => { - const wrapper = renderComponent(allProperties); - const headerRowDiv = wrapper.find('div').at(0); - expect(headerRowDiv.props().style).toBe(allProperties.style); - }); - it('does not pass style if not available from props', () => { + it('does pass the height if available from props', () => { const wrapper = renderComponent(requiredProps); const headerRowDiv = wrapper.find('div').at(0); - expect(headerRowDiv.props().style).toBeUndefined(); + expect(headerRowDiv.props().style).toEqual({ height: 35 }); }); }); }); diff --git a/packages/react-data-grid/src/__tests__/Row.spec.tsx b/packages/react-data-grid/src/__tests__/Row.spec.tsx index 9a12449269..22279d6d8e 100644 --- a/packages/react-data-grid/src/__tests__/Row.spec.tsx +++ b/packages/react-data-grid/src/__tests__/Row.spec.tsx @@ -47,7 +47,8 @@ describe('Row', () => { colOverscanStartIdx: 0, colOverscanEndIdx: 20, isScrolling: true, - scrollLeft: 0 + scrollLeft: 0, + lastFrozenColumnIndex: -1 }; it('passes classname property', () => { @@ -73,7 +74,7 @@ describe('Row', () => { it('should render all frozen and visible and overscan cells', () => { const columns = lockColumns(); - const { cells } = setup({ ...requiredProperties, columns }); + const { cells } = setup({ ...requiredProperties, columns, lastFrozenColumnIndex: LAST_LOCKED_CELL_IDX }); const { colOverscanStartIdx, colOverscanEndIdx } = requiredProperties; const renderedRange = colOverscanEndIdx - colOverscanStartIdx + 1; expect(cells.length).toBe(renderedRange); @@ -81,7 +82,7 @@ describe('Row', () => { it('first frozen cell should be rendered after the unfrozen cells', () => { const columns = lockColumns(); - const { cells } = setup({ ...requiredProperties, columns }); + const { cells } = setup({ ...requiredProperties, columns, lastFrozenColumnIndex: LAST_LOCKED_CELL_IDX }); const firstFrozenColumn = columns.filter(c => c.frozen === true)[0]; expect(cells.at(cells.length - LAST_LOCKED_CELL_IDX - 1).props().column).toBe(firstFrozenColumn); }); diff --git a/packages/react-data-grid/src/__tests__/Viewport.spec.tsx b/packages/react-data-grid/src/__tests__/Viewport.spec.tsx deleted file mode 100644 index 0ac36152ee..0000000000 --- a/packages/react-data-grid/src/__tests__/Viewport.spec.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; - -import Viewport, { ViewportProps } from '../Viewport'; -import Canvas from '../Canvas'; -import helpers, { Row } from '../helpers/test/GridPropHelpers'; -import { SCROLL_DIRECTION, CellNavigationMode } from '../common/enums'; -import EventBus from '../masks/EventBus'; -import { CalculatedColumn } from '../common/types'; - -const viewportProps: ViewportProps = { - rowOffsetHeight: 0, - totalWidth: 400, - columnMetrics: { - columns: helpers.columns, - minColumnWidth: 80, - totalWidth: 2600, - totalColumnWidth: 2600, - width: 2600 - }, - rowGetter() { return {}; }, - rowsCount: 50, - rowHeight: 35, - onScroll() { }, - minHeight: 500, - cellMetaData: { - onCellClick() { }, - onCellDoubleClick() { }, - rowKey: 'id', - onCellMouseDown() { }, - onCellMouseEnter() { }, - onCellContextMenu() { }, - onDragEnter() { }, - onRowExpandToggle() { }, - onCellExpand() { } - }, - interactionMasksMetaData: { - onGridRowsUpdated() { }, - onDragHandleDoubleClick() { }, - onCommit() { } - }, - rowKey: 'id', - enableCellSelect: true, - enableCellAutoFocus: true, - cellNavigationMode: CellNavigationMode.NONE, - eventBus: new EventBus(), - editorPortalTarget: document.body -}; - -const viewportPropsNoColumns: ViewportProps = { // when creating anew plan copying from an existing one the viewport got initialised with 0 columns rendered - rowOffsetHeight: 0, - totalWidth: 400, - columnMetrics: { - columns: helpers.columns, - minColumnWidth: 80, - totalColumnWidth: 0, - totalWidth: 0, - width: 2010 - }, - rowGetter() { return {}; }, - rowsCount: 50, - rowHeight: 35, - onScroll() { }, - minHeight: 500, - cellMetaData: { - onCellClick() { }, - onCellDoubleClick() { }, - rowKey: 'id', - onCellMouseDown() { }, - onCellMouseEnter() { }, - onCellContextMenu() { }, - onDragEnter() { }, - onRowExpandToggle() { }, - onCellExpand() { } - }, - interactionMasksMetaData: { - onGridRowsUpdated() { }, - onDragHandleDoubleClick() { }, - onCommit() { } - }, - rowKey: 'id', - enableCellSelect: true, - enableCellAutoFocus: true, - cellNavigationMode: CellNavigationMode.NONE, - eventBus: new EventBus(), - editorPortalTarget: document.body -}; - -describe('', () => { - it('renders a Canvas component', () => { - const wrapper = shallow(); - const canvas = wrapper.find(Canvas); - expect(canvas).toBeDefined(); - }); - - it('should update scroll state onScroll', () => { - const scrollLeft = 0; - const scrollTop = 200; - const wrapper = shallow(); - const canvas = wrapper.find(Canvas); - canvas.props().onScroll({ scrollTop, scrollLeft }); - expect(wrapper.state()).toEqual({ - colOverscanEndIdx: helpers.columns.length, - colOverscanStartIdx: 0, - colVisibleEndIdx: helpers.columns.length, - colVisibleStartIdx: 0, - rowOverscanEndIdx: 23, - rowOverscanStartIdx: 6, - height: viewportProps.minHeight, - scrollLeft, - scrollTop, - rowVisibleEndIdx: 21, - rowVisibleStartIdx: 6, - isScrolling: true, - lastFrozenColumnIndex: -1, - scrollDirection: SCROLL_DIRECTION.DOWN - }); - }); - - it('should set the max number of columns when column rendered are zeroed', () => { - const wrapper = shallow>(); - expect(wrapper.state().colVisibleEndIdx).toEqual(helpers.columns.length); - }); - - it('should update when given different number of columns', () => { - const wrapper = shallow>(); - const extraColumn: CalculatedColumn = { - key: 'description', - name: 'Description', - idx: 3, - width: 100, - left: 0 - }; - const updatedColumns = helpers.columns.concat(extraColumn); - const newProps = { ...viewportProps, columnMetrics: { ...viewportProps.columnMetrics, columns: updatedColumns } }; - wrapper.setProps(newProps); - expect(wrapper.state()).toEqual({ - colOverscanEndIdx: updatedColumns.length, - colOverscanStartIdx: 0, - colVisibleEndIdx: updatedColumns.length, - colVisibleStartIdx: 0, - rowOverscanEndIdx: 28, - rowOverscanStartIdx: 0, - height: viewportProps.minHeight, - scrollLeft: 0, - scrollTop: 0, - rowVisibleEndIdx: 14, - rowVisibleStartIdx: 0, - lastFrozenColumnIndex: 0, - isScrolling: false - }); - }); - - it('should update when given height changed', () => { - const wrapper = shallow>(); - const newHeight = 1000; - const newProps = { ...viewportProps, minHeight: newHeight }; - wrapper.setProps(newProps); - expect(wrapper.state()).toEqual({ - colOverscanEndIdx: helpers.columns.length, - colOverscanStartIdx: 0, - colVisibleEndIdx: helpers.columns.length, - colVisibleStartIdx: 0, - rowOverscanEndIdx: 29, - rowOverscanStartIdx: 0, - height: newHeight, - scrollLeft: 0, - scrollTop: 0, - rowVisibleEndIdx: 29, - rowVisibleStartIdx: 0, - isScrolling: true, - scrollDirection: SCROLL_DIRECTION.NONE, - lastFrozenColumnIndex: -1 - }); - }); -}); diff --git a/packages/react-data-grid/src/common/utils/__tests__/RowComparer.spec.ts b/packages/react-data-grid/src/common/utils/__tests__/RowComparer.spec.ts index 2c72456967..db4156e0cd 100644 --- a/packages/react-data-grid/src/common/utils/__tests__/RowComparer.spec.ts +++ b/packages/react-data-grid/src/common/utils/__tests__/RowComparer.spec.ts @@ -40,7 +40,8 @@ const defaultProps: RowRendererProps = { treeDepth: 0, siblingIndex: 0, numberSiblings: 0 - } + }, + lastFrozenColumnIndex: 0 }; describe('RowComparer shouldRowUpdate', () => { diff --git a/packages/react-data-grid/src/utils/__tests__/viewportUtils.spec.ts b/packages/react-data-grid/src/utils/__tests__/viewportUtils.spec.ts index ad7c59add5..aa516725c9 100644 --- a/packages/react-data-grid/src/utils/__tests__/viewportUtils.spec.ts +++ b/packages/react-data-grid/src/utils/__tests__/viewportUtils.spec.ts @@ -1,373 +1,180 @@ import { - getGridState, - getNonFrozenRenderedColumnCount, - getVisibleBoundaries, - getNonFrozenVisibleColStartIdx, getScrollDirection, - getRowOverscanStartIdx, - getRowOverscanEndIdx, - OVERSCAN_ROWS, - getColOverscanStartIdx, - getColOverscanEndIdx, - VisibleBoundaries + getVerticalRangeToRender, + VerticalRangeToRenderParams, + getHorizontalRangeToRender, + HorizontalRangeToRenderParams } from '../viewportUtils'; import { SCROLL_DIRECTION } from '../../common/enums'; -import { CalculatedColumn } from '../../common/types'; +import { ColumnMetrics } from '../../common/types'; interface Row { - col1?: string; - col2?: string; - col3?: string; - col4?: string; - col5?: string; - col6?: string; - col7?: string; - col8?: string; - col9?: string; + [key: string]: unknown; } describe('viewportUtils', () => { - const getColumns = (): CalculatedColumn[] => [ - { idx: 0, key: 'col1', name: 'col1', width: 100, left: 0 }, - { idx: 1, key: 'col2', name: 'col2', width: 100, left: 200 }, - { idx: 2, key: 'col3', name: 'col3', width: 100, left: 300 }, - { idx: 3, key: 'col4', name: 'col4', width: 100, left: 400 }, - { idx: 4, key: 'col5', name: 'col5', width: 100, left: 500 }, - { idx: 5, key: 'col6', name: 'col6', width: 100, left: 600 } - ]; - - describe('getGridState', () => { - const getState = (propsOverrides = {}) => { - const props = { - columnMetrics: { - columns: [ - { idx: 0, key: 'col1', name: 'col1', width: 100, left: 0 }, - { idx: 1, key: 'col2', name: 'col2', width: 100, left: 100 } - ], - width: 0, - totalColumnWidth: 0, - totalWidth: 0, - minColumnWidth: 0 - }, - minHeight: 100, - rowOffsetHeight: 5, - rowHeight: 20, - rowsCount: 100, - ...propsOverrides - }; - const state = getGridState(props); + describe('getVerticalRangeToRender', () => { + function getRange(overrides: Pick) { + return getVerticalRangeToRender({ + height: 500, + rowHeight: 50, + scrollTop: 200, + rowsCount: 1000, + scrollDirection: SCROLL_DIRECTION.DOWN, + ...overrides + }); + } + + it('should use rowHeight to calculate the range', () => { + expect(getRange({ rowHeight: 50 })).toEqual({ + rowVisibleStartIdx: 4, + rowVisibleEndIdx: 14, + rowOverscanStartIdx: 2, + rowOverscanEndIdx: 16 + }); + }); - return { props, state }; - }; + it('should use height to calculate the range', () => { + expect(getRange({ height: 250 })).toEqual({ + rowVisibleStartIdx: 4, + rowVisibleEndIdx: 9, + rowOverscanStartIdx: 2, + rowOverscanEndIdx: 11 + }); + }); - it('should correctly set canvas height', () => { - const { state } = getState(); - expect(state.height).toBe(95); + it('should use scrollTop to calculate the range', () => { + expect(getRange({ scrollTop: 500 })).toEqual({ + rowVisibleStartIdx: 10, + rowVisibleEndIdx: 20, + rowOverscanStartIdx: 8, + rowOverscanEndIdx: 22 + }); }); - it('should correctly set visible rows count when rowsCount is greater than the rendered rows count', () => { - const { state } = getState(); - expect(state.rowOverscanEndIdx).toBe(8); + it('should use rowsCount to calculate the range', () => { + expect(getRange({ rowsCount: 5, scrollTop: 0 })).toEqual({ + rowVisibleStartIdx: 0, + rowVisibleEndIdx: 5, + rowOverscanStartIdx: 0, + rowOverscanEndIdx: 5 + }); }); - it('should correctly set visible rows count when rowsCount is less than the rendered rows count', () => { - const { state } = getState({ rowsCount: 8 }); - expect(state.rowOverscanEndIdx).toBe(8); + it('should use overscanRowCount to calculate the range', () => { + expect(getRange({ overscanRowCount: 10 })).toEqual({ + rowVisibleStartIdx: 4, + rowVisibleEndIdx: 14, + rowOverscanStartIdx: 2, + rowOverscanEndIdx: 24 + }); }); - it('should correctly set visible column count', () => { - const { state, props } = getState(); - expect(state.colVisibleEndIdx).toBe(props.columnMetrics.columns.length); + it('should use scrollDirection to calculate the range', () => { + expect(getRange({ overscanRowCount: 10, scrollDirection: SCROLL_DIRECTION.UP })).toEqual({ + rowVisibleStartIdx: 4, + rowVisibleEndIdx: 14, + rowOverscanStartIdx: 0, + rowOverscanEndIdx: 16 + }); }); }); - describe('getNonFrozenRenderedColumnCount', () => { - const viewportWidth = 100; - - const getCols = (): CalculatedColumn[] => [ - { idx: 0, key: 'col1', name: 'col1', width: 20, left: 0 }, - { idx: 1, key: 'col2', name: 'col2', width: 20, left: 20 }, - { idx: 2, key: 'col3', name: 'col3', width: 20, left: 40 }, - { idx: 3, key: 'col4', name: 'col4', width: 20, left: 60 }, - { idx: 4, key: 'col5', name: 'col5', width: 20, left: 80 }, - { idx: 5, key: 'col6', name: 'col6', width: 1, left: 100 }, - { idx: 6, key: 'col7', name: 'col7', width: 1, left: 101 }, - { idx: 7, key: 'col8', name: 'col8', width: 1, left: 102 }, - { idx: 8, key: 'col9', name: 'col9', width: 1, left: 103 } - ]; - - const verifyNonFrozenRenderedColumnCount = (width = viewportWidth, columns = getCols(), scrollLeft = 0) => { - const columnMetrics = { + describe('getHorizontalRangeToRender', () => { + function getColumnMetrics(): ColumnMetrics { + const columns = [...Array(500).keys()].map(i => ({ idx: i, key: `col${i}`, name: `col${i}`, width: 100, left: i * 100 })); + return { columns, width: 0, - totalColumnWidth: 0, + totalColumnWidth: 200, totalWidth: 0, - minColumnWidth: 0 + minColumnWidth: 80, + lastFrozenColumnIndex: -1 }; - return getNonFrozenRenderedColumnCount(columnMetrics, width, scrollLeft); - }; - - it('correctly set rendered columns count for a set width', () => { - const count = verifyNonFrozenRenderedColumnCount(); - expect(count).toBe(5); // col1, col2, col3, col4, col5 - }); - - it('should return all columns that fit width after last frozen column', () => { - const columns = getCols(); - columns[1].frozen = true; - const count = verifyNonFrozenRenderedColumnCount(100, columns); - expect(count).toBe(3); // col3, col4, col5 - }); - - it('should return all columns that fit width after last frozen column when grid is scrolled', () => { - // 10 px of first non frozen column are hidden, with remaining 10 px visible - const scrollLeft = 50; - // This means that there are 10px of extra space to fill with columns - const columns = getCols(); - columns[1].frozen = true; - const count = verifyNonFrozenRenderedColumnCount(100, columns, scrollLeft); - expect(count).toBe(5); // col5, col6, col7, col8, col9 - }); - }); - - describe('getVisibleBoundaries', () => { - const GRID_HEIGHT = 350; - const ROW_HEIGHT = 35; - const TOTAL_ROWS = 100; - const EXPECTED_NUMBER_VISIBLE_ROWS = 10; + } - describe('When scroll top is 0', () => { - it('should set the rowVisibleStartIdx to be 0', () => { - const scrollTop = 0; - const { rowVisibleStartIdx } = getVisibleBoundaries(GRID_HEIGHT, ROW_HEIGHT, scrollTop, TOTAL_ROWS); - expect(rowVisibleStartIdx).toBe(0); + function getRange>(overrides: Pick, K>) { + return getHorizontalRangeToRender({ + columnMetrics: getColumnMetrics(), + scrollLeft: 200, + viewportWidth: 1000, + scrollDirection: SCROLL_DIRECTION.RIGHT, + ...overrides }); + } - it('should set the rowVisibleEndIdx to be last rendered row', () => { - const scrollTop = 0; - const { rowVisibleEndIdx } = getVisibleBoundaries(GRID_HEIGHT, ROW_HEIGHT, scrollTop, TOTAL_ROWS); - expect(rowVisibleEndIdx).toBe(EXPECTED_NUMBER_VISIBLE_ROWS); + it('should use scrollLeft to calculate the range', () => { + expect(getRange({ scrollLeft: 300 })).toEqual({ + colVisibleStartIdx: 3, + colVisibleEndIdx: 13, + colOverscanStartIdx: 1, + colOverscanEndIdx: 15 }); }); - describe('When scrolling', () => { - const scrollDown = (getScrollTop: (n: number) => number, assert: (n: number, boundaries: VisibleBoundaries) => void) => { - const NUMBER_OF_TESTS = 10; - for (let n = 1; n < NUMBER_OF_TESTS; n++) { - const boundaries = getVisibleBoundaries(GRID_HEIGHT, ROW_HEIGHT, getScrollTop(n), TOTAL_ROWS); - assert(n, boundaries); - } - }; - describe('When incrementing scroll by n*rowHeight', () => { - it('should increase rowVisibleStartIdx by n rows', () => { - const getScrollTop = (n: number) => n * ROW_HEIGHT; - scrollDown(getScrollTop, (n, { rowVisibleStartIdx }) => { - expect(rowVisibleStartIdx).toBe(n); - }); - }); - - it('should increase rowVisibleEndIdx by (n + total rendered rows)', () => { - const getScrollTop = (n: number) => n * ROW_HEIGHT; - scrollDown(getScrollTop, (n, { rowVisibleEndIdx }) => { - expect(rowVisibleEndIdx).toBe(EXPECTED_NUMBER_VISIBLE_ROWS + n); - }); - }); - }); - - describe('When incrementing scroll by a decimal number within 0.5 buffer of n*rowHeight', () => { - it('should increase rowVisibleEndIdx by (n + total rendered rows)', () => { - const clientScrollError = 0.5; - const getScrollTop = (n: number) => (n * ROW_HEIGHT) - clientScrollError; - scrollDown(getScrollTop, (n, { rowVisibleEndIdx }) => { - expect(rowVisibleEndIdx).toBe(EXPECTED_NUMBER_VISIBLE_ROWS + n); - }); - }); - - it('should increase rowVisibleEndIdx by n rows', () => { - const clientScrollError = 0.5; - const getScrollTop = (n: number) => (n * ROW_HEIGHT) - clientScrollError; - scrollDown(getScrollTop, (n, { rowVisibleStartIdx }) => { - expect(rowVisibleStartIdx).toBe(n); - }); - }); + it('should use viewportWidth to calculate the range', () => { + expect(getRange({ viewportWidth: 500 })).toEqual({ + colVisibleStartIdx: 2, + colVisibleEndIdx: 7, + colOverscanStartIdx: 0, + colOverscanEndIdx: 9 }); }); - describe('getNonFrozenVisibleColStartIdx', () => { - it('should return 0 if no frozen columns and grid not scrolled left', () => { - const scrollLeft = 0; - const colVisibleStartIdx = getNonFrozenVisibleColStartIdx(getColumns(), scrollLeft); - expect(colVisibleStartIdx).toBe(0); - }); - - it('should return first fully visible column when scrolled left', () => { - const scrollLeft = 100; - const colVisibleStartIdx = getNonFrozenVisibleColStartIdx(getColumns(), scrollLeft); - expect(colVisibleStartIdx).toBe(1); - }); - - it('should return first partially visible column when scrolled left (left bound)', () => { - const scrollLeft = 99; - const colVisibleStartIdx = getNonFrozenVisibleColStartIdx(getColumns(), scrollLeft); - expect(colVisibleStartIdx).toBe(0); - }); - - it('should return first partially visible column when scrolled left (right bound)', () => { - const scrollLeft = 101; - const colVisibleStartIdx = getNonFrozenVisibleColStartIdx(getColumns(), scrollLeft); - expect(colVisibleStartIdx).toBe(1); - }); - - const expectIdxWhenColumsFrozen = (scrollLeft: number) => { - const columns = getColumns(); - columns[1].frozen = true; - const colVisibleStartIdx = getNonFrozenVisibleColStartIdx(columns, scrollLeft); - return expect(colVisibleStartIdx); - }; - - it('should return first non frozen column that appears after last frozen column', () => { - const scrollLeft = 0; - expectIdxWhenColumsFrozen(scrollLeft).toBe(2); - }); - - it('should return first fully visible non frozen column that appears after last frozen column when scrolled left', () => { - const scrollLeft = 200; - expectIdxWhenColumsFrozen(scrollLeft).toBe(4); - }); - - it('should return first partially visible non frozen column that appears after last frozen column when scrolled left', () => { - const scrollLeft = 201; - expectIdxWhenColumsFrozen(scrollLeft).toBe(4); - }); - - it('should return first partially visible non frozen column that appears after last frozen column when scrolled left', () => { - const scrollLeft = 199; - expectIdxWhenColumsFrozen(scrollLeft).toBe(3); + it('should use overscanColumnCount to calculate the range', () => { + expect(getRange({ overscanColumnCount: 5 })).toEqual({ + colVisibleStartIdx: 2, + colVisibleEndIdx: 12, + colOverscanStartIdx: 0, + colOverscanEndIdx: 17 }); }); - describe('getScrollDirection', () => { - it('should return SCROLL_DIRECTION.DOWN iF previous scrollTop is less than current scrollTop', () => { - const prevScroll = { scrollTop: 100 }; - const currentScrollTop = 200; - const currentScrollLeft = 0; - expect(getScrollDirection(prevScroll, currentScrollTop, currentScrollLeft)).toBe(SCROLL_DIRECTION.DOWN); - }); - - it('should return SCROLL_DIRECTION.UP iF previous scrollTop is greater than current scrollTop', () => { - const prevScroll = { scrollTop: 200 }; - const currentScrollTop = 0; - const currentScrollLeft = 0; - expect(getScrollDirection(prevScroll, currentScrollTop, currentScrollLeft)).toBe(SCROLL_DIRECTION.UP); - }); - - it('should return SCROLL_DIRECTION.RIGHT iF previous scrollLeft is less than current scrollLeft', () => { - const prevScroll = { scrollLeft: 200 }; - const currentScrollTop = 0; - const currentScrollLeft = 400; - expect(getScrollDirection(prevScroll, currentScrollTop, currentScrollLeft)).toBe(SCROLL_DIRECTION.RIGHT); - }); - - it('should return SCROLL_DIRECTION.LEFT iF previous scrollLeft is greater than current scrollLeft', () => { - const prevScroll = { scrollLeft: 200 }; - const currentScrollTop = 0; - const currentScrollLeft = 0; - expect(getScrollDirection(prevScroll, currentScrollTop, currentScrollLeft)).toBe(SCROLL_DIRECTION.LEFT); - }); - - it('should return SCROLL_DIRECTION.NONE if current scroll is equal to previous scroll', () => { - const prevScroll = { scrollLeft: 200, scrollTop: 0 }; - const currentScrollTop = 0; - const currentScrollLeft = 200; - expect(getScrollDirection(prevScroll, currentScrollTop, currentScrollLeft)).toBe(SCROLL_DIRECTION.NONE); + it('should use overscanColumnCount to calculate the range', () => { + expect(getRange({ overscanColumnCount: 5, scrollDirection: SCROLL_DIRECTION.LEFT, scrollLeft: 1000 })).toEqual({ + colVisibleStartIdx: 10, + colVisibleEndIdx: 20, + colOverscanStartIdx: 5, + colOverscanEndIdx: 22 }); }); - describe('getRowOverscanStartIdx', () => { - const rowVisibleStartIdx = 2; - it('should return rowVisibleStartIdx - OVERSCAN_ROWS if scroll direction is upwards', () => { - expect(getRowOverscanStartIdx(SCROLL_DIRECTION.UP, rowVisibleStartIdx)).toBe(rowVisibleStartIdx - OVERSCAN_ROWS); - }); - - it('should return rowVisibleStartIdx if scroll direction is left', () => { - expect(getRowOverscanStartIdx(SCROLL_DIRECTION.LEFT, rowVisibleStartIdx)).toBe(rowVisibleStartIdx); - }); - - it('should return rowVisibleStartIdx if scroll direction is right', () => { - expect(getRowOverscanStartIdx(SCROLL_DIRECTION.RIGHT, rowVisibleStartIdx)).toBe(rowVisibleStartIdx); - }); - - it('should return rowVisibleStartIdx if scroll direction is downwards', () => { - expect(getRowOverscanStartIdx(SCROLL_DIRECTION.DOWN, rowVisibleStartIdx)).toBe(rowVisibleStartIdx); - }); + it('should use frozen columns to calculate the range', () => { + const columnMetrics = getColumnMetrics(); + columnMetrics.columns[0].frozen = true; + columnMetrics.columns[1].frozen = true; + columnMetrics.columns[2].frozen = true; + columnMetrics.lastFrozenColumnIndex = 2; - it('should return 0 if rowVisibleStartIdx negative', () => { - expect(getRowOverscanStartIdx(SCROLL_DIRECTION.DOWN, -1)).toBe(0); + expect(getRange({ scrollLeft: 500, columnMetrics })).toEqual({ + colVisibleStartIdx: 8, + colVisibleEndIdx: 15, + colOverscanStartIdx: 6, + colOverscanEndIdx: 17 }); }); + }); - describe('getRowOverscanEndIdx', () => { - const rowVisibleEndIdx = 20; - const totalNumberOfRows = 30; - it('should return rowVisibleStartIdx + OVERSCAN_ROWS if scroll direction is downward', () => { - expect(getRowOverscanEndIdx(SCROLL_DIRECTION.DOWN, rowVisibleEndIdx, totalNumberOfRows)).toBe(rowVisibleEndIdx + OVERSCAN_ROWS); - }); - - it('should return totalNumberOfRows if scroll direction is downward and rowVisibleEndIdx == totalNumberRows', () => { - expect(getRowOverscanEndIdx(SCROLL_DIRECTION.DOWN, totalNumberOfRows, totalNumberOfRows)).toBe(totalNumberOfRows); - }); - - it('should return rowVisibleEndIdx if scroll direction is left', () => { - expect(getRowOverscanEndIdx(SCROLL_DIRECTION.LEFT, rowVisibleEndIdx, totalNumberOfRows)).toBe(rowVisibleEndIdx); - }); - - it('should return rowVisibleEndIdx if scroll direction is right', () => { - expect(getRowOverscanEndIdx(SCROLL_DIRECTION.RIGHT, rowVisibleEndIdx, totalNumberOfRows)).toBe(rowVisibleEndIdx); - }); - - it('should return rowVisibleEndIdx if scroll direction is upwards', () => { - expect(getRowOverscanEndIdx(SCROLL_DIRECTION.UP, rowVisibleEndIdx, totalNumberOfRows)).toBe(rowVisibleEndIdx); - }); + describe('getScrollDirection', () => { + const prevScroll = { scrollTop: 100, scrollLeft: 100 }; + it('should return SCROLL_DIRECTION.DOWN iF previous scrollTop is less than current scrollTop', () => { + expect(getScrollDirection(prevScroll, { ...prevScroll, scrollTop: 101 })).toBe(SCROLL_DIRECTION.DOWN); }); - describe('getColOverscanStartIdx', () => { - const colVisibleStartIdx = 5; - const lastFrozenColumnIdx = -1; - it('should return colVisibleStartIdx if scroll direction is downward', () => { - expect(getColOverscanStartIdx(SCROLL_DIRECTION.DOWN, colVisibleStartIdx, lastFrozenColumnIdx)).toBe(colVisibleStartIdx); - }); - - it('should return colVisibleStartIdx if scroll direction is upwards', () => { - expect(getColOverscanStartIdx(SCROLL_DIRECTION.UP, colVisibleStartIdx, lastFrozenColumnIdx)).toBe(colVisibleStartIdx); - }); - - it('should return 0 if scroll direction is left', () => { - expect(getColOverscanStartIdx(SCROLL_DIRECTION.LEFT, colVisibleStartIdx, lastFrozenColumnIdx)).toBe(0); - }); - - it('should return 0 if scroll direction is right', () => { - expect(getColOverscanStartIdx(SCROLL_DIRECTION.RIGHT, colVisibleStartIdx, lastFrozenColumnIdx)).toBe(0); - }); + it('should return SCROLL_DIRECTION.UP iF previous scrollTop is greater than current scrollTop', () => { + expect(getScrollDirection(prevScroll, { ...prevScroll, scrollTop: 99 })).toBe(SCROLL_DIRECTION.UP); }); - describe('getColOverscanEndIdx', () => { - const colVisibleEndIdx = 20; - const totalNumberOfColumns = 30; - it('should return colVisibleStartIdx if scroll direction is downward', () => { - expect(getColOverscanEndIdx(SCROLL_DIRECTION.DOWN, colVisibleEndIdx, totalNumberOfColumns)).toBe(colVisibleEndIdx); - }); - - it('should return colVisibleEndIdx if scroll direction is upwards', () => { - expect(getColOverscanEndIdx(SCROLL_DIRECTION.UP, colVisibleEndIdx, totalNumberOfColumns)).toBe(colVisibleEndIdx); - }); + it('should return SCROLL_DIRECTION.RIGHT iF previous scrollLeft is less than current scrollLeft', () => { + expect(getScrollDirection(prevScroll, { ...prevScroll, scrollLeft: 101 })).toBe(SCROLL_DIRECTION.RIGHT); + }); - it('should return totalNumberOfColumns if scroll direction is left', () => { - expect(getColOverscanEndIdx(SCROLL_DIRECTION.LEFT, colVisibleEndIdx, totalNumberOfColumns)).toBe(totalNumberOfColumns); - }); + it('should return SCROLL_DIRECTION.LEFT iF previous scrollLeft is greater than current scrollLeft', () => { + expect(getScrollDirection(prevScroll, { ...prevScroll, scrollLeft: 99 })).toBe(SCROLL_DIRECTION.LEFT); + }); - it('should return totalNumberOfColumns if scroll direction is right', () => { - expect(getColOverscanEndIdx(SCROLL_DIRECTION.RIGHT, colVisibleEndIdx, totalNumberOfColumns)).toBe(totalNumberOfColumns); - }); + it('should return SCROLL_DIRECTION.NONE if current scroll is equal to previous scroll', () => { + expect(getScrollDirection(prevScroll, { ...prevScroll })).toBe(SCROLL_DIRECTION.NONE); }); }); }); diff --git a/packages/react-data-grid/src/utils/viewportUtils.ts b/packages/react-data-grid/src/utils/viewportUtils.ts index 83629ead23..5f5655109e 100644 --- a/packages/react-data-grid/src/utils/viewportUtils.ts +++ b/packages/react-data-grid/src/utils/viewportUtils.ts @@ -14,7 +14,7 @@ function getColumnCountForWidth(columns: CalculatedColumn[], initialWidth: let count = 0; columns.forEach((column, idx) => { - if (idx! >= colVisibleStartIdx) { + if (idx >= colVisibleStartIdx) { width -= column.width; if (width >= 0) { count++; @@ -25,7 +25,7 @@ function getColumnCountForWidth(columns: CalculatedColumn[], initialWidth: return count; } -interface VerticalRangeToRenderParams { +export interface VerticalRangeToRenderParams { height: number; rowHeight: number; scrollTop: number; @@ -65,7 +65,7 @@ interface HorizontalRangeToRender { colOverscanEndIdx: number; } -interface HorizontalRangeToRenderParams { +export interface HorizontalRangeToRenderParams { columnMetrics: ColumnMetrics; viewportWidth: number; scrollLeft: number; From e1d9aa20820c2c930a63ebd96b8c491d1d79bf92 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Wed, 25 Sep 2019 09:55:05 -0400 Subject: [PATCH 15/20] Check -webkit-sticky Co-Authored-By: Nicolas Stepien <567105+MayhemYDG@users.noreply.github.com> --- packages/react-data-grid/style/rdg-cell.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-data-grid/style/rdg-cell.less b/packages/react-data-grid/style/rdg-cell.less index d75f62fa55..1e4aaff261 100644 --- a/packages/react-data-grid/style/rdg-cell.less +++ b/packages/react-data-grid/style/rdg-cell.less @@ -34,7 +34,7 @@ z-index: 3 } -@supports (position: sticky) { +@supports (position: sticky) or (position: -webkit-sticky) { .react-grid-Cell--frozen { display: inline-block; position: -webkit-sticky; From 3bbd40193d42e6054c0269b384f3014acc9ec56d Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Wed, 25 Sep 2019 09:17:08 -0500 Subject: [PATCH 16/20] Add a few comments Delete unused ref --- packages/react-data-grid/src/Canvas.tsx | 8 ++++---- packages/react-data-grid/src/Viewport.tsx | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/react-data-grid/src/Canvas.tsx b/packages/react-data-grid/src/Canvas.tsx index 154769ad76..4347b903e9 100644 --- a/packages/react-data-grid/src/Canvas.tsx +++ b/packages/react-data-grid/src/Canvas.tsx @@ -92,9 +92,7 @@ export default class Canvas extends React.PureComponent> { handleScroll = (e: React.UIEvent) => { const { scrollLeft, scrollTop } = e.currentTarget; - if (!isPositionStickySupported()) { - this.setScrollLeft(scrollLeft); - } + this.freezeColumnsOnLegacyBrowsers(scrollLeft); this.props.onScroll({ scrollLeft, scrollTop }); }; @@ -177,7 +175,8 @@ export default class Canvas extends React.PureComponent> { return false; } - setScrollLeft(scrollLeft: number) { + freezeColumnsOnLegacyBrowsers(scrollLeft: number) { + if (isPositionStickySupported()) return; const { current } = this.interactionMasks; if (current) { current.setScrollLeft(scrollLeft); @@ -353,6 +352,7 @@ export default class Canvas extends React.PureComponent> { {...this.props.interactionMasksMetaData} /> + {/* Set minHeight to show horizontal scrollbar when there are no rows */}
{rows}
diff --git a/packages/react-data-grid/src/Viewport.tsx b/packages/react-data-grid/src/Viewport.tsx index cf41285c2b..8617dd1ad0 100644 --- a/packages/react-data-grid/src/Viewport.tsx +++ b/packages/react-data-grid/src/Viewport.tsx @@ -66,7 +66,6 @@ export default function Viewport({ enableIsScrolling, ...props }: ViewportProps) { - const canvas = useRef>(null); const viewport = useRef(null); const resetScrollStateTimeoutId = useRef(null); const [scrollState, setScrollState] = useState(null); @@ -175,7 +174,6 @@ export default function Viewport({ > {scrollState && ( - ref={canvas} rowKey={props.rowKey} width={columnMetrics.width} totalColumnWidth={columnMetrics.totalColumnWidth} From 62b76ecec8e459445dd3b0f5e5c7490721b2f48b Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Wed, 25 Sep 2019 09:26:02 -0500 Subject: [PATCH 17/20] Fix tests --- packages/react-data-grid/src/Canvas.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/react-data-grid/src/Canvas.tsx b/packages/react-data-grid/src/Canvas.tsx index 4347b903e9..96c6e8ccb7 100644 --- a/packages/react-data-grid/src/Canvas.tsx +++ b/packages/react-data-grid/src/Canvas.tsx @@ -92,7 +92,8 @@ export default class Canvas extends React.PureComponent> { handleScroll = (e: React.UIEvent) => { const { scrollLeft, scrollTop } = e.currentTarget; - this.freezeColumnsOnLegacyBrowsers(scrollLeft); + // Freeze columns on legacy browsers + this.setScrollLeft(scrollLeft); this.props.onScroll({ scrollLeft, scrollTop }); }; @@ -175,7 +176,7 @@ export default class Canvas extends React.PureComponent> { return false; } - freezeColumnsOnLegacyBrowsers(scrollLeft: number) { + setScrollLeft(scrollLeft: number) { if (isPositionStickySupported()) return; const { current } = this.interactionMasks; if (current) { From a15a96588e36ef10f898b526a9300e4b7363531a Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Wed, 25 Sep 2019 13:09:36 -0500 Subject: [PATCH 18/20] Cleanup scroll state logic --- packages/react-data-grid/src/Canvas.tsx | 16 +- packages/react-data-grid/src/Grid.tsx | 69 ++++--- packages/react-data-grid/src/Viewport.tsx | 185 +++++++----------- .../src/utils/viewportUtils.ts | 18 +- 4 files changed, 119 insertions(+), 169 deletions(-) diff --git a/packages/react-data-grid/src/Canvas.tsx b/packages/react-data-grid/src/Canvas.tsx index 96c6e8ccb7..21f84b43fd 100644 --- a/packages/react-data-grid/src/Canvas.tsx +++ b/packages/react-data-grid/src/Canvas.tsx @@ -9,7 +9,8 @@ import * as rowUtils from './RowUtils'; import { getColumnScrollPosition, isPositionStickySupported } from './utils'; import { EventTypes } from './common/enums'; import { CalculatedColumn, Position, ScrollPosition, SubRowDetails, RowRenderer, RowRendererProps, RowData } from './common/types'; -import { ViewportProps, ScrollState } from './Viewport'; +import { ViewportProps } from './Viewport'; +import { HorizontalRangeToRender, VerticalRangeToRender } from './utils/viewportUtils'; type SharedViewportProps = Pick, 'rowKey' @@ -33,18 +34,7 @@ type SharedViewportProps = Pick, | 'interactionMasksMetaData' >; -type SharedViewportState = Pick; +type SharedViewportState = ScrollPosition & HorizontalRangeToRender & VerticalRangeToRender; export interface CanvasProps extends SharedViewportProps, SharedViewportState { columns: CalculatedColumn[]; diff --git a/packages/react-data-grid/src/Grid.tsx b/packages/react-data-grid/src/Grid.tsx index 1c9b900e78..92e2d3a79a 100644 --- a/packages/react-data-grid/src/Grid.tsx +++ b/packages/react-data-grid/src/Grid.tsx @@ -55,6 +55,7 @@ export interface GridProps extends SharedDataGridProps, SharedDataGridStat } export default function Grid({ emptyRowsView, headerRows, ...props }: GridProps) { + const grid = useRef(null); const header = useRef>(null); const scrollLeft = useRef(0); @@ -69,7 +70,10 @@ export default function Grid({ emptyRowsView, headerRows, ...props }: GridPro } return ( -
+
ref={header} columnMetrics={props.columnMetrics} @@ -89,36 +93,39 @@ export default function Grid({ emptyRowsView, headerRows, ...props }: GridPro {createElement(emptyRowsView)}
) : ( - - rowKey={props.rowKey} - rowHeight={props.rowHeight} - rowRenderer={props.rowRenderer} - rowGetter={props.rowGetter} - rowsCount={props.rowsCount} - selectedRows={props.selectedRows} - columnMetrics={props.columnMetrics} - onScroll={onScroll} - cellMetaData={props.cellMetaData} - rowOffsetHeight={props.rowOffsetHeight} - minHeight={props.minHeight} - scrollToRowIndex={props.scrollToRowIndex} - contextMenu={props.contextMenu} - rowSelection={props.rowSelection} - getSubRowDetails={props.getSubRowDetails} - rowGroupRenderer={props.rowGroupRenderer} - enableCellSelect={props.enableCellSelect} - enableCellAutoFocus={props.enableCellAutoFocus} - cellNavigationMode={props.cellNavigationMode} - eventBus={props.eventBus} - interactionMasksMetaData={props.interactionMasksMetaData} - RowsContainer={props.RowsContainer} - editorPortalTarget={props.editorPortalTarget} - overscanRowCount={props.overscanRowCount} - overscanColumnCount={props.overscanColumnCount} - enableIsScrolling={props.enableIsScrolling} - onViewportKeydown={props.onViewportKeydown} - onViewportKeyup={props.onViewportKeyup} - /> + grid.current && ( + + rowKey={props.rowKey} + rowHeight={props.rowHeight} + rowRenderer={props.rowRenderer} + rowGetter={props.rowGetter} + rowsCount={props.rowsCount} + selectedRows={props.selectedRows} + columnMetrics={props.columnMetrics} + onScroll={onScroll} + cellMetaData={props.cellMetaData} + rowOffsetHeight={props.rowOffsetHeight} + minHeight={props.minHeight} + scrollToRowIndex={props.scrollToRowIndex} + contextMenu={props.contextMenu} + rowSelection={props.rowSelection} + getSubRowDetails={props.getSubRowDetails} + rowGroupRenderer={props.rowGroupRenderer} + enableCellSelect={props.enableCellSelect} + enableCellAutoFocus={props.enableCellAutoFocus} + cellNavigationMode={props.cellNavigationMode} + eventBus={props.eventBus} + interactionMasksMetaData={props.interactionMasksMetaData} + RowsContainer={props.RowsContainer} + editorPortalTarget={props.editorPortalTarget} + overscanRowCount={props.overscanRowCount} + overscanColumnCount={props.overscanColumnCount} + enableIsScrolling={props.enableIsScrolling} + onViewportKeydown={props.onViewportKeydown} + onViewportKeyup={props.onViewportKeyup} + viewportWidth={grid.current.offsetWidth} + /> + ) )}
); diff --git a/packages/react-data-grid/src/Viewport.tsx b/packages/react-data-grid/src/Viewport.tsx index 8617dd1ad0..0d8121b23b 100644 --- a/packages/react-data-grid/src/Viewport.tsx +++ b/packages/react-data-grid/src/Viewport.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useState, useEffect } from 'react'; +import React, { useRef, useState, useMemo } from 'react'; import Canvas from './Canvas'; import { getVerticalRangeToRender, getHorizontalRangeToRender, getScrollDirection } from './utils/viewportUtils'; @@ -9,14 +9,6 @@ import { SCROLL_DIRECTION } from './common/enums'; export interface ScrollState { scrollTop: number; scrollLeft: number; - rowVisibleStartIdx: number; - rowVisibleEndIdx: number; - rowOverscanStartIdx: number; - rowOverscanEndIdx: number; - colVisibleStartIdx: number; - colVisibleEndIdx: number; - colOverscanStartIdx: number; - colOverscanEndIdx: number; scrollDirection: SCROLL_DIRECTION; } @@ -52,6 +44,7 @@ type SharedGridProps = Pick, export interface ViewportProps extends SharedGridProps { onScroll(scrollState: ScrollState): void; + viewportWidth: number; } export default function Viewport({ @@ -64,19 +57,17 @@ export default function Viewport({ overscanRowCount, overscanColumnCount, enableIsScrolling, + viewportWidth, ...props }: ViewportProps) { - const viewport = useRef(null); const resetScrollStateTimeoutId = useRef(null); - const [scrollState, setScrollState] = useState(null); + const [scrollState, setScrollState] = useState({ + scrollLeft: 0, + scrollTop: 0, + scrollDirection: SCROLL_DIRECTION.NONE + }); const [isScrolling, setIsScrolling] = useState(undefined); - const canvasHeight = minHeight - rowOffsetHeight; - - function getDOMNodeOffsetWidth() { - return viewport.current ? viewport.current.offsetWidth : 0; - } - function clearScrollTimer() { if (resetScrollStateTimeoutId.current !== null) { window.clearTimeout(resetScrollStateTimeoutId.current); @@ -103,115 +94,79 @@ export default function Viewport({ resetScrollStateAfterDelay(); } - let previousScrollPosition: ScrollPosition | undefined; - if (scrollState !== null) { - previousScrollPosition = { - scrollLeft: scrollState.scrollLeft, - scrollTop: scrollState.scrollTop - }; - } + const scrollDirection = getScrollDirection(scrollState, { scrollLeft, scrollTop }); + const newScrollState = { scrollLeft, scrollTop, scrollDirection }; + setScrollState(newScrollState); + handleScroll(newScrollState); + } - const scrollDirection = getScrollDirection(previousScrollPosition, { scrollLeft, scrollTop }); - const nextScrollState = { - scrollLeft, + const canvasHeight = minHeight - rowOffsetHeight; + const { scrollLeft, scrollTop, scrollDirection } = scrollState; + + const verticalRangeToRender = useMemo(() => { + return getVerticalRangeToRender({ + height: canvasHeight, + rowHeight, scrollTop, + rowsCount, scrollDirection, - ...getVerticalRangeToRender({ - height: canvasHeight, - rowHeight, - scrollTop, - rowsCount, - scrollDirection, - overscanRowCount - }), - ...getHorizontalRangeToRender({ - columnMetrics, - scrollLeft, - viewportWidth: getDOMNodeOffsetWidth(), - scrollDirection, - overscanColumnCount - }) - }; - - setScrollState(nextScrollState); - handleScroll(nextScrollState); - } - - useEffect(() => { - const scrollDirection = SCROLL_DIRECTION.NONE; - setScrollState(prevScrollState => { - const scrollTop = prevScrollState ? prevScrollState.scrollTop : 0; - const scrollLeft = prevScrollState ? prevScrollState.scrollLeft : 0; + overscanRowCount + }); + }, [canvasHeight, overscanRowCount, rowHeight, rowsCount, scrollDirection, scrollTop]); - return { - scrollTop, - scrollLeft, - scrollDirection, - ...getVerticalRangeToRender({ - height: canvasHeight, - rowHeight, - scrollTop, - rowsCount, - scrollDirection, - overscanRowCount - }), - ...getHorizontalRangeToRender({ - columnMetrics, - scrollLeft, - viewportWidth: getDOMNodeOffsetWidth(), - scrollDirection, - overscanColumnCount - }) - }; + const horizontalRangeToRender = useMemo(() => { + return getHorizontalRangeToRender({ + columnMetrics, + scrollLeft, + viewportWidth, + scrollDirection, + overscanColumnCount }); - }, [canvasHeight, columnMetrics, overscanColumnCount, overscanRowCount, rowHeight, rowsCount]); + }, [columnMetrics, overscanColumnCount, scrollDirection, scrollLeft, viewportWidth]); return (
- {scrollState && ( - - rowKey={props.rowKey} - width={columnMetrics.width} - totalColumnWidth={columnMetrics.totalColumnWidth} - columns={columnMetrics.columns} - lastFrozenColumnIndex={columnMetrics.lastFrozenColumnIndex} - rowGetter={props.rowGetter} - rowsCount={rowsCount} - selectedRows={props.selectedRows} - rowRenderer={props.rowRenderer} - scrollTop={scrollState.scrollTop} - scrollLeft={scrollState.scrollLeft} - rowOverscanStartIdx={scrollState.rowOverscanStartIdx} - rowOverscanEndIdx={scrollState.rowOverscanEndIdx} - rowVisibleStartIdx={scrollState.rowVisibleStartIdx} - rowVisibleEndIdx={scrollState.rowVisibleEndIdx} - colVisibleStartIdx={scrollState.colVisibleStartIdx} - colVisibleEndIdx={scrollState.colVisibleEndIdx} - colOverscanStartIdx={scrollState.colOverscanStartIdx} - colOverscanEndIdx={scrollState.colOverscanEndIdx} - cellMetaData={props.cellMetaData} - height={canvasHeight} - rowHeight={rowHeight} - onScroll={onScroll} - scrollToRowIndex={props.scrollToRowIndex} - contextMenu={props.contextMenu} - rowSelection={props.rowSelection} - getSubRowDetails={props.getSubRowDetails} - rowGroupRenderer={props.rowGroupRenderer} - isScrolling={isScrolling} - enableCellSelect={props.enableCellSelect} - enableCellAutoFocus={props.enableCellAutoFocus} - cellNavigationMode={props.cellNavigationMode} - eventBus={props.eventBus} - RowsContainer={props.RowsContainer} - editorPortalTarget={props.editorPortalTarget} - interactionMasksMetaData={props.interactionMasksMetaData} - /> - )} + + rowKey={props.rowKey} + width={columnMetrics.width} + totalColumnWidth={columnMetrics.totalColumnWidth} + columns={columnMetrics.columns} + lastFrozenColumnIndex={columnMetrics.lastFrozenColumnIndex} + rowGetter={props.rowGetter} + rowsCount={rowsCount} + selectedRows={props.selectedRows} + rowRenderer={props.rowRenderer} + scrollTop={scrollTop} + scrollLeft={scrollLeft} + rowOverscanStartIdx={verticalRangeToRender.rowOverscanStartIdx} + rowOverscanEndIdx={verticalRangeToRender.rowOverscanEndIdx} + rowVisibleStartIdx={verticalRangeToRender.rowVisibleStartIdx} + rowVisibleEndIdx={verticalRangeToRender.rowVisibleEndIdx} + colVisibleStartIdx={horizontalRangeToRender.colVisibleStartIdx} + colVisibleEndIdx={horizontalRangeToRender.colVisibleEndIdx} + colOverscanStartIdx={horizontalRangeToRender.colOverscanStartIdx} + colOverscanEndIdx={horizontalRangeToRender.colOverscanEndIdx} + cellMetaData={props.cellMetaData} + height={canvasHeight} + rowHeight={rowHeight} + onScroll={onScroll} + scrollToRowIndex={props.scrollToRowIndex} + contextMenu={props.contextMenu} + rowSelection={props.rowSelection} + getSubRowDetails={props.getSubRowDetails} + rowGroupRenderer={props.rowGroupRenderer} + isScrolling={isScrolling} + enableCellSelect={props.enableCellSelect} + enableCellAutoFocus={props.enableCellAutoFocus} + cellNavigationMode={props.cellNavigationMode} + eventBus={props.eventBus} + RowsContainer={props.RowsContainer} + editorPortalTarget={props.editorPortalTarget} + interactionMasksMetaData={props.interactionMasksMetaData} + />
); } diff --git a/packages/react-data-grid/src/utils/viewportUtils.ts b/packages/react-data-grid/src/utils/viewportUtils.ts index 5f5655109e..81f3843262 100644 --- a/packages/react-data-grid/src/utils/viewportUtils.ts +++ b/packages/react-data-grid/src/utils/viewportUtils.ts @@ -34,7 +34,7 @@ export interface VerticalRangeToRenderParams { overscanRowCount?: number; } -interface VerticalRangeToRender { +export interface VerticalRangeToRender { rowVisibleStartIdx: number; rowVisibleEndIdx: number; rowOverscanStartIdx: number; @@ -58,7 +58,7 @@ export function getVerticalRangeToRender({ return { rowVisibleStartIdx, rowVisibleEndIdx, rowOverscanStartIdx, rowOverscanEndIdx }; } -interface HorizontalRangeToRender { +export interface HorizontalRangeToRender { colVisibleStartIdx: number; colVisibleEndIdx: number; colOverscanStartIdx: number; @@ -102,14 +102,12 @@ export function getHorizontalRangeToRender({ return { colVisibleStartIdx, colVisibleEndIdx, colOverscanStartIdx, colOverscanEndIdx }; } -export function getScrollDirection(prevScroll: ScrollPosition | undefined, nextScroll: ScrollPosition): SCROLL_DIRECTION { - if (prevScroll !== undefined) { - if (nextScroll.scrollTop !== prevScroll.scrollTop) { - return nextScroll.scrollTop - prevScroll.scrollTop >= 0 ? SCROLL_DIRECTION.DOWN : SCROLL_DIRECTION.UP; - } - if (nextScroll.scrollLeft !== prevScroll.scrollLeft) { - return nextScroll.scrollLeft - prevScroll.scrollLeft >= 0 ? SCROLL_DIRECTION.RIGHT : SCROLL_DIRECTION.LEFT; - } +export function getScrollDirection(prevScroll: ScrollPosition, nextScroll: ScrollPosition): SCROLL_DIRECTION { + if (nextScroll.scrollTop !== prevScroll.scrollTop) { + return nextScroll.scrollTop - prevScroll.scrollTop >= 0 ? SCROLL_DIRECTION.DOWN : SCROLL_DIRECTION.UP; + } + if (nextScroll.scrollLeft !== prevScroll.scrollLeft) { + return nextScroll.scrollLeft - prevScroll.scrollLeft >= 0 ? SCROLL_DIRECTION.RIGHT : SCROLL_DIRECTION.LEFT; } return SCROLL_DIRECTION.NONE; } From 2018db0493725cfc98efeb7b063cbc45d932161e Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Thu, 26 Sep 2019 07:40:09 -0500 Subject: [PATCH 19/20] Cleanup --- packages/react-data-grid/src/Viewport.tsx | 10 ++-------- packages/react-data-grid/src/utils/viewportUtils.ts | 10 ++++------ 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/packages/react-data-grid/src/Viewport.tsx b/packages/react-data-grid/src/Viewport.tsx index 0d8121b23b..2494a7157b 100644 --- a/packages/react-data-grid/src/Viewport.tsx +++ b/packages/react-data-grid/src/Viewport.tsx @@ -130,6 +130,8 @@ export default function Viewport({ onKeyUp={props.onViewportKeyup} > + {...verticalRangeToRender} + {...horizontalRangeToRender} rowKey={props.rowKey} width={columnMetrics.width} totalColumnWidth={columnMetrics.totalColumnWidth} @@ -141,14 +143,6 @@ export default function Viewport({ rowRenderer={props.rowRenderer} scrollTop={scrollTop} scrollLeft={scrollLeft} - rowOverscanStartIdx={verticalRangeToRender.rowOverscanStartIdx} - rowOverscanEndIdx={verticalRangeToRender.rowOverscanEndIdx} - rowVisibleStartIdx={verticalRangeToRender.rowVisibleStartIdx} - rowVisibleEndIdx={verticalRangeToRender.rowVisibleEndIdx} - colVisibleStartIdx={horizontalRangeToRender.colVisibleStartIdx} - colVisibleEndIdx={horizontalRangeToRender.colVisibleEndIdx} - colOverscanStartIdx={horizontalRangeToRender.colOverscanStartIdx} - colOverscanEndIdx={horizontalRangeToRender.colOverscanEndIdx} cellMetaData={props.cellMetaData} height={canvasHeight} rowHeight={rowHeight} diff --git a/packages/react-data-grid/src/utils/viewportUtils.ts b/packages/react-data-grid/src/utils/viewportUtils.ts index 81f3843262..ad4b0c34a5 100644 --- a/packages/react-data-grid/src/utils/viewportUtils.ts +++ b/packages/react-data-grid/src/utils/viewportUtils.ts @@ -103,11 +103,9 @@ export function getHorizontalRangeToRender({ } export function getScrollDirection(prevScroll: ScrollPosition, nextScroll: ScrollPosition): SCROLL_DIRECTION { - if (nextScroll.scrollTop !== prevScroll.scrollTop) { - return nextScroll.scrollTop - prevScroll.scrollTop >= 0 ? SCROLL_DIRECTION.DOWN : SCROLL_DIRECTION.UP; - } - if (nextScroll.scrollLeft !== prevScroll.scrollLeft) { - return nextScroll.scrollLeft - prevScroll.scrollLeft >= 0 ? SCROLL_DIRECTION.RIGHT : SCROLL_DIRECTION.LEFT; - } + if (nextScroll.scrollTop > prevScroll.scrollTop) return SCROLL_DIRECTION.DOWN; + if (nextScroll.scrollTop < prevScroll.scrollTop) return SCROLL_DIRECTION.UP; + if (nextScroll.scrollLeft > prevScroll.scrollLeft) return SCROLL_DIRECTION.RIGHT; + if (nextScroll.scrollLeft < prevScroll.scrollLeft) return SCROLL_DIRECTION.LEFT; return SCROLL_DIRECTION.NONE; } From 414290d6893d102cc64f6f063a2f61769eddc6f6 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Thu, 26 Sep 2019 08:38:15 -0500 Subject: [PATCH 20/20] Ignore first render so we can get the viewport width --- packages/react-data-grid/src/Grid.tsx | 13 +++++++++--- packages/react-data-grid/src/Viewport.tsx | 24 ++++++++++++----------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/packages/react-data-grid/src/Grid.tsx b/packages/react-data-grid/src/Grid.tsx index 92e2d3a79a..d3718a6f8f 100644 --- a/packages/react-data-grid/src/Grid.tsx +++ b/packages/react-data-grid/src/Grid.tsx @@ -1,4 +1,4 @@ -import React, { useRef, createElement } from 'react'; +import React, { useRef, createElement, useEffect } from 'react'; import { isValidElementType } from 'react-is'; import Header from './Header'; @@ -55,6 +55,7 @@ export interface GridProps extends SharedDataGridProps, SharedDataGridStat } export default function Grid({ emptyRowsView, headerRows, ...props }: GridProps) { + const isWidthInitialized = useRef(false); const grid = useRef(null); const header = useRef>(null); const scrollLeft = useRef(0); @@ -69,6 +70,12 @@ export default function Grid({ emptyRowsView, headerRows, ...props }: GridPro } } + useEffect(() => { + // Delay rendering until width is initialized + // Width is needed to calculate the number of displayed columns + isWidthInitialized.current = true; + }, []); + return (
({ emptyRowsView, headerRows, ...props }: GridPro {createElement(emptyRowsView)}
) : ( - grid.current && ( + isWidthInitialized.current && ( rowKey={props.rowKey} rowHeight={props.rowHeight} @@ -123,7 +130,7 @@ export default function Grid({ emptyRowsView, headerRows, ...props }: GridPro enableIsScrolling={props.enableIsScrolling} onViewportKeydown={props.onViewportKeydown} onViewportKeyup={props.onViewportKeyup} - viewportWidth={grid.current.offsetWidth} + viewportWidth={grid.current ? grid.current.offsetWidth : 0} /> ) )} diff --git a/packages/react-data-grid/src/Viewport.tsx b/packages/react-data-grid/src/Viewport.tsx index 2494a7157b..0055197b0a 100644 --- a/packages/react-data-grid/src/Viewport.tsx +++ b/packages/react-data-grid/src/Viewport.tsx @@ -61,11 +61,9 @@ export default function Viewport({ ...props }: ViewportProps) { const resetScrollStateTimeoutId = useRef(null); - const [scrollState, setScrollState] = useState({ - scrollLeft: 0, - scrollTop: 0, - scrollDirection: SCROLL_DIRECTION.NONE - }); + const [scrollLeft, setScrollLeft] = useState(0); + const [scrollTop, setScrollTop] = useState(0); + const [scrollDirection, setScrollDirection] = useState(SCROLL_DIRECTION.NONE); const [isScrolling, setIsScrolling] = useState(undefined); function clearScrollTimer() { @@ -88,20 +86,24 @@ export default function Viewport({ setIsScrolling(false); } - function onScroll({ scrollLeft, scrollTop }: ScrollPosition) { + function onScroll({ scrollLeft: newScrollLeft, scrollTop: newScrollTop }: ScrollPosition) { if (enableIsScrolling) { setIsScrolling(true); resetScrollStateAfterDelay(); } - const scrollDirection = getScrollDirection(scrollState, { scrollLeft, scrollTop }); - const newScrollState = { scrollLeft, scrollTop, scrollDirection }; - setScrollState(newScrollState); - handleScroll(newScrollState); + const newScrollDirection = getScrollDirection({ scrollLeft, scrollTop }, { scrollLeft: newScrollLeft, scrollTop: newScrollTop }); + setScrollLeft(newScrollLeft); + setScrollTop(newScrollTop); + setScrollDirection(newScrollDirection); + handleScroll({ + scrollLeft: newScrollLeft, + scrollTop: newScrollTop, + scrollDirection: newScrollDirection + }); } const canvasHeight = minHeight - rowOffsetHeight; - const { scrollLeft, scrollTop, scrollDirection } = scrollState; const verticalRangeToRender = useMemo(() => { return getVerticalRangeToRender({