Skip to content
Merged
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
152 changes: 64 additions & 88 deletions src/DataGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,4 @@
import {
useCallback,
useImperativeHandle,
useLayoutEffect,
useMemo,
useRef,
useState
} from 'react';
import { useCallback, useImperativeHandle, useLayoutEffect, useMemo, useState } from 'react';
import type { Key, KeyboardEvent } from 'react';
import { flushSync } from 'react-dom';

Expand Down Expand Up @@ -76,13 +69,7 @@ import type { PartialPosition } from './ScrollToCell';
import ScrollToCell from './ScrollToCell';
import { default as defaultRenderSortStatus } from './sortStatus';
import { cellDragHandleClassname, cellDragHandleFrozenClassname } from './style/cell';
import {
focusSinkClassname,
focusSinkHeaderAndSummaryClassname,
rootClassname,
viewportDraggingClassname
} from './style/core';
import { rowSelected, rowSelectedWithFrozenCell } from './style/row';
import { rootClassname, viewportDraggingClassname } from './style/core';
import SummaryRow from './SummaryRow';

export interface SelectCellState extends Position {
Expand Down Expand Up @@ -386,11 +373,6 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
(): SelectCellState | EditCellState<R> => ({ idx: -1, rowIdx: minRowIdx - 1, mode: 'SELECT' })
);

/**
* refs
*/
const focusSinkRef = useRef<HTMLDivElement>(null);

/**
* computed values
*/
Expand Down Expand Up @@ -499,37 +481,19 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
const selectCellLatest = useLatestFunc(selectCell);
const selectHeaderCellLatest = useLatestFunc(selectHeaderCell);

/**
* callbacks
*/
const focusCell = useCallback(
(shouldScroll = true) => {
const cell = getCellToScroll(gridRef.current!);
if (cell === null) return;

if (shouldScroll) {
scrollIntoView(cell);
}

cell.focus({ preventScroll: true });
},
[gridRef]
);

/**
* effects
*/
useLayoutEffect(() => {
if (shouldFocusCell) {
if (focusSinkRef.current !== null && selectedPosition.idx === -1) {
focusSinkRef.current.focus({ preventScroll: true });
scrollIntoView(focusSinkRef.current);
if (selectedPosition.idx === -1) {
focusRow(gridRef.current!);
} else {
focusCell();
focusCell(gridRef.current!);
}
setShouldFocusCell(false);
}
}, [shouldFocusCell, focusCell, selectedPosition.idx]);
}, [shouldFocusCell, selectedPosition.idx, gridRef]);

useImperativeHandle(
ref,
Expand Down Expand Up @@ -632,9 +596,13 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
if (cellEvent.isGridDefaultPrevented()) return;
}

if (!(event.target instanceof Element)) return;
const isCellEvent = event.target.closest('.rdg-cell') !== null;
const isRowEvent = isTreeGrid && event.target === focusSinkRef.current;
const { target } = event;

if (!(target instanceof Element)) return;

const isCellEvent = target.closest('.rdg-cell') !== null;
const isRowEvent = isTreeGrid && target.role === 'row';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wonder if we can/should move the focus logic to the TreeGrid component. I will check


if (!isCellEvent && !isRowEvent) return;

