Skip to content

Commit

Permalink
Use native copy/paste event handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
amanmahajan7 committed Dec 9, 2024
1 parent c945bca commit 31eeb9c
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 45 deletions.
68 changes: 31 additions & 37 deletions src/DataGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react';
import {
forwardRef,
useCallback,
useImperativeHandle,
useMemo,
useRef,
useState,
type ClipboardEvent
} from 'react';
import type { Key, KeyboardEvent, RefAttributes } from 'react';
import { flushSync } from 'react-dom';
import clsx from 'clsx';
Expand Down Expand Up @@ -161,8 +169,10 @@ 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>>;
onCopy?: Maybe<(args: CopyEvent<NoInfer<R>>, event: ClipboardEvent<HTMLDivElement>) => void>;
onPaste?: Maybe<
(args: PasteEvent<NoInfer<R>>, event: ClipboardEvent<HTMLDivElement>) => NoInfer<R>
>;

/**
* Event props
Expand Down Expand Up @@ -599,30 +609,6 @@ function DataGrid<R, SR, K extends Key>(
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);
Expand Down Expand Up @@ -670,15 +656,18 @@ function DataGrid<R, SR, K extends Key>(
updateRow(columns[selectedPosition.idx], selectedPosition.rowIdx, selectedPosition.row);
}

function handleCopy() {
function handleCopy(event: React.ClipboardEvent<HTMLDivElement>) {
if (!selectedCellIsWithinViewportBounds) return;
// copy highlighted text only
if (window.getSelection()?.isCollapsed === false) return;
const { idx, rowIdx } = selectedPosition;
const sourceRow = rows[rowIdx];
const sourceColumnKey = columns[idx].key;
setCopiedCell({ row: sourceRow, columnKey: sourceColumnKey });
onCopy?.({ sourceRow, sourceColumnKey });
onCopy?.({ sourceRow, sourceColumnKey }, event);
}

function handlePaste() {
function handlePaste(event: ClipboardEvent<HTMLDivElement>) {
if (!onPaste || !onRowsChange || copiedCell === null || !isCellEditable(selectedPosition)) {
return;
}
Expand All @@ -687,12 +676,15 @@ function DataGrid<R, SR, K extends Key>(
const targetColumn = columns[idx];
const targetRow = rows[rowIdx];

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

updateRow(targetColumn, rowIdx, updatedTargetRow);
}
Expand All @@ -712,7 +704,7 @@ function DataGrid<R, SR, K extends Key>(
return;
}

if (isCellEditable(selectedPosition) && isDefaultCellInput(event)) {
if (isCellEditable(selectedPosition) && isDefaultCellInput(event, onPaste != null)) {
setSelectedPosition(({ idx, rowIdx }) => ({
idx,
rowIdx,
Expand Down Expand Up @@ -1121,6 +1113,8 @@ function DataGrid<R, SR, K extends Key>(
ref={gridRef}
onScroll={handleScroll}
onKeyDown={handleKeyDown}
onCopy={handleCopy}
onPaste={handlePaste}
data-testid={testId}
>
<DataGridDefaultRenderersProvider value={defaultGridComponents}>
Expand Down
4 changes: 2 additions & 2 deletions src/TreeDataGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { forwardRef, useCallback, useMemo } from 'react';
import type { Key, RefAttributes } from 'react';

import { useLatestFunc } from './hooks';
import { assertIsValidKeyGetter, isCtrlKeyHeldDown } from './utils';
import { assertIsValidKeyGetter, cKey, isCtrlKeyHeldDown, vKey } from './utils';
import type {
CellKeyboardEvent,
CellKeyDownArgs,
Expand Down Expand Up @@ -324,7 +324,7 @@ function TreeDataGrid<R, SR, K extends Key>(

// Prevent copy/paste on group rows
// eslint-disable-next-line @typescript-eslint/no-deprecated
if (isCtrlKeyHeldDown(event) && (event.keyCode === 67 || event.keyCode === 86)) {
if (isCtrlKeyHeldDown(event) && (event.keyCode === cKey || event.keyCode === vKey)) {
event.preventGridDefault();
}
}
Expand Down
4 changes: 1 addition & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,9 +254,7 @@ export interface CopyEvent<TRow> {
sourceRow: TRow;
}

export interface PasteEvent<TRow> {
sourceColumnKey: string;
sourceRow: TRow;
export interface PasteEvent<TRow> extends CopyEvent<TRow> {
targetColumnKey: string;
targetRow: TRow;
}
Expand Down
13 changes: 10 additions & 3 deletions src/utils/keyboardUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,17 @@ export function isCtrlKeyHeldDown(e: React.KeyboardEvent): boolean {
return (e.ctrlKey || e.metaKey) && e.key !== 'Control';
}

export function isDefaultCellInput(event: React.KeyboardEvent<HTMLDivElement>): boolean {
const vKey = 86;
// 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
export const cKey = 67;
export const vKey = 86;

export function isDefaultCellInput(
event: React.KeyboardEvent<HTMLDivElement>,
isUserHandlingPaste: boolean
): boolean {
// eslint-disable-next-line @typescript-eslint/no-deprecated
if (isCtrlKeyHeldDown(event) && event.keyCode !== vKey) return false;
if (isCtrlKeyHeldDown(event) && (event.keyCode !== vKey || isUserHandlingPaste)) return false;
return !nonInputKeys.has(event.key);
}

Expand Down

0 comments on commit 31eeb9c

Please sign in to comment.