@@ -132,33 +132,54 @@ export function useGraphEditor({ weighted = false, initialPreset }) {
132132 ) ;
133133 } ;
134134
135+ const deleteNode = ( id ) => {
136+ setNodes ( ( ns ) => ns . filter ( ( n ) => n . id !== id ) ) ;
137+ setEdges ( ( es ) => es . filter ( ( e ) => e . source !== id && e . target !== id ) ) ;
138+ if ( startIdRef . current === id ) startIdRef . current = null ;
139+ if ( finishIdRef . current === id ) finishIdRef . current = null ;
140+ if ( pendingEdgeRef . current === id ) pendingEdgeRef . current = null ;
141+ } ;
142+
143+ const deleteEdge = ( id ) => setEdges ( ( es ) => es . filter ( ( e ) => e . id !== id ) ) ;
144+
145+ // directed only: swap endpoints so the arrow flips (id/key kept stable)
146+ const reverseEdge = ( id ) => {
147+ if ( ! directedRef . current ) return ;
148+ setEdges ( ( es ) => es . map ( ( e ) => ( e . id === id ? { ...e , source : e . target , target : e . source } : e ) ) ) ;
149+ } ;
150+
151+ // Persistent modes: add-node drops a node on every pane click; add-edge
152+ // chains edges through consecutively clicked nodes; delete removes the
153+ // clicked node/edge. Esc returns to select.
135154 const onPaneClick = ( event ) => {
136155 if ( isRunningRef . current ) return ;
137156 if ( modeRef . current === 'add-node' ) {
138157 addNodeAt ( screenToFlowPosition ( { x : event . clientX , y : event . clientY } ) ) ;
139- setModeBoth ( 'idle' ) ;
158+ } else {
159+ pendingEdgeRef . current = null ; // clicking empty lifts the edge anchor
140160 }
141- pendingEdgeRef . current = null ;
142161 } ;
143162
144163 const onNodeClick = ( _event , node ) => {
145164 if ( isRunningRef . current ) return ;
146- if ( modeRef . current === 'add-edge' ) {
165+ const m = modeRef . current ;
166+ if ( m === 'add-edge' ) {
147167 if ( pendingEdgeRef . current == null ) {
148168 pendingEdgeRef . current = node . id ;
149169 } else {
150170 addEdge ( pendingEdgeRef . current , node . id ) ;
151- pendingEdgeRef . current = null ;
152- setModeBoth ( 'idle' ) ;
171+ pendingEdgeRef . current = node . id ; // chain: anchor moves to this node
153172 }
173+ } else if ( m === 'delete' ) {
174+ deleteNode ( node . id ) ;
154175 }
176+ // idle: React Flow handles selection
155177 } ;
156178
157- const onNodesDelete = ( deleted ) => {
158- for ( const n of deleted ) {
159- if ( n . id === startIdRef . current ) startIdRef . current = null ;
160- if ( n . id === finishIdRef . current ) finishIdRef . current = null ;
161- }
179+ const onEdgeClick = ( _event , edge ) => {
180+ if ( isRunningRef . current ) return ;
181+ if ( modeRef . current === 'delete' ) deleteEdge ( edge . id ) ;
182+ // idle: React Flow handles selection
162183 } ;
163184
164185 useEffect ( ( ) => {
@@ -167,13 +188,23 @@ export function useGraphEditor({ weighted = false, initialPreset }) {
167188 const el = e . target ;
168189 if ( el && el . closest && el . closest ( 'input,select,textarea,[role="combobox"],button' ) ) return ;
169190
170- const selected = nodesRef . current . find ( ( n ) => n . selected ) ;
191+ const selNode = ( ) => nodesRef . current . find ( ( n ) => n . selected ) ;
192+ const selEdge = ( ) => edgesRef . current . find ( ( n ) => n . selected ) ;
171193 const k = e . key . toLowerCase ( ) ;
194+
172195 if ( k === 'n' ) { setModeBoth ( 'add-node' ) ; pendingEdgeRef . current = null ; }
173196 else if ( k === 'e' ) { setModeBoth ( 'add-edge' ) ; pendingEdgeRef . current = null ; }
174- else if ( k === 's' && selected ) setRole ( selected . id , 'start' ) ;
175- else if ( k === 'f' && selected ) setRole ( selected . id , 'finish' ) ;
197+ else if ( k === 'd' ) { setModeBoth ( 'delete' ) ; pendingEdgeRef . current = null ; }
176198 else if ( e . key === 'Escape' ) { setModeBoth ( 'idle' ) ; pendingEdgeRef . current = null ; }
199+ else if ( k === 's' ) { const n = selNode ( ) ; if ( n ) setRole ( n . id , 'start' ) ; }
200+ else if ( k === 'f' ) { const n = selNode ( ) ; if ( n ) setRole ( n . id , 'finish' ) ; }
201+ else if ( k === 'x' ) { const ed = selEdge ( ) ; if ( ed ) reverseEdge ( ed . id ) ; }
202+ else if ( e . key === 'Delete' || e . key === 'Backspace' ) {
203+ const n = selNode ( ) ;
204+ const ed = selEdge ( ) ;
205+ if ( n ) deleteNode ( n . id ) ;
206+ else if ( ed ) deleteEdge ( ed . id ) ;
207+ }
177208 } ;
178209 window . addEventListener ( 'keydown' , onKey ) ;
179210 return ( ) => window . removeEventListener ( 'keydown' , onKey ) ;
@@ -210,7 +241,7 @@ export function useGraphEditor({ weighted = false, initialPreset }) {
210241 // reactive
211242 nodes, edges, directed, mode, status, isRunning, weighted,
212243 // react flow wiring
213- onNodesChange, onEdgesChange, onNodesDelete , onPaneClick, onNodeClick,
244+ onNodesChange, onEdgesChange, onPaneClick, onNodeClick, onEdgeClick ,
214245 // methods
215246 run, getContext, setWeight, setDirected, loadPreset, clear, setSpeed,
216247 } ;
0 commit comments