モチベーション
Electronの勉強のためにTaskTabのクローンを作っていたら複数選択で行き詰まった
行き詰まった理由
- 一口に複数選択といってもOSやライブラリなどで仕様が異なる
- 直感的でない仕様がある
- 複数選択のライブラリはあるがテーブルと蜜結合していることが多く使い辛い(使いたかったのはリストのため)
仕様を考える
一口に複数選択といってもOSやライブラリなどで仕様が異なる
全ての仕様を網羅することはできないので、MacのファイルマネージャーであるFinder(≒Google Slide)の仕様を目指します。
Finderの複数選択はibash/better-multiselectでほぼ分析されていますが、一部足りない仕様があったため追記しています。
意訳
ルール
アンカー:選択を開始する位置
フォーカス:選択を終了する位置
...
1. クリック
1. アンカー・フォーカスをクリックされた要素に移動する
2. 選択範囲をクリックされた要素に置換する
2. コマンドクリック
1. クリックされた要素が選択されていない
1. アンカー・フォーカスをクリックされた要素に移動する
2. 選択範囲にクリックされた要素を追加する
2. クリックされた要素が既に選択されている
1. アンカー・フォーカスを以下の優先順位で移動する
1. クリックされた要素より後方の要素の中で最小の要素
2. クリックされた要素より前方の要素の中で最大の要素
+ 3. どちらもなければリストの先頭の要素
2. 選択範囲からクリックされた要素を削除する
3. シフトクリック
- 1. 選択範囲からアンカーとフォーカス間の要素全てを削除する
+ 1. 選択範囲からアンカーとフォーカス間の要素とそれらに連続する要素全てを削除する
2. フォーカスをクリックされた要素に移動する
3. 選択範囲にアンカーとフォーカスの間の要素を全て追加する
4. コマンドクリックはシフトクリックよりも優先される
追記内容について
2.2.1.3
クリックされた要素が既に選択されており、他に選択された要素がない場合のルールです。
3.1
主に2つの選択範囲を結合するような選択を行ったときに関連するルールです。
直感的には「アンカー・フォーカス間の要素」が削除されるかのように思いますが、実際は「アンカー・フォーカスが所属する連続した選択範囲の要素全て」が削除されます。
実装
おまけ
前述のCodeSandboxのサンプルではレンダリングのパフォーマンス最適化は何も考えられておらず、選択範囲が変わるごとに全てのList
が再レンダリングされています。
これをuseReducer
、React.memo
を活用し、変更のあった要素のみを再レンダリングするようリファクタリングしたものが以下になります。