Skip to content

Reimplement drag fill #3766

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Apr 24, 2025
148 changes: 115 additions & 33 deletions src/DataGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
assertIsValidKeyGetter,
canExitGrid,
createCellEvent,
getCellStyle,
getColSpan,
getLeftRightKey,
getNextSelectedCellPosition,
Expand Down Expand Up @@ -67,14 +68,14 @@ import {
DataGridDefaultRenderersContext,
useDefaultRenderers
} from './DataGridDefaultRenderersContext';
import DragHandle from './DragHandle';
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 {
focusSinkClassname,
focusSinkHeaderAndSummaryClassname,
Expand Down Expand Up @@ -332,7 +333,7 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
);
const [isColumnResizing, setColumnResizing] = useState(false);
const [isDragging, setDragging] = useState(false);
const [draggedOverRowIdx, setOverRowIdx] = useState<number | undefined>(undefined);
const [draggedOverRowIdx, setDraggedOverRowIdx] = useState<number | undefined>(undefined);
const [scrollToPosition, setScrollToPosition] = useState<PartialPosition | null>(null);
const [shouldFocusCell, setShouldFocusCell] = useState(false);
const [previousRowIdx, setPreviousRowIdx] = useState(-1);
Expand Down Expand Up @@ -391,7 +392,6 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
/**
* refs
*/
const latestDraggedOverRowIdx = useRef(draggedOverRowIdx);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

No longer needed as we are using react event handlers

const focusSinkRef = useRef<HTMLDivElement>(null);

/**
Expand Down Expand Up @@ -506,20 +506,22 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
/**
* callbacks
*/
const setDraggedOverRowIdx = useCallback((rowIdx?: number) => {
setOverRowIdx(rowIdx);
latestDraggedOverRowIdx.current = rowIdx;
}, []);
const focusCellOrCellContent = useCallback(
(shouldScroll = true) => {
const cell = getCellToScroll(gridRef.current!);
if (cell === null) return;

const focusCellOrCellContent = useCallback(() => {
const cell = getCellToScroll(gridRef.current!);
if (cell === null) return;
if (shouldScroll) {
scrollIntoView(cell);
}

scrollIntoView(cell);
// Focus cell content when available instead of the cell itself
const elementToFocus = cell.querySelector<Element & HTMLOrSVGElement>('[tabindex="0"]') ?? cell;
elementToFocus.focus({ preventScroll: true });
}, [gridRef]);
// Focus cell content when available instead of the cell itself
const elementToFocus =
cell.querySelector<Element & HTMLOrSVGElement>('[tabindex="0"]') ?? cell;
elementToFocus.focus({ preventScroll: true });
},
[gridRef]
);

/**
* effects
Expand Down Expand Up @@ -735,6 +737,80 @@ 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) {
return;
}
setDragging(true);
event.currentTarget.setPointerCapture(event.pointerId);
}

function handleDragHandlePointerMove(event: React.PointerEvent<HTMLDivElement>) {
// find dragged over row using the pointer position
const gridEl = gridRef.current!;
const headerAndTopSummaryRowsHeight = headerRowsHeight + topSummaryRowsCount * summaryRowHeight;
const offset =
scrollTop -
headerAndTopSummaryRowsHeight +
event.clientY -
gridEl.getBoundingClientRect().top;
Comment on lines +754 to +758
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is there a better way?

const overRowIdx = findRowIdx(offset);
setDraggedOverRowIdx(overRowIdx);
const ariaRowIndex = headerAndTopSummaryRowsCount + overRowIdx + 1;
const el = gridEl.querySelector(
`:scope > [aria-rowindex="${ariaRowIndex}"] > [aria-colindex="${selectedPosition.idx + 1}"]`
);
scrollIntoView(el);
}

function handleDragHandleLostPointerCapture() {
setDragging(false);
if (draggedOverRowIdx === undefined) return;

const { rowIdx } = selectedPosition;
const [startRowIndex, endRowIndex] =
rowIdx < draggedOverRowIdx
? [rowIdx + 1, draggedOverRowIdx + 1]
: [draggedOverRowIdx, rowIdx];
updateRows(startRowIndex, endRowIndex);
setDraggedOverRowIdx(undefined);
}

function handleDragHandleClick() {
// keep the focus on the cell but do not scroll
focusCellOrCellContent(false);
}

function handleDragHandleDoubleClick(event: React.MouseEvent<HTMLDivElement>) {
event.stopPropagation();
updateRows(selectedPosition.rowIdx + 1, rows.length);
}

function updateRows(startRowIdx: number, endRowIdx: number) {
if (onRowsChange == null) return;

const { rowIdx, idx } = selectedPosition;
const column = columns[idx];
const sourceRow = rows[rowIdx];
const updatedRows = [...rows];
const indexes: number[] = [];
for (let i = startRowIdx; i < endRowIdx; i++) {
if (isCellEditable({ rowIdx: i, idx })) {
const updatedRow = onFill!({ columnKey: column.key, sourceRow, targetRow: rows[i] });
if (updatedRow !== rows[i]) {
updatedRows[i] = updatedRow;
indexes.push(i);
}
}
}

if (indexes.length > 0) {
onRowsChange(updatedRows, { indexes, column });
}
}

/**
* utils
*/
Expand Down Expand Up @@ -890,7 +966,7 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
return isDraggedOver ? selectedPosition.idx : undefined;
}

