ソート可能な DnD
この機能で追加されるもの
見出しへのリンク@omnicajs/vue-remote/remote は、sortable drag-and-drop のために次の 3 つを公開します。
RemoteSortableContainerRemoteSortableItemRemoteDragHandle
これにより、リモート側のコードは並べ替え可能なリストやボードを宣言でき、Vue の host adapter が hit-testing、ポインター追跡、キーボードによるキャンセル、DOM 状態更新を担当します。
この機能のために追加の host component を登録する必要はありません。host の Vue runtime は、これらの remote wrapper が出力する内部 DnD binding をすでに理解しています。
Remote API
見出しへのリンクimport { RemoteDragHandle, RemoteSortableContainer, RemoteSortableItem, type RemoteSortableEvent,} from '@omnicajs/vue-remote/remote'主な props:
RemoteSortableContainercontainerId: 論理コンテナキー。orientation:'vertical' | 'horizontal'。accepts?: 許可する itemtypeの一覧。disabled?: コンテナでの drop を無効化。onDragenter,onDragleave,onDragmove,onDrop。
RemoteSortableItemitemId: 安定した item キー。containerId: 現在のコンテナキー。index: 現在の表示順インデックス。type:accepts判定に使う item 種別。payload?: イベントに含まれる任意のシリアライズ可能 payload。disabled?: この item の drag を無効化。onDragstart,onDragend,onDragcancel。
RemoteDragHandlefor?: 明示的にドラッグ対象にするitemId。disabled?: handle での pointer 開始を無効化。
3 つの component はすべて as を受け取るため、描画ルートはネイティブタグでも host component のルートでも構いません。
Host と remote の役割
見出しへのリンク- Remote 側は container、item、handle と並べ替えロジックを宣言します。
- Host runtime は、それらに対応する実 DOM 要素を解決します。
- Host 側で pointer session を管理し、
before、after、insideを計算します。 - 受け入れ可否は
item.typeと target container のacceptsで判定されます。 Escapeは進行中の drag をキャンセルし、onDragcancelを発火します。
この分離により、reorder の状態管理は remote 側に置いたまま、低レベルな DOM 処理は信頼できる host 側に残せます。
イベントの流れ
見出しへのリンクRemoteDragHandleでのpointerdownが pending session を開始します。- ドラッグ閾値を超えると、source item の
onDragstartが発火します。 - 移動中、target container は
onDragenter、onDragmove、onDragleaveを受け取ります。 - 許可された target 上で
pointerupが起こると、container のonDropが呼ばれます。 - source item は必ず
onDragendで終了し、Escapeやpointercancelの場合はonDragcancelになります。
RemoteSortableEvent には正規化された drag 状態が含まれます。
accepteditemId,type,payloadsourceContainerId,targetContainerIdtargetItemId,targetIndexplacementpointersessionId
Placement モデル
見出しへのリンクbeforeとafterは既存 item の端にホバーしたときに使われます。insideは、空コンテナを含めてコンテナ自体へ drop するときに使われます。targetIndexは target container の並び順に合わせて正規化済みです。RemoteDragHandleのforは、handle がドラッグ対象 item の subtree 内にある場合は省略できます。
例: remote SFC での sortable backlog
見出しへのリンク<script setup lang="ts">import { ref } from 'vue'import { RemoteDragHandle, RemoteSortableContainer, RemoteSortableItem, type RemoteSortableEvent,} from '@omnicajs/vue-remote/remote'
type Task = { id: string; title: string;}
const tasks = ref<Task[]>([ { id: 'task-1', title: 'Design DnD contract' }, { id: 'task-2', title: 'Cover host runtime' }, { id: 'task-3', title: 'Ship docs' },])
const moveTask = (event: RemoteSortableEvent) => { if (!event.accepted || event.targetIndex == null) { return }
const fromIndex = tasks.value.findIndex(task => task.id === event.itemId)
if (fromIndex < 0) { return }
const [task] = tasks.value.splice(fromIndex, 1) const nextIndex = Math.min(event.targetIndex, tasks.value.length)
tasks.value.splice(nextIndex, 0, task)}</script>
<template> <RemoteSortableContainer as="ul" class="task-list" container-id="backlog" :accepts="['task']" orientation="vertical" :on-drop="moveTask" > <RemoteSortableItem v-for="(task, index) in tasks" :key="task.id" as="li" class="task-row" container-id="backlog" :index="index" :item-id="task.id" :payload="{ title: task.title }" type="task" > <RemoteDragHandle as="button" class="task-handle" :for="task.id" > Drag </RemoteDragHandle>
<span>{{ task.title }}</span> </RemoteSortableItem> </RemoteSortableContainer></template>カンバンのような構成では、複数の RemoteSortableContainer を使い、onDrop の中で source と target の配列を更新します。
スタイリング用の host DOM 状態
見出しへのリンクドラッグ中、host runtime は実 DOM に次の状態属性を追加します。
- 静的マーカー:
data-dnd-sortable-container,data-dnd-sortable-item,data-dnd-handle - アクティブセッション:
data-dnd-source,data-dnd-dragging,data-dnd-target,data-dnd-drag-over - placement と可否:
data-dnd-placement,data-dnd-drop-allowed,data-dnd-drop-forbidden
そのため、内部 transport props を DOM に漏らさずに、host CSS から drag 状態をスタイリングできます。