feat: add cross-parent drag-and-drop with validation feedback
Enable moving nodes between different parents in the tree editor. Drop targets show blue indicator for valid drops and red pulsing glow for invalid drops (e.g., dropping onto solution nodes or onto descendants of the dragged node). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit was merged in pull request #48.
This commit is contained in:
@@ -17,8 +17,8 @@ const DRAFT_STORAGE_KEY = 'tree-editor-draft'
|
||||
// Helper to generate unique IDs
|
||||
const generateId = () => crypto.randomUUID()
|
||||
|
||||
// Helper to find a node in the tree structure
|
||||
const findNodeInTree = (
|
||||
// Helper to find a node in the tree structure (exported for drag validation)
|
||||
export const findNodeInTree = (
|
||||
nodeId: string,
|
||||
structure: TreeStructure | null
|
||||
): TreeStructure | null => {
|
||||
@@ -144,6 +144,7 @@ interface TreeEditorState {
|
||||
|
||||
// Actions - Node ordering
|
||||
reorderNodes: (parentId: string, fromIndex: number, toIndex: number) => void
|
||||
moveNode: (nodeId: string, targetParentId: string, targetIndex: number) => void
|
||||
reorderOptions: (nodeId: string, fromIndex: number, toIndex: number) => void
|
||||
|
||||
// Actions - Selection
|
||||
@@ -496,6 +497,37 @@ export const useTreeEditorStore = create<TreeEditorState>()(
|
||||
get().autoSaveDraft()
|
||||
},
|
||||
|
||||
moveNode: (nodeId: string, targetParentId: string, targetIndex: number) => {
|
||||
set((state) => {
|
||||
// Find and remove from current parent
|
||||
const currentParent = findParentNode(nodeId, state.treeStructure)
|
||||
if (!currentParent?.children) return
|
||||
|
||||
const sourceIndex = currentParent.children.findIndex(c => c.id === nodeId)
|
||||
if (sourceIndex === -1) return
|
||||
|
||||
const [movedNode] = currentParent.children.splice(sourceIndex, 1)
|
||||
|
||||
// Find target parent and insert
|
||||
const targetParent = findNodeInTree(targetParentId, state.treeStructure)
|
||||
if (!targetParent) return
|
||||
|
||||
if (!targetParent.children) {
|
||||
targetParent.children = []
|
||||
}
|
||||
|
||||
// Adjust index if moving within same parent and source was before target
|
||||
let adjustedIndex = targetIndex
|
||||
if (currentParent.id === targetParent.id && sourceIndex < targetIndex) {
|
||||
adjustedIndex = targetIndex - 1
|
||||
}
|
||||
|
||||
targetParent.children.splice(adjustedIndex, 0, movedNode)
|
||||
state.isDirty = true
|
||||
})
|
||||
get().autoSaveDraft()
|
||||
},
|
||||
|
||||
reorderOptions: (nodeId: string, fromIndex: number, toIndex: number) => {
|
||||
set((state) => {
|
||||
const node = findNodeInTree(nodeId, state.treeStructure)
|
||||
|
||||
Reference in New Issue
Block a user