React クリップボードにコピーしたファイルを貼り付ける
React
+TypeScript
の環境でクリップボードにコピーしたファイルを貼り付ける方法を紹介します。
React ドラッグ&ドロップでファイルを選択するで紹介したコンポーネントの、ドラッグアンドドロップの要素にフォーカスを当てた状態で、貼り付けをしたときに、そのファイルを追加します。
以下が完成品です。
import { useCallback, useRef, useState } from 'react';
const App = () => {
const [files, setFiles] = useState<File[]>([]);
const attachRef = useRef<HTMLInputElement>(null);
const [dragging, setDragging] = useState<number>(0);
const handleInpuFileChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files == null) return;
const files = Array.from(e.target.files);
setFiles((current) => current.concat(files));
if (attachRef.current) attachRef.current.value = '';
}, []);
const onDragEnter = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
setDragging((current) => current + 1);
}, []);
const onDragLeave = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
setDragging((current) => current - 1);
}, []);
const onDragOver = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
e.dataTransfer.dropEffect = 'copy';
}, []);
const onDrop = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
e.stopPropagation();
const files = Array.from(e.dataTransfer.items)
.map((item) => item.getAsFile())
.filter((file): file is File => file !== null);
setFiles((current) => current.concat(files));
}, []);
const onPaste = useCallback(
(e: React.ClipboardEvent<HTMLDivElement>) => {
const file = e.clipboardData.items[0].getAsFile() ?? undefined;
if (file === undefined) return; // ファイルでない場合は処理終了
setFiles((current) => current.concat(file));
},
[]
);
return (
<>
<div>
{files.map((f) => (
<div key={f.name}>{f.name}</div>
))}
</div>
<div>
<input type="button" value="参照" onClick={() => attachRef.current?.click()}></input>
<input type="file" style={{ display: 'none' }} ref={attachRef} multiple onChange={handleInpuFileChange}></input>
</div>
<div
style={{ border: 'dashed 1px #000', padding: 20, backgroundColor: dragging > 0 ? '#ccc' : undefined }}
tabIndex={0}
onDragEnter={onDragEnter}
onDragLeave={onDragLeave}
onDragOver={onDragOver}
onDrop={onDrop}
onPaste={onPaste}
>
ここにファイルをドロップしてください。
</div>
</>
);
};
export default App;
解説
div
要素はデフォルトではフォーカスを当てることができないので、tabindex=0
の属性を設定して、フォーカスを当てられるようにします。
フォーカスが当たっている状態でctrl+cなどで貼り付けを行うと、onPaste
イベントが発生します。
<div
style={{ border: 'dashed 1px #000', padding: 20, backgroundColor: dragging > 0 ? '#ccc' : undefined }}
tabIndex={0}
onDragEnter={onDragEnter}
onDragLeave={onDragLeave}
onDragOver={onDragOver}
onDrop={onDrop}
onPaste={onPaste}
onPaste
イベントでは、e.clipboardData.items[0]
で貼り付けたデータを取得できます。
getAsFile
で、ファイルとして取得します。ファイルでない場合はundefined
になるので、その場合は処理を終了します。
const onPaste = useCallback(
(e: React.ClipboardEvent<HTMLDivElement>) => {
const file = e.clipboardData.items[0].getAsFile() ?? undefined;
if (file === undefined) return; // ファイルでない場合は処理終了
setFiles((current) => current.concat(file));
},
[]
);
以上で、クリップボードから貼り付けでのファイル選択は完了です。
クリップボードからファイルを選択するという用途はあまりないかもしれませんが、例えばslackやteamsなどで実装されているような、スクリーンショットでクリップボードをコピーした画像を貼り付けるような形で、実装するケースがありそうです。