ペースト時にスタイルの正規化を選択範囲に適用

basecamp/lexxy

Lexxyエディタでスタイル付きテキストをペーストした直後のタイピングで、ペースト元のスタイルが維持されてしまう問題が修正されました。この変更により、ペースト後の入力は自動的に正規化されたスタイルとフォーマットを引き継ぐようになります。

背景

これまで $canonicalizePastedStyles 関数は、ペーストされたテキストノードのスタイルを正規化するだけでした。テキストノードが選択状態にある場合でも、選択範囲(selection)自体のスタイルとフォーマットは更新されませんでした。このため、ユーザーがペースト直後に文字を入力すると、正規化前のペースト元スタイルが保持される問題がありました。

Lexicalエディタでは、selectionオブジェクトがタイピング時のスタイルとフォーマットを管理しています。テキストノードのスタイルを変更しても、selectionに反映しなければ次の入力には影響しません。

技術的な変更

$canonicalizePastedStyles 関数が拡張され、テキストノードが選択状態の場合にselectionのスタイルとフォーマットも更新するようになりました。

変更前:

function $canonicalizePastedStyles(textNode, canonicalizers = []) {
  if ($hasPastedStyles(textNode)) {
    $setPastedStyles(textNode, false)

    const canonicalizedCSS = canonicalizers.reduce((css, canonicalizer) => {
      return canonicalizer.applyCanonicalization(css)
    }, textNode.getStyle())

    textNode.setStyle(canonicalizedCSS)
  }
}

変更後:

function $canonicalizePastedStyles(textNode, canonicalizers = []) {
  if ($hasPastedStyles(textNode)) {
    $setPastedStyles(textNode, false)

    const canonicalizedCSS = applyCanonicalizers(textNode.getStyle(), canonicalizers)
    textNode.setStyle(canonicalizedCSS)

    const selection = $getSelection()
    if (textNode.isSelected(selection)) {
      selection.setStyle(textNode.getStyle())
      selection.setFormat(textNode.getFormat())
    }
  }
}

textNode.isSelected(selection) で選択状態を確認し、選択されている場合は selection.setStyle()selection.setFormat() で正規化後のスタイルとフォーマットを適用します。これにより、ペースト直後の入力が自動的に正規化されたスタイルを引き継ぎます。

追加されたテストケースでは、color: rgb(...)background-color: white を含むHTMLをペーストした直後に and more... と入力しています。期待される出力は、正規化された color: var(--highlight-1) のみを持つ <mark> タグで全体がラップされた状態です。

test "canonicalizes selection styles on paste" do
  find_editor.paste "styled text", html: %(some <span style="color: #{highlight_1_rgb}; background-color: white;">styled text</span>)
  find_editor.send " and more..."

  assert_equal_html %(<p>some <mark style="color: var(--highlight-1);">styled text and more...</mark></p>), find_editor.value
end

設計判断

正規化ロジックを applyCanonicalizers ヘルパー関数として抽出する判断が採られました。

export function applyCanonicalizers(styles, canonicalizers = []) {
  return canonicalizers.reduce((css, canonicalizer) => {
    return canonicalizer.applyCanonicalization(css)
  }, styles)
}

このリファクタリングにより、reduce を使った正規化処理が2箇所で重複していたコードが共通化されました。関数名も applyCanonicalizers と複数形になっており、複数の正規化処理を順次適用する意図が明確に表現されています。

selectionへのスタイル適用は、textNode.isSelected(selection) による条件分岐で保護されています。選択されていないテキストノードに対しては従来通りノード自体のスタイル更新のみが行われ、余計な処理が実行されません。

まとめ

本PRは、ペースト直後のタイピングで意図しないスタイルが保持される問題を、selectionオブジェクトへの正規化適用で解決しています。テキストノードとselectionの両方を更新することで、ペースト操作とその後の入力が一貫した正規化ルールに従うようになりました。

記事メタデータ

Generated by:
Claude Sonnet 4.5 for DiffDaily

この記事は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リンク記法の正確性

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

対象読者への適合性 ✓ PASS

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

Lexicalエディタの内部実装に関する内容であり、専門知識を持つエンジニアという対象読者に適した技術レベルと表現で書かれています。

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

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

各セクションが総論→各論の構成になっており、各段落はトピックセンテンスで始まり、1段落1トピックの原則が守られています。段落の長さも適切です。

Diff内容との照合 ✓ PASS

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

記事内で引用されているコードブロック(変更前後、テストケース、ヘルパー関数)は、提供されたDiff情報と完全に一致しています。ファイル名も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

Lexicalフレームワークの専門用語(`selection`, `textNode`, `$canonicalizePastedStyles`など)が正確に使用されています。

説明の技術的正確性 ✓ PASS

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

「selectionオブジェクトのスタイルが更新されていなかった」という問題の原因と、「selectionのスタイルとフォーマットも更新する」という解決策の説明が、Diffの内容と整合しており技術的に正確です。

事実の突合 ✓ PASS

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

記事内のすべての主張(問題点、解決策、リファクタリング)は、PRのDescriptionやDiff内のコード変更によって裏付けられており、ハルシネーションは検出されませんでした。

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

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

PR番号(#733)や関数名、ファイルパスなどの固有名詞はすべて正確です。

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

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

記事のタイトル「ペースト時にスタイルの正規化を選択範囲に適用」は、PRのタイトル「Apply canonicalization to selection on paste」の内容を的確に反映しています。

外部知識の正確性 ✓ PASS

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

PR情報に含まれない外部知識(バージョン情報、リリース予定など)の記述はなく、すべての情報が提供された資料に基づいています。

時間表現の正確性 ✓ PASS

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

「これまで」や「修正されました」といった時間表現が、PRの変更内容を正確に反映しており、誤解を招く表現はありません。