@メンション直後の不要スペースをゼロ幅文字で除去
@Zach's のような所有格表現でメンション末尾に余分なスペースが入る問題を、U+2060(WORD JOINER) への置き換えで解消しました。これにより、メンションと後続テキストが視覚的に隙間なく表示されます。
背景
Lexical エディタでは、インライン添付ノード(@mention)を挿入した際に後続テキストの起点として通常スペース(" ")を補完する実装になっていました。この設計は後続テキストが存在しない場合のカーソル位置確保を目的としたものですが、's のように句読点や所有格をメンション直後に入力すると、レンダリング上に不要な半角スペースが現れる副作用がありました。
同様の問題は importDOM 経由で HTML からメンションを読み込む際にも発生しており、直接入力と HTML ロードの両経路での修正が必要な状態でした。
技術的な変更
変更は src/editor/contents.js と src/nodes/custom_action_text_attachment_node.js の2ファイルに渡り、「スペース」を「WORD JOINER」へ置き換えるという一貫したアプローチが採られています。
contents.js の変更: メンション挿入後に後続テキストノードを生成する箇所で、挿入されたノード群にインラインデコレータノードが含まれるかどうかを判定し、trailing spacer を切り替えるようにしました。
変更前:
const textNodeAfter = this.#cloneTextNodeFormatting(anchorNode, selection, textAfterCursor || " ")
変更後:
const trailingSpacer = this.#hasInlineDecoratorNode(replacementNodes) ? "\u2060" : " "
const textNodeAfter = this.#cloneTextNodeFormatting(anchorNode, selection, textAfterCursor || trailingSpacer)
判定ロジックは新設のプライベートメソッド #hasInlineDecoratorNode に切り出されており、replacementNodes の中に CustomActionTextAttachmentNode のインラインインスタンスが含まれているかを Array.prototype.some で確認します。
#hasInlineDecoratorNode(nodes) {
return nodes.some(node => node instanceof CustomActionTextAttachmentNode && node.isInline())
}
custom_action_text_attachment_node.js の変更: importDOM パスでは、HTML からパースされたノードリストに末尾スペースを追加する箇所を、同じく U+2060 へ直接置き換えています。
変更前:
nodes.push($createTextNode(" "))
変更後:
nodes.push($createTextNode("\u2060"))
これにより、エディタへの直接入力と HTML ロードの両経路で動作が統一されました。
設計判断
スペースをゼロ幅文字に置き換えるというアプローチの要点は、メンション後に何らかのテキストノードを置くという既存の構造を維持しつつ、視覚的な問題だけを取り除く点にあります。後続テキストが存在しない場合のフォールバック文字として U+2060 が採用されており、スペースを単に削除するのではなく「不可視の接続文字」へ差し替えるという最小変更で問題を解決しています。
U+2060 を選択した理由はその二つの性質、「幅ゼロで表示されない」かつ「改行禁止(WORD JOINER の名のとおり)」にあります。後者によってメンションと直後のテキストが行末で分断されないという副次的な恩恵も得られます。なお、インラインデコレータノードを含まない通常の置換では従来どおり半角スペースが使われるため、メンション以外の挿入挙動に影響はありません。
テストは Playwright による E2E テストとして追加されており、@mention 挿入後に 's を入力した際のテキストノード内容を DOM レベルで検証します。スペースで始まらないこと(/^ / に一致しないこと)と 's を含むことを明示的にアサートしており、回帰検知の網として機能します。
まとめ
ゼロ幅の WORD JOINER を trailing spacer として採用することで、既存のノード構造を変えずに視覚的な問題を解消しています。スペースの単純な削除ではなく「不可視の接続文字」への置き換えというアプローチは、エディタフレームワークの制約下での文字レベルの設計判断として参考になります。