ReactのSpreadSheet的なコンポーネントを探す
やりたいこと
- 選択範囲のコピー&ペースト
- Excelからのコピー&ペースト
- Tabキーで右のセルに移動
- Enterキーで右のセルに移動(右端セルでEnterキー押下時は新規行生成してフォーカスは新規行の名前を除く最初のセル)
- 矢印キーでセル移動(一番右のセルで右矢印を押下すると次の行の最初のセルに移動)(あくまで行追加はEnter)
- セルにボタンが配置でき、任意の処理を実行できる
- 名前は行生成時に自動生成(ユーザ変更可)(デフォルト値: 型名 + 行番号)
- セルに対して、クリック一回(あるいはキーで移動してフォーカスを当てた時)は上書きモード、もう一度クリックで編集モード(Excelと同様)
Google SpreadSheetで入力してデータだけGASなんかで取りたくなるけど、React の SPA に上記機能を組み込んでくれってさ。
前提条件が抜けた。
- React v17 : create-react-app でプロジェクト作成、eject はしたくない
- TypeScript
- react-redux : 表示・編集対応のデータは store に格納されている
- react-router-dom(これはあんまり関係ないか)
- material-ui
とりあえず npm trends でそれっぽい名前のレポジトリを比較して、ダウンロード数の多いやつから試してみる
React Table - Hooks for building lightweight, fast and extendable datagrids for React
セル選択っていう概念がない。
セル選択してドラッグして範囲選択→コピペ、というのを自前で実装するのは骨が折れそう。
様々な機能が hooks で提供されているのは良さげ。組み込みやすそう。
Demos - Common Features ⋅ Storybook
かなり操作感が希望に近い。
Excel-like grid component built with React, with editors, keyboard navigation, copy & paste, and the like
とあるが、デモではセルの範囲選択が確認できなかった。デモでは無効化されているだけ?
nadbm/react-datasheet: Excel-like data grid (table) component for React
おー、すごい。
ほぼ希望を満たしている。
デモでセルの範囲選択、Excelへのコピー&ペースト、Excelからのコピー&ペーストができることを確認。
React datasheet が前提条件の構成で動作するか実際に試してみる
↑ここで作ったプロジェクトに実装していく
まずは扱うデータの型定義。
こんなんでいけるかな?
export interface Color {
r: number;
g: number;
b: number;
}
export const isColor = (item: unknown): item is Color => {
if (item && typeof item === 'object') {
const temp = item as Record<string, unknown>;
return (
typeof temp.r === 'number' && typeof temp.g === 'number' && typeof temp.b === 'number'
);
}
return false;
};
export interface Point {
id: string;
x: number;
y: number;
z: number;
color: Color;
}
export const isPoint = (item: unknown): item is Point => {
if (item && typeof item === 'object') {
const temp = item as Record<string, unknown>;
return (
typeof temp.id === 'string' &&
typeof temp.x === 'number' &&
typeof temp.y === 'number' &&
typeof temp.z === 'number' &&
isColor(temp.color)
);
}
return false;
};
export interface Line {
id: string;
start: string;
end: string;
color: Color;
}
export const isLine = (item: unknown): item is Line => {
if (item && typeof item === 'object') {
const temp = item as Record<string, unknown>;
return (
typeof temp.id === 'string' &&
typeof temp.start === 'string' &&
typeof temp.end === 'string' &&
isColor(temp.color)
);
}
return false;
};
export interface Solid {
id: string;
points: Point[];
lines: Line[];
}
export const isSolid = (item: unknown): item is Solid => {
if (item && typeof item === 'object') {
const temp = item as Record<string, unknown>;
return (
typeof temp.id === 'string' &&
Array.isArray(temp.points) &&
Array.isArray(temp.lines) &&
temp.points.every(isPoint) &&
temp.lines.every(isLine)
);
}
return false;
};
表示するデータ。
export const defaultColor: Color = {
r: 0,
g: 0,
b: 0,
};
export const data: Solid[] = [
// 立方体
{
id: 'solid1',
points: [
{
id: 'p1',
x: 0,
y: 0,
z: 0,
color: defaultColor,
},
{
id: 'p2',
x: 6,
y: 0,
z: 0,
color: defaultColor,
},
{
id: 'p3',
x: 6,
y: 0,
z: 6,
color: defaultColor,
},
{
id: 'p4',
x: 0,
y: 0,
z: 6,
color: defaultColor,
},
{
id: 'p5',
x: 0,
y: 6,
z: 0,
color: defaultColor,
},
{
id: 'p6',
x: 6,
y: 6,
z: 0,
color: defaultColor,
},
{
id: 'p7',
x: 6,
y: 6,
z: 6,
color: defaultColor,
},
{
id: 'p8',
x: 0,
y: 6,
z: 6,
color: defaultColor,
},
],
lines: [
{
id: 'l1',
start: 'p1',
end: 'p2',
color: defaultColor,
},
{
id: 'l2',
start: 'p2',
end: 'p3',
color: defaultColor,
},
{
id: 'l3',
start: 'p3',
end: 'p4',
color: defaultColor,
},
{
id: 'l4',
start: 'p1',
end: 'p4',
color: defaultColor,
},
{
id: 'l5',
start: 'p1',
end: 'p5',
color: defaultColor,
},
{
id: 'l6',
start: 'p2',
end: 'p6',
color: defaultColor,
},
{
id: 'l7',
start: 'p3',
end: 'p7',
color: defaultColor,
},
{
id: 'l8',
start: 'p4',
end: 'p8',
color: defaultColor,
},
{
id: 'l9',
start: 'p5',
end: 'p6',
color: defaultColor,
},
{
id: 'l10',
start: 'p6',
end: 'p7',
color: defaultColor,
},
{
id: 'l11',
start: 'p7',
end: 'p8',
color: defaultColor,
},
{
id: 'l12',
start: 'p5',
end: 'p8',
color: defaultColor,
},
],
},
];
データを適当に作ってページングを実装してみたが、1ページあたりの行数を変更するとなぜかセルの選択ができなくなる。
insert処理も仮実装してみたが、こちらも同様で insertボタンを押すタイミングでセル選択が解除されてしまい、意図した箇所に行が挿入できない。
どちらも操作のタイミングで StrictMode のエラーが出ている(これは Material-UI の問題)が、ここに起因しているような印象。
これ以上の調査は時間がもったいないので、別のライブラリを試してみる
めっちゃ見た目 Excel。
なのに、Excel へのペースト時にセルがカンマ区切りになってしまうので、複数セルをコピペしたときに想定通りの内容にならない。
clipboardへのコピー時の文字列を変更できればワンチャンありかも
セルの範囲選択ができないのが辛い。
自分で作った。