Skip to content

Commit 6307e0e

Browse files
committed
fix(canvas): correct z-index layering for selected blocks and connected edges
1 parent e270756 commit 6307e0e

File tree

1 file changed

+52
-5
lines changed
  • apps/sim/app/workspace/[workspaceId]/w/[workflowId]

1 file changed

+52
-5
lines changed

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

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,6 @@ const edgeTypes: EdgeTypes = {
201201
const defaultEdgeOptions = { type: 'custom' }
202202

203203
const reactFlowStyles = [
204-
'[&_.react-flow__edges]:!z-0',
205-
'[&_.react-flow__node]:z-[21]',
206204
'[&_.react-flow__handle]:!z-[30]',
207205
'[&_.react-flow__edge-labels]:!z-[1001]',
208206
'[&_.react-flow__pane]:select-none',
@@ -2478,6 +2476,7 @@ const WorkflowContent = React.memo(
24782476

24792477
// Local state for nodes - allows smooth drag without store updates on every frame
24802478
const [displayNodes, setDisplayNodes] = useState<Node[]>([])
2479+
const [lastInteractedNodeId, setLastInteractedNodeId] = useState<string | null>(null)
24812480

24822481
const selectedNodeIds = useMemo(
24832482
() => displayNodes.filter((node) => node.selected).map((node) => node.id),
@@ -2489,6 +2488,14 @@ const WorkflowContent = React.memo(
24892488
syncPanelWithSelection(selectedNodeIds)
24902489
}, [selectedNodeIdsKey])
24912490

2491+
// Keep the most recently selected block on top even after deselection, so a
2492+
// dragged block doesn't suddenly drop behind other overlapping blocks.
2493+
useEffect(() => {
2494+
if (selectedNodeIds.length > 0) {
2495+
setLastInteractedNodeId(selectedNodeIds[selectedNodeIds.length - 1])
2496+
}
2497+
}, [selectedNodeIdsKey])
2498+
24922499
useEffect(() => {
24932500
// Check for pending selection (from paste/duplicate), otherwise preserve existing selection
24942501
if (pendingSelection && pendingSelection.length > 0) {
@@ -3723,18 +3730,51 @@ const WorkflowContent = React.memo(
37233730
[removeEdge, edges, blocks, addNotification, activeWorkflowId]
37243731
)
37253732

3733+
// Elevate nodes using React Flow's native zIndex so selected/recent blocks
3734+
// always sit above edges and other blocks.
3735+
//
3736+
// Z-index layers (regular blocks):
3737+
// 21 — default
3738+
// 22 — last interacted (dragged/selected, now deselected) so it stays on
3739+
// top of siblings until another block is touched
3740+
// 31 — currently selected (above connected edges at z-22 and handles at z-30)
3741+
//
3742+
// Subflow container nodes are skipped — they use depth-based zIndex for
3743+
// correct parent/child layering and must not be bumped.
3744+
// Child blocks inside containers already carry zIndex 1000 and are bumped by
3745+
// +10 when selected so they stay above their sibling child blocks.
3746+
const nodesForRender = useMemo(() => {
3747+
return displayNodes.map((node) => {
3748+
if (node.type === 'subflowNode') return node
3749+
const base = node.zIndex ?? 21
3750+
const target = node.selected
3751+
? base + 10
3752+
: node.id === lastInteractedNodeId
3753+
? Math.max(base, 22)
3754+
: base
3755+
if (target === node.zIndex) return node
3756+
return { ...node, zIndex: target }
3757+
})
3758+
}, [displayNodes, lastInteractedNodeId])
3759+
37263760
/** Transforms edges to include selection state and delete handlers. Memoized to prevent re-renders. */
37273761
const edgesWithSelection = useMemo(() => {
37283762
const nodeMap = new Map(displayNodes.map((n) => [n.id, n]))
3763+
const elevatedNodeIdSet = new Set(
3764+
lastInteractedNodeId ? [...selectedNodeIds, lastInteractedNodeId] : selectedNodeIds
3765+
)
37293766

37303767
return edgesForDisplay.map((edge) => {
37313768
const sourceNode = nodeMap.get(edge.source)
37323769
const targetNode = nodeMap.get(edge.target)
37333770
const parentLoopId = sourceNode?.parentId || targetNode?.parentId
37343771
const edgeContextId = `${edge.id}${parentLoopId ? `-${parentLoopId}` : ''}`
3772+
const connectedToElevated =
3773+
elevatedNodeIdSet.has(edge.source) || elevatedNodeIdSet.has(edge.target)
37353774

37363775
return {
37373776
...edge,
3777+
zIndex: connectedToElevated ? 22 : 0,
37383778
data: {
37393779
...edge.data,
37403780
isSelected: selectedEdges.has(edgeContextId),
@@ -3745,7 +3785,14 @@ const WorkflowContent = React.memo(
37453785
},
37463786
}
37473787
})
3748-
}, [edgesForDisplay, displayNodes, selectedEdges, handleEdgeDelete])
3788+
}, [
3789+
edgesForDisplay,
3790+
displayNodes,
3791+
selectedNodeIds,
3792+
selectedEdges,
3793+
handleEdgeDelete,
3794+
lastInteractedNodeId,
3795+
])
37493796

37503797
/** Handles Delete/Backspace to remove selected edges or blocks. */
37513798
useEffect(() => {
@@ -3885,7 +3932,7 @@ const WorkflowContent = React.memo(
38853932
{showTrainingModal && <TrainingModal />}
38863933

38873934
<ReactFlow
3888-
nodes={displayNodes}
3935+
nodes={nodesForRender}
38893936
edges={edgesWithSelection}
38903937
onNodesChange={onNodesChange}
38913938
onEdgesChange={onEdgesChange}
@@ -3952,7 +3999,7 @@ const WorkflowContent = React.memo(
39523999
onNodeDragStart={effectivePermissions.canEdit ? onNodeDragStart : undefined}
39534000
snapToGrid={snapToGrid}
39544001
snapGrid={snapGrid}
3955-
elevateEdgesOnSelect={true}
4002+
elevateEdgesOnSelect={false}
39564003
onlyRenderVisibleElements={false}
39574004
deleteKeyCode={null}
39584005
elevateNodesOnSelect={false}

0 commit comments

Comments
 (0)