Keyboard copy event problem
Describe the bug After updating to the latest version of Google Chrome, the ability to multi-select and copy (CMD + C) multiple rows and columns is no longer working. It also occurs in Microsoft Edge on a Windows machine. It seems that keyboard events are not functioning properly in the new Chrome version.
Steps to Reproduce:
- Select multiple rows and columns within a table or spreadsheet-like interface.
- Attempt to use the CMD + C keyboard shortcut to copy the selected data.
- Notice that the copy functionality does not work as expected. I’ve attached a GIF below that demonstrates the issue.
Please investigate.
Current behavior CMD + C is not copying
Expected behavior CMD + C can can copyable and this used to happen
Screenshots or gifs
Your environment details
- Device: Desktop / Mac
- OS: MacOS
- Browser: Chrome Version 134.0.6998.45 (Official Build) (arm64)
- "@silevis/reactgrid": "^4.1.15",
maybe instead of “document.execCommand(‘copy’)” we could use something like “navigator.clipboard.writeText(text_to_copy)”.
There already seems to be a different handling for safari, that likely needs to be done on chrome too now:
https://github.com/silevis/reactgrid/blob/1cb04270a2d51e84238f4e6e0223d883ad6854d0/src/lib/Functions/handleCopy.ts#L18-L26
The company I work for uses this component. One morning I was able to copy it, but today I started getting emails saying I can't copy it. When I checked that I could copy it, I checked my chrome version and it was 133 and there was an update. When I updated to 134 I couldn't copy it. So I was sure that this problem was caused by my Chrome version. I also needed to solve this problem, so I wrote a wrapper like the one below. I'm not saying it's absolutely the right solution, but it saved my ass.
import React, { useState, useEffect, useCallback } from 'react'
import { ReactGrid, ReactGridProps, Cell, TextCell, Range } from '@silevis/reactgrid'
import '@silevis/reactgrid/styles.css'
interface CustomReactGridProps extends ReactGridProps {
onCopy?: (text: string) => void
}
type CustomCellMatrix = Cell[][]
const CustomReactGrid: React.FC<CustomReactGridProps> = props => {
const [selectedRanges, setSelectedRanges] = useState<Range[]>([])
const handleSelectionChanged = useCallback((ranges: Range[]) => {
if (ranges.length > 0) {
setSelectedRanges([ranges[0]])
} else {
setSelectedRanges([])
}
}, [])
const handleCopy = useCallback(
(e: ClipboardEvent) => {
if (!selectedRanges.length) return
const range = selectedRanges[0]
try {
// Use the selected range directly
const selectedCells = getCellsInRange(props.rows, props.columns, range)
const clipboardText = formatCellsForClipboard(selectedCells)
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard
.writeText(clipboardText)
.then(() => {
if (props.onCopy) props.onCopy(clipboardText)
})
.catch(err => console.error('Clipboard error:', err))
}
} catch (error) {
console.error('Error during copy:', error)
}
e.preventDefault()
},
[props.rows, props.columns, selectedRanges, props.onCopy]
)
useEffect(() => {
document.addEventListener('copy', handleCopy)
return () => document.removeEventListener('copy', handleCopy)
}, [handleCopy])
const getCellsInRange = (rows: any[], columns: any[], range: Range) => {
const cellMatrix: CustomCellMatrix = []
// Determine range using first and last
const firstRow = range.first.row
const lastRow = range.last.row
const firstCol = range.first.column
const lastCol = range.last.column
// Find indices
const firstRowIndex = rows.findIndex(r => r.rowId === firstRow.rowId)
const lastRowIndex = rows.findIndex(r => r.rowId === lastRow.rowId)
const firstColIndex = columns.findIndex(c => c.columnId === firstCol.columnId)
const lastColIndex = columns.findIndex(c => c.columnId === lastCol.columnId)
// Get all cells in the selected range
for (let r = firstRowIndex; r <= lastRowIndex; r += 1) {
const cellRow: Cell[] = []
cellMatrix.push(cellRow)
for (let c = firstColIndex; c <= lastColIndex; c += 1) {
const cell = rows[r]?.cells?.[c] || { type: 'text', text: '' }
cellRow.push(cell)
}
}
return cellMatrix
}
const formatCellsForClipboard = (cellMatrix: CustomCellMatrix): string => {
if (!cellMatrix.length) return ''
return cellMatrix
.map(row =>
row
.map(cell => {
if ('text' in cell) {
return (cell as TextCell).text
}
return ''
})
.join('\t')
)
.join('\n')
}
return <ReactGrid {...props} onSelectionChanged={handleSelectionChanged} />
}
export default CustomReactGrid
I'm open this PR to try fix this issue