Skip to content

Commit ba6c8ea

Browse files
authored
Use native copy/paste event handlers (#3667)
- Added `onCellCopy` and `onCellPaste` props - Grid now uses native `onCopy` and `onPaste` events - Copy state has been removed as it can be implemented outside the grid.
1 parent 3a53387 commit ba6c8ea

File tree

11 files changed

+256
-301
lines changed

11 files changed

+256
-301
lines changed

src/Cell.tsx

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,9 @@ import { useRovingTabIndex } from './hooks';
55
import { createCellEvent, getCellClassname, getCellStyle, isCellEditableUtil } from './utils';
66
import type { CellRendererProps } from './types';
77

8-
const cellCopied = css`
9-
@layer rdg.Cell {
10-
background-color: #ccccff;
11-
}
12-
`;
13-
14-
const cellCopiedClassname = `rdg-cell-copied ${cellCopied}`;
15-
168
const cellDraggedOver = css`
179
@layer rdg.Cell {
1810
background-color: #ccccff;
19-
20-
&.${cellCopied} {
21-
background-color: #9999ff;
22-
}
2311
}
2412
`;
2513

@@ -29,7 +17,6 @@ function Cell<R, SR>({
2917
column,
3018
colSpan,
3119
isCellSelected,
32-
isCopied,
3320
isDraggedOver,
3421
row,
3522
rowIdx,
@@ -48,7 +35,6 @@ function Cell<R, SR>({
4835
className = getCellClassname(
4936
column,
5037
{
51-
[cellCopiedClassname]: isCopied,
5238
[cellDraggedOverClassname]: isDraggedOver
5339
},
5440
typeof cellClass === 'function' ? cellClass(row) : cellClass,

src/DataGrid.tsx

Lines changed: 23 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,19 @@ import {
3939
import type {
4040
CalculatedColumn,
4141
CellClickArgs,
42+
CellClipboardEvent,
43+
CellCopyEvent,
4244
CellKeyboardEvent,
4345
CellKeyDownArgs,
4446
CellMouseEvent,
4547
CellNavigationMode,
48+
CellPasteEvent,
4649
CellSelectArgs,
4750
Column,
4851
ColumnOrColumnGroup,
49-
CopyEvent,
5052
Direction,
5153
FillEvent,
5254
Maybe,
53-
PasteEvent,
5455
Position,
5556
Renderers,
5657
RowsChangeData,
@@ -175,8 +176,6 @@ export interface DataGridProps<R, SR = unknown, K extends Key = Key> extends Sha
175176
onSortColumnsChange?: Maybe<(sortColumns: SortColumn[]) => void>;
176177
defaultColumnOptions?: Maybe<DefaultColumnOptions<NoInfer<R>, NoInfer<SR>>>;
177178
onFill?: Maybe<(event: FillEvent<NoInfer<R>>) => NoInfer<R>>;
178-
onCopy?: Maybe<(event: CopyEvent<NoInfer<R>>) => void>;
179-
onPaste?: Maybe<(event: PasteEvent<NoInfer<R>>) => NoInfer<R>>;
180179

181180
/**
182181
* Event props
@@ -196,6 +195,12 @@ export interface DataGridProps<R, SR = unknown, K extends Key = Key> extends Sha
196195
onCellKeyDown?: Maybe<
197196
(args: CellKeyDownArgs<NoInfer<R>, NoInfer<SR>>, event: CellKeyboardEvent) => void
198197
>;
198+
onCellCopy?: Maybe<
199+
(args: CellCopyEvent<NoInfer<R>, NoInfer<SR>>, event: CellClipboardEvent) => void
200+
>;
201+
onCellPaste?: Maybe<
202+
(args: CellPasteEvent<NoInfer<R>, NoInfer<SR>>, event: CellClipboardEvent) => NoInfer<R>
203+
>;
199204
/** Function called whenever cell selection is changed */
200205
onSelectedCellChange?: Maybe<(args: CellSelectArgs<NoInfer<R>, NoInfer<SR>>) => void>;
201206
/** Called when the grid is scrolled */
@@ -260,8 +265,8 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
260265
onColumnResize,
261266
onColumnsReorder,
262267
onFill,
263-
onCopy,
264-
onPaste,
268+
onCellCopy,
269+
onCellPaste,
265270
// Toggles and modes
266271
enableVirtualization: rawEnableVirtualization,
267272
// Miscellaneous
@@ -310,7 +315,6 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
310315
const [measuredColumnWidths, setMeasuredColumnWidths] = useState(
311316
(): ReadonlyMap<string, number> => new Map()
312317
);
313-
const [copiedCell, setCopiedCell] = useState<{ row: R; columnKey: string } | null>(null);
314318
const [isDragging, setDragging] = useState(false);
315319
const [draggedOverRowIdx, setOverRowIdx] = useState<number | undefined>(undefined);
316320
const [scrollToPosition, setScrollToPosition] = useState<PartialPosition | null>(null);
@@ -608,39 +612,13 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
608612
);
609613
if (cellEvent.isGridDefaultPrevented()) return;
610614
}
615+
611616
if (!(event.target instanceof Element)) return;
612617
const isCellEvent = event.target.closest('.rdg-cell') !== null;
613618
const isRowEvent = isTreeGrid && event.target === focusSinkRef.current;
614619
if (!isCellEvent && !isRowEvent) return;
615620

616-
// eslint-disable-next-line @typescript-eslint/no-deprecated
617-
const { keyCode } = event;
618-
619-
if (
620-
selectedCellIsWithinViewportBounds &&
621-
(onPaste != null || onCopy != null) &&
622-
isCtrlKeyHeldDown(event)
623-
) {
624-
// event.key may differ by keyboard input language, so we use event.keyCode instead
625-
// event.nativeEvent.code cannot be used either as it would break copy/paste for the DVORAK layout
626-
const cKey = 67;
627-
const vKey = 86;
628-
if (keyCode === cKey) {
629-
// copy highlighted text only
630-
if (window.getSelection()?.isCollapsed === false) return;
631-
handleCopy();
632-
return;
633-
}
634-
if (keyCode === vKey) {
635-
handlePaste();
636-
return;
637-
}
638-
}
639-
640621
switch (event.key) {
641-
case 'Escape':
642-
setCopiedCell(null);
643-
return;
644622
case 'ArrowUp':
645623
case 'ArrowDown':
646624
case 'ArrowLeft':
@@ -684,31 +662,21 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
684662
updateRow(columns[selectedPosition.idx], selectedPosition.rowIdx, selectedPosition.row);
685663
}
686664

687-
function handleCopy() {
665+
function handleCellCopy(event: CellClipboardEvent) {
666+
if (!selectedCellIsWithinViewportBounds) return;
688667
const { idx, rowIdx } = selectedPosition;
689-
const sourceRow = rows[rowIdx];
690-
const sourceColumnKey = columns[idx].key;
691-
setCopiedCell({ row: sourceRow, columnKey: sourceColumnKey });
692-
onCopy?.({ sourceRow, sourceColumnKey });
668+
onCellCopy?.({ row: rows[rowIdx], column: columns[idx] }, event);
693669
}
694670

695-
function handlePaste() {
696-
if (!onPaste || !onRowsChange || copiedCell === null || !isCellEditable(selectedPosition)) {
671+
function handleCellPaste(event: CellClipboardEvent) {
672+
if (!onCellPaste || !onRowsChange || !isCellEditable(selectedPosition)) {
697673
return;
698674
}
699675

700676
const { idx, rowIdx } = selectedPosition;
701-
const targetColumn = columns[idx];
702-
const targetRow = rows[rowIdx];
703-
704-
const updatedTargetRow = onPaste({
705-
sourceRow: copiedCell.row,
706-
sourceColumnKey: copiedCell.columnKey,
707-
targetRow,
708-
targetColumnKey: targetColumn.key
709-
});
710-
711-
updateRow(targetColumn, rowIdx, updatedTargetRow);
677+
const column = columns[idx];
678+
const updatedRow = onCellPaste({ row: rows[rowIdx], column }, event);
679+
updateRow(column, rowIdx, updatedRow);
712680
}
713681

714682
function handleCellInput(event: KeyboardEvent<HTMLDivElement>) {
@@ -726,7 +694,7 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
726694
return;
727695
}
728696

729-
if (isCellEditable(selectedPosition) && isDefaultCellInput(event)) {
697+
if (isCellEditable(selectedPosition) && isDefaultCellInput(event, onCellPaste != null)) {
730698
setSelectedPosition(({ idx, rowIdx }) => ({
731699
idx,
732700
rowIdx,
@@ -1051,11 +1019,6 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
10511019
onCellContextMenu: onCellContextMenuLatest,
10521020
rowClass,
10531021
gridRowStart,
1054-
copiedCellIdx:
1055-
copiedCell !== null && copiedCell.row === row
1056-
? columns.findIndex((c) => c.key === copiedCell.columnKey)
1057-
: undefined,
1058-
10591022
selectedCellIdx: selectedRowIdx === rowIdx ? selectedIdx : undefined,
10601023
draggedOverCellIdx: getDraggedOverCellIdx(rowIdx),
10611024
setDraggedOverRowIdx: isDragging ? setDraggedOverRowIdx : undefined,
@@ -1135,6 +1098,8 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
11351098
ref={gridRef}
11361099
onScroll={handleScroll}
11371100
onKeyDown={handleKeyDown}
1101+
onCopy={handleCellCopy}
1102+
onPaste={handleCellPaste}
11381103
data-testid={testId}
11391104
data-cy={dataCy}
11401105
>

src/Row.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ function Row<R, SR>({
1414
selectedCellIdx,
1515
isRowSelectionDisabled,
1616
isRowSelected,
17-
copiedCellIdx,
1817
draggedOverCellIdx,
1918
lastFrozenColumnIndex,
2019
row,
@@ -72,7 +71,6 @@ function Row<R, SR>({
7271
colSpan,
7372
row,
7473
rowIdx,
75-
isCopied: copiedCellIdx === idx,
7674
isDraggedOver: draggedOverCellIdx === idx,
7775
isCellSelected,
7876
onClick: onCellClick,

src/TreeDataGrid.tsx

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ import { useCallback, useMemo } from 'react';
22
import type { Key } from 'react';
33

44
import { useLatestFunc } from './hooks';
5-
import { assertIsValidKeyGetter, isCtrlKeyHeldDown } from './utils';
5+
import { assertIsValidKeyGetter } from './utils';
66
import type {
7+
CellClipboardEvent,
8+
CellCopyEvent,
79
CellKeyboardEvent,
810
CellKeyDownArgs,
11+
CellPasteEvent,
912
Column,
1013
GroupRow,
1114
Maybe,
@@ -53,6 +56,8 @@ export function TreeDataGrid<R, SR = unknown, K extends Key = Key>({
5356
rowHeight: rawRowHeight,
5457
rowKeyGetter: rawRowKeyGetter,
5558
onCellKeyDown: rawOnCellKeyDown,
59+
onCellCopy: rawOnCellCopy,
60+
onCellPaste: rawOnCellPaste,
5661
onRowsChange,
5762
selectedRows: rawSelectedRows,
5863
onSelectedRowsChange: rawOnSelectedRowsChange,
@@ -318,14 +323,25 @@ export function TreeDataGrid<R, SR = unknown, K extends Key = Key>({
318323
selectCell({ idx, rowIdx: parentRowAndIndex[1] });
319324
}
320325
}
326+
}
321327

322-
// Prevent copy/paste on group rows
323-
// eslint-disable-next-line @typescript-eslint/no-deprecated
324-
if (isCtrlKeyHeldDown(event) && (event.keyCode === 67 || event.keyCode === 86)) {
325-
event.preventGridDefault();
328+
// Prevent copy/paste on group rows
329+
function handleCellCopy(
330+
{ row, column }: CellCopyEvent<NoInfer<R>, NoInfer<SR>>,
331+
event: CellClipboardEvent
332+
) {
333+
if (!isGroupRow(row)) {
334+
rawOnCellCopy?.({ row, column }, event);
326335
}
327336
}
328337

338+
function handleCellPaste(
339+
{ row, column }: CellPasteEvent<NoInfer<R>, NoInfer<SR>>,
340+
event: CellClipboardEvent
341+
) {
342+
return isGroupRow(row) ? row : rawOnCellPaste!({ row, column }, event);
343+
}
344+
329345
function handleRowsChange(updatedRows: R[], { indexes, column }: RowsChangeData<R, SR>) {
330346
if (!onRowsChange) return;
331347
const updatedRawRows = [...rawRows];
@@ -361,7 +377,6 @@ export function TreeDataGrid<R, SR = unknown, K extends Key = Key>({
361377
onCellContextMenu,
362378
onRowChange,
363379
lastFrozenColumnIndex,
364-
copiedCellIdx,
365380
draggedOverCellIdx,
366381
setDraggedOverRowIdx,
367382
selectedCellEditor,
@@ -400,7 +415,6 @@ export function TreeDataGrid<R, SR = unknown, K extends Key = Key>({
400415
onCellContextMenu,
401416
onRowChange,
402417
lastFrozenColumnIndex,
403-
copiedCellIdx,
404418
draggedOverCellIdx,
405419
setDraggedOverRowIdx,
406420
selectedCellEditor
@@ -422,6 +436,8 @@ export function TreeDataGrid<R, SR = unknown, K extends Key = Key>({
422436
selectedRows={selectedRows}
423437
onSelectedRowsChange={onSelectedRowsChange}
424438
onCellKeyDown={handleKeyDown}
439+
onCellCopy={handleCellCopy}
440+
onCellPaste={rawOnCellPaste ? handleCellPaste : undefined}
425441
renderers={{
426442
...renderers,
427443
renderRow

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ export type {
3434
SelectHeaderRowEvent,
3535
SelectRowEvent,
3636
FillEvent,
37-
CopyEvent,
38-
PasteEvent,
3937
SortDirection,
4038
SortColumn,
4139
ColSpanArgs,
@@ -49,5 +47,7 @@ export type {
4947
CellClickArgs,
5048
CellKeyDownArgs,
5149
CellKeyboardEvent,
50+
CellCopyEvent,
51+
CellPasteEvent,
5252
CellSelectArgs
5353
} from './types';

src/types.ts

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,6 @@ export interface CellRendererProps<TRow, TSummaryRow>
153153
Omit<React.ComponentProps<'div'>, 'children' | 'onClick' | 'onDoubleClick' | 'onContextMenu'> {
154154
column: CalculatedColumn<TRow, TSummaryRow>;
155155
colSpan: number | undefined;
156-
isCopied: boolean;
157156
isDraggedOver: boolean;
158157
isCellSelected: boolean;
159158
onClick: RenderRowProps<TRow, TSummaryRow>['onCellClick'];
@@ -171,25 +170,27 @@ export type CellMouseEvent = CellEvent<React.MouseEvent<HTMLDivElement>>;
171170

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

173+
export type CellClipboardEvent = React.ClipboardEvent<HTMLDivElement>;
174+
174175
export interface CellClickArgs<TRow, TSummaryRow = unknown> {
175-
rowIdx: number;
176-
row: TRow;
177176
column: CalculatedColumn<TRow, TSummaryRow>;
177+
row: TRow;
178+
rowIdx: number;
178179
selectCell: (enableEditor?: boolean) => void;
179180
}
180181

181182
interface SelectCellKeyDownArgs<TRow, TSummaryRow = unknown> {
182183
mode: 'SELECT';
183-
row: TRow;
184184
column: CalculatedColumn<TRow, TSummaryRow>;
185+
row: TRow;
185186
rowIdx: number;
186187
selectCell: (position: Position, enableEditor?: Maybe<boolean>) => void;
187188
}
188189

189190
export interface EditCellKeyDownArgs<TRow, TSummaryRow = unknown> {
190191
mode: 'EDIT';
191-
row: TRow;
192192
column: CalculatedColumn<TRow, TSummaryRow>;
193+
row: TRow;
193194
rowIdx: number;
194195
navigate: () => void;
195196
onClose: (commitChanges?: boolean, shouldFocusCell?: boolean) => void;
@@ -224,7 +225,6 @@ export interface RenderRowProps<TRow, TSummaryRow = unknown>
224225
extends BaseRenderRowProps<TRow, TSummaryRow> {
225226
row: TRow;
226227
lastFrozenColumnIndex: number;
227-
copiedCellIdx: number | undefined;
228228
draggedOverCellIdx: number | undefined;
229229
selectedCellEditor: ReactElement<RenderEditCellProps<TRow>> | undefined;
230230
onRowChange: (column: CalculatedColumn<TRow, TSummaryRow>, rowIdx: number, newRow: TRow) => void;
@@ -253,17 +253,13 @@ export interface FillEvent<TRow> {
253253
targetRow: TRow;
254254
}
255255

256-
export interface CopyEvent<TRow> {
257-
sourceColumnKey: string;
258-
sourceRow: TRow;
256+
interface CellCopyPasteEvent<TRow, TSummaryRow = unknown> {
257+
column: CalculatedColumn<TRow, TSummaryRow>;
258+
row: TRow;
259259
}
260260

261-
export interface PasteEvent<TRow> {
262-
sourceColumnKey: string;
263-
sourceRow: TRow;
264-
targetColumnKey: string;
265-
targetRow: TRow;
266-
}
261+
export type CellCopyEvent<TRow, TSummaryRow = unknown> = CellCopyPasteEvent<TRow, TSummaryRow>;
262+
export type CellPasteEvent<TRow, TSummaryRow = unknown> = CellCopyPasteEvent<TRow, TSummaryRow>;
267263

268264
export interface GroupRow<TRow> {
269265
readonly childRows: readonly TRow[];

0 commit comments

Comments
 (0)