Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -620,9 +620,9 @@ See the [`PositionChangeArgs`](#positionchangeargstrow-tsummaryrow) type in the

###### `onFill?: Maybe<(event: FillEvent<R>) => R>`

###### `onScroll?: Maybe<(event: React.UIEvent<HTMLDivElement>) => void>`
###### `onScroll?: React.UIEventHandler<HTMLDivElement> | undefined`

Callback triggered when the grid is scrolled.
Native DOM `onScroll` prop.

###### `onColumnResize?: Maybe<(column: CalculatedColumn<R, SR>, width: number) => void>`

Expand Down
107 changes: 33 additions & 74 deletions src/DataGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useImperativeHandle, useLayoutEffect, useMemo, useState } from 'react';
import { useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react';
import type { Key, KeyboardEvent } from 'react';
import { flushSync } from 'react-dom';

Expand All @@ -11,17 +11,22 @@ import {
useColumnWidths,
useGridDimensions,
useLatestFunc,
useScrollState,
useScrollToPosition,
useViewportColumns,
useViewportRows,
type HeaderRowSelectionContextValue
type ActivePosition,
type HeaderRowSelectionContextValue,
type PartialPosition
} from './hooks';
import {
abs,
assertIsValidKeyGetter,
canExitGrid,
classnames,
createCellEvent,
focusCell,
getCellStyle,
getCellToScroll,
getColSpan,
getLeftRightKey,
getNextActivePosition,
Expand Down Expand Up @@ -66,8 +71,6 @@ import EditCell from './EditCell';
import GroupedColumnHeaderRow from './GroupedColumnHeaderRow';
import HeaderRow from './HeaderRow';
import { defaultRenderRow } from './Row';
import type { PartialPosition } from './ScrollToCell';
import ScrollToCell from './ScrollToCell';
import { default as defaultRenderSortStatus } from './sortStatus';
import { cellDragHandleClassname, cellDragHandleFrozenClassname } from './style/cell';
import {
Expand Down Expand Up @@ -106,6 +109,7 @@ type SharedDivProps = Pick<
| 'aria-rowcount'
| 'className'
| 'style'
| 'onScroll'
>;

export interface DataGridProps<R, SR = unknown, K extends Key = Key> extends SharedDivProps {
Expand Down Expand Up @@ -190,8 +194,6 @@ export interface DataGridProps<R, SR = unknown, K extends Key = Key> extends Sha
>;
/** Function called whenever the active position is changed */
onActivePositionChange?: Maybe<(args: PositionChangeArgs<NoInfer<R>, NoInfer<SR>>) => void>;
/** Callback triggered when the grid is scrolled */
onScroll?: Maybe<(event: React.UIEvent<HTMLDivElement>) => void>;
/** Callback triggered when column is resized */
onColumnResize?: Maybe<(column: CalculatedColumn<R, SR>, width: number) => void>;
/** Callback triggered when columns are reordered */
Expand Down Expand Up @@ -303,19 +305,22 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
const enableVirtualization = rawEnableVirtualization ?? true;
const direction = rawDirection ?? 'ltr';

/**
* ref
*/
const gridRef = useRef<HTMLDivElement>(null);

/**
* states
*/
const [scrollTop, setScrollTop] = useState(0);
const [scrollLeft, setScrollLeft] = useState(0);
const { scrollTop, scrollLeft } = useScrollState(gridRef);
const [gridWidth, gridHeight] = useGridDimensions({ gridRef });
const [columnWidthsInternal, setColumnWidthsInternal] = useState(
(): ColumnWidths => columnWidthsRaw ?? new Map()
);
const [isColumnResizing, setIsColumnResizing] = useState(false);
const [isDragging, setIsDragging] = useState(false);
const [draggedOverRowIdx, setDraggedOverRowIdx] = useState<number | undefined>(undefined);
const [scrollToPosition, setScrollToPosition] = useState<PartialPosition | null>(null);
const [shouldFocusPosition, setShouldFocusPosition] = useState(false);
const [previousRowIdx, setPreviousRowIdx] = useState(-1);

const isColumnWidthsControlled =
Expand All @@ -336,7 +341,6 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
[columnWidths]
);

const [gridRef, gridWidth, gridHeight] = useGridDimensions();
const {
columns,
colSpanColumns,
Expand Down Expand Up @@ -383,6 +387,7 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
const {
activePosition,
setActivePosition,
setPositionToFocus,
activePositionIsInActiveBounds,
activePositionIsInViewport,
activePositionIsRow,
Expand All @@ -391,15 +396,16 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
getActiveColumn,
getActiveRow
} = useActivePosition<R, SR>({
gridRef,
columns,
rows,
isTreeGrid,
maxColIdx,
minRowIdx,
maxRowIdx,
setDraggedOverRowIdx,
setShouldFocusPosition
setDraggedOverRowIdx
});
const { setScrollToPosition, scrollToPositionElement } = useScrollToPosition({ gridRef });

const defaultGridComponents = useMemo(
() => ({
Expand Down Expand Up @@ -496,19 +502,8 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
const selectHeaderCellLatest = useLatestFunc(selectHeaderCell);

/**
* effects
* Misc hooks
*/
useLayoutEffect(() => {
if (shouldFocusPosition) {
if (activePositionIsRow) {
focusRow(gridRef.current!);
} else {
focusCell(gridRef.current!);
}
setShouldFocusPosition(false);
}
}, [shouldFocusPosition, activePositionIsRow, gridRef]);

useImperativeHandle(
ref,
(): DataGridHandle => ({
Expand Down Expand Up @@ -634,16 +629,6 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
}
}

function handleScroll(event: React.UIEvent<HTMLDivElement>) {
const { scrollTop, scrollLeft } = event.currentTarget;
flushSync(() => {
setScrollTop(scrollTop);
// scrollLeft is nagative when direction is rtl
setScrollLeft(abs(scrollLeft));
});
onScroll?.(event);
}

function updateRow(column: CalculatedColumn<R, SR>, rowIdx: number, row: R) {
if (typeof onRowsChange !== 'function') return;
if (row === rows[rowIdx]) return;
Expand Down Expand Up @@ -808,8 +793,11 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
// Avoid re-renders if the selected cell state is the same
scrollIntoView(getCellToScroll(gridRef.current!));
} else {
setShouldFocusPosition(options?.shouldFocus === true);
setActivePosition({ ...position, mode: 'ACTIVE' });
const newPosition: ActivePosition = { ...position, mode: 'ACTIVE' };
setActivePosition(newPosition);
if (options?.shouldFocus) {
setPositionToFocus(newPosition);
}
}

if (onActivePositionChange && !samePosition) {
Expand Down Expand Up @@ -992,8 +980,11 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
const colSpan = getColSpan(column, lastFrozenColumnIndex, { type: 'ROW', row });

function closeEditor(shouldFocus: boolean) {
setShouldFocusPosition(shouldFocus);
setActivePosition(({ idx, rowIdx }) => ({ idx, rowIdx, mode: 'ACTIVE' }));
const newPosition: ActivePosition = { idx: activePosition.idx, rowIdx, mode: 'ACTIVE' };
setActivePosition(newPosition);
if (shouldFocus) {
setPositionToFocus(newPosition);
}
}

function onRowChange(row: R, commitChanges: boolean, shouldFocus: boolean) {
Expand Down Expand Up @@ -1131,7 +1122,7 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
}}
dir={direction}
ref={gridRef}
onScroll={handleScroll}
onScroll={onScroll}
onKeyDown={handleKeyDown}
onCopy={handleCellCopy}
onPaste={handleCellPaste}
Expand Down Expand Up @@ -1281,43 +1272,11 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
{/* render empty cells that span only 1 column so we can safely measure column widths, regardless of colSpan */}
{renderMeasuringCells(viewportColumns)}

{scrollToPosition !== null && (
<ScrollToCell
scrollToPosition={scrollToPosition}
setScrollToCellPosition={setScrollToPosition}
gridRef={gridRef}
/>
)}
{scrollToPositionElement}
</div>
);
}

function getRowToScroll(gridEl: HTMLDivElement) {
return gridEl.querySelector<HTMLDivElement>(':scope > [role="row"][tabindex="0"]');
}

function getCellToScroll(gridEl: HTMLDivElement) {
return gridEl.querySelector<HTMLDivElement>(':scope > [role="row"] > [tabindex="0"]');
}

function isSamePosition(p1: Position, p2: Position) {
return p1.idx === p2.idx && p1.rowIdx === p2.rowIdx;
}

function focusElement(element: HTMLDivElement | null, shouldScroll: boolean) {
if (element === null) return;

if (shouldScroll) {
scrollIntoView(element);
}

element.focus({ preventScroll: true });
}

function focusRow(gridEl: HTMLDivElement) {
focusElement(getRowToScroll(gridEl), true);
}

function focusCell(gridEl: HTMLDivElement, shouldScroll = true) {
focusElement(getCellToScroll(gridEl), shouldScroll);
}
43 changes: 0 additions & 43 deletions src/ScrollToCell.tsx

This file was deleted.

2 changes: 2 additions & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ export * from './useGridDimensions';
export * from './useLatestFunc';
export * from './useRovingTabIndex';
export * from './useRowSelection';
export * from './useScrollState';
export * from './useScrollToPosition';
export * from './useViewportColumns';
export * from './useViewportRows';
27 changes: 21 additions & 6 deletions src/hooks/useActivePosition.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useState } from 'react';
import { useLayoutEffect, useState } from 'react';

import { focusCell, focusRow } from '../utils';
import type { CalculatedColumn, Position, StateSetter } from '../types';

interface ActivePosition extends Position {
export interface ActivePosition extends Position {
readonly mode: 'ACTIVE';
}

Expand All @@ -20,27 +21,30 @@ const initialActivePosition: ActivePosition = {
};

export function useActivePosition<R, SR>({
gridRef,
columns,
rows,
isTreeGrid,
maxColIdx,
minRowIdx,
maxRowIdx,
setDraggedOverRowIdx,
setShouldFocusPosition
setDraggedOverRowIdx
}: {
gridRef: React.RefObject<HTMLDivElement | null>;
columns: readonly CalculatedColumn<R, SR>[];
rows: readonly R[];
isTreeGrid: boolean;
maxColIdx: number;
minRowIdx: number;
maxRowIdx: number;
setDraggedOverRowIdx: StateSetter<number | undefined>;
setShouldFocusPosition: StateSetter<boolean>;
}) {
const [activePosition, setActivePosition] = useState<ActivePosition | EditPosition<R>>(
initialActivePosition
);
const [positionToFocus, setPositionToFocus] = useState<ActivePosition | EditPosition<R> | null>(
null
);

/**
* Returns whether the given position represents a valid cell or row position in the grid.
Expand Down Expand Up @@ -123,14 +127,25 @@ export function useActivePosition<R, SR>({
mode: 'ACTIVE'
};
setActivePosition(newPosition);
setShouldFocusPosition(false);
setPositionToFocus(null);
({ resolvedActivePosition, validatedPosition } = getResolvedValues(newPosition));
}
}

useLayoutEffect(() => {
if (positionToFocus !== null) {
if (positionToFocus.idx === -1) {
focusRow(gridRef.current!);
} else {
focusCell(gridRef.current!);
}
}
}, [positionToFocus, gridRef]);

return {
activePosition: resolvedActivePosition,
setActivePosition,
setPositionToFocus,
activePositionIsInActiveBounds: validatedPosition.isPositionInActiveBounds,
activePositionIsInViewport: validatedPosition.isPositionInViewport,
activePositionIsRow: validatedPosition.isRowInActiveBounds,
Expand Down
Loading