From 31eeb9c38e2d9597f17d1ea7a5a0ecc98ea2fc81 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Mon, 9 Dec 2024 10:03:30 -0600 Subject: [PATCH 01/13] Use native copy/paste event handlers --- src/DataGrid.tsx | 68 +++++++++++++++++--------------------- src/TreeDataGrid.tsx | 4 +-- src/types.ts | 4 +-- src/utils/keyboardUtils.ts | 13 ++++++-- 4 files changed, 44 insertions(+), 45 deletions(-) diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index dc6f6aeb58..33667ffbe7 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -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'; @@ -161,8 +169,10 @@ export interface DataGridProps extends Sha onSortColumnsChange?: Maybe<(sortColumns: SortColumn[]) => void>; defaultColumnOptions?: Maybe, NoInfer>>; onFill?: Maybe<(event: FillEvent>) => NoInfer>; - onCopy?: Maybe<(event: CopyEvent>) => void>; - onPaste?: Maybe<(event: PasteEvent>) => NoInfer>; + onCopy?: Maybe<(args: CopyEvent>, event: ClipboardEvent) => void>; + onPaste?: Maybe< + (args: PasteEvent>, event: ClipboardEvent) => NoInfer + >; /** * Event props @@ -599,30 +609,6 @@ function DataGrid( 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); @@ -670,15 +656,18 @@ function DataGrid( updateRow(columns[selectedPosition.idx], selectedPosition.rowIdx, selectedPosition.row); } - function handleCopy() { + function handleCopy(event: React.ClipboardEvent) { + 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) { if (!onPaste || !onRowsChange || copiedCell === null || !isCellEditable(selectedPosition)) { return; } @@ -687,12 +676,15 @@ function DataGrid( 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); } @@ -712,7 +704,7 @@ function DataGrid( return; } - if (isCellEditable(selectedPosition) && isDefaultCellInput(event)) { + if (isCellEditable(selectedPosition) && isDefaultCellInput(event, onPaste != null)) { setSelectedPosition(({ idx, rowIdx }) => ({ idx, rowIdx, @@ -1121,6 +1113,8 @@ function DataGrid( ref={gridRef} onScroll={handleScroll} onKeyDown={handleKeyDown} + onCopy={handleCopy} + onPaste={handlePaste} data-testid={testId} > diff --git a/src/TreeDataGrid.tsx b/src/TreeDataGrid.tsx index 16e4f87232..fafc9198fb 100644 --- a/src/TreeDataGrid.tsx +++ b/src/TreeDataGrid.tsx @@ -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, @@ -324,7 +324,7 @@ function TreeDataGrid( // 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(); } } diff --git a/src/types.ts b/src/types.ts index 561bb651be..60dcac07d7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -254,9 +254,7 @@ export interface CopyEvent { sourceRow: TRow; } -export interface PasteEvent { - sourceColumnKey: string; - sourceRow: TRow; +export interface PasteEvent extends CopyEvent { targetColumnKey: string; targetRow: TRow; } diff --git a/src/utils/keyboardUtils.ts b/src/utils/keyboardUtils.ts index e12609eb4f..af1c84a1ed 100644 --- a/src/utils/keyboardUtils.ts +++ b/src/utils/keyboardUtils.ts @@ -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): 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, + 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); } From 403f417f51eb5e4c7b08cc4df62ce1d1bd8068c3 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Wed, 11 Dec 2024 21:40:44 -0600 Subject: [PATCH 02/13] optional types --- src/DataGrid.tsx | 42 ++++++++++++++--------------- src/types.ts | 6 ++++- src/utils/keyboardUtils.ts | 4 +-- test/browser/utils.tsx | 4 ++- website/routes/AllFeatures.lazy.tsx | 2 ++ 5 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index 33667ffbe7..49bff4ec72 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -1,12 +1,4 @@ -import { - forwardRef, - useCallback, - useImperativeHandle, - useMemo, - useRef, - useState, - type ClipboardEvent -} from 'react'; +import { forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react'; import type { Key, KeyboardEvent, RefAttributes } from 'react'; import { flushSync } from 'react-dom'; import clsx from 'clsx'; @@ -41,6 +33,7 @@ import { import type { CalculatedColumn, CellClickArgs, + CellClipboardEvent, CellKeyboardEvent, CellKeyDownArgs, CellMouseEvent, @@ -169,10 +162,8 @@ export interface DataGridProps extends Sha onSortColumnsChange?: Maybe<(sortColumns: SortColumn[]) => void>; defaultColumnOptions?: Maybe, NoInfer>>; onFill?: Maybe<(event: FillEvent>) => NoInfer>; - onCopy?: Maybe<(args: CopyEvent>, event: ClipboardEvent) => void>; - onPaste?: Maybe< - (args: PasteEvent>, event: ClipboardEvent) => NoInfer - >; + onCopy?: Maybe<(args: CopyEvent>, event: CellClipboardEvent) => void>; + onPaste?: Maybe<(args: PasteEvent>, event: CellClipboardEvent) => NoInfer>; /** * Event props @@ -656,19 +647,26 @@ function DataGrid( updateRow(columns[selectedPosition.idx], selectedPosition.rowIdx, selectedPosition.row); } - function handleCopy(event: React.ClipboardEvent) { + function handleCopy(event: CellClipboardEvent) { 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 }, event); + + // copy highlighted text only + if (window.getSelection()?.isCollapsed === false) { + setCopiedCell(null); + return; + } + + if (onPaste) { + setCopiedCell({ row: sourceRow, columnKey: sourceColumnKey }); + } } - function handlePaste(event: ClipboardEvent) { - if (!onPaste || !onRowsChange || copiedCell === null || !isCellEditable(selectedPosition)) { + function handlePaste(event: CellClipboardEvent) { + if (!onPaste || !onRowsChange || !isCellEditable(selectedPosition)) { return; } @@ -678,8 +676,8 @@ function DataGrid( const updatedTargetRow = onPaste( { - sourceRow: copiedCell.row, - sourceColumnKey: copiedCell.columnKey, + sourceRow: copiedCell?.row, + sourceColumnKey: copiedCell?.columnKey, targetRow, targetColumnKey: targetColumn.key }, @@ -704,7 +702,7 @@ function DataGrid( return; } - if (isCellEditable(selectedPosition) && isDefaultCellInput(event, onPaste != null)) { + if (isCellEditable(selectedPosition) && isDefaultCellInput(event, copiedCell !== null)) { setSelectedPosition(({ idx, rowIdx }) => ({ idx, rowIdx, diff --git a/src/types.ts b/src/types.ts index 60dcac07d7..1ebcb98079 100644 --- a/src/types.ts +++ b/src/types.ts @@ -167,6 +167,8 @@ export type CellMouseEvent = CellEvent>; export type CellKeyboardEvent = CellEvent>; +export type CellClipboardEvent = React.ClipboardEvent; + export interface CellClickArgs { rowIdx: number; row: TRow; @@ -254,7 +256,9 @@ export interface CopyEvent { sourceRow: TRow; } -export interface PasteEvent extends CopyEvent { +export interface PasteEvent { + sourceColumnKey: string | undefined; + sourceRow: TRow | undefined; targetColumnKey: string; targetRow: TRow; } diff --git a/src/utils/keyboardUtils.ts b/src/utils/keyboardUtils.ts index af1c84a1ed..68150ef9bb 100644 --- a/src/utils/keyboardUtils.ts +++ b/src/utils/keyboardUtils.ts @@ -59,10 +59,10 @@ export const vKey = 86; export function isDefaultCellInput( event: React.KeyboardEvent, - isUserHandlingPaste: boolean + isCopyingCell: boolean ): boolean { // eslint-disable-next-line @typescript-eslint/no-deprecated - if (isCtrlKeyHeldDown(event) && (event.keyCode !== vKey || isUserHandlingPaste)) return false; + if (isCtrlKeyHeldDown(event) && (event.keyCode !== vKey || isCopyingCell)) return false; return !nonInputKeys.has(event.key); } diff --git a/test/browser/utils.tsx b/test/browser/utils.tsx index 3b00f7538b..f8b8bb3c7f 100644 --- a/test/browser/utils.tsx +++ b/test/browser/utils.tsx @@ -1,4 +1,4 @@ -import { act, render, screen } from '@testing-library/react'; +import { act, fireEvent, render, screen } from '@testing-library/react'; import { page, userEvent } from '@vitest/browser/context'; import { css } from '@linaria/core'; @@ -126,6 +126,7 @@ export async function copySelectedCellOld() { } export function copySelectedCell() { + fireEvent.copy(document.activeElement!); return userEvent.keyboard('{Control>}c{/Control}'); } @@ -137,6 +138,7 @@ export async function pasteSelectedCellOld() { } export function pasteSelectedCell() { + fireEvent.paste(document.activeElement!); return userEvent.keyboard('{Control>}v{/Control}'); } diff --git a/website/routes/AllFeatures.lazy.tsx b/website/routes/AllFeatures.lazy.tsx index 4d412f687b..89c0683eb7 100644 --- a/website/routes/AllFeatures.lazy.tsx +++ b/website/routes/AllFeatures.lazy.tsx @@ -182,6 +182,8 @@ function AllFeatures() { targetColumnKey, targetRow }: PasteEvent): Row { + if (sourceColumnKey === undefined || sourceRow === undefined) return targetRow; + const incompatibleColumns = ['email', 'zipCode', 'date']; if ( sourceColumnKey === 'avatar' || From 3288bf3ac41da32e1fec1c84af6f71c58b3ea28e Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Thu, 12 Dec 2024 16:02:46 -0600 Subject: [PATCH 03/13] Move celly copying logic outside the grid --- src/Cell.tsx | 14 ---- src/DataGrid.tsx | 54 ++++--------- src/Row.tsx | 2 - src/TreeDataGrid.tsx | 10 +-- src/index.ts | 3 +- src/types.ts | 15 +--- src/utils/keyboardUtils.ts | 7 +- website/routes/AllFeatures.lazy.tsx | 113 +++++++++++++++++++--------- 8 files changed, 97 insertions(+), 121 deletions(-) diff --git a/src/Cell.tsx b/src/Cell.tsx index 29cd201693..43cd02a188 100644 --- a/src/Cell.tsx +++ b/src/Cell.tsx @@ -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; - } } `; @@ -30,7 +18,6 @@ function Cell( column, colSpan, isCellSelected, - isCopied, isDraggedOver, row, rowIdx, @@ -51,7 +38,6 @@ function Cell( className = getCellClassname( column, { - [cellCopiedClassname]: isCopied, [cellDraggedOverClassname]: isDraggedOver }, typeof cellClass === 'function' ? cellClass(row) : cellClass, diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index 49bff4ec72..da14f70786 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -41,11 +41,10 @@ import type { CellSelectArgs, Column, ColumnOrColumnGroup, - CopyEvent, + CopyPasteEvent, Direction, FillEvent, Maybe, - PasteEvent, Position, Renderers, RowsChangeData, @@ -162,8 +161,12 @@ export interface DataGridProps extends Sha onSortColumnsChange?: Maybe<(sortColumns: SortColumn[]) => void>; defaultColumnOptions?: Maybe, NoInfer>>; onFill?: Maybe<(event: FillEvent>) => NoInfer>; - onCopy?: Maybe<(args: CopyEvent>, event: CellClipboardEvent) => void>; - onPaste?: Maybe<(args: PasteEvent>, event: CellClipboardEvent) => NoInfer>; + onCopy?: Maybe< + (args: CopyPasteEvent, NoInfer>, event: CellClipboardEvent) => void + >; + onPaste?: Maybe< + (args: CopyPasteEvent, NoInfer>, event: CellClipboardEvent) => NoInfer + >; /** * Event props @@ -297,7 +300,6 @@ function DataGrid( const [measuredColumnWidths, setMeasuredColumnWidths] = useState( (): ReadonlyMap => new Map() ); - const [copiedCell, setCopiedCell] = useState<{ row: R; columnKey: string } | null>(null); const [isDragging, setDragging] = useState(false); const [draggedOverRowIdx, setOverRowIdx] = useState(undefined); const [scrollToPosition, setScrollToPosition] = useState(null); @@ -595,15 +597,13 @@ function DataGrid( ); 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; switch (event.key) { - case 'Escape': - setCopiedCell(null); - return; case 'ArrowUp': case 'ArrowDown': case 'ArrowLeft': @@ -650,19 +650,7 @@ function DataGrid( function handleCopy(event: CellClipboardEvent) { if (!selectedCellIsWithinViewportBounds) return; const { idx, rowIdx } = selectedPosition; - const sourceRow = rows[rowIdx]; - const sourceColumnKey = columns[idx].key; - onCopy?.({ sourceRow, sourceColumnKey }, event); - - // copy highlighted text only - if (window.getSelection()?.isCollapsed === false) { - setCopiedCell(null); - return; - } - - if (onPaste) { - setCopiedCell({ row: sourceRow, columnKey: sourceColumnKey }); - } + onCopy?.({ row: rows[rowIdx], column: columns[idx] }, event); } function handlePaste(event: CellClipboardEvent) { @@ -671,20 +659,9 @@ function DataGrid( } 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 - }, - event - ); - - updateRow(targetColumn, rowIdx, updatedTargetRow); + const column = columns[idx]; + const updatedRow = onPaste({ row: rows[rowIdx], column }, event); + updateRow(column, rowIdx, updatedRow); } function handleCellInput(event: KeyboardEvent) { @@ -702,7 +679,7 @@ function DataGrid( return; } - if (isCellEditable(selectedPosition) && isDefaultCellInput(event, copiedCell !== null)) { + if (isCellEditable(selectedPosition) && isDefaultCellInput(event, onPaste != null)) { setSelectedPosition(({ idx, rowIdx }) => ({ idx, rowIdx, @@ -1027,11 +1004,6 @@ function DataGrid( 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, diff --git a/src/Row.tsx b/src/Row.tsx index 9b811a3397..9b9d8e0097 100644 --- a/src/Row.tsx +++ b/src/Row.tsx @@ -15,7 +15,6 @@ function Row( selectedCellIdx, isRowSelectionDisabled, isRowSelected, - copiedCellIdx, draggedOverCellIdx, lastFrozenColumnIndex, row, @@ -75,7 +74,6 @@ function Row( colSpan, row, rowIdx, - isCopied: copiedCellIdx === idx, isDraggedOver: draggedOverCellIdx === idx, isCellSelected, onClick: onCellClick, diff --git a/src/TreeDataGrid.tsx b/src/TreeDataGrid.tsx index fafc9198fb..aa46a01157 100644 --- a/src/TreeDataGrid.tsx +++ b/src/TreeDataGrid.tsx @@ -2,7 +2,7 @@ import { forwardRef, useCallback, useMemo } from 'react'; import type { Key, RefAttributes } from 'react'; import { useLatestFunc } from './hooks'; -import { assertIsValidKeyGetter, cKey, isCtrlKeyHeldDown, vKey } from './utils'; +import { assertIsValidKeyGetter } from './utils'; import type { CellKeyboardEvent, CellKeyDownArgs, @@ -321,12 +321,6 @@ function TreeDataGrid( selectCell({ idx, rowIdx: parentRowAndIndex[1] }); } } - - // Prevent copy/paste on group rows - // eslint-disable-next-line @typescript-eslint/no-deprecated - if (isCtrlKeyHeldDown(event) && (event.keyCode === cKey || event.keyCode === vKey)) { - event.preventGridDefault(); - } } function handleRowsChange(updatedRows: R[], { indexes, column }: RowsChangeData) { @@ -364,7 +358,6 @@ function TreeDataGrid( onCellContextMenu, onRowChange, lastFrozenColumnIndex, - copiedCellIdx, draggedOverCellIdx, setDraggedOverRowIdx, selectedCellEditor, @@ -403,7 +396,6 @@ function TreeDataGrid( onCellContextMenu, onRowChange, lastFrozenColumnIndex, - copiedCellIdx, draggedOverCellIdx, setDraggedOverRowIdx, selectedCellEditor diff --git a/src/index.ts b/src/index.ts index d76283ea28..a3845ef26e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,8 +29,7 @@ export type { SelectHeaderRowEvent, SelectRowEvent, FillEvent, - CopyEvent, - PasteEvent, + CopyPasteEvent, SortDirection, SortColumn, ColSpanArgs, diff --git a/src/types.ts b/src/types.ts index 1ebcb98079..b588593692 100644 --- a/src/types.ts +++ b/src/types.ts @@ -149,7 +149,6 @@ export interface CellRendererProps > { column: CalculatedColumn; colSpan: number | undefined; - isCopied: boolean; isDraggedOver: boolean; isCellSelected: boolean; onClick: RenderRowProps['onCellClick']; @@ -222,7 +221,6 @@ export interface RenderRowProps extends BaseRenderRowProps { row: TRow; lastFrozenColumnIndex: number; - copiedCellIdx: number | undefined; draggedOverCellIdx: number | undefined; selectedCellEditor: ReactElement> | undefined; onRowChange: (column: CalculatedColumn, rowIdx: number, newRow: TRow) => void; @@ -251,16 +249,9 @@ export interface FillEvent { targetRow: TRow; } -export interface CopyEvent { - sourceColumnKey: string; - sourceRow: TRow; -} - -export interface PasteEvent { - sourceColumnKey: string | undefined; - sourceRow: TRow | undefined; - targetColumnKey: string; - targetRow: TRow; +export interface CopyPasteEvent { + row: TRow; + column: CalculatedColumn; } export interface GroupRow { diff --git a/src/utils/keyboardUtils.ts b/src/utils/keyboardUtils.ts index 68150ef9bb..5bb352521e 100644 --- a/src/utils/keyboardUtils.ts +++ b/src/utils/keyboardUtils.ts @@ -54,15 +54,14 @@ export function isCtrlKeyHeldDown(e: React.KeyboardEvent): boolean { // 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; +const vKey = 86; export function isDefaultCellInput( event: React.KeyboardEvent, - isCopyingCell: boolean + isUserHandlingPaste: boolean ): boolean { // eslint-disable-next-line @typescript-eslint/no-deprecated - if (isCtrlKeyHeldDown(event) && (event.keyCode !== vKey || isCopyingCell)) return false; + if (isCtrlKeyHeldDown(event) && (event.keyCode !== vKey || isUserHandlingPaste)) return false; return !nonInputKeys.has(event.key); } diff --git a/website/routes/AllFeatures.lazy.tsx b/website/routes/AllFeatures.lazy.tsx index 89c0683eb7..db65f9298f 100644 --- a/website/routes/AllFeatures.lazy.tsx +++ b/website/routes/AllFeatures.lazy.tsx @@ -1,9 +1,10 @@ import { useState } from 'react'; import { createLazyFileRoute } from '@tanstack/react-router'; import { css } from '@linaria/core'; +import clsx from 'clsx'; import DataGrid, { SelectColumn, textEditor } from '../../src'; -import type { Column, CopyEvent, FillEvent, PasteEvent } from '../../src'; +import type { CalculatedColumn, Column, CopyPasteEvent, FillEvent } from '../../src'; import { textEditorClassname } from '../../src/editors/textEditor'; import { useDirection } from '../directionContext'; @@ -22,6 +23,8 @@ const highlightClassname = css` } `; +const copiedRowClassname = css``; + export interface Row { id: string; avatar: string; @@ -171,18 +174,22 @@ function AllFeatures() { const initialRows = Route.useLoaderData(); const [rows, setRows] = useState(initialRows); const [selectedRows, setSelectedRows] = useState((): ReadonlySet => new Set()); + const [copiedCell, setCopiedCell] = useState<{ row: Row; column: CalculatedColumn } | null>( + null + ); function handleFill({ columnKey, sourceRow, targetRow }: FillEvent): Row { return { ...targetRow, [columnKey]: sourceRow[columnKey as keyof Row] }; } - function handlePaste({ - sourceColumnKey, - sourceRow, - targetColumnKey, - targetRow - }: PasteEvent): Row { - if (sourceColumnKey === undefined || sourceRow === undefined) return targetRow; + function handlePaste({ row, column }: CopyPasteEvent): Row { + if (!copiedCell) { + return row; + } + + const sourceColumnKey = copiedCell.column.key; + const sourceRow = copiedCell.row; + const targetColumnKey = column.key; const incompatibleColumns = ['email', 'zipCode', 'date']; if ( @@ -192,42 +199,74 @@ function AllFeatures() { incompatibleColumns.includes(sourceColumnKey)) && sourceColumnKey !== targetColumnKey) ) { - return targetRow; + return row; } - return { ...targetRow, [targetColumnKey]: sourceRow[sourceColumnKey as keyof Row] }; + return { ...row, [targetColumnKey]: sourceRow[sourceColumnKey as keyof Row] }; } - function handleCopy({ sourceRow, sourceColumnKey }: CopyEvent): void { - if (window.isSecureContext) { - navigator.clipboard.writeText(sourceRow[sourceColumnKey as keyof Row]); + function handleCopy( + { row, column }: CopyPasteEvent, + event: React.ClipboardEvent + ): void { + // copy highlighted text only + if (window.getSelection()?.isCollapsed === false) { + setCopiedCell(null); + return; } + + setCopiedCell({ row, column }); + event.clipboardData.setData('text/plain', row[column.key as keyof Row]); + event.preventDefault(); } return ( - row.id === 'id_2'} - onSelectedRowsChange={setSelectedRows} - className="fill-grid" - rowClass={(row, index) => - row.id.includes('7') || index === 0 ? highlightClassname : undefined - } - direction={direction} - onCellClick={(args, event) => { - if (args.column.key === 'title') { - event.preventGridDefault(); - args.selectCell(true); - } - }} - /> + <> + {copiedCell && ( + + )} + row.id === 'id_2'} + onSelectedRowsChange={setSelectedRows} + className="fill-grid" + rowClass={(row, index) => { + return clsx({ + [highlightClassname]: row.id.includes('7') || index === 0, + [copiedRowClassname]: copiedCell?.row === row + }); + }} + direction={direction} + onCellClick={(args, event) => { + if (args.column.key === 'title') { + event.preventGridDefault(); + args.selectCell(true); + } + }} + onCellKeyDown={(_, event) => { + if (event.key === 'Escape') { + setCopiedCell(null); + } + }} + /> + ); } From 2eab97fb15c5fa0952df30790b7b7f9e7bc8f3ce Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Thu, 12 Dec 2024 16:34:58 -0600 Subject: [PATCH 04/13] Rename `onCopy/Paste` -> `onCellCopy/Paste` --- src/DataGrid.tsx | 34 ++++++++++++++--------------- src/index.ts | 2 +- src/types.ts | 2 +- test/browser/TreeDataGrid.test.tsx | 4 ++-- test/browser/copyPaste.test.tsx | 4 ++-- website/routes/AllFeatures.lazy.tsx | 12 +++++----- 6 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index da14f70786..25f9f2e0c8 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -34,6 +34,7 @@ import type { CalculatedColumn, CellClickArgs, CellClipboardEvent, + CellCopyPasteEvent, CellKeyboardEvent, CellKeyDownArgs, CellMouseEvent, @@ -41,7 +42,6 @@ import type { CellSelectArgs, Column, ColumnOrColumnGroup, - CopyPasteEvent, Direction, FillEvent, Maybe, @@ -161,12 +161,6 @@ export interface DataGridProps extends Sha onSortColumnsChange?: Maybe<(sortColumns: SortColumn[]) => void>; defaultColumnOptions?: Maybe, NoInfer>>; onFill?: Maybe<(event: FillEvent>) => NoInfer>; - onCopy?: Maybe< - (args: CopyPasteEvent, NoInfer>, event: CellClipboardEvent) => void - >; - onPaste?: Maybe< - (args: CopyPasteEvent, NoInfer>, event: CellClipboardEvent) => NoInfer - >; /** * Event props @@ -186,6 +180,12 @@ export interface DataGridProps extends Sha onCellKeyDown?: Maybe< (args: CellKeyDownArgs, NoInfer>, event: CellKeyboardEvent) => void >; + onCellCopy?: Maybe< + (args: CellCopyPasteEvent, NoInfer>, event: CellClipboardEvent) => void + >; + onCellPaste?: Maybe< + (args: CellCopyPasteEvent, NoInfer>, event: CellClipboardEvent) => NoInfer + >; /** Function called whenever cell selection is changed */ onSelectedCellChange?: Maybe<(args: CellSelectArgs, NoInfer>) => void>; /** Called when the grid is scrolled */ @@ -251,8 +251,8 @@ function DataGrid( onColumnResize, onColumnsReorder, onFill, - onCopy, - onPaste, + onCellCopy, + onCellPaste, // Toggles and modes enableVirtualization: rawEnableVirtualization, // Miscellaneous @@ -647,20 +647,20 @@ function DataGrid( updateRow(columns[selectedPosition.idx], selectedPosition.rowIdx, selectedPosition.row); } - function handleCopy(event: CellClipboardEvent) { + function handleCellCopy(event: CellClipboardEvent) { if (!selectedCellIsWithinViewportBounds) return; const { idx, rowIdx } = selectedPosition; - onCopy?.({ row: rows[rowIdx], column: columns[idx] }, event); + onCellCopy?.({ row: rows[rowIdx], column: columns[idx] }, event); } - function handlePaste(event: CellClipboardEvent) { - if (!onPaste || !onRowsChange || !isCellEditable(selectedPosition)) { + function handleCellPaste(event: CellClipboardEvent) { + if (!onCellPaste || !onRowsChange || !isCellEditable(selectedPosition)) { return; } const { idx, rowIdx } = selectedPosition; const column = columns[idx]; - const updatedRow = onPaste({ row: rows[rowIdx], column }, event); + const updatedRow = onCellPaste({ row: rows[rowIdx], column }, event); updateRow(column, rowIdx, updatedRow); } @@ -679,7 +679,7 @@ function DataGrid( return; } - if (isCellEditable(selectedPosition) && isDefaultCellInput(event, onPaste != null)) { + if (isCellEditable(selectedPosition) && isDefaultCellInput(event, onCellPaste != null)) { setSelectedPosition(({ idx, rowIdx }) => ({ idx, rowIdx, @@ -1083,8 +1083,8 @@ function DataGrid( ref={gridRef} onScroll={handleScroll} onKeyDown={handleKeyDown} - onCopy={handleCopy} - onPaste={handlePaste} + onCopy={handleCellCopy} + onPaste={handleCellPaste} data-testid={testId} > diff --git a/src/index.ts b/src/index.ts index a3845ef26e..be00221294 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,7 +29,7 @@ export type { SelectHeaderRowEvent, SelectRowEvent, FillEvent, - CopyPasteEvent, + CellCopyPasteEvent, SortDirection, SortColumn, ColSpanArgs, diff --git a/src/types.ts b/src/types.ts index b588593692..b1130e876d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -249,7 +249,7 @@ export interface FillEvent { targetRow: TRow; } -export interface CopyPasteEvent { +export interface CellCopyPasteEvent { row: TRow; column: CalculatedColumn; } diff --git a/test/browser/TreeDataGrid.test.tsx b/test/browser/TreeDataGrid.test.tsx index c5073b4617..8744e564c5 100644 --- a/test/browser/TreeDataGrid.test.tsx +++ b/test/browser/TreeDataGrid.test.tsx @@ -99,7 +99,7 @@ function TestGrid({ groupBy }: { groupBy: string[] }) { (): ReadonlySet => new Set([]) ); - function onPaste(event: PasteEvent) { + function onCellPaste(event: PasteEvent) { return { ...event.targetRow, [event.targetColumnKey]: event.sourceRow[event.sourceColumnKey as keyof Row] @@ -120,7 +120,7 @@ function TestGrid({ groupBy }: { groupBy: string[] }) { expandedGroupIds={expandedGroupIds} onExpandedGroupIdsChange={setExpandedGroupIds} onRowsChange={setRows} - onPaste={onPaste} + onCellPaste={onCellPaste} /> ); } diff --git a/test/browser/copyPaste.test.tsx b/test/browser/copyPaste.test.tsx index f78c389f67..520533acfb 100644 --- a/test/browser/copyPaste.test.tsx +++ b/test/browser/copyPaste.test.tsx @@ -70,8 +70,8 @@ function CopyPasteTest({ rows={rows} bottomSummaryRows={bottomSummaryRows} onRowsChange={setRows} - onPaste={onPasteCallback ? onPaste : undefined} - onCopy={onCopyCallback ? onCopySpy : undefined} + onCellPaste={onPasteCallback ? onPaste : undefined} + onCellCopy={onCopyCallback ? onCopySpy : undefined} /> ); } diff --git a/website/routes/AllFeatures.lazy.tsx b/website/routes/AllFeatures.lazy.tsx index db65f9298f..5903819ef9 100644 --- a/website/routes/AllFeatures.lazy.tsx +++ b/website/routes/AllFeatures.lazy.tsx @@ -4,7 +4,7 @@ import { css } from '@linaria/core'; import clsx from 'clsx'; import DataGrid, { SelectColumn, textEditor } from '../../src'; -import type { CalculatedColumn, Column, CopyPasteEvent, FillEvent } from '../../src'; +import type { CalculatedColumn, CellCopyPasteEvent, Column, FillEvent } from '../../src'; import { textEditorClassname } from '../../src/editors/textEditor'; import { useDirection } from '../directionContext'; @@ -182,7 +182,7 @@ function AllFeatures() { return { ...targetRow, [columnKey]: sourceRow[columnKey as keyof Row] }; } - function handlePaste({ row, column }: CopyPasteEvent): Row { + function handleCellPaste({ row, column }: CellCopyPasteEvent): Row { if (!copiedCell) { return row; } @@ -205,8 +205,8 @@ function AllFeatures() { return { ...row, [targetColumnKey]: sourceRow[sourceColumnKey as keyof Row] }; } - function handleCopy( - { row, column }: CopyPasteEvent, + function handleCellCopy( + { row, column }: CellCopyPasteEvent, event: React.ClipboardEvent ): void { // copy highlighted text only @@ -241,8 +241,8 @@ function AllFeatures() { rowKeyGetter={rowKeyGetter} onRowsChange={setRows} onFill={handleFill} - onCopy={handleCopy} - onPaste={handlePaste} + onCellCopy={handleCellCopy} + onCellPaste={handleCellPaste} rowHeight={30} selectedRows={selectedRows} isRowSelectionDisabled={(row) => row.id === 'id_2'} From 459b184254150a20f165da885195b339d834dad1 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Thu, 12 Dec 2024 16:38:49 -0600 Subject: [PATCH 05/13] Cleanup --- src/types.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/types.ts b/src/types.ts index b1130e876d..a0b13a6f19 100644 --- a/src/types.ts +++ b/src/types.ts @@ -169,24 +169,24 @@ export type CellKeyboardEvent = CellEvent>; export type CellClipboardEvent = React.ClipboardEvent; export interface CellClickArgs { - rowIdx: number; - row: TRow; column: CalculatedColumn; + row: TRow; + rowIdx: number; selectCell: (enableEditor?: boolean) => void; } interface SelectCellKeyDownArgs { mode: 'SELECT'; - row: TRow; column: CalculatedColumn; + row: TRow; rowIdx: number; selectCell: (position: Position, enableEditor?: Maybe) => void; } export interface EditCellKeyDownArgs { mode: 'EDIT'; - row: TRow; column: CalculatedColumn; + row: TRow; rowIdx: number; navigate: () => void; onClose: (commitChanges?: boolean, shouldFocusCell?: boolean) => void; @@ -250,8 +250,8 @@ export interface FillEvent { } export interface CellCopyPasteEvent { - row: TRow; column: CalculatedColumn; + row: TRow; } export interface GroupRow { From dd210ea4785a7a9184a76896a172da792f8fbf10 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Mon, 24 Mar 2025 16:20:44 -0500 Subject: [PATCH 06/13] Fix types --- test/browser/TreeDataGrid.test.tsx | 8 ++++---- test/browser/copyPaste.test.tsx | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/test/browser/TreeDataGrid.test.tsx b/test/browser/TreeDataGrid.test.tsx index 4043efc8c6..13ad53f7d6 100644 --- a/test/browser/TreeDataGrid.test.tsx +++ b/test/browser/TreeDataGrid.test.tsx @@ -5,7 +5,7 @@ import type { Column } from '../../src'; import { SelectColumn, textEditor, TreeDataGrid } from '../../src'; import { focusSinkClassname } from '../../src/style/core'; import { rowSelected } from '../../src/style/row'; -import type { PasteEvent } from '../../src/types'; +import type { CellCopyPasteEvent } from '../../src/types'; import { copySelectedCell, getCellsAtRowIndex, @@ -98,10 +98,10 @@ function TestGrid({ groupBy }: { groupBy: string[] }) { (): ReadonlySet => new Set([]) ); - function onCellPaste(event: PasteEvent) { + function onCellPaste(event: CellCopyPasteEvent): Row { return { - ...event.targetRow, - [event.targetColumnKey]: event.sourceRow[event.sourceColumnKey as keyof Row] + ...event.row, + [event.column.key]: event.row[event.column.key as keyof Row] }; } diff --git a/test/browser/copyPaste.test.tsx b/test/browser/copyPaste.test.tsx index 8f86184e42..5a00e1378f 100644 --- a/test/browser/copyPaste.test.tsx +++ b/test/browser/copyPaste.test.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { page, userEvent } from '@vitest/browser/context'; import { DataGrid } from '../../src'; -import type { Column, PasteEvent } from '../../src'; +import type { CellCopyPasteEvent, Column } from '../../src'; import { copySelectedCell, getCellsAtRowIndex, getSelectedCell, pasteSelectedCell } from './utils'; interface Row { @@ -54,9 +54,10 @@ function CopyPasteTest({ }) { const [rows, setRows] = useState(initialRows); - function onPaste({ sourceColumnKey, sourceRow, targetColumnKey, targetRow }: PasteEvent) { + function onPaste({ column, row }: CellCopyPasteEvent): Row { onPasteSpy(); - return { ...targetRow, [targetColumnKey]: sourceRow[sourceColumnKey as keyof Row] }; + const columnKey = column.key; + return { ...row, [columnKey]: row[columnKey as keyof Row] }; } return ( From 86603d44df19912f2fe258d20630f7cff7895aac Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Mon, 24 Mar 2025 22:56:56 -0500 Subject: [PATCH 07/13] Tweaks --- test/browser/copyPaste.test.tsx | 2 +- website/routes/AllFeatures.tsx | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/test/browser/copyPaste.test.tsx b/test/browser/copyPaste.test.tsx index 5a00e1378f..64fdd9582f 100644 --- a/test/browser/copyPaste.test.tsx +++ b/test/browser/copyPaste.test.tsx @@ -91,7 +91,7 @@ test('should not allow copy/paste if onPaste & onCopy is undefined', async () => expect(onPasteSpy).not.toHaveBeenCalled(); }); -test('should allow copy if only onCopy is specified', async () => { +test.only('should allow copy if only onCopy is specified', async () => { setup(false, true); await userEvent.click(getCellsAtRowIndex(0)[0]); await copySelectedCell(); diff --git a/website/routes/AllFeatures.tsx b/website/routes/AllFeatures.tsx index 3254c44bea..076cc4d2ba 100644 --- a/website/routes/AllFeatures.tsx +++ b/website/routes/AllFeatures.tsx @@ -206,9 +206,10 @@ function AllFeatures() { const initialRows = Route.useLoaderData(); const [rows, setRows] = useState(initialRows); const [selectedRows, setSelectedRows] = useState((): ReadonlySet => new Set()); - const [copiedCell, setCopiedCell] = useState<{ row: Row; column: CalculatedColumn } | null>( - null - ); + const [copiedCell, setCopiedCell] = useState<{ + readonly row: Row; + readonly column: CalculatedColumn; + } | null>(null); function handleFill({ columnKey, sourceRow, targetRow }: FillEvent): Row { return { ...targetRow, [columnKey]: sourceRow[columnKey as keyof Row] }; From ca2fe20c4766607e4a09db352aa61de687bac1da Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Tue, 25 Mar 2025 12:18:04 -0500 Subject: [PATCH 08/13] Fix copy/paste tests --- test/browser/copyPaste.test.tsx | 161 ++++++++++---------------------- 1 file changed, 50 insertions(+), 111 deletions(-) diff --git a/test/browser/copyPaste.test.tsx b/test/browser/copyPaste.test.tsx index 64fdd9582f..2608db9c41 100644 --- a/test/browser/copyPaste.test.tsx +++ b/test/browser/copyPaste.test.tsx @@ -3,6 +3,7 @@ import { page, userEvent } from '@vitest/browser/context'; import { DataGrid } from '../../src'; import type { CellCopyPasteEvent, Column } from '../../src'; +import type { CellClipboardEvent } from '../../src/types'; import { copySelectedCell, getCellsAtRowIndex, getSelectedCell, pasteSelectedCell } from './utils'; interface Row { @@ -41,21 +42,17 @@ const bottomSummaryRows: readonly Row[] = [ } ]; -const copyCellClassName = 'rdg-cell-copied'; -const onPasteSpy = vi.fn(); -const onCopySpy = vi.fn(); - -function CopyPasteTest({ - onPasteCallback = true, - onCopyCallback = false -}: { - onPasteCallback?: boolean; - onCopyCallback?: boolean; -}) { +const onCellPasteSpy = vi.fn(); +const onCellCopySpy = vi.fn(); + +function CopyPasteTest() { const [rows, setRows] = useState(initialRows); - function onPaste({ column, row }: CellCopyPasteEvent): Row { - onPasteSpy(); + function onCellPaste( + { column, row }: CellCopyPasteEvent, + event: CellClipboardEvent + ): Row { + onCellPasteSpy({ column, row }, event); const columnKey = column.key; return { ...row, [columnKey]: row[columnKey as keyof Row] }; } @@ -66,135 +63,77 @@ function CopyPasteTest({ rows={rows} bottomSummaryRows={bottomSummaryRows} onRowsChange={setRows} - onCellPaste={onPasteCallback ? onPaste : undefined} - onCellCopy={onCopyCallback ? onCopySpy : undefined} + onCellPaste={onCellPaste} + onCellCopy={onCellCopySpy} /> ); } -function setup(onPasteCallback = true, onCopyCallback = false) { - onPasteSpy.mockReset(); - onCopySpy.mockReset(); - page.render(); +function setup() { + onCellPasteSpy.mockReset(); + onCellCopySpy.mockReset(); + page.render(); } -test('should not allow copy/paste if onPaste & onCopy is undefined', async () => { - setup(false, false); - await userEvent.click(getCellsAtRowIndex(0)[0]); - await copySelectedCell(); - await expect.element(getSelectedCell()).not.toHaveClass(copyCellClassName); - expect(onCopySpy).not.toHaveBeenCalled(); - await userEvent.keyboard('{arrowdown}'); - await pasteSelectedCell(); - await userEvent.keyboard('{escape}'); - expect(getCellsAtRowIndex(1)[0]).toHaveTextContent('a2'); - expect(onPasteSpy).not.toHaveBeenCalled(); -}); - -test.only('should allow copy if only onCopy is specified', async () => { - setup(false, true); - await userEvent.click(getCellsAtRowIndex(0)[0]); - await copySelectedCell(); - await expect.element(getSelectedCell()).toHaveClass(copyCellClassName); - expect(onCopySpy).toHaveBeenCalledExactlyOnceWith({ - sourceRow: initialRows[0], - sourceColumnKey: 'col' - }); - await userEvent.keyboard('{arrowdown}'); - await pasteSelectedCell(); - expect(getCellsAtRowIndex(1)[0]).toHaveTextContent('a2'); - expect(onPasteSpy).not.toHaveBeenCalled(); -}); - -test('should allow copy/paste if only onPaste is specified', async () => { - setup(true, false); +test('should call onCellCopy on cell copy', async () => { + setup(); await userEvent.click(getCellsAtRowIndex(0)[0]); await copySelectedCell(); - await expect.element(getSelectedCell()).toHaveClass(copyCellClassName); - expect(onCopySpy).not.toHaveBeenCalled(); - await userEvent.keyboard('{arrowdown}'); - await pasteSelectedCell(); - expect(getCellsAtRowIndex(1)[0]).toHaveTextContent('a1'); - expect(onPasteSpy).toHaveBeenCalledOnce(); + expect(onCellCopySpy).toHaveBeenCalledExactlyOnceWith( + { + row: initialRows[0], + column: expect.objectContaining(columns[0]) + }, + expect.anything() + ); }); -test('should allow copy/paste if both onPaste & onCopy is specified', async () => { - setup(true, true); +test('should call onCellPaste on cell paste', async () => { + setup(); await userEvent.click(getCellsAtRowIndex(0)[0]); - await copySelectedCell(); - await expect.element(getSelectedCell()).toHaveClass(copyCellClassName); - expect(onCopySpy).toHaveBeenCalledExactlyOnceWith({ - sourceRow: initialRows[0], - sourceColumnKey: 'col' - }); - await userEvent.keyboard('{arrowdown}'); await pasteSelectedCell(); - expect(getCellsAtRowIndex(1)[0]).toHaveTextContent('a1'); - expect(onPasteSpy).toHaveBeenCalledOnce(); + expect(onCellPasteSpy).toHaveBeenCalledExactlyOnceWith( + { + row: initialRows[0], + column: expect.objectContaining(columns[0]) + }, + expect.anything() + ); }); test('should not allow paste on readonly cells', async () => { - setup(); - await userEvent.click(getCellsAtRowIndex(1)[0]); - await copySelectedCell(); - await expect.element(getSelectedCell()).toHaveClass(copyCellClassName); - await userEvent.keyboard('{arrowdown}'); - await pasteSelectedCell(); - expect(getCellsAtRowIndex(2)[0]).toHaveTextContent('a3'); -}); - -test('should allow copying a readonly cell, and pasting the value into a writable cell', async () => { setup(); await userEvent.click(getCellsAtRowIndex(2)[0]); - await copySelectedCell(); - await expect.element(getSelectedCell()).toHaveClass(copyCellClassName); - await userEvent.keyboard('{arrowup}'); await pasteSelectedCell(); - expect(getCellsAtRowIndex(1)[0]).toHaveTextContent('a3'); + expect(onCellPasteSpy).not.toHaveBeenCalled(); }); -test('should cancel copy/paste on escape', async () => { +test('should allow copying a readonly cell', async () => { setup(); - await userEvent.click(getCellsAtRowIndex(0)[0]); + await userEvent.click(getCellsAtRowIndex(2)[0]); await copySelectedCell(); - await expect.element(getSelectedCell()).toHaveClass(copyCellClassName); - await userEvent.keyboard('{escape}'); - await expect.element(getSelectedCell()).not.toHaveClass(copyCellClassName); - await userEvent.keyboard('{arrowdown}'); - await pasteSelectedCell(); - expect(getCellsAtRowIndex(1)[0]).toHaveTextContent('a2'); + expect(onCellCopySpy).toHaveBeenCalledExactlyOnceWith( + { + row: initialRows[2], + column: expect.objectContaining(columns[0]) + }, + expect.anything() + ); }); -test('should not allow copy on header or summary cells', async () => { +test('should not allow copy/paste on header or summary cells', async () => { setup(); await userEvent.tab(); await copySelectedCell(); - await expect.element(getSelectedCell()).not.toHaveClass(copyCellClassName); - await userEvent.keyboard('{arrowdown}'); + expect(onCellCopySpy).not.toHaveBeenCalled(); await pasteSelectedCell(); - await expect.element(getSelectedCell()).toHaveTextContent('a1'); - expect(onPasteSpy).not.toHaveBeenCalled(); - await userEvent.keyboard('{Control>}{end}'); - await copySelectedCell(); - await expect.element(getSelectedCell()).not.toHaveClass(copyCellClassName); - await userEvent.keyboard('{arrowup}'); - await pasteSelectedCell(); - await expect.element(getSelectedCell()).toHaveTextContent('a3'); - expect(onPasteSpy).not.toHaveBeenCalled(); -}); + expect(onCellPasteSpy).not.toHaveBeenCalled(); -test('should not allow paste on header or summary cells', async () => { - setup(); - await userEvent.click(getCellsAtRowIndex(0)[0]); - await copySelectedCell(); - await userEvent.keyboard('{arrowup}'); - await pasteSelectedCell(); - await expect.element(getSelectedCell()).toHaveTextContent('Col'); - expect(onPasteSpy).not.toHaveBeenCalled(); await userEvent.keyboard('{Control>}{end}'); + await copySelectedCell(); + expect(onCellCopySpy).not.toHaveBeenCalled(); await pasteSelectedCell(); - await expect.element(getSelectedCell()).toHaveTextContent('s1'); - expect(onPasteSpy).not.toHaveBeenCalled(); + expect(onCellPasteSpy).not.toHaveBeenCalled(); }); test('should not start editing when pressing ctrl+', async () => { From 6a93f61b27a2015d0b391609ab885bcaf171d9a7 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Tue, 25 Mar 2025 12:35:01 -0500 Subject: [PATCH 09/13] Use `userEvent.copy/paste` --- test/browser/TreeDataGrid.test.tsx | 14 +++----------- test/browser/copyPaste.test.tsx | 20 ++++++++++---------- test/browser/utils.tsx | 10 +--------- 3 files changed, 14 insertions(+), 30 deletions(-) diff --git a/test/browser/TreeDataGrid.test.tsx b/test/browser/TreeDataGrid.test.tsx index 13ad53f7d6..e592615f5b 100644 --- a/test/browser/TreeDataGrid.test.tsx +++ b/test/browser/TreeDataGrid.test.tsx @@ -6,15 +6,7 @@ import { SelectColumn, textEditor, TreeDataGrid } from '../../src'; import { focusSinkClassname } from '../../src/style/core'; import { rowSelected } from '../../src/style/row'; import type { CellCopyPasteEvent } from '../../src/types'; -import { - copySelectedCell, - getCellsAtRowIndex, - getHeaderCells, - getRows, - getSelectedCell, - getTreeGrid, - pasteSelectedCell -} from './utils'; +import { getCellsAtRowIndex, getHeaderCells, getRows, getSelectedCell, getTreeGrid } from './utils'; const rowSelectedClassname = 'rdg-row-selected'; @@ -381,11 +373,11 @@ test('copy/paste when grouping is enabled', async () => { setup(['year']); await userEvent.click(page.getByRole('gridcell', { name: '2021' })); await userEvent.click(page.getByRole('gridcell', { name: 'USA' })); - await copySelectedCell(); + await userEvent.copy(); await expect.element(getSelectedCell()).toHaveClass('rdg-cell-copied'); await userEvent.keyboard('{arrowdown}'); await expect.element(getSelectedCell()).toHaveTextContent('Canada'); - await pasteSelectedCell(); + await userEvent.paste(); await expect.element(getSelectedCell()).toHaveTextContent('USA'); }); diff --git a/test/browser/copyPaste.test.tsx b/test/browser/copyPaste.test.tsx index 2608db9c41..683dacd873 100644 --- a/test/browser/copyPaste.test.tsx +++ b/test/browser/copyPaste.test.tsx @@ -4,7 +4,7 @@ import { page, userEvent } from '@vitest/browser/context'; import { DataGrid } from '../../src'; import type { CellCopyPasteEvent, Column } from '../../src'; import type { CellClipboardEvent } from '../../src/types'; -import { copySelectedCell, getCellsAtRowIndex, getSelectedCell, pasteSelectedCell } from './utils'; +import { getCellsAtRowIndex, getSelectedCell } from './utils'; interface Row { col: string; @@ -63,8 +63,8 @@ function CopyPasteTest() { rows={rows} bottomSummaryRows={bottomSummaryRows} onRowsChange={setRows} - onCellPaste={onCellPaste} onCellCopy={onCellCopySpy} + onCellPaste={onCellPaste} /> ); } @@ -78,7 +78,7 @@ function setup() { test('should call onCellCopy on cell copy', async () => { setup(); await userEvent.click(getCellsAtRowIndex(0)[0]); - await copySelectedCell(); + await userEvent.copy(); expect(onCellCopySpy).toHaveBeenCalledExactlyOnceWith( { row: initialRows[0], @@ -91,7 +91,7 @@ test('should call onCellCopy on cell copy', async () => { test('should call onCellPaste on cell paste', async () => { setup(); await userEvent.click(getCellsAtRowIndex(0)[0]); - await pasteSelectedCell(); + await userEvent.paste(); expect(onCellPasteSpy).toHaveBeenCalledExactlyOnceWith( { row: initialRows[0], @@ -104,14 +104,14 @@ test('should call onCellPaste on cell paste', async () => { test('should not allow paste on readonly cells', async () => { setup(); await userEvent.click(getCellsAtRowIndex(2)[0]); - await pasteSelectedCell(); + await userEvent.paste(); expect(onCellPasteSpy).not.toHaveBeenCalled(); }); test('should allow copying a readonly cell', async () => { setup(); await userEvent.click(getCellsAtRowIndex(2)[0]); - await copySelectedCell(); + await userEvent.copy(); expect(onCellCopySpy).toHaveBeenCalledExactlyOnceWith( { row: initialRows[2], @@ -124,15 +124,15 @@ test('should allow copying a readonly cell', async () => { test('should not allow copy/paste on header or summary cells', async () => { setup(); await userEvent.tab(); - await copySelectedCell(); + await userEvent.copy(); expect(onCellCopySpy).not.toHaveBeenCalled(); - await pasteSelectedCell(); + await userEvent.paste(); expect(onCellPasteSpy).not.toHaveBeenCalled(); await userEvent.keyboard('{Control>}{end}'); - await copySelectedCell(); + await userEvent.copy(); expect(onCellCopySpy).not.toHaveBeenCalled(); - await pasteSelectedCell(); + await userEvent.paste(); expect(onCellPasteSpy).not.toHaveBeenCalled(); }); diff --git a/test/browser/utils.tsx b/test/browser/utils.tsx index 6b0b605339..e2cbaeebb5 100644 --- a/test/browser/utils.tsx +++ b/test/browser/utils.tsx @@ -1,4 +1,4 @@ -import { page, userEvent } from '@vitest/browser/context'; +import { page } from '@vitest/browser/context'; import { css } from '@linaria/core'; import { DataGrid } from '../../src'; @@ -58,14 +58,6 @@ export function validateCellPosition(columnIdx: number, rowIdx: number) { expect(cell.parentNode).toHaveAttribute('aria-rowindex', `${rowIdx + 1}`); } -export function copySelectedCell() { - return userEvent.keyboard('{Control>}c{/Control}'); -} - -export function pasteSelectedCell() { - return userEvent.keyboard('{Control>}v{/Control}'); -} - export async function scrollGrid({ scrollLeft, scrollTop From c77bd40b3876da6060f634e57257938ad52c4e73 Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Tue, 25 Mar 2025 16:20:38 -0500 Subject: [PATCH 10/13] fix TreeDataGrid tests --- src/TreeDataGrid.tsx | 23 +++++++++++++++ test/browser/TreeDataGrid.test.tsx | 47 +++++++++++++++++++++--------- test/browser/copyPaste.test.tsx | 4 +-- 3 files changed, 59 insertions(+), 15 deletions(-) diff --git a/src/TreeDataGrid.tsx b/src/TreeDataGrid.tsx index 37297871d5..f3c4979466 100644 --- a/src/TreeDataGrid.tsx +++ b/src/TreeDataGrid.tsx @@ -4,6 +4,8 @@ import type { Key } from 'react'; import { useLatestFunc } from './hooks'; import { assertIsValidKeyGetter } from './utils'; import type { + CellClipboardEvent, + CellCopyPasteEvent, CellKeyboardEvent, CellKeyDownArgs, Column, @@ -53,6 +55,8 @@ export function TreeDataGrid({ rowHeight: rawRowHeight, rowKeyGetter: rawRowKeyGetter, onCellKeyDown: rawOnCellKeyDown, + onCellCopy: rawOnCellCopy, + onCellPaste: rawOnCellPaste, onRowsChange, selectedRows: rawSelectedRows, onSelectedRowsChange: rawOnSelectedRowsChange, @@ -320,6 +324,23 @@ export function TreeDataGrid({ } } + // Prevent copy/paste on group rows + function handleCellCopy( + { row, column }: CellCopyPasteEvent, NoInfer>, + event: CellClipboardEvent + ) { + if (!isGroupRow(row)) { + rawOnCellCopy?.({ row, column }, event); + } + } + + function handleCellPaste( + { row, column }: CellCopyPasteEvent, NoInfer>, + event: CellClipboardEvent + ) { + return isGroupRow(row) ? row : rawOnCellPaste!({ row, column }, event); + } + function handleRowsChange(updatedRows: R[], { indexes, column }: RowsChangeData) { if (!onRowsChange) return; const updatedRawRows = [...rawRows]; @@ -414,6 +435,8 @@ export function TreeDataGrid({ selectedRows={selectedRows} onSelectedRowsChange={onSelectedRowsChange} onCellKeyDown={handleKeyDown} + onCellCopy={handleCellCopy} + onCellPaste={rawOnCellPaste ? handleCellPaste : undefined} renderers={{ ...renderers, renderRow diff --git a/test/browser/TreeDataGrid.test.tsx b/test/browser/TreeDataGrid.test.tsx index e592615f5b..883719fda4 100644 --- a/test/browser/TreeDataGrid.test.tsx +++ b/test/browser/TreeDataGrid.test.tsx @@ -5,7 +5,6 @@ import type { Column } from '../../src'; import { SelectColumn, textEditor, TreeDataGrid } from '../../src'; import { focusSinkClassname } from '../../src/style/core'; import { rowSelected } from '../../src/style/row'; -import type { CellCopyPasteEvent } from '../../src/types'; import { getCellsAtRowIndex, getHeaderCells, getRows, getSelectedCell, getTreeGrid } from './utils'; const rowSelectedClassname = 'rdg-row-selected'; @@ -79,6 +78,9 @@ const initialRows: readonly Row[] = [ } ]; +const onCellCopySpy = vi.fn(); +const onCellPasteSpy = vi.fn(({ row }: { row: Row }) => row); + function rowKeyGetter(row: Row) { return row.id; } @@ -90,13 +92,6 @@ function TestGrid({ groupBy }: { groupBy: string[] }) { (): ReadonlySet => new Set([]) ); - function onCellPaste(event: CellCopyPasteEvent): Row { - return { - ...event.row, - [event.column.key]: event.row[event.column.key as keyof Row] - }; - } - return ( ); } @@ -123,6 +119,8 @@ function rowGrouper(rows: readonly Row[], columnKey: string) { } function setup(groupBy: string[]) { + onCellCopySpy.mockClear(); + onCellPasteSpy.mockClear(); page.render(); } @@ -372,13 +370,36 @@ test('cell navigation in a treegrid', async () => { test('copy/paste when grouping is enabled', async () => { setup(['year']); await userEvent.click(page.getByRole('gridcell', { name: '2021' })); + await userEvent.copy(); + expect(onCellCopySpy).not.toHaveBeenCalled(); + await userEvent.paste(); + expect(onCellPasteSpy).not.toHaveBeenCalled(); + await userEvent.click(page.getByRole('gridcell', { name: 'USA' })); await userEvent.copy(); - await expect.element(getSelectedCell()).toHaveClass('rdg-cell-copied'); - await userEvent.keyboard('{arrowdown}'); - await expect.element(getSelectedCell()).toHaveTextContent('Canada'); + expect(onCellCopySpy).toHaveBeenCalledExactlyOnceWith( + { + column: expect.objectContaining(columns[2]), + row: { + country: 'USA', + id: 2, + year: 2021 + } + }, + expect.anything() + ); await userEvent.paste(); - await expect.element(getSelectedCell()).toHaveTextContent('USA'); + expect(onCellPasteSpy).toHaveBeenCalledExactlyOnceWith( + { + column: expect.objectContaining(columns[2]), + row: { + country: 'USA', + id: 2, + year: 2021 + } + }, + expect.anything() + ); }); test('update row using cell renderer', async () => { diff --git a/test/browser/copyPaste.test.tsx b/test/browser/copyPaste.test.tsx index 683dacd873..07df43004b 100644 --- a/test/browser/copyPaste.test.tsx +++ b/test/browser/copyPaste.test.tsx @@ -42,8 +42,8 @@ const bottomSummaryRows: readonly Row[] = [ } ]; -const onCellPasteSpy = vi.fn(); const onCellCopySpy = vi.fn(); +const onCellPasteSpy = vi.fn(); function CopyPasteTest() { const [rows, setRows] = useState(initialRows); @@ -70,8 +70,8 @@ function CopyPasteTest() { } function setup() { - onCellPasteSpy.mockReset(); onCellCopySpy.mockReset(); + onCellPasteSpy.mockReset(); page.render(); } From fe6f445be77194e263b461ba6a5017bff702c6ef Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Tue, 25 Mar 2025 16:24:49 -0500 Subject: [PATCH 11/13] Tweak tests --- test/browser/copyPaste.test.tsx | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/test/browser/copyPaste.test.tsx b/test/browser/copyPaste.test.tsx index 07df43004b..7fefe9a38a 100644 --- a/test/browser/copyPaste.test.tsx +++ b/test/browser/copyPaste.test.tsx @@ -3,7 +3,6 @@ import { page, userEvent } from '@vitest/browser/context'; import { DataGrid } from '../../src'; import type { CellCopyPasteEvent, Column } from '../../src'; -import type { CellClipboardEvent } from '../../src/types'; import { getCellsAtRowIndex, getSelectedCell } from './utils'; interface Row { @@ -43,20 +42,14 @@ const bottomSummaryRows: readonly Row[] = [ ]; const onCellCopySpy = vi.fn(); -const onCellPasteSpy = vi.fn(); +const onCellPasteSpy = vi.fn(({ column, row }: CellCopyPasteEvent) => { + const columnKey = column.key; + return { ...row, [columnKey]: row[columnKey as keyof Row] }; +}); function CopyPasteTest() { const [rows, setRows] = useState(initialRows); - function onCellPaste( - { column, row }: CellCopyPasteEvent, - event: CellClipboardEvent - ): Row { - onCellPasteSpy({ column, row }, event); - const columnKey = column.key; - return { ...row, [columnKey]: row[columnKey as keyof Row] }; - } - return ( ); } function setup() { - onCellCopySpy.mockReset(); - onCellPasteSpy.mockReset(); + onCellCopySpy.mockClear(); + onCellPasteSpy.mockClear(); page.render(); } From 63eb607545e013d74cef19e758707c74231bde5e Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Thu, 3 Apr 2025 12:34:22 -0500 Subject: [PATCH 12/13] Update example --- website/routes/AllFeatures.tsx | 43 ++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/website/routes/AllFeatures.tsx b/website/routes/AllFeatures.tsx index 076cc4d2ba..b2bac4a53d 100644 --- a/website/routes/AllFeatures.tsx +++ b/website/routes/AllFeatures.tsx @@ -215,27 +215,36 @@ function AllFeatures() { return { ...targetRow, [columnKey]: sourceRow[columnKey as keyof Row] }; } - function handleCellPaste({ row, column }: CellCopyPasteEvent): Row { - if (copiedCell === null) { - return row; - } - - const sourceColumnKey = copiedCell.column.key; - const sourceRow = copiedCell.row; + function handleCellPaste( + { row, column }: CellCopyPasteEvent, + event: React.ClipboardEvent + ): Row { const targetColumnKey = column.key; - const incompatibleColumns = ['email', 'zipCode', 'date']; - if ( - sourceColumnKey === 'avatar' || - ['id', 'avatar'].includes(targetColumnKey) || - ((incompatibleColumns.includes(targetColumnKey) || - incompatibleColumns.includes(sourceColumnKey)) && - sourceColumnKey !== targetColumnKey) - ) { - return row; + if (copiedCell !== null) { + const sourceColumnKey = copiedCell.column.key; + const sourceRow = copiedCell.row; + + const incompatibleColumns = ['email', 'zipCode', 'date']; + if ( + sourceColumnKey === 'avatar' || + ['id', 'avatar'].includes(targetColumnKey) || + ((incompatibleColumns.includes(targetColumnKey) || + incompatibleColumns.includes(sourceColumnKey)) && + sourceColumnKey !== targetColumnKey) + ) { + return row; + } + + return { ...row, [targetColumnKey]: sourceRow[sourceColumnKey as keyof Row] }; + } + + const copiedText = event.clipboardData.getData('text/plain'); + if (copiedText !== '') { + return { ...row, [targetColumnKey]: copiedText }; } - return { ...row, [targetColumnKey]: sourceRow[sourceColumnKey as keyof Row] }; + return row; } function handleCellCopy( From 2fdd7389ad49e00f3a91eb02f93ccf6533a08e2c Mon Sep 17 00:00:00 2001 From: Aman Mahajan Date: Thu, 10 Apr 2025 11:47:46 -0500 Subject: [PATCH 13/13] Add `CellCopyEvent` and `CellPasteEvent` events --- src/DataGrid.tsx | 7 ++++--- src/TreeDataGrid.tsx | 7 ++++--- src/index.ts | 3 ++- src/types.ts | 5 ++++- test/browser/copyPaste.test.tsx | 4 ++-- website/routes/AllFeatures.tsx | 6 +++--- 6 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index 55f666ac46..d8047136c7 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -40,11 +40,12 @@ import type { CalculatedColumn, CellClickArgs, CellClipboardEvent, - CellCopyPasteEvent, + CellCopyEvent, CellKeyboardEvent, CellKeyDownArgs, CellMouseEvent, CellNavigationMode, + CellPasteEvent, CellSelectArgs, Column, ColumnOrColumnGroup, @@ -195,10 +196,10 @@ export interface DataGridProps extends Sha (args: CellKeyDownArgs, NoInfer>, event: CellKeyboardEvent) => void >; onCellCopy?: Maybe< - (args: CellCopyPasteEvent, NoInfer>, event: CellClipboardEvent) => void + (args: CellCopyEvent, NoInfer>, event: CellClipboardEvent) => void >; onCellPaste?: Maybe< - (args: CellCopyPasteEvent, NoInfer>, event: CellClipboardEvent) => NoInfer + (args: CellPasteEvent, NoInfer>, event: CellClipboardEvent) => NoInfer >; /** Function called whenever cell selection is changed */ onSelectedCellChange?: Maybe<(args: CellSelectArgs, NoInfer>) => void>; diff --git a/src/TreeDataGrid.tsx b/src/TreeDataGrid.tsx index f3c4979466..8fd9abaa1f 100644 --- a/src/TreeDataGrid.tsx +++ b/src/TreeDataGrid.tsx @@ -5,9 +5,10 @@ import { useLatestFunc } from './hooks'; import { assertIsValidKeyGetter } from './utils'; import type { CellClipboardEvent, - CellCopyPasteEvent, + CellCopyEvent, CellKeyboardEvent, CellKeyDownArgs, + CellPasteEvent, Column, GroupRow, Maybe, @@ -326,7 +327,7 @@ export function TreeDataGrid({ // Prevent copy/paste on group rows function handleCellCopy( - { row, column }: CellCopyPasteEvent, NoInfer>, + { row, column }: CellCopyEvent, NoInfer>, event: CellClipboardEvent ) { if (!isGroupRow(row)) { @@ -335,7 +336,7 @@ export function TreeDataGrid({ } function handleCellPaste( - { row, column }: CellCopyPasteEvent, NoInfer>, + { row, column }: CellPasteEvent, NoInfer>, event: CellClipboardEvent ) { return isGroupRow(row) ? row : rawOnCellPaste!({ row, column }, event); diff --git a/src/index.ts b/src/index.ts index 738dd53aed..6d111cacfd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -34,7 +34,6 @@ export type { SelectHeaderRowEvent, SelectRowEvent, FillEvent, - CellCopyPasteEvent, SortDirection, SortColumn, ColSpanArgs, @@ -48,5 +47,7 @@ export type { CellClickArgs, CellKeyDownArgs, CellKeyboardEvent, + CellCopyEvent, + CellPasteEvent, CellSelectArgs } from './types'; diff --git a/src/types.ts b/src/types.ts index 007bb54143..82b22178df 100644 --- a/src/types.ts +++ b/src/types.ts @@ -253,11 +253,14 @@ export interface FillEvent { targetRow: TRow; } -export interface CellCopyPasteEvent { +interface CellCopyPasteEvent { column: CalculatedColumn; row: TRow; } +export type CellCopyEvent = CellCopyPasteEvent; +export type CellPasteEvent = CellCopyPasteEvent; + export interface GroupRow { readonly childRows: readonly TRow[]; readonly id: string; diff --git a/test/browser/copyPaste.test.tsx b/test/browser/copyPaste.test.tsx index 7fefe9a38a..e7bf3bbc49 100644 --- a/test/browser/copyPaste.test.tsx +++ b/test/browser/copyPaste.test.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { page, userEvent } from '@vitest/browser/context'; import { DataGrid } from '../../src'; -import type { CellCopyPasteEvent, Column } from '../../src'; +import type { CellPasteEvent, Column } from '../../src'; import { getCellsAtRowIndex, getSelectedCell } from './utils'; interface Row { @@ -42,7 +42,7 @@ const bottomSummaryRows: readonly Row[] = [ ]; const onCellCopySpy = vi.fn(); -const onCellPasteSpy = vi.fn(({ column, row }: CellCopyPasteEvent) => { +const onCellPasteSpy = vi.fn(({ column, row }: CellPasteEvent) => { const columnKey = column.key; return { ...row, [columnKey]: row[columnKey as keyof Row] }; }); diff --git a/website/routes/AllFeatures.tsx b/website/routes/AllFeatures.tsx index b2bac4a53d..21b16e10ed 100644 --- a/website/routes/AllFeatures.tsx +++ b/website/routes/AllFeatures.tsx @@ -5,7 +5,7 @@ import { css } from '@linaria/core'; import clsx from 'clsx'; import { DataGrid, SelectColumn, textEditor } from '../../src'; -import type { CalculatedColumn, CellCopyPasteEvent, Column, FillEvent } from '../../src'; +import type { CalculatedColumn, CellCopyEvent, CellPasteEvent, Column, FillEvent } from '../../src'; import { textEditorClassname } from '../../src/editors/textEditor'; import { useDirection } from '../directionContext'; @@ -216,7 +216,7 @@ function AllFeatures() { } function handleCellPaste( - { row, column }: CellCopyPasteEvent, + { row, column }: CellCopyEvent, event: React.ClipboardEvent ): Row { const targetColumnKey = column.key; @@ -248,7 +248,7 @@ function AllFeatures() { } function handleCellCopy( - { row, column }: CellCopyPasteEvent, + { row, column }: CellPasteEvent, event: React.ClipboardEvent ): void { // copy highlighted text only