Copy Cut Paste
Support for cut, copy, and paste is enabled by default. You can disable these features by passing disableCut
, disableCopy
, and disablePaste
props to the ReactGrid
component, respectively. If you'd like to implement your own logic, the following props are available:
onCut?: (event: React.ClipboardEvent<HTMLDivElement>, cellsRange: NumericalRange, cellsLookup: CellsLookup) => boolean;
onCopy?: (event: React.ClipboardEvent<HTMLDivElement>, cellsRange: NumericalRange, cellsLookup: CellsLookup) => boolean;
onPaste?: (event: React.ClipboardEvent<HTMLDivElement>, cellsRange: NumericalRange, cellsLookup: CellsLookup) => boolean;
Parameters
Name | Type | Description |
---|---|---|
event | React.ClipboardEvent<HTMLDivElement> | The clipboard event triggered by the cut, copy, or paste action. |
cellsRange | NumericalRange | The range of cells involved in the cut, copy, or paste action. |
cellsLookup | CellsLookup | A map object that provides callbacks to get and set cell values. Each key is a string combining row and column indices, and the value is an object with rowIndex , colIndex , onStringValueRequested (callback to get the cell value), and onStringValueReceived (callback to set the cell value). |
Return Value
The return value of these handlers determines whether the custom logic should override the default cut, copy, or paste behavior.
Return Value | Type | Description |
---|---|---|
true | boolean | Prevent the default behavior. |
false | boolean | Default behavior should proceed without being prevented. It is used in case you want to perform additional actions before allowing the default behavior. |
Live example
Below is an example with overridden logic for cut, copy, and paste. Additionally, handlePaste
utilizes the setSelectedArea
method from the useReactGridAPI hook to highlight the new cell area after pasting the data.
Code
const peopleData = [ { id: "66d61077035753f369ddbb16", name: "Jordan Rodriquez", age: 30, email: "jordanrodriquez@cincyr.com", company: "Zaggles", }, { id: "66d61077794e7949ab167fd5", email: "allysonrios@satiance.com", name: "Allyson Rios", age: 30, company: "Zoxy", }, { id: "66d61077dd754e88981ae434", name: "Pickett Lucas", age: 25, email: "pickettlucas@zoxy.com", company: "Techade", }, { id: "66d61077115e2f8748c334d9", name: "Louella David", age: 37, email: "louelladavid@techade.com", company: "Ginkogene", }, { id: "66d61077540d53374b427e4b", name: "Tricia Greene", age: 27, email: "triciagreene@ginkogene.com", company: "Naxdis", }, ]; const cellStyles = { header: { backgroundColor: "#55bc71", display: "flex", alignItems: "center", justifyContent: "center", fontWeight: "bold", }, }; const getRows = (people: Person[]): Row[] => [ // header row { rowIndex: 0, height: 40, }, // data rows ...people.map((_, i) => ({ rowIndex: i + 1, height: 40, })), ]; const getColumns = (): Column[] => [ { colIndex: 0, width: 220 }, { colIndex: 1, width: 220 }, { colIndex: 2, width: 220 }, { colIndex: 3, width: 220 }, ]; type UpdatePerson = <T>(id: string, key: string, newValue: T) => void; const generateCells = (people: Person[], updatePerson: UpdatePerson): Cell[] => { const generateHeaderCells = () => { const titles = ["Name", "Age", "Email", "Company"]; return titles.map((title, colIndex) => ({ rowIndex: 0, colIndex, Template: NonEditableCell, props: { value: title, style: cellStyles.header, }, })); }; const generateRowCells = (rowIndex: number, person: Person): Cell[] => { const { id, name, age, email, company } = person; return [ { rowIndex, colIndex: 0, Template: TextCell, props: { text: name, onTextChanged: (newText: string) => updatePerson(id, "name", newText), }, }, { rowIndex, colIndex: 1, Template: NumberCell, props: { value: age, onValueChanged: (newValue: number) => updatePerson(id, "age", newValue), }, }, { rowIndex, colIndex: 2, Template: TextCell, props: { text: email, onTextChanged: (newText: string) => updatePerson(id, "email", newText), }, }, { rowIndex, colIndex: 3, Template: TextCell, props: { text: company, onTextChanged: (newText: string) => updatePerson(id, "company", newText), }, }, ]; }; const headerCells = generateHeaderCells(); const rowCells = people.flatMap((person, idx) => generateRowCells(idx + 1, person)); return [...headerCells, ...rowCells]; }; const ReactGridExample = () => { const [people, setPeople] = useState(peopleData); const updatePerson = (id, key, newValue) => { setPeople((prev) => { return prev.map((p) => (p.id === id ? { ...p, [key]: newValue } : p)); }); }; const rows = getRows(people); const columns = getColumns(); const cells = generateCells(people, updatePerson); const gridAPI = useReactGridAPI("cut-copy-paste-example"); return ( <div> <ReactGrid id="cut-copy-paste-example" rows={rows} columns={columns} cells={cells} onCut={(event, cellsRange, cellsLookup) => handleCut(event, cellsRange, cellsLookup)} onCopy={(event, cellsRange, cellsLookup) => handleCopy(event, cellsRange, cellsLookup)} onPaste={(event, cellsRange, cellsLookup) => handlePaste(event, cellsRange, cellsLookup, gridAPI)} /> </div> ); }; const handleCut = ( event: React.ClipboardEvent<HTMLDivElement>, cellsRange: NumericalRange, cellsLookup: CellsLookup ) => { const { startRowIdx, endRowIdx, startColIdx, endColIdx } = cellsRange; const cellsLookupCallbacks: CellsLookupCallbacks[] = []; for (let rowIdx = startRowIdx; rowIdx < endRowIdx; rowIdx++) { for (let colIdx = startColIdx; colIdx < endColIdx; colIdx++) { const element = cellsLookup.get(`${rowIdx} ${colIdx}`); if (element) { cellsLookupCallbacks.push(element); } } } const values = cellsLookupCallbacks .filter((element) => element && Object.keys(element).length > 0) .map((element) => element.onStringValueRequested()); cellsLookupCallbacks.forEach((element) => element && element.onStringValueReceived?.("")); const htmlData = ` <table> ${Array.from( { length: cellsRange.endRowIdx - cellsRange.startRowIdx }, (_, rowIndex) => ` <tr> ${Array.from({ length: cellsRange.endColIdx - cellsRange.startColIdx }, (_, colIndex) => { const cell = cellsLookup.get(`${cellsRange.startRowIdx + rowIndex} ${cellsRange.startColIdx + colIndex}`); const value = cell?.onStringValueRequested?.() || ""; return `<td>${value}</td>`; }).join("")} </tr> ` ).join("")} </table> `; event.clipboardData.setData("text/html", htmlData); event.clipboardData.setData("text/plain", values.join(" ")); // Prevent the default cut behavior return true; }; const handleCopy = ( event: React.ClipboardEvent<HTMLDivElement>, cellsRange: NumericalRange, cellsLookup: CellsLookup ) => { const { startRowIdx, endRowIdx, startColIdx, endColIdx } = cellsRange; const cellsLookupCallbacks: CellsLookupCallbacks[] = []; for (let rowIdx = startRowIdx; rowIdx < endRowIdx; rowIdx++) { for (let colIdx = startColIdx; colIdx < endColIdx; colIdx++) { const element = cellsLookup.get(`${rowIdx} ${colIdx}`); if (element) { cellsLookupCallbacks.push(element); } } } const values = cellsLookupCallbacks .filter((element) => element && Object.keys(element).length > 0) .map((element) => element.onStringValueRequested()); const htmlData = ` <table> ${Array.from( { length: cellsRange.endRowIdx - cellsRange.startRowIdx }, (_, rowIndex) => ` <tr> ${Array.from({ length: cellsRange.endColIdx - cellsRange.startColIdx }, (_, colIndex) => { const cell = cellsLookup.get(`${cellsRange.startRowIdx + rowIndex} ${cellsRange.startColIdx + colIndex}`); const value = cell?.onStringValueRequested?.() || ""; return `<td>${value}</td>`; }).join("")} </tr> ` ).join("")} </table> `; event.clipboardData.setData("text/html", htmlData); event.clipboardData.setData("text/plain", values.join(" ")); // Prevent the default copy behavior return true; }; const handlePaste = ( event: React.ClipboardEvent<HTMLDivElement>, cellsRange: NumericalRange, cellsLookup: CellsLookup, gridAPI?: ReactGridAPI ) => { const html = event.clipboardData.getData("text/html"); const parser = new DOMParser(); const doc = parser.parseFromString(html, "text/html"); const rows = doc.querySelectorAll("tr"); // Prevent the default paste behavior - no action will be performed if (rows.length === 0) return true; const firstRowCells = rows[0].querySelectorAll("td"); if (rows.length === 1 && firstRowCells.length === 1) { const singleValue = firstRowCells[0].textContent || ""; for (let rowIndex = cellsRange.startRowIdx; rowIndex < cellsRange.endRowIdx; rowIndex++) { for (let colIndex = cellsRange.startColIdx; colIndex < cellsRange.endColIdx; colIndex++) { const gridCell = cellsLookup.get(`${rowIndex} ${colIndex}`); gridCell?.onStringValueReceived(singleValue); } } } else { rows.forEach((row, rowIndex) => { const cells = row.querySelectorAll("td"); cells.forEach((cell, colIndex) => { const value = cell.textContent || ""; const gridCell = cellsLookup.get(`${cellsRange.startRowIdx + rowIndex} ${cellsRange.startColIdx + colIndex}`); if (gridCell) { gridCell.onStringValueReceived?.(value); } }); }); } if (gridAPI) { let newSelectedArea; // If only one cell was pasted if (rows.length === 1 && firstRowCells.length === 1) { newSelectedArea = { startRowIdx: cellsRange.startRowIdx, endRowIdx: cellsRange.endRowIdx, startColIdx: cellsRange.startColIdx, endColIdx: cellsRange.endColIdx, }; } // If multiple cells were pasted else { const endRowIdx = Math.min(cellsRange.startRowIdx + rows.length, gridAPI.getRows().length); const endColIdx = Math.min( cellsRange.startColIdx + rows[0].querySelectorAll("td").length, gridAPI.getColumns().length ); newSelectedArea = { startRowIdx: cellsRange.startRowIdx, endRowIdx: endRowIdx, startColIdx: cellsRange.startColIdx, endColIdx: endColIdx, }; } gridAPI.setSelectedArea(newSelectedArea); } // Prevent the default paste behavior return true; }; render(<ReactGridExample />, document.getElementById("root"));
Preview