Lexicalのトリプルクリックハンドラを上流で無効化し、選択動作を修正

basecamp/lexxy

Lexicalのトリプルクリックハンドラが引き起こす不正な選択動作を、イベントのstopPropagationで無効化するPreventLexicalTripleClickExtensionを追加しました。これにより、ソフト改行を含む段落でのトリプルクリックや、トリプルクリックドラッグによる複数行選択が正常に動作するようになります。

背景

Lexicalはfacebook/lexical#4512にてトリプルクリック時の選択範囲を調整するハンドラを導入しました。このハンドラの目的は、ブラウザの「過剰選択(overselection)」動作への対処です。トリプルクリックによる選択範囲が次のブロックの先頭(offset 0)まで広がると、選択の変換処理で問題が起きる場合があり、その具体例として見出しツールが次の行も見出しに変換してしまう現象や、テーブルセルの削除が隣接セルに波及する現象が挙げられていました。

しかし、この対処は実際のユースケースを損なうものでした。2025年6月にはLexical本体にバグレポートfacebook/lexical#7592が提出されており、<br>タグによるソフト改行を含む段落でのトリプルクリック、およびトリプルクリックドラッグによる複数行選択が正常に動作しないことが報告されています。Lexxyは独自の見出しツールを持ち、Lexicalのハンドラでカバーしようとしていたテーブルセル削除の問題も既に解消されているため、このハンドラはLexxy上では害のみをもたらす状態でした。

技術的な変更

新たに追加されたPreventLexicalTripleClickExtensionが、キャプチャフェーズでclickイベントを捕捉し、Lexicalのバブリングフェーズのハンドラより先に実行されることで問題を解決します。

src/extensions/prevent_lexical_triple_click_extension.jsとして追加されたクラスは、LexxyExtensionを継承し、defineExtensionを介してLexicalの拡張機構に登録されます。editor.registerRootListenerでルートDOM要素にclickリスナーを設定し、{ capture: true }オプションによりキャプチャフェーズでの受信を宣言します。

export class PreventLexicalTripleClickExtension extends LexxyExtension {
  get lexicalExtension() {
    return defineExtension({
      name: "lexxy/prevent-lexical-triple-click",
      register: (editor) => editor.registerRootListener((rootElement) => {
        if (rootElement) {
          return registerEventListener(
            rootElement,
            "click",
            this.#handleTripleClick.bind(this),
            { capture: true }
          )
        }
      })
    })
  }

  #handleTripleClick(event) {
    if (event.detail === 3) {
      event.stopPropagation()
    }
  }
}

ハンドラはevent.detail === 3、すなわちトリプルクリックのみを対象にstopPropagation()を呼び出します。これによりイベントはキャプチャフェーズで伝播を止め、LexicalのバブリングフェーズのonClickハンドラには届きません。ブラウザ本来のトリプルクリック選択動作はそのまま維持されます。

src/elements/editor.jsでは、既存の拡張機能リストにPreventLexicalTripleClickExtensionを追記する1行の変更のみで統合されています。

// 変更後
import { PreventLexicalTripleClickExtension } from "../extensions/prevent_lexical_triple_click_extension.js"

// ...
[
  RewritableHistoryExtension,
  AttachmentsExtension,
  FormatEscapeExtension,
  LinkOpenerExtension,
  PreventLexicalTripleClickExtension  // 追加
]

ブラウザテストtest/browser/tests/editor/prevent_lexical_triple_click.test.jsも合わせて追加されており、2つのシナリオを検証します。

  • ソフト改行(<br>)を含む段落内の1行をトリプルクリックしたとき、その視覚的な1行のみが選択されること
  • トリプルクリックドラッグで複数の行にまたがる選択が正常に行えること

テストコード内のtripleClickDrag関数は、ブラウザのクリックカウントを1→2→3とマウスイベントで段階的に送信し、ドラッグ操作を再現しています。

設計判断

イベント伝播をキャプチャフェーズで止める方式が採用されました。Lexicalのハンドラはバブリングフェーズに登録されているため、キャプチャフェーズでstopPropagation()を呼ぶことで、ブラウザのデフォルト選択動作を妨げることなくLexical固有のロジックのみを無効化できます。

PR本文では、問題の根本的な対処としてLexical本体への改善(選択処理ロジックをアプリケーションが上書きできるハンドラへ移動する、またはトリプルクリックハンドラを完全に削除するなど)を期待する姿勢が示されています。Lexxy側での対処はあくまで上流が修正されるまでの暫定措置として位置付けられており、Lexxyの見出しツールやテーブルが該当の問題を示さないという事実を根拠に、Lexicalのハンドラが提供していた保護を不要と判断しています。

まとめ

本PRは、Lexicalの上流実装の副作用をキャプチャフェーズのイベント遮断という最小限の手術で封じ込める変更です。Lexxy固有のツールが問題の原因となる動作を再現しないことを確認したうえで、ブラウザ本来の選択動作を優先する判断が下されており、上流での修正後に容易に差し戻せるよう拡張として独立したモジュールに切り出された点も注目されます。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
81c01250

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文(総論)、背景・技術詳細・設計判断(各論)、まとめ(結論)という「総論→各論→結論」の構成が明確に適用されており、非常に分かりやすい記事構成です。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライト(```言語:ファイルパス)やGitHubのPR/Issueへのリンク記法が、ガイドラインに沿って正しく使用されています。

対象読者への適合性 ✓ PASS

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

イベントのキャプチャ/バブリングフェーズやLexicalの拡張機構など、専門的なトピックを前提としており、対象読者であるエンジニアに適切なレベルの内容です。

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

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

各セクションが総論→各論の構造を持ち、各段落がトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が徹底されており、非常に高い可読性を実現しています。

Diff内容との照合 ✓ PASS

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

Diffで追加・変更されたコード(`PreventLexicalTripleClickExtension`の実装、`editor.js`への統合部分)が、省略記法も適切に使いながら正確に引用されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「stopPropagation」「キャプチャフェーズ」「バブリングフェーズ」「event.detail」といった技術用語が、文脈に沿って正確かつ効果的に使用されています。

説明の技術的正確性 ✓ PASS

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

「キャプチャフェーズでイベントを止めることでLexicalのハンドラのみを無効化する」という核心部分の説明が、コードの動作と一致しており、技術的に正確かつ明快です。

事実の突合 ✓ PASS

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

記事内の主張はすべてPRのDescriptionで裏付けられており、ハルシネーション(捏造)は見られません。背景、問題点、Lexxy側の事情などが正確に反映されています。

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

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

PR番号(#1050)、関連Issue/PR番号(#4512, #7592)などの数値や、ファイル名、クラス名といった固有名詞がすべて正確に記載されています。

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

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

「Lexicalのトリプルクリックハンドラを上流で無効化し、選択動作を修正」という記事タイトルは、PRの主題である「イベント伝播を止めてLexicalのハンドラ実行を防ぐ」という内容を的確に要約しています。

外部知識の正確性 ✓ PASS

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

記事内の「2025年6月」という日付はPR Descriptionに記載されている情報であり、PRに記載のない外部知識の追加や捏造はありません。

時間表現の正確性 ✓ PASS

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

PR Descriptionの「was filed」「no longer exhibit」といった表現を「提出されており」「既に解消されている」と正確に反映しており、時間表現の歪曲はありません。