Open

ReactのSpreadSheet的なコンポーネントを探す

13
ピン留めされたアイテム

やりたいこと

  • 選択範囲のコピー&ペースト
  • 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

React datasheet が前提条件の構成で動作するか実際に試してみる

まずは扱うデータの型定義。
こんなんでいけるかな?

src/models/solid.ts
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;
};

表示するデータ。

src/models/solid.ts
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,
            },
        ],
    },
];

https://kazunori-kimura.github.io/react-datasheet-sample/

データを適当に作ってページングを実装してみたが、1ページあたりの行数を変更するとなぜかセルの選択ができなくなる。

insert処理も仮実装してみたが、こちらも同様で insertボタンを押すタイミングでセル選択が解除されてしまい、意図した箇所に行が挿入できない。

どちらも操作のタイミングで StrictMode のエラーが出ている(これは Material-UI の問題)が、ここに起因しているような印象。

これ以上の調査は時間がもったいないので、別のライブラリを試してみる

https://www.npmjs.com/package/react-spreadsheet

めっちゃ見た目 Excel。
なのに、Excel へのペースト時にセルがカンマ区切りになってしまうので、複数セルをコピペしたときに想定通りの内容にならない。
clipboardへのコピー時の文字列を変更できればワンチャンありかも

作成者以外のコメントは許可されていません