@@ -100,6 +100,7 @@ interface CanvasState {
100100 type : 'input' | 'output' ;
101101 } | null ;
102102 selectionRect : SelectionRect | null ; // Box selection state
103+ canvasMouseDownPos : Position ;
103104}
104105
105106export interface NodeGraphApi {
@@ -303,6 +304,7 @@ export function NodeGraph(): m.Component<NodeGraphAttrs> {
303304 undockCandidate : null ,
304305 hoveredPort : null ,
305306 selectionRect : null ,
307+ canvasMouseDownPos : { x : 0 , y : 0 } ,
306308 } ;
307309
308310 let latestVnode : m . Vnode < NodeGraphAttrs > | null = null ;
@@ -1386,24 +1388,46 @@ export function NodeGraph(): m.Component<NodeGraphAttrs> {
13861388 return ;
13871389 }
13881390
1389- // Clear selection if not holding Shift or Cmd/Ctrl (or if multiselect is disabled)
1390- if ( ! multiselect || ( ! e . shiftKey && ! e . metaKey && ! e . ctrlKey ) ) {
1391- canvasState . selectedNodes . clear ( ) ;
1391+ // Start panning and store position to detect click vs drag
1392+ canvasState . isPanning = true ;
1393+ canvasState . panStart = { x : e . clientX , y : e . clientY } ;
1394+ canvasState . canvasMouseDownPos = { x : e . clientX , y : e . clientY } ;
1395+ e . preventDefault ( ) ;
1396+ }
1397+ } ,
1398+ onclick : ( e : MouseEvent ) => {
1399+ const target = e . target as HTMLElement ;
1400+ // Clear selection on canvas click (only if mouse didn't move significantly)
1401+ if (
1402+ target . classList . contains ( 'pf-canvas' ) ||
1403+ target . tagName === 'svg'
1404+ ) {
1405+ const dx = Math . abs ( e . clientX - canvasState . canvasMouseDownPos . x ) ;
1406+ const dy = Math . abs ( e . clientY - canvasState . canvasMouseDownPos . y ) ;
1407+ const threshold = 3 ; // Pixels of movement tolerance
13921408
1393- // Call onSelectionClear callback
1409+ // Only clear if it was a click (not a drag)
1410+ if ( dx <= threshold && dy <= threshold ) {
1411+ canvasState . selectedNodes . clear ( ) ;
13941412 const { onSelectionClear} = vnode . attrs ;
13951413 if ( onSelectionClear !== undefined ) {
13961414 onSelectionClear ( ) ;
13971415 }
13981416 }
1399-
1400- canvasState . isPanning = true ;
1401- canvasState . panStart = { x : e . clientX , y : e . clientY } ;
1402- e . preventDefault ( ) ;
14031417 }
14041418 } ,
14051419 onkeydown : ( e : KeyboardEvent ) => {
1406- if ( e . key === 'Delete' || e . key === 'Backspace' ) {
1420+ if ( e . key === 'Escape' ) {
1421+ // Deselect all nodes with Escape key
1422+ if ( canvasState . selectedNodes . size > 0 ) {
1423+ canvasState . selectedNodes . clear ( ) ;
1424+ const { onSelectionClear} = vnode . attrs ;
1425+ if ( onSelectionClear !== undefined ) {
1426+ onSelectionClear ( ) ;
1427+ }
1428+ e . preventDefault ( ) ;
1429+ }
1430+ } else if ( e . key === 'Delete' || e . key === 'Backspace' ) {
14071431 const { onNodeRemove} = vnode . attrs ;
14081432 if ( canvasState . selectedNodes . size > 0 && onNodeRemove ) {
14091433 // Delete all selected nodes
0 commit comments