@@ -37,9 +37,10 @@ const nodeTypes = {
3737interface CanvasProps {
3838 onDragStart : ( type : string ) => void
3939 onRegisterAddNode : ( handler : ( blockType : string ) => void ) => void
40+ readOnly ?: boolean
4041}
4142
42- function FlowCanvas ( { onRegisterAddNode } : { onRegisterAddNode : ( handler : ( blockType : string ) => void ) => void } ) {
43+ function FlowCanvas ( { onRegisterAddNode, readOnly = false } : { onRegisterAddNode : ( handler : ( blockType : string ) => void ) => void ; readOnly ?: boolean } ) {
4344 const {
4445 nodes,
4546 edges,
@@ -88,8 +89,9 @@ function FlowCanvas({ onRegisterAddNode }: { onRegisterAddNode: (handler: (block
8889 // Validation is now triggered manually via the Validate button in Header
8990 // Removed automatic validation on nodes/edges change
9091
91- // Keyboard shortcuts for undo/redo/delete/group/expand
92+ // Keyboard shortcuts for undo/redo/delete/group/expand (disabled in read-only mode)
9293 useEffect ( ( ) => {
94+ if ( readOnly ) return
9395 const handleKeyDown = ( e : KeyboardEvent ) => {
9496 // Check for Ctrl (Windows/Linux) or Cmd (Mac)
9597 const isMod = e . ctrlKey || e . metaKey
@@ -130,7 +132,7 @@ function FlowCanvas({ onRegisterAddNode }: { onRegisterAddNode: (handler: (block
130132
131133 window . addEventListener ( 'keydown' , handleKeyDown )
132134 return ( ) => window . removeEventListener ( 'keydown' , handleKeyDown )
133- } , [ undo , redo , removeNode , removeEdge , selectedNodeId , selectedEdgeId , setSelectedEdgeId , nodes ] )
135+ } , [ readOnly , undo , redo , removeNode , removeEdge , selectedNodeId , selectedEdgeId , setSelectedEdgeId , nodes ] )
134136
135137 // Find a suitable position for a new node
136138 const findAvailablePosition = useCallback ( ( ) => {
@@ -400,6 +402,22 @@ function FlowCanvas({ onRegisterAddNode }: { onRegisterAddNode: (handler: (block
400402 [ nodes , setNodes ]
401403 )
402404
405+ // In read-only mode we still need to propagate selection changes so that
406+ // ReactFlow sets node.selected=true (which reveals the group-block expand button
407+ // and drives ConfigPanel display). We filter out position/remove/add changes so
408+ // nothing can mutate the graph structure.
409+ const onNodesChangeReadOnly = useCallback (
410+ ( changes : any ) => {
411+ const allowed = changes . filter ( ( c : any ) =>
412+ c . type === 'select' || c . type === 'dimensions' || c . type === 'reset'
413+ )
414+ if ( allowed . length > 0 ) {
415+ setNodes ( applyNodeChanges ( allowed , nodes ) )
416+ }
417+ } ,
418+ [ nodes , setNodes ]
419+ )
420+
403421 const onEdgesChange = useCallback (
404422 ( changes : any ) => {
405423 setEdges ( applyEdgeChanges ( changes , edges ) )
@@ -688,32 +706,33 @@ function FlowCanvas({ onRegisterAddNode }: { onRegisterAddNode: (handler: (block
688706 return (
689707 < div
690708 className = "flex-1 h-full"
691- onDrop = { onDrop }
692- onDragOver = { onDragOver }
709+ onDrop = { readOnly ? undefined : onDrop }
710+ onDragOver = { readOnly ? undefined : onDragOver }
693711 >
694- < HistoryToolbar />
712+ { ! readOnly && < HistoryToolbar /> }
695713 < ReactFlow
696714 nodes = { nodesWithHandlers }
697715 edges = { edges }
698- onNodesChange = { onNodesChange }
699- onEdgesChange = { onEdgesChange }
700- onConnect = { onConnect }
701- onNodeClick = { isInteractive ? onNodeClick : undefined }
702- onEdgeClick = { isInteractive ? onEdgeClick : undefined }
703- onPaneClick = { isInteractive ? onPaneClick : undefined }
704- onPaneContextMenu = { isInteractive ? onPaneContextMenu : undefined }
705- onNodeContextMenu = { isInteractive ? onNodeContextMenu : undefined }
706- onReconnect = { onReconnect }
707- edgesReconnectable = { true }
716+ onNodesChange = { readOnly ? onNodesChangeReadOnly : onNodesChange }
717+ onEdgesChange = { readOnly ? undefined : onEdgesChange }
718+ onConnect = { readOnly ? undefined : onConnect }
719+ onNodeClick = { onNodeClick }
720+ onEdgeClick = { readOnly ? undefined : onEdgeClick }
721+ onPaneClick = { onPaneClick }
722+ onPaneContextMenu = { readOnly ? undefined : ( isInteractive ? onPaneContextMenu : undefined ) }
723+ onNodeContextMenu = { readOnly ? undefined : ( isInteractive ? onNodeContextMenu : undefined ) }
724+ onReconnect = { readOnly ? undefined : onReconnect }
725+ edgesReconnectable = { ! readOnly }
708726 nodeTypes = { nodeTypes }
709727 connectionLineComponent = { CustomConnectionLine }
710728 fitView
711729 minZoom = { 0.5 }
712730 maxZoom = { 1.5 }
713- nodesDraggable = { isInteractive }
714- nodesConnectable = { isInteractive }
715- elementsSelectable = { isInteractive }
716- onInteractiveChange = { setIsInteractive }
731+ nodesDraggable = { readOnly ? false : isInteractive }
732+ nodesConnectable = { readOnly ? false : isInteractive }
733+ elementsSelectable = { true }
734+ deleteKeyCode = { readOnly ? null : undefined }
735+ onInteractiveChange = { readOnly ? undefined : setIsInteractive }
717736 defaultEdgeOptions = { {
718737 animated : true ,
719738 style : { stroke : '#6366f1' , strokeWidth : 2 }
@@ -751,7 +770,7 @@ function FlowCanvas({ onRegisterAddNode }: { onRegisterAddNode: (handler: (block
751770 position = "bottom-right"
752771 />
753772 </ ReactFlow >
754- { contextMenu && (
773+ { ! readOnly && contextMenu && (
755774 < ContextMenu
756775 x = { contextMenu . x }
757776 y = { contextMenu . y }
@@ -803,10 +822,10 @@ function FlowCanvas({ onRegisterAddNode }: { onRegisterAddNode: (handler: (block
803822 )
804823}
805824
806- export default function Canvas ( { onDragStart, onRegisterAddNode } : CanvasProps ) {
825+ export default function Canvas ( { onDragStart, onRegisterAddNode, readOnly = false } : CanvasProps ) {
807826 return (
808827 < ReactFlowProvider >
809- < FlowCanvas onRegisterAddNode = { onRegisterAddNode } />
828+ < FlowCanvas onRegisterAddNode = { onRegisterAddNode } readOnly = { readOnly } />
810829 </ ReactFlowProvider >
811830 )
812831}
0 commit comments