switch (event.key) {
Expand Down Expand Up @@ -734,7 +702,7 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
function handleDragHandlePointerDown(event: React.PointerEvent<HTMLDivElement>) {
// keep the focus on the cell
event.preventDefault();
if (event.pointerType === 'mouse' && event.buttons !== 1) {
if (event.pointerType === 'mouse' && event.button !== 0) {
return;
}
setDragging(true);
Expand Down Expand Up @@ -774,7 +742,7 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr

function handleDragHandleClick() {
// keep the focus on the cell but do not scroll
focusCell(false);
focusCell(gridRef.current!, false);
}

function handleDragHandleDoubleClick(event: React.MouseEvent<HTMLDivElement>) {
Expand Down Expand Up @@ -870,20 +838,32 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
const isRowSelected = selectedCellIsWithinSelectionBounds && idx === -1;

switch (key) {
case 'ArrowUp':
return { idx, rowIdx: rowIdx - 1 };
case 'ArrowUp': {
const nextRowIdx = rowIdx - 1;
return {
// avoid selecting header rows
idx: idx === -1 && nextRowIdx < -topSummaryRowsCount ? 0 : idx,
rowIdx: nextRowIdx
};
}
case 'ArrowDown':
return { idx, rowIdx: rowIdx + 1 };
case leftKey:
return { idx: idx - 1, rowIdx };
case leftKey: {
const nextIdx = idx - 1;
return {
// avoid selecting header rows
idx: rowIdx < -topSummaryRowsCount && nextIdx < 0 ? 0 : nextIdx,
rowIdx
};
}
case rightKey:
return { idx: idx + 1, rowIdx };
case 'Tab':
return { idx: idx + (shiftKey ? -1 : 1), rowIdx };
case 'Home':
// If row is selected then move focus to the first row
if (isRowSelected) return { idx, rowIdx: minRowIdx };
return { idx: 0, rowIdx: ctrlKey ? minRowIdx : rowIdx };
// If row is selected then move focus to the first header row's cell.
if (isRowSelected || ctrlKey) return { idx: 0, rowIdx: minRowIdx };
return { idx: 0, rowIdx };
case 'End':
// If row is selected then move focus to the last row.
if (isRowSelected) return { idx, rowIdx: maxRowIdx };
Expand Down Expand Up @@ -1149,7 +1129,8 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
lastFrozenColumnIndex,
onRowChange: handleFormatterRowChangeLatest,
selectCell: selectCellLatest,
selectedCellEditor: getCellEditor(rowIdx)
selectedCellEditor: getCellEditor(rowIdx),
isTreeGrid
})
);
}
Expand Down Expand Up @@ -1179,9 +1160,6 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
templateRows += ` repeat(${bottomSummaryRowsCount}, ${summaryRowHeight}px)`;
}

const isGroupRowFocused =
selectedPosition.idx === -1 && selectedPosition.rowIdx !== minRowIdx - 1;

return (
<div
role={role}
Expand All @@ -1204,18 +1182,10 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
)}
style={{
...style,
// set scrollPadding to correctly position non-sticky cells after scrolling
scrollPaddingInlineStart:
selectedPosition.idx > lastFrozenColumnIndex || scrollToPosition?.idx !== undefined
? `${totalFrozenColumnWidth}px`
: undefined,
scrollPaddingBlock:
isRowIdxWithinViewportBounds(selectedPosition.rowIdx) ||
scrollToPosition?.rowIdx !== undefined
? `${headerRowsHeight + topSummaryRowsCount * summaryRowHeight}px ${
bottomSummaryRowsCount * summaryRowHeight
}px`
: undefined,
// set scrollPadding to correctly scroll to non-sticky cells/rows
scrollPaddingInlineStart: totalFrozenColumnWidth,
scrollPaddingBlockStart: headerRowsHeight + topSummaryRowsCount * summaryRowHeight,
scrollPaddingBlockEnd: bottomSummaryRowsCount * summaryRowHeight,
gridTemplateColumns,
gridTemplateRows: templateRows,
'--rdg-header-row-height': `${headerRowHeight}px`,
Expand Down Expand Up @@ -1289,6 +1259,7 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
selectedCellIdx={isSummaryRowSelected ? selectedPosition.idx : undefined}
isTop
selectCell={selectCellLatest}
isTreeGrid={isTreeGrid}
/>
);
})}
Expand Down Expand Up @@ -1322,6 +1293,7 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
selectedCellIdx={isSummaryRowSelected ? selectedPosition.idx : undefined}
isTop={false}
selectCell={selectCellLatest}
isTreeGrid={isTreeGrid}
/>
);
})}
Expand All @@ -1334,24 +1306,6 @@ 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)}

{/* extra div is needed for row navigation in a treegrid */}
{isTreeGrid && (
<div
ref={focusSinkRef}
tabIndex={isGroupRowFocused ? 0 : -1}
className={classnames(focusSinkClassname, {
[focusSinkHeaderAndSummaryClassname]: !isRowIdxWithinViewportBounds(
selectedPosition.rowIdx
),
[rowSelected]: isGroupRowFocused,
[rowSelectedWithFrozenCell]: isGroupRowFocused && lastFrozenColumnIndex !== -1
})}
style={{
gridRowStart: selectedPosition.rowIdx + headerAndTopSummaryRowsCount + 1
}}
/>
)}

