Skip to content

Commit 80ede82

Browse files
committed
fix copy-paste subflows deselecting children
1 parent 28943eb commit 80ede82

File tree

3 files changed

+26
-46
lines changed

3 files changed

+26
-46
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ export {
55
getClampedPositionForNode,
66
isInEditableElement,
77
resolveParentChildSelectionConflicts,
8-
selectNodesDeferred,
98
validateTriggerPaste,
109
} from '@/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-canvas-helpers'
1110
export { useFloatBoundarySync, useFloatDrag, useFloatResize } from './float'

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/utils/workflow-canvas-helpers.ts

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -65,30 +65,6 @@ export function clearDragHighlights(): void {
6565
document.body.style.cursor = ''
6666
}
6767

68-
/**
69-
* Selects nodes by their IDs after paste/duplicate operations.
70-
* Defers selection to next animation frame to allow displayNodes to sync from store first.
71-
* This is necessary because the component uses controlled state (nodes={displayNodes})
72-
* and newly added blocks need time to propagate through the store → derivedNodes → displayNodes cycle.
73-
* Automatically resolves parent-child selection conflicts to prevent wiggle during drag.
74-
*/
75-
export function selectNodesDeferred(
76-
nodeIds: string[],
77-
setDisplayNodes: (updater: (nodes: Node[]) => Node[]) => void,
78-
blocks: Record<string, { data?: { parentId?: string } }>
79-
): void {
80-
const idsSet = new Set(nodeIds)
81-
requestAnimationFrame(() => {
82-
setDisplayNodes((nodes) => {
83-
const withSelection = nodes.map((node) => ({
84-
...node,
85-
selected: idsSet.has(node.id),
86-
}))
87-
return resolveParentChildSelectionConflicts(withSelection, blocks)
88-
})
89-
})
90-
}
91-
9268
interface BlockData {
9369
height?: number
9470
data?: {

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ import {
4848
getClampedPositionForNode,
4949
isInEditableElement,
5050
resolveParentChildSelectionConflicts,
51-
selectNodesDeferred,
5251
useAutoLayout,
5352
useCurrentWorkflow,
5453
useNodeUtilities,
@@ -358,6 +357,9 @@ const WorkflowContent = React.memo(() => {
358357
new Map()
359358
)
360359

360+
/** Stores node IDs to select on next derivedNodes sync (for paste/duplicate operations). */
361+
const pendingSelectionRef = useRef<Set<string> | null>(null)
362+
361363
/** Re-applies diff markers when blocks change after socket rehydration. */
362364
const blocksRef = useRef(blocks)
363365
useEffect(() => {
@@ -689,19 +691,16 @@ const WorkflowContent = React.memo(() => {
689691
return
690692
}
691693

694+
// Set pending selection before adding blocks - sync effect will apply it
695+
pendingSelectionRef.current = new Set(pastedBlocksArray.map((b) => b.id))
696+
692697
collaborativeBatchAddBlocks(
693698
pastedBlocksArray,
694699
pastedEdges,
695700
pastedLoops,
696701
pastedParallels,
697702
pastedSubBlockValues
698703
)
699-
700-
selectNodesDeferred(
701-
pastedBlocksArray.map((b) => b.id),
702-
setDisplayNodes,
703-
blocks
704-
)
705704
}, [
706705
hasClipboard,
707706
clipboard,
@@ -738,19 +737,16 @@ const WorkflowContent = React.memo(() => {
738737
return
739738
}
740739

740+
// Set pending selection before adding blocks - sync effect will apply it
741+
pendingSelectionRef.current = new Set(pastedBlocksArray.map((b) => b.id))
742+
741743
collaborativeBatchAddBlocks(
742744
pastedBlocksArray,
743745
pastedEdges,
744746
pastedLoops,
745747
pastedParallels,
746748
pastedSubBlockValues
747749
)
748-
749-
selectNodesDeferred(
750-
pastedBlocksArray.map((b) => b.id),
751-
setDisplayNodes,
752-
blocks
753-
)
754750
}, [
755751
contextMenuBlocks,
756752
copyBlocks,
@@ -884,19 +880,16 @@ const WorkflowContent = React.memo(() => {
884880
return
885881
}
886882

883+
// Set pending selection before adding blocks - sync effect will apply it
884+
pendingSelectionRef.current = new Set(pastedBlocks.map((b) => b.id))
885+
887886
collaborativeBatchAddBlocks(
888887
pastedBlocks,
889888
pasteData.edges,
890889
pasteData.loops,
891890
pasteData.parallels,
892891
pasteData.subBlockValues
893892
)
894-
895-
selectNodesDeferred(
896-
pastedBlocks.map((b) => b.id),
897-
setDisplayNodes,
898-
blocks
899-
)
900893
}
901894
}
902895
}
@@ -1959,15 +1952,27 @@ const WorkflowContent = React.memo(() => {
19591952
}, [isShiftPressed])
19601953

19611954
useEffect(() => {
1962-
// Preserve selection state when syncing from derivedNodes
1955+
// Check for pending selection (from paste/duplicate), otherwise preserve existing selection
1956+
const pendingSelection = pendingSelectionRef.current
1957+
pendingSelectionRef.current = null
1958+
19631959
setDisplayNodes((currentNodes) => {
1960+
if (pendingSelection) {
1961+
// Apply pending selection and resolve parent-child conflicts
1962+
const withSelection = derivedNodes.map((node) => ({
1963+
...node,
1964+
selected: pendingSelection.has(node.id),
1965+
}))
1966+
return resolveParentChildSelectionConflicts(withSelection, blocks)
1967+
}
1968+
// Preserve existing selection state
19641969
const selectedIds = new Set(currentNodes.filter((n) => n.selected).map((n) => n.id))
19651970
return derivedNodes.map((node) => ({
19661971
...node,
19671972
selected: selectedIds.has(node.id),
19681973
}))
19691974
})
1970-
}, [derivedNodes])
1975+
}, [derivedNodes, blocks])
19711976

19721977
/** Handles ActionBar remove-from-subflow events. */
19731978
useEffect(() => {

0 commit comments

Comments
 (0)