隣接するメンション間でのドラッグドロップを正しく処理するように修正
Lexxy のインライン @mention が連続している場合でも、ドラッグ操作で位置入れ替えができるようになりました。ドロップインジケータはメンションの境界にのみ表示され、無意味な内部位置へのドロップは防止されます。
背景
連続した メンション 同士の間に空白がなくなると、ドラッグで移動させようとしても何も起こらず、ドロップカーソルがメンション内部に現れるという問題が報告されました。これは、ブラウザが隙間を caret として認識できず、Lexical がデコレータ内部のポイントを編集可能位置に変換できなかったためです。結果として、ユーザーはメンションの順序を編集できず、チャット入力体験が劣化していました。
技術的な変更
ドロップ位置の解決ロジックに 隣接デコレータ検出 を追加し、カーソルがデコレータ内部にある場合はその前後にスナップするようにしました。具体的には src/editor/attachments/custom/drag_and_drop.js に以下のコードが加わります。
@@
- if (caret.node === rootElement) {
- return this.#nearestLineCaret(rootElement, event.clientY)
- } else {
- return caret
- }
+ if (caret.node === rootElement) {
+ return this.#nearestLineCaret(rootElement, event.clientY)
+ }
+
+ // 隣接するメンション間で caret がデコレータ内部に入った場合は
+ // その前後にスナップする。
+ const decorator = this.#decoratorElementContaining(caret.node)
+ if (decorator) {
+ return this.#dropPointBesideDecorator(decorator, event.clientX)
+ } else {
+ return caret
+ }
新たに導入したヘルパー #decoratorElementContaining と #dropPointBesideDecorator は、デコレータ要素を取得し、マウス X 座標が要素の中心より左か右かで "before" / "after" を決定します。
#decoratorElementContaining(node) {
const element = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement
return element?.closest("[data-lexxy-decorator][data-lexical-node-key]")
}
#dropPointBesideDecorator(decorator, clientX) {
const rect = decorator.getBoundingClientRect()
const placement = clientX > rect.left + rect.width / 2 ? "after" : "before"
return { decoratorKey: decorator.dataset.lexicalNodeKey, placement }
}
#moveAttachment も拡張し、dropPoint に decoratorKey が含まれる場合は #moveBesideNode を呼び出し、Lexical のノード API でメンションを隣接位置へ移動させます。これにより、テキスト選択を組み立て直す必要がなくなり、確実にノード単位で並び替えが行われます。
@@
- const selection = $createRangeSelectionFromDom({
- anchorNode: dropPoint.node,
- anchorOffset: dropPoint.offset,
- focusNode: dropPoint.node,
- focusOffset: dropPoint.offset
- }, this.#editor)
- if (!selection) return
+ if (dropPoint.decoratorKey) {
+ this.#moveBesideNode(draggedNode, dropPoint)
+ } else {
+ this.#moveToCaret(draggedNode, dropPoint)
+ }
さらに、CustomActionTextAttachmentNode#createDOM でメンション要素に draggable 属性と data-lexical-node-key を付与し、ブラウザ側からドラッグ開始が認識されるようにしました。これに合わせて MentionDragAndDrop コントローラ(高優先度)を追加し、DRAGSTART と DROP をインラインメンション向けに処理します。
設計判断
既存の ブロックレベル添付ファイルハンドラ は figure.attachment に限定されており、インラインメンションに影響しないように設計されています。新たに導入した MentionDragAndDropExtension はリッチテキストモードでのみ有効化し、ブロックハンドラと競合しないよう 高優先度 で登録しました。これにより、従来のブロック添付ロジックはそのまま残しつつ、インラインデコレータ専用のドラッグロジックを追加できました。変更点は最小限に抑えられ、後方互換性とコードベースのシンプルさが保たれています。
まとめ
今回の修正により、隣接したメンション間でもドラッグで順序入れ替えが可能になり、ドロップインジケータはメンションの端に正しく表示されます。DOM に draggable とノードキーを付与し、Lexical のノード API で位置を確定する設計は、既存ブロックハンドラを壊さずインライン操作を拡張する安全なアプローチです。これにより、チャット編集におけるメンション操作性が大幅に向上しました。