TssGrid MIT

3社のグリッドを調べて「セル結合」の落とし所を決めた話

設計 株式会社スリースターソフトウェア

TssGrid は「ビルド不要・依存ゼロ」を売りにした軽量グリッドです。だからこそ、機能を1つ足すたびに 「これは肥大化に見合うか?」を毎回考えます。今回はずっと避けてきた セル結合 に手を出すにあたって、 メジャーな3つのライブラリ ― ag-grid / Handsontable / jspreadsheet ― が どう実装しているかを調べ、TssGrid としての「落とし所」を決めた記録です。

結論から: 矩形(colspan×rowspan)結合は入れる。ただし結合中はソートをロックし、 行移動は縦結合が無いときだけ許可。auto-split のような重い割り切りはしない。これで コア +71行 に収めました。

なぜ今まで避けていたか

セル結合は「見た目だけ」の機能に見えて、実は座標系の前提を壊すのがやっかいです。 1セル=1行1列という素直なモデルに、「このセルは3列ぶん」「この行とこの行はつながっている」という 例外が差し込まれる。選択・編集・コピー・行挿入・Undo ― ほぼ全機能が結合を意識し始めます。 ここを雑に作ると、軽量どころか一番の複雑性発生源になります。

3社はどう割り切っているか

実装方針を、業務フォーム視点でざっくり並べるとこうでした。

観点ag-gridHandsontablejspreadsheet
結合の持ち方列スパン中心(行はピン留め思想)mergeCells 配列(矩形)colspan/rowspan 属性
ソートとの関係仮想化前提で強い結合とソートは相性注意基本シンプル
狙う規模大規模・仮想スクロールExcel ライク全部入り軽量・スプレッドシート
重さ中〜大

TssGrid が狙うのは「日本の業務フォーム」。1万行を仮想スクロールで捌く世界ではなく、 帳票の見出しをきれいに結合したい世界です。だから ag-grid 的な重武装は要らない。 jspreadsheet の軽さに、Handsontable の「矩形 mergeCells」の素直さを足すのが筋だと判断しました。

何を捨てたか

  • 結合中のソート ― 完全にロック。結合した帳票を並べ替える要求は実務でほぼ無く、 整合性コストに見合わない。
  • 縦結合がある状態の行移動 ― 禁止。行順を崩すと縦結合が分断され、 それを自動で割る(auto-split)処理は重い。横結合だけなら行と一緒に動くので許可。

「できない」を最初に決めると、残りの実装は驚くほど素直になります。

何を残したか

逆に、業務で本当に要るものは妥協しませんでした。矩形結合(colspan×rowspan)Undo 対応(結合と、結合時に消えた従属セルの値の両方を復元)、 行/列の挿入削除での座標追従。API はこれだけです。

grid.setMerge(0, 1, 3);      // (0,1) から3列を横結合
grid.setMerge(1, 0, 1, 2);   // (1,0) から2行を縦結合
grid.setMerge(2, 2, 2, 2);   // 2×2 の矩形結合
grid.mergeSelection();       // 選択範囲をまとめて結合
grid.removeMerge(0, 1);      // 解除(Undo 可)

実装の勘所:対称化で行数を抑える

横結合(colspan)と縦結合(rowspan)を別々に書くと2倍に膨らむ。そこで 「アンカー(左上)+ 覆われた従属セル」という1つのモデルに統一し、 列方向・行方向を対称に扱う関数だけ用意しました。既存の選択・描画スキップの仕組みを使い回したので、 フル矩形結合でもコアの増分は +71 行に収まっています。

動かしてみる

下のグリッドで実際にセル結合を試せます。セルを範囲選択して「選択を結合」(または右クリック → セルを結合)。結合セルを選んで「結合を解除」。横・縦・矩形どれもOK、Undo(Ctrl+Z)も効きます。

またはセルを範囲選択して右クリック
軽量ライブラリで機能を足すコツは、足す前に「何を許さないか」を先に決めること。 セル結合では「ソート」と「縦結合中の行移動」を捨てたことで、軽さを保てました。