function renderDragHandle() {
function getDragHandle() {
if (
onFill == null ||
selectedPosition.mode === 'EDIT' ||
Expand All @@ -905,24 +981,31 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
return;
}

const isLastRow = rowIdx === maxRowIdx;
const columnWidth = getColumnWidth(column);
const colSpan = column.colSpan?.({ type: 'ROW', row: rows[rowIdx] }) ?? 1;
const { insetInlineStart, ...style } = getCellStyle(column, colSpan);
const marginEnd = 'calc(var(--rdg-drag-handle-size) * -0.5 + 1px)';
const isLastColumn = column.idx + colSpan - 1 === maxColIdx;
const dragHandleStyle: React.CSSProperties = {
...style,
gridRowStart: headerAndTopSummaryRowsCount + rowIdx + 1,
marginInlineEnd: isLastColumn ? undefined : marginEnd,
marginBlockEnd: isLastRow ? undefined : marginEnd,
insetInlineStart: insetInlineStart
? `calc(${insetInlineStart} + ${columnWidth}px + var(--rdg-drag-handle-size) * -0.5 - 1px)`
: undefined
};

return (
<DragHandle
gridRowStart={headerAndTopSummaryRowsCount + rowIdx + 1}
rows={rows}
column={column}
columnWidth={columnWidth}
maxColIdx={maxColIdx}
isLastRow={rowIdx === maxRowIdx}
selectedPosition={selectedPosition}
isCellEditable={isCellEditable}
latestDraggedOverRowIdx={latestDraggedOverRowIdx}
onRowsChange={onRowsChange}
onClick={focusCellOrCellContent}
onFill={onFill}
setDragging={setDragging}
setDraggedOverRowIdx={setDraggedOverRowIdx}
<div
style={dragHandleStyle}
className={clsx(cellDragHandleClassname, column.frozen && cellDragHandleFrozenClassname)}
onPointerDown={handleDragHandlePointerDown}
onPointerMove={isDragging ? handleDragHandlePointerMove : undefined}
onLostPointerCapture={isDragging ? handleDragHandleLostPointerCapture : undefined}
onClick={handleDragHandleClick}
onDoubleClick={handleDragHandleDoubleClick}
/>
);
}
Expand Down Expand Up @@ -1055,7 +1138,6 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
gridRowStart,
selectedCellIdx: selectedRowIdx === rowIdx ? selectedIdx : undefined,
draggedOverCellIdx: getDraggedOverCellIdx(rowIdx),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can probably add another div and get rid of draggedOverCellIdx

setDraggedOverRowIdx: isDragging ? setDraggedOverRowIdx : undefined,
lastFrozenColumnIndex,
onRowChange: handleFormatterRowChangeLatest,
selectCell: selectCellLatest,
Expand Down Expand Up @@ -1239,7 +1321,7 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
)}
</DataGridDefaultRenderersContext>

{renderDragHandle()}
{getDragHandle()}

{/* render empty cells that span only 1 column so we can safely measure column widths, regardless of colSpan */}
{renderMeasuringCells(viewportColumns)}
Expand Down
151 changes: 0 additions & 151 deletions src/DragHandle.tsx

This file was deleted.

Loading