react-konva-grid
Version:
Declarative React Canvas Grid primitive for Data table, Pivot table, Excel Worksheets
143 lines (131 loc) • 4.1 kB
text/typescript
import React, { useCallback, useEffect, useMemo, useRef } from "react";
import { SelectionProps, CellInterface, GridRef, SelectionArea } from "../Grid";
import { selectionFromActiveCell, prepareClipboardData } from "./../helpers";
import { KeyCodes } from "../types";
interface CopyProps {
selections: SelectionArea[];
activeCell?: CellInterface | null;
getValue: (cell: CellInterface) => any;
gridRef: React.MutableRefObject<GridRef>;
onPaste?: (rows: string[][], activeCell: CellInterface) => void;
}
enum MimeType {
html = "text/html",
csv = "text/csv",
plain = "text/plain",
json = "application/json",
}
/**
* Copy paste hook
* Usage
*
* useCopyPaste ({
* onPaste: (text) => {
* }
* })
*/
const useCopyPaste = ({
selections = [],
activeCell = null,
getValue,
gridRef,
onPaste,
}: CopyProps) => {
const selectionRef = useRef({ selections, activeCell, getValue });
/* Keep selections and activeCell upto date */
useEffect(() => {
selectionRef.current = { selections, activeCell, getValue };
});
const currentSelections = () => {
const sel = selectionRef.current.selections.length
? selectionRef.current.selections
: selectionFromActiveCell(selectionRef.current.activeCell);
return sel[sel.length - 1];
};
useEffect(() => {
if (!gridRef.current) return;
document.addEventListener("copy", (e) => {
if (gridRef.current?.container !== document.activeElement) return;
handleCopy(e);
});
document.addEventListener("paste", (e) => {
if (gridRef.current?.container !== document.activeElement) return;
handlePaste(e);
});
}, []);
const handleCopy = useCallback(
(e: ClipboardEvent) => {
/* Only copy the last selection */
const { bounds } = currentSelections();
const { top, left, right, bottom } = bounds;
const rows = [];
for (let i = top; i <= bottom; i++) {
const row = [];
for (let j = left; j <= right; j++) {
const value =
selectionRef.current.getValue({ rowIndex: i, columnIndex: j }) ??
"";
row.push(value);
}
rows.push(row);
}
const [html, csv] = prepareClipboardData(rows);
e.clipboardData.setData(MimeType.html, html);
e.clipboardData.setData(MimeType.plain, csv);
e.clipboardData.setData(MimeType.csv, csv);
e.clipboardData.setData(MimeType.json, JSON.stringify(rows));
e.preventDefault();
},
[currentSelections]
);
const handlePaste = (e: ClipboardEvent) => {
const { items } = e.clipboardData;
const mimeTypes = [MimeType.html, MimeType.csv, MimeType.plain];
let type;
let value;
for (type of mimeTypes) {
value = e.clipboardData.getData(type);
if (value) break;
}
if (!type || !value) {
console.warn("No clipboard data to paste");
return;
}
const rows = [];
if (/^text\/html/.test(type)) {
const domparser = new DOMParser();
const doc = domparser.parseFromString(value, type);
const supportedNodes = "table, p, h1, h2, h3, h4, h5, h6";
const nodes = doc.querySelectorAll(supportedNodes);
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (node.nodeName === "TABLE") {
const tableRows = doc.querySelectorAll("tr");
for (const tableRow of tableRows) {
const row = [];
const cells = tableRow.querySelectorAll("td");
for (const cell of cells) {
row.push(cell.textContent);
}
rows.push(row);
}
} else {
// Single nodes
rows.push([node.textContent]);
}
}
} else {
const values = value.split("\n");
for (const val of values) {
const row = [];
for (const cell of val.split(",")) {
row.push(cell.replace(/^\"|\"$/gi, ""));
}
rows.push(row);
}
}
onPaste && onPaste(rows, selectionRef.current.activeCell);
};
return {};
};
export default useCopyPaste;