{scrollToPosition !== null && (
<ScrollToCell
scrollToPosition={scrollToPosition}
Expand All @@ -1363,10 +1317,32 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
);
}

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);
}
11 changes: 5 additions & 6 deletions src/GroupCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,19 @@ function GroupCell<R, SR>({

return (
<div
key={column.key}
role="gridcell"
aria-colindex={column.idx + 1}
aria-selected={isCellSelected}
tabIndex={tabIndex}
key={column.key}
// tabIndex={undefined} prevents clicks on the cell
// from stealing focus from the row.
// onMouseDown={preventDefault} would break mousewheel clicks
tabIndex={tabIndex === -1 ? undefined : tabIndex}
className={getCellClassname(column)}
style={{
...getCellStyle(column),
cursor: isLevelMatching ? 'pointer' : 'default'
}}
onMouseDown={(event) => {
// prevents clicking on the cell from stealing focus from focusSink
event.preventDefault();
}}
onClick={isLevelMatching ? toggleGroup : undefined}
onFocus={onFocus}
>
Expand Down
8 changes: 5 additions & 3 deletions src/GroupRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { memo, useMemo } from 'react';
import { css } from 'ecij';

import { RowSelectionContext, type RowSelectionContextValue } from './hooks';
import { classnames, getRowStyle } from './utils';
import { classnames } from './utils';
import type { BaseRenderRowProps, GroupRow } from './types';
import { SELECT_COLUMN_KEY } from './Columns';
import GroupCell from './GroupCell';
Expand Down Expand Up @@ -44,6 +44,7 @@ function GroupedRow<R, SR>({
isRowSelectionDisabled: _isRowSelectionDisabled,
...props
}: GroupRowRendererProps<R, SR>) {
const isPositionOnRow = selectedCellIdx === -1;
// Select is always the first column
const idx = viewportColumns[0].key === SELECT_COLUMN_KEY ? row.level + 1 : row.level;

Expand All @@ -64,15 +65,16 @@ function GroupedRow<R, SR>({
aria-setsize={row.setSize}
aria-posinset={row.posInSet + 1} // aria-posinset is 1-based
aria-expanded={row.isExpanded}
tabIndex={isPositionOnRow ? 0 : -1}
className={classnames(
rowClassname,
groupRowClassname,
`rdg-row-${rowIdx % 2 === 0 ? 'even' : 'odd'}`,
selectedCellIdx === -1 && rowSelectedClassname,
isPositionOnRow && rowSelectedClassname,
className
)}
onMouseDown={handleSelectGroup}
style={getRowStyle(gridRowStart)}
style={{ gridRowStart }}
{...props}
>
{viewportColumns.map((column) => (
Expand Down
14 changes: 4 additions & 10 deletions src/HeaderCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,15 @@ const resizeHandleClassname = `rdg-resize-handle ${resizeHandle}`;

const cellDraggableClassname = 'rdg-cell-draggable';

const cellDragging = css`
const cellDraggingOrOver = css`
@layer rdg.HeaderCell {
background-color: var(--rdg-header-draggable-background-color);
}
`;

const cellDraggingClassname = `rdg-cell-dragging ${cellDragging}`;
const cellDraggingClassname = `rdg-cell-dragging ${cellDraggingOrOver}`;

const cellOver = css`
@layer rdg.HeaderCell {
background-color: var(--rdg-header-draggable-background-color);
}
`;

const cellOverClassname = `rdg-cell-drag-over ${cellOver}`;
const cellOverClassname = `rdg-cell-drag-over ${cellDraggingOrOver}`;

const dragImageClassname = css`
@layer rdg.HeaderCell {
Expand Down Expand Up @@ -336,7 +330,7 @@ function ResizeHandle<R, SR>({
const isRtl = direction === 'rtl';

function onPointerDown(event: React.PointerEvent<HTMLDivElement>) {
if (event.pointerType === 'mouse' && event.buttons !== 1) {
if (event.pointerType === 'mouse' && event.button !== 0) {
return;
}

Expand Down
5 changes: 2 additions & 3 deletions src/HeaderRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ function HeaderRow<R, SR, K extends React.Key>({
direction
}: HeaderRowProps<R, SR, K>) {
const [draggedColumnKey, setDraggedColumnKey] = useState<string>();
const isPositionOnRow = selectedCellIdx === -1;

const cells = [];
for (let index = 0; index < columns.length; index++) {
Expand Down Expand Up @@ -98,9 +99,7 @@ function HeaderRow<R, SR, K extends React.Key>({
aria-rowindex={rowIdx} // aria-rowindex is 1 based
className={classnames(
headerRowClassname,
{
[rowSelectedClassname]: selectedCellIdx === -1
},
isPositionOnRow && rowSelectedClassname,
headerRowClass
)}
>
Expand Down
Loading