leaf-multiple オプションで <wa-tree> の葉ノード複数選択を実現

shoelace-style/webawesome

このPRは <wa-tree> コンポーネントに leaf-multiple という新しい selection オプションを追加し、葉ノードの複数選択を可能にしつつ、親ノードは展開・折りたたみだけに限定します。これにより、ツリー構造の UI で「親は操作対象外、子は自由に選択できる」ユースケースをシンプルに実装できます。

背景

従来の <wa-tree>singlemultipleleaf の三種の selection 挙動しか提供しておらず、葉ノードのみを対象にした複数選択は実装できませんでした。leaf モードは葉の単一選択に留まり、multiple は親も含めて全ノードが選択対象になるため、用途が限定されていました。

leaf-multiple の導入は、Issue #1989 で報告された「葉ノードだけを複数選択したい」要望に直接応えるものです。親ノードは従来通り展開/折りたたみのトリガーに留め、選択ロジックを分離することで UI の一貫性を保ちます。

技術的な変更

API拡張

selection プロパティに新たなリテラル 'leaf-multiple' が加えられ、型定義が 'single' | 'multiple' | 'leaf' | 'leaf-multiple' に拡張されました。デフォルトは従来通り 'single' のままで、既存の使用コードに影響を与えません。

@@ -83,8 +83,9 @@ export default class WaTree extends WebAwesomeElement {
   * displays checkboxes and allows more than one node to be selected. Leaf allows only leaf nodes to be selected.
+   * Leaf-multiple allows multiple leaf nodes to be selected while parent nodes only expand and collapse.
    */
-  @property() selection: 'single' | 'multiple' | 'leaf' = 'single';
+  @property() selection: 'single' | 'multiple' | 'leaf' | 'leaf-multiple' = 'single';

この変更により、開発者は <wa-tree selection="leaf-multiple"> と記述するだけで新機能を利用できます。

ツリ項目の初期化ロジック変更

initTreeItem で各 wa-tree-itemselectable フラグが設定されるロジックが拡張され、leaf-multiple 時は葉ノードだけが選択可能になります。これにより、親ノードはクリック時に選択状態にならず、展開操作だけが行われます。

@@ -149,7 +150,7 @@ export default class WaTree extends WebAwesomeElement {
   private initTreeItem = (item: WaTreeItem) => {
     item.updateComplete.then(() => {
-      item.selectable = this.selection === 'multiple';
+      item.selectable = this.selection === 'multiple' || (this.selection === 'leaf-multiple' && item.isLeaf);

結果として、leaf-multiple モードでは UI が自動的に葉ノードのみチェック可能であることを保証します。

選択処理の分岐追加

selectItem メソッドに leaf-multiple 用の分岐が挿入され、葉ノードは選択状態をトグルし、親ノードは expanded プロパティだけを切り替えるようになりました。これにより、ユーザーが Enter キーやクリックで親ノードを操作したときに選択が付与されることはありません。

@@ -194,6 +195,12 @@ export default class WaTree extends WebAwesomeElement {
         syncCheckboxes(selectedItem);
+    } else if (this.selection === 'leaf-multiple') {
+      if (selectedItem.isLeaf) {
+        selectedItem.selected = !selectedItem.selected;
+      } else {
+        selectedItem.expanded = !selectedItem.expanded;
+      }
     } else if (this.selection === 'single' || selectedItem.isLeaf) {

このロジックはテストで検証され、親ノードの展開と葉ノードの独立した選択が正しく動作することが保証されています。

ARIA属性と選択可能フラグの同期

handleSelectionChangeleaf-multiple を判定対象に加え、aria-multiselectabletrue に設定すると同時に、全アイテムの selectable フラグを更新します。ARIA の適切な設定により、支援技術が複数選択可能なツリーであることを認識できます。

@@ -361,13 +368,14 @@ export default class WaTree extends WebAwesomeElement {
   @watch('selection')
   async handleSelectionChange() {
     const isSelectionMultiple = this.selection === 'multiple';
+    const isSelectionLeafMultiple = this.selection === 'leaf-multiple';
     const items = this.getAllTreeItems();

-    this.setAttribute('aria-multiselectable', isSelectionMultiple ? 'true' : 'false');
+    this.setAttribute('aria-multiselectable', isSelectionMultiple || isSelectionLeafMultiple ? 'true' : 'false');

     for (const item of items) {
       item.updateComplete.then(() => {
-        item.selectable = isSelectionMultiple;
+        item.selectable = isSelectionMultiple || (isSelectionLeafMultiple && item.isLeaf);
       });
     }

この変更により、ARIA の意味論的な一貫性が保たれ、アクセシビリティ要件を満たす形で新機能が提供されます。

テストの追加

tree.test.ts には leaf-multiple 動作を検証するテストが 4 つ追加されました。代表的なテストは、葉ノードだけが selectable になること、複数葉ノードが独立して選択できること、親ノードが選択されずに展開だけが行われることを確認しています。

@@ -67,6 +67,18 @@ describe('<wa-tree>', () => {
           }
         });

+        it('should set selectable on leaf items only when selection is "leaf-multiple"', async () => {
+          el.selection = 'leaf-multiple';
+          await el.updateComplete;
+
+          const items = el.querySelectorAll<WaTreeItem>('wa-tree-item');
+          await Promise.all([...items].map(item => item.updateComplete));
+
+          for (const item of items) {
+            expect(item.selectable).to.equal(item.isLeaf);
+          }
+        });

これらのテストにより、実装が期待通りの振る舞いを示すことが CI で自動的に保証されます。

設計判断

leaf-multiple を新たなリテラルとして selection 列挙型に組み込んだ設計は、既存の API を拡張しつつ後方互換性を最大化する選択です。別属性を導入するとコンポーネントのプロパティが増え、使用側の学習コストが上がりますが、今回のアプローチは「選択モードは一元管理」という一貫性を保ちます。

また、ARIA の aria-multiselectable フラグや selectable プロパティの更新ロジックを共通化したことで、機能追加による分岐増加を最小限に抑えつつ、アクセシビリティ要件も満たすバランスが取れています。テストを通じた振る舞いの明示は、将来の拡張やリファクタリング時の安全性を高める設計上の利点です。

まとめ

leaf-multiple の導入により、開発者は <wa-tree> で「葉だけの複数選択」機能をシンプルに利用でき、既存コードへの影響はなく、アクセシビリティも維持された形で UI の表現力が向上します。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
bc035af5

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文、背景、技術的な変更、設計判断、まとめの5つのセクションが揃っており、総論→各論→結論の構成が明確です。

カスタムMarkdown構文 ⚠ WARNING

シンタックスハイライト・GitHubリンク記法の正確性

PRリンクが「[PR #2486](URL)」となっており、要求された「[#2486](URL)」形式ではありません。ファイル名付きシンタックスハイライトは使用されていませんが、必須ではありません。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

専門的な Web コンポーネント開発者向けの記述で、初心者向けの余計な説明はありません。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションは総論パラグラフ→各論パラグラフ→結論パラグラフで構成され、段落はトピックセンテンスで開始し、1段落1トピック、長さも適切です。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事内の diff コードブロックは PR の diff と完全に一致しており、ファイル名・行変更も正確に反映されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「selection」「leaf-multiple」「ARIA」「selectable」などの用語は PR と一致しており、誤用はありません。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

技術的な説明は PR の内容と合致し、因果関係も論理的です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

すべての主張は PR のタイトル、説明、diff、Issue #1989 で裏付けられており、外部知識の付加はありません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR 番号 #2486、Issue 番号 #1989、コード行番号等の数値は正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルは PR の趣旨「leaf-multiple オプションの追加」を適切に表現しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

バージョンサポート状況やリリース日程など PR には記載されていない外部情報は含まれていません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現の歪曲はなく、PR の記述と一致しています。