Skip to content
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

Use native copy/paste event handlers #3667

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
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
14 changes: 0 additions & 14 deletions src/Cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,9 @@ import { useRovingTabIndex } from './hooks';
import { createCellEvent, getCellClassname, getCellStyle, isCellEditableUtil } from './utils';
import type { CellRendererProps } from './types';

const cellCopied = css`
@layer rdg.Cell {
background-color: #ccccff;
}
`;

const cellCopiedClassname = `rdg-cell-copied ${cellCopied}`;

const cellDraggedOver = css`
@layer rdg.Cell {
background-color: #ccccff;

&.${cellCopied} {
background-color: #9999ff;
}
}
`;

Expand All @@ -29,7 +17,6 @@ function Cell<R, SR>({
column,
colSpan,
isCellSelected,
isCopied,
isDraggedOver,
row,
rowIdx,
Expand All @@ -48,7 +35,6 @@ function Cell<R, SR>({
className = getCellClassname(
column,
{
[cellCopiedClassname]: isCopied,
[cellDraggedOverClassname]: isDraggedOver
},
typeof cellClass === 'function' ? cellClass(row) : cellClass,
Expand Down
80 changes: 22 additions & 58 deletions src/DataGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,18 @@ import {
import type {
CalculatedColumn,
CellClickArgs,
CellClipboardEvent,
CellCopyPasteEvent,
CellKeyboardEvent,
CellKeyDownArgs,
CellMouseEvent,
CellNavigationMode,
CellSelectArgs,
Column,
ColumnOrColumnGroup,
CopyEvent,
Direction,
FillEvent,
Maybe,
PasteEvent,
Position,
Renderers,
RowsChangeData,
Expand Down Expand Up @@ -175,8 +175,6 @@ export interface DataGridProps<R, SR = unknown, K extends Key = Key> extends Sha
onSortColumnsChange?: Maybe<(sortColumns: SortColumn[]) => void>;
defaultColumnOptions?: Maybe<DefaultColumnOptions<NoInfer<R>, NoInfer<SR>>>;
onFill?: Maybe<(event: FillEvent<NoInfer<R>>) => NoInfer<R>>;
onCopy?: Maybe<(event: CopyEvent<NoInfer<R>>) => void>;
onPaste?: Maybe<(event: PasteEvent<NoInfer<R>>) => NoInfer<R>>;

/**
* Event props
Expand All @@ -196,6 +194,12 @@ export interface DataGridProps<R, SR = unknown, K extends Key = Key> extends Sha
onCellKeyDown?: Maybe<
(args: CellKeyDownArgs<NoInfer<R>, NoInfer<SR>>, event: CellKeyboardEvent) => void
>;
onCellCopy?: Maybe<
(args: CellCopyPasteEvent<NoInfer<R>, NoInfer<SR>>, event: CellClipboardEvent) => void
>;
onCellPaste?: Maybe<
(args: CellCopyPasteEvent<NoInfer<R>, NoInfer<SR>>, event: CellClipboardEvent) => NoInfer<R>
>;
/** Function called whenever cell selection is changed */
onSelectedCellChange?: Maybe<(args: CellSelectArgs<NoInfer<R>, NoInfer<SR>>) => void>;
/** Called when the grid is scrolled */
Expand Down Expand Up @@ -260,8 +264,8 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
onColumnResize,
onColumnsReorder,
onFill,
onCopy,
onPaste,
onCellCopy,
onCellPaste,
// Toggles and modes
enableVirtualization: rawEnableVirtualization,
// Miscellaneous
Expand Down Expand Up @@ -310,7 +314,6 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
const [measuredColumnWidths, setMeasuredColumnWidths] = useState(
(): ReadonlyMap<string, number> => new Map()
);
const [copiedCell, setCopiedCell] = useState<{ row: R; columnKey: string } | null>(null);
const [isDragging, setDragging] = useState(false);
const [draggedOverRowIdx, setOverRowIdx] = useState<number | undefined>(undefined);
const [scrollToPosition, setScrollToPosition] = useState<PartialPosition | null>(null);
Expand Down Expand Up @@ -608,39 +611,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;
if (!isCellEvent && !isRowEvent) return;

// eslint-disable-next-line @typescript-eslint/no-deprecated
const { keyCode } = event;

if (
selectedCellIsWithinViewportBounds &&
(onPaste != null || onCopy != null) &&
isCtrlKeyHeldDown(event)
) {
// event.key may differ by keyboard input language, so we use event.keyCode instead
// event.nativeEvent.code cannot be used either as it would break copy/paste for the DVORAK layout
const cKey = 67;
const vKey = 86;
if (keyCode === cKey) {
// copy highlighted text only
if (window.getSelection()?.isCollapsed === false) return;
handleCopy();
return;
}
if (keyCode === vKey) {
handlePaste();
return;
}
}

switch (event.key) {
case 'Escape':
setCopiedCell(null);
return;
case 'ArrowUp':
case 'ArrowDown':
case 'ArrowLeft':
Expand Down Expand Up @@ -684,31 +661,21 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
updateRow(columns[selectedPosition.idx], selectedPosition.rowIdx, selectedPosition.row);
}

function handleCopy() {
function handleCellCopy(event: CellClipboardEvent) {
if (!selectedCellIsWithinViewportBounds) return;
const { idx, rowIdx } = selectedPosition;
const sourceRow = rows[rowIdx];
const sourceColumnKey = columns[idx].key;
setCopiedCell({ row: sourceRow, columnKey: sourceColumnKey });
onCopy?.({ sourceRow, sourceColumnKey });
onCellCopy?.({ row: rows[rowIdx], column: columns[idx] }, event);
}

function handlePaste() {
if (!onPaste || !onRowsChange || copiedCell === null || !isCellEditable(selectedPosition)) {
function handleCellPaste(event: CellClipboardEvent) {
if (!onCellPaste || !onRowsChange || !isCellEditable(selectedPosition)) {
return;
}

const { idx, rowIdx } = selectedPosition;
const targetColumn = columns[idx];
const targetRow = rows[rowIdx];

const updatedTargetRow = onPaste({
sourceRow: copiedCell.row,
sourceColumnKey: copiedCell.columnKey,
targetRow,
targetColumnKey: targetColumn.key
});

updateRow(targetColumn, rowIdx, updatedTargetRow);
const column = columns[idx];
const updatedRow = onCellPaste({ row: rows[rowIdx], column }, event);
updateRow(column, rowIdx, updatedRow);
}

function handleCellInput(event: KeyboardEvent<HTMLDivElement>) {
Expand All @@ -726,7 +693,7 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
return;
}

if (isCellEditable(selectedPosition) && isDefaultCellInput(event)) {
if (isCellEditable(selectedPosition) && isDefaultCellInput(event, onCellPaste != null)) {
setSelectedPosition(({ idx, rowIdx }) => ({
idx,
rowIdx,
Expand Down Expand Up @@ -1051,11 +1018,6 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
onCellContextMenu: onCellContextMenuLatest,
rowClass,
gridRowStart,
copiedCellIdx:
copiedCell !== null && copiedCell.row === row
? columns.findIndex((c) => c.key === copiedCell.columnKey)
: undefined,

selectedCellIdx: selectedRowIdx === rowIdx ? selectedIdx : undefined,
draggedOverCellIdx: getDraggedOverCellIdx(rowIdx),
setDraggedOverRowIdx: isDragging ? setDraggedOverRowIdx : undefined,
Expand Down Expand Up @@ -1135,6 +1097,8 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
ref={gridRef}
onScroll={handleScroll}
onKeyDown={handleKeyDown}
onCopy={handleCellCopy}
onPaste={handleCellPaste}
data-testid={testId}
data-cy={dataCy}
>
Expand Down
2 changes: 0 additions & 2 deletions src/Row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ function Row<R, SR>({
selectedCellIdx,
isRowSelectionDisabled,
isRowSelected,
copiedCellIdx,
draggedOverCellIdx,
lastFrozenColumnIndex,
row,
Expand Down Expand Up @@ -72,7 +71,6 @@ function Row<R, SR>({
colSpan,
row,
rowIdx,
isCopied: copiedCellIdx === idx,
isDraggedOver: draggedOverCellIdx === idx,
isCellSelected,
onClick: onCellClick,
Expand Down
29 changes: 22 additions & 7 deletions src/TreeDataGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { useCallback, useMemo } from 'react';
import type { Key } from 'react';

import { useLatestFunc } from './hooks';
import { assertIsValidKeyGetter, isCtrlKeyHeldDown } from './utils';
import { assertIsValidKeyGetter } from './utils';
import type {
CellClipboardEvent,
CellCopyPasteEvent,
CellKeyboardEvent,
CellKeyDownArgs,
Column,
Expand Down Expand Up @@ -53,6 +55,8 @@ export function TreeDataGrid<R, SR = unknown, K extends Key = Key>({
rowHeight: rawRowHeight,
rowKeyGetter: rawRowKeyGetter,
onCellKeyDown: rawOnCellKeyDown,
onCellCopy: rawOnCellCopy,
onCellPaste: rawOnCellPaste,
onRowsChange,
selectedRows: rawSelectedRows,
onSelectedRowsChange: rawOnSelectedRowsChange,
Expand Down Expand Up @@ -318,14 +322,25 @@ export function TreeDataGrid<R, SR = unknown, K extends Key = Key>({
selectCell({ idx, rowIdx: parentRowAndIndex[1] });
}
}
}

// Prevent copy/paste on group rows
// eslint-disable-next-line @typescript-eslint/no-deprecated
if (isCtrlKeyHeldDown(event) && (event.keyCode === 67 || event.keyCode === 86)) {
event.preventGridDefault();
// Prevent copy/paste on group rows
function handleCellCopy(
{ row, column }: CellCopyPasteEvent<NoInfer<R>, NoInfer<SR>>,
event: CellClipboardEvent
) {
if (!isGroupRow(row)) {
rawOnCellCopy?.({ row, column }, event);
}
}

function handleCellPaste(
{ row, column }: CellCopyPasteEvent<NoInfer<R>, NoInfer<SR>>,
event: CellClipboardEvent
) {
return isGroupRow(row) ? row : rawOnCellPaste!({ row, column }, event);
}

function handleRowsChange(updatedRows: R[], { indexes, column }: RowsChangeData<R, SR>) {
if (!onRowsChange) return;
const updatedRawRows = [...rawRows];
Expand Down Expand Up @@ -361,7 +376,6 @@ export function TreeDataGrid<R, SR = unknown, K extends Key = Key>({
onCellContextMenu,
onRowChange,
lastFrozenColumnIndex,
copiedCellIdx,
draggedOverCellIdx,
setDraggedOverRowIdx,
selectedCellEditor,
Expand Down Expand Up @@ -400,7 +414,6 @@ export function TreeDataGrid<R, SR = unknown, K extends Key = Key>({
onCellContextMenu,
onRowChange,
lastFrozenColumnIndex,
copiedCellIdx,
draggedOverCellIdx,
setDraggedOverRowIdx,
selectedCellEditor
Expand All @@ -422,6 +435,8 @@ export function TreeDataGrid<R, SR = unknown, K extends Key = Key>({
selectedRows={selectedRows}
onSelectedRowsChange={onSelectedRowsChange}
onCellKeyDown={handleKeyDown}
onCellCopy={handleCellCopy}
onCellPaste={rawOnCellPaste ? handleCellPaste : undefined}
renderers={{
...renderers,
renderRow
Expand Down
3 changes: 1 addition & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ export type {
SelectHeaderRowEvent,
SelectRowEvent,
FillEvent,
CopyEvent,
PasteEvent,
CellCopyPasteEvent,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should we add two separate events (CellCopyEvent and CellPasteEvent)?

SortDirection,
SortColumn,
ColSpanArgs,
Expand Down
25 changes: 9 additions & 16 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,6 @@ export interface CellRendererProps<TRow, TSummaryRow>
Omit<React.ComponentProps<'div'>, 'children' | 'onClick' | 'onDoubleClick' | 'onContextMenu'> {
column: CalculatedColumn<TRow, TSummaryRow>;
colSpan: number | undefined;
isCopied: boolean;
isDraggedOver: boolean;
isCellSelected: boolean;
onClick: RenderRowProps<TRow, TSummaryRow>['onCellClick'];
Expand All @@ -171,25 +170,27 @@ export type CellMouseEvent = CellEvent<React.MouseEvent<HTMLDivElement>>;

export type CellKeyboardEvent = CellEvent<React.KeyboardEvent<HTMLDivElement>>;

export type CellClipboardEvent = React.ClipboardEvent<HTMLDivElement>;

export interface CellClickArgs<TRow, TSummaryRow = unknown> {
rowIdx: number;
row: TRow;
column: CalculatedColumn<TRow, TSummaryRow>;
row: TRow;
rowIdx: number;
selectCell: (enableEditor?: boolean) => void;
}

interface SelectCellKeyDownArgs<TRow, TSummaryRow = unknown> {
mode: 'SELECT';
row: TRow;
column: CalculatedColumn<TRow, TSummaryRow>;
row: TRow;
rowIdx: number;
selectCell: (position: Position, enableEditor?: Maybe<boolean>) => void;
}

export interface EditCellKeyDownArgs<TRow, TSummaryRow = unknown> {
mode: 'EDIT';
row: TRow;
column: CalculatedColumn<TRow, TSummaryRow>;
row: TRow;
rowIdx: number;
navigate: () => void;
onClose: (commitChanges?: boolean, shouldFocusCell?: boolean) => void;
Expand Down Expand Up @@ -224,7 +225,6 @@ export interface RenderRowProps<TRow, TSummaryRow = unknown>
extends BaseRenderRowProps<TRow, TSummaryRow> {
row: TRow;
lastFrozenColumnIndex: number;
copiedCellIdx: number | undefined;
draggedOverCellIdx: number | undefined;
selectedCellEditor: ReactElement<RenderEditCellProps<TRow>> | undefined;
onRowChange: (column: CalculatedColumn<TRow, TSummaryRow>, rowIdx: number, newRow: TRow) => void;
Expand Down Expand Up @@ -253,16 +253,9 @@ export interface FillEvent<TRow> {
targetRow: TRow;
}

export interface CopyEvent<TRow> {
sourceColumnKey: string;
sourceRow: TRow;
}

export interface PasteEvent<TRow> {
sourceColumnKey: string;
sourceRow: TRow;
targetColumnKey: string;
targetRow: TRow;
export interface CellCopyPasteEvent<TRow, TSummaryRow = unknown> {
column: CalculatedColumn<TRow, TSummaryRow>;
row: TRow;
}

export interface GroupRow<TRow> {
Expand Down
Loading