reactgrid icon indicating copy to clipboard operation
reactgrid copied to clipboard

Keyboard copy event problem

Open arasovic opened this issue 10 months ago • 4 comments

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:

  1. Select multiple rows and columns within a table or spreadsheet-like interface.
  2. Attempt to use the CMD + C keyboard shortcut to copy the selected data.
  3. 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

Image

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)”.

arasovic avatar Mar 10 '25 13:03 arasovic

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

MinnDevelopment avatar Mar 11 '25 17:03 MinnDevelopment

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


arasovic avatar Mar 11 '25 17:03 arasovic

I'm open this PR to try fix this issue

Luisgustavom1 avatar Mar 12 '25 16:03 Luisgustavom1