fix(alignment): only snap top-level dragged nodes
Code review flagged silent corruption when dragging a mixed selection of a parent + one of its children: the child's ids stayed in pendingSnap even though nodeBox excluded it from the bbox. On drag stop we shifted the child's parent-relative position by the same delta the parent already moves by — double-snapping the child off-screen. Restrict the pendingSnap id set to nodes that contributed a box (top-level only). Children follow their parent's move automatically; no extra shift is needed.
This commit is contained in:
@@ -74,23 +74,33 @@ export function useAlignmentGuides() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const all = getNodes()
|
const all = getNodes()
|
||||||
const ids = new Set((dragNodes.length > 0 ? dragNodes : [dragNode]).map((n) => n.id))
|
const draggedIds = new Set((dragNodes.length > 0 ? dragNodes : [dragNode]).map((n) => n.id))
|
||||||
const draggedBoxes = all.filter((n) => ids.has(n.id)).map(nodeBox).filter((b): b is Box => b !== null)
|
// Restrict the snap to top-level dragged nodes. nodeBox returns null for
|
||||||
if (draggedBoxes.length === 0) {
|
// parented nodes; if we kept their ids in pendingSnap, onNodeDragStop
|
||||||
|
// would shift their parent-relative position by the same delta the parent
|
||||||
|
// already moves by, double-snapping the child. Children follow the parent
|
||||||
|
// automatically; no extra shift needed.
|
||||||
|
const draggedBoxEntries = all
|
||||||
|
.filter((n) => draggedIds.has(n.id))
|
||||||
|
.map((n) => ({ id: n.id, box: nodeBox(n) }))
|
||||||
|
.filter((e): e is { id: string; box: Box } => e.box !== null)
|
||||||
|
if (draggedBoxEntries.length === 0) {
|
||||||
clearState()
|
clearState()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const dragged = draggedBoxes.length === 1 ? draggedBoxes[0] : unionBox(draggedBoxes)
|
const snapIds = new Set(draggedBoxEntries.map((e) => e.id))
|
||||||
|
const boxes = draggedBoxEntries.map((e) => e.box)
|
||||||
|
const dragged = boxes.length === 1 ? boxes[0] : unionBox(boxes)
|
||||||
if (!dragged) return
|
if (!dragged) return
|
||||||
const candidates = all
|
const candidates = all
|
||||||
.filter((n) => !ids.has(n.id))
|
.filter((n) => !draggedIds.has(n.id))
|
||||||
.map(nodeBox)
|
.map(nodeBox)
|
||||||
.filter((b): b is Box => b !== null)
|
.filter((b): b is Box => b !== null)
|
||||||
const result = computeSnap(dragged, candidates, settings.threshold)
|
const result = computeSnap(dragged, candidates, settings.threshold)
|
||||||
setGuides(result.guides)
|
setGuides(result.guides)
|
||||||
pendingSnapRef.current =
|
pendingSnapRef.current =
|
||||||
result.deltaX !== 0 || result.deltaY !== 0
|
result.deltaX !== 0 || result.deltaY !== 0
|
||||||
? { deltaX: result.deltaX, deltaY: result.deltaY, ids }
|
? { deltaX: result.deltaX, deltaY: result.deltaY, ids: snapIds }
|
||||||
: null
|
: null
|
||||||
}, [settings.enabled, settings.threshold, getNodes, clearState])
|
}, [settings.enabled, settings.threshold, getNodes, clearState])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user