ツールバーボタンクリック後にCtrl+Zが効かない問題の修正

basecamp/lexxy

ツールバーをマウスでクリックしたあとキーボードショートカットが機能しなくなるバグを、2つの独立した原因を修正することで解消しました。フォーカス管理の不備と不要な editor.update() ネストが原因でした。

背景

ツールバーボタンをマウスでクリックしたあと、Ctrl+Zを押してもアンドゥが効かない問題が報告されていました。原因は単一ではなく、フォーカスの問題ネストされた editor.update() の問題という2つの独立したバグが組み合わさっていました。

問題の一つ目は、マウスでツールバーボタンをクリックするとフォーカスがそのボタンに移動し、エディタに戻らないことです。この状態でCtrl+Zを押すと、キーイベントはLexicalのUndo Handlerではなくブラウザに送られるため、アンドゥが機能しませんでした。ツールバーの「元に戻す」ボタン自体は正常に動作していましたが、それはボタンが UNDO_COMMAND を明示的にLexicalへディスパッチしているからです。

問題の二つ目は、dispatchInsertCodeBlock が自身の処理を this.editor.update() でラップしていたことです。しかしこのメソッドはツールバーの editor.update() 呼び出しの内部からすでに実行されており、ネストした update() が生まれていました。bold・italic・strikethrough・quoteなど他のフォーマットディスパッチャーとも一貫性がなく、不整合な状態でした。

技術的な変更

2つの問題はそれぞれ独立したファイルへの修正で対処されました。

src/elements/toolbar.js には、マウスクリックによるコマンドディスパッチのあとに editor.focus() を呼び出す1行が追加されました。

変更前:

this.editor.update(() => {
  this.editor.dispatchCommand(command, payload)
}, { tag: isKeyboard ? SKIP_DOM_SELECTION_TAG : undefined })

変更後:

this.editor.update(() => {
  this.editor.dispatchCommand(command, payload)
}, { tag: isKeyboard ? SKIP_DOM_SELECTION_TAG : undefined })

if (!isKeyboard) this.editor.focus()

isKeyboard フラグで分岐しており、キーボード操作の場合は focus() を呼びません。これはキーボードでツールバーを操作している場合にフォーカスをエディタへ戻してしまうと、アクセシビリティ上の問題が生じるためです。

src/editor/command_dispatcher.js では、dispatchInsertCodeBlock から不要な editor.update() ラッパーが取り除かれました。

変更前:

dispatchInsertCodeBlock() {
  this.editor.update(() => {
    if (this.selection.hasSelectedWordsInSingleLine) {
      this.editor.dispatchCommand(FORMAT_TEXT_COMMAND, "code")
    } else {
      this.contents.toggleNodeWrappingAllSelectedLines((node) => $isCodeNode(node), () => new CodeNode("plain"))
    }
  })
}

変更後:

dispatchInsertCodeBlock() {
  if (this.selection.hasSelectedWordsInSingleLine) {
    this.editor.dispatchCommand(FORMAT_TEXT_COMMAND, "code")
  } else {
    this.contents.toggleNodeWrappingAllSelectedLines((node) => $isCodeNode(node), () => new CodeNode("plain"))
  }
}

あわせて test/browser/tests/toolbar.test.js に4つのリグレッションテストが追加されました。インラインコード・部分選択・単一行コードブロック・複数行コードブロックの各シナリオで、フォーマット適用後にCtrl+Zでアンドゥできることを検証しています。

設計判断

isKeyboard による分岐でアクセシビリティとUXを両立する設計が採用されました。マウス操作後は editor.focus() でエディタに戻し、キーボード操作後はツールバー上のフォーカスをそのまま維持します。キーボードユーザーがTab/Enterでツールバーを操作する場合、フォーカスをエディタへ強制的に戻すとツールバー上のナビゲーションが壊れるため、この分岐は意図的です。

dispatchInsertCodeBlock の修正は、他のフォーマットディスパッチャーとの一貫性を回復する変更です。PRでは「bold・italic・strikethrough・quoteは同様のラッパーを持っていない」と明示されており、コードブロックだけが特殊扱いになっていた状態を解消しています。

まとめ

この修正は、フォーカス管理の欠如とLexical更新コンテキストの誤ったネストという2つの独立した原因を同時に解消しています。if (!isKeyboard) this.editor.focus() という1行の追加と editor.update() ラッパーの削除という最小限の変更で、他のフォーマット操作との整合性を保ちながら問題を修正した判断といえます。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
0b608c92

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

品質レビュー結果

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

Review Criteria:

記事構成 ✓ PASS

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

リード文(総論)→背景・技術的な変更・設計判断(各論)→まとめ(結論)の3部構成が明確に適用されており、記事の全体像が掴みやすいです。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライト(`言語:ファイルパス`)およびGitHubのPRリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

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

LexicalのAPI(`editor.update`, `editor.focus`など)を前提とした説明になっており、専門知識を持つエンジニアという対象読者に適した内容です。

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

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

各セクションが総論から各論へと展開され、各段落はトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られており、非常に高い可読性を実現しています。

Diff内容との照合 ✓ PASS

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

記事で引用されているコード変更(`toolbar.js`への`editor.focus()`の追加、`command_dispatcher.js`からの`editor.update()`ラッパーの削除)は、提供されたDiff情報と完全に一致しています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「フォーカス」、「ネストされたupdate」、「コマンドディスパッチ」などの技術用語はPR情報と一致しており、文脈上も正確に使用されています。

説明の技術的正確性 ✓ PASS

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

フォーカスがエディタに戻らない問題と`editor.update()`のネストという2つの根本原因に関する説明は、PRの分析と一致しており、技術的に正確です。

事実の突合 ✓ PASS

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

記事で言及されているすべての事実(2つのバグの原因、修正内容、アクセシビリティへの配慮、テスト追加)は、PRのDescriptionとDiffによって裏付けられており、ハルシネーションは検出されませんでした。

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

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

PR番号`#823`が正確に記載・リンクされています。

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

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

記事のタイトル「ツールバーボタンクリック後にCtrl+Zが効かない問題の修正」は、元のPRタイトル「Fix Ctrl+Z not working after toolbar formatting」の内容を的確に表現しています。

外部知識の正確性 ✓ PASS

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

記事の内容はすべて提供されたPR情報に基づいており、PRに記載のない外部知識(バージョン情報、リリース予定など)の追記は見られませんでした。

時間表現の正確性 ✓ PASS

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

「〜でした」「〜ていました」といった過去の問題を示す表現と、「〜しました」「〜されました」という今回の修正内容を示す表現が正確に使い分けられており、時間的な歪曲はありません。