@@ -312,6 +312,8 @@ function render({ model, el }) {
312312 state : null ,
313313 _hoverSi : - 1 , _hoverI : - 1 , // index of hovered marker group / marker (-1 = none)
314314 _hovBar : - 1 , // index of hovered bar (-1 = none)
315+ lastWidgetId : null , // id of the last clicked/dragged widget (for on_key Delete etc.)
316+ mouseX : 0 , mouseY : 0 , // last known canvas-relative cursor position
315317 // 2D extras (null for non-2D panels)
316318 cbCanvas : _p2d ? _p2d . cbCanvas : null ,
317319 cbCtx : _p2d ? _p2d . cbCtx : null ,
@@ -1221,7 +1223,25 @@ function render({ model, el }) {
12211223 _scheduleCommit ( ) ;
12221224 } , { passive : false } ) ;
12231225
1226+ overlayCanvas . addEventListener ( 'mousemove' , ( e ) => {
1227+ const rect = overlayCanvas . getBoundingClientRect ( ) ;
1228+ p . mouseX = e . clientX - rect . left ;
1229+ p . mouseY = e . clientY - rect . top ;
1230+ } ) ;
1231+
1232+ // Keyboard shortcuts
1233+ // Built-in: r=reset view. Registered keys are forwarded to Python first.
12241234 overlayCanvas . addEventListener ( 'keydown' , ( e ) => {
1235+ const st = p . state ; if ( ! st ) return ;
1236+ const regKeys = st . registered_keys || [ ] ;
1237+ if ( regKeys . includes ( e . key ) || regKeys . includes ( '*' ) ) {
1238+ _emitEvent ( p . id , 'on_key' , null , {
1239+ key : e . key ,
1240+ last_widget_id : p . lastWidgetId || null ,
1241+ mouse_x : p . mouseX , mouse_y : p . mouseY ,
1242+ } ) ;
1243+ e . preventDefault ( ) ; return ;
1244+ }
12251245 if ( e . key . toLowerCase ( ) === 'r' ) {
12261246 p . state . azimuth = - 60 ; p . state . elevation = 30 ; p . state . zoom = 1 ;
12271247 draw3d ( p ) ;
@@ -1677,6 +1697,7 @@ function render({ model, el }) {
16771697 const hit = _ovHitTest2d ( mx , my , p ) ;
16781698 if ( hit ) {
16791699 p . ovDrag2d = hit ;
1700+ p . lastWidgetId = ( st . overlay_widgets || [ ] ) [ hit . idx ] ?. id || null ;
16801701 overlayCanvas . style . cursor = 'move' ;
16811702 e . preventDefault ( ) ; return ;
16821703 }
@@ -1727,10 +1748,11 @@ function render({ model, el }) {
17271748
17281749 // Status bar + tooltip + widget hover cursor
17291750 overlayCanvas . addEventListener ( 'mousemove' , ( e ) => {
1730- if ( p . ovDrag2d ) return ; // handled by document mousemove
1731- const st = p . state ; if ( ! st ) return ;
17321751 const rect = overlayCanvas . getBoundingClientRect ( ) ;
17331752 const mx = e . clientX - rect . left , my = e . clientY - rect . top ;
1753+ p . mouseX = mx ; p . mouseY = my ;
1754+ if ( p . ovDrag2d ) return ; // handled by document mousemove
1755+ const st = p . state ; if ( ! st ) return ;
17341756
17351757 // Update cursor based on widget hit
17361758 const whit = _ovHitTest2d ( mx , my , p ) ;
@@ -1778,8 +1800,28 @@ function render({ model, el }) {
17781800 } ) ;
17791801
17801802 // Keyboard shortcuts
1803+ // Built-ins: r=reset zoom, c=colorbar toggle, l=log scale, s=symlog scale.
1804+ // Any key listed in st.registered_keys (or '*' for all keys) is forwarded
1805+ // to Python via on_key and suppresses the matching built-in.
17811806 overlayCanvas . addEventListener ( 'keydown' , ( e ) => {
17821807 const st = p . state ; if ( ! st ) return ;
1808+ const regKeys = st . registered_keys || [ ] ;
1809+ if ( regKeys . includes ( e . key ) || regKeys . includes ( '*' ) ) {
1810+ const imgW = Math . max ( 1 , p . pw - PAD_L - PAD_R ) , imgH = Math . max ( 1 , p . ph - PAD_T - PAD_B ) ;
1811+ const [ imgX , imgY ] = _canvasToImg2d ( p . mouseX , p . mouseY , st , imgW , imgH ) ;
1812+ const xArr = st . x_axis || [ ] , yArr = st . y_axis || [ ] ;
1813+ const iw = st . image_width || 1 , ih = st . image_height || 1 ;
1814+ const physX = xArr . length >= 2 ?_axisFracToVal ( xArr , imgX / iw ) :imgX ;
1815+ const physY = yArr . length >= 2 ?_axisFracToVal ( yArr , imgY / ih ) :imgY ;
1816+ _emitEvent ( p . id , 'on_key' , null , {
1817+ key :e . key ,
1818+ last_widget_id :p . lastWidgetId || null ,
1819+ mouse_x :p . mouseX , mouse_y :p . mouseY ,
1820+ img_x :imgX , img_y :imgY ,
1821+ phys_x :physX , phys_y :physY ,
1822+ } ) ;
1823+ e . preventDefault ( ) ; return ;
1824+ }
17831825 const key = e . key . toLowerCase ( ) ;
17841826 if ( key === 'r' ) {
17851827 st . zoom = 1 ; st . center_x = 0.5 ; st . center_y = 0.5 ;
@@ -1836,7 +1878,7 @@ function render({ model, el }) {
18361878 if ( e . button !== 0 ) return ;
18371879 const st = p . state ; if ( ! st ) return ;
18381880 const hit = _ovHitTest1d ( e . clientX - overlayCanvas . getBoundingClientRect ( ) . left , e . clientY - overlayCanvas . getBoundingClientRect ( ) . top , p ) ;
1839- if ( hit ) { p . ovDrag = hit ; overlayCanvas . style . cursor = ( hit . mode === 'edge0' || hit . mode === 'edge1' ) ?'ew-resize' :'move' ; e . preventDefault ( ) ; return ; }
1881+ if ( hit ) { p . ovDrag = hit ; p . lastWidgetId = ( p . state . overlay_widgets || [ ] ) [ hit . idx ] ?. id || null ; overlayCanvas . style . cursor = ( hit . mode === 'edge0' || hit . mode === 'edge1' ) ?'ew-resize' :'move' ; e . preventDefault ( ) ; return ; }
18401882 panStart = { mx :e . clientX , x0 :st . view_x0 , x1 :st . view_x1 } ;
18411883 p . isPanning = true ; overlayCanvas . style . cursor = 'grabbing' ; e . preventDefault ( ) ;
18421884 } ) ;
@@ -1874,15 +1916,34 @@ function render({ model, el }) {
18741916 }
18751917 } ) ;
18761918
1919+ // Keyboard shortcuts
1920+ // Built-in: r=reset view. Any key in st.registered_keys (or '*') is
1921+ // forwarded to Python via on_key and suppresses the matching built-in.
18771922 overlayCanvas . addEventListener ( 'keydown' , ( e ) => {
1878- if ( e . key . toLowerCase ( ) === 'r' ) { const st = p . state ; if ( ! st ) return ; st . view_x0 = 0 ; st . view_x1 = 1 ; draw1d ( p ) ; model . set ( `panel_${ p . id } _json` , JSON . stringify ( st ) ) ; model . save_changes ( ) ; e . preventDefault ( ) ; }
1923+ const st = p . state ; if ( ! st ) return ;
1924+ const regKeys = st . registered_keys || [ ] ;
1925+ if ( regKeys . includes ( e . key ) || regKeys . includes ( '*' ) ) {
1926+ const r = _plotRect1d ( p . pw , p . ph ) ;
1927+ const xArr = st . x_axis || [ ] ;
1928+ const frac = _canvasXToFrac1d ( p . mouseX , st . view_x0 , st . view_x1 , r ) ;
1929+ const physX = xArr . length >= 2 ?_fracToX1d ( xArr , frac ) :frac ;
1930+ _emitEvent ( p . id , 'on_key' , null , {
1931+ key :e . key ,
1932+ last_widget_id :p . lastWidgetId || null ,
1933+ mouse_x :p . mouseX , mouse_y :p . mouseY ,
1934+ phys_x :physX ,
1935+ } ) ;
1936+ e . preventDefault ( ) ; return ;
1937+ }
1938+ if ( e . key . toLowerCase ( ) === 'r' ) { st . view_x0 = 0 ; st . view_x1 = 1 ; draw1d ( p ) ; model . set ( `panel_${ p . id } _json` , JSON . stringify ( st ) ) ; model . save_changes ( ) ; e . preventDefault ( ) ; }
18791939 } ) ;
18801940 overlayCanvas . tabIndex = 0 ; overlayCanvas . style . outline = 'none' ;
18811941 overlayCanvas . addEventListener ( 'mouseenter' , ( ) => overlayCanvas . focus ( ) ) ;
18821942 overlayCanvas . addEventListener ( 'mousemove' , ( e ) => {
18831943 const st = p . state ; if ( ! st ) return ;
18841944 const rect = overlayCanvas . getBoundingClientRect ( ) ;
18851945 const mx = e . clientX - rect . left , my = e . clientY - rect . top ;
1946+ p . mouseX = mx ; p . mouseY = my ;
18861947 const r = _plotRect1d ( p . pw , p . ph ) ;
18871948 if ( mx < r . x || mx > r . x + r . w || my < r . y || my > r . y + r . h ) {
18881949 p . statusBar . style . display = 'none' ; tooltip . style . display = 'none' ;
@@ -2618,6 +2679,7 @@ function render({ model, el }) {
26182679 const hit = _ovHitTest1d ( e . clientX - rect . left , e . clientY - rect . top , p ) ;
26192680 if ( hit ) {
26202681 p . ovDrag = hit ;
2682+ p . lastWidgetId = ( p . state . overlay_widgets || [ ] ) [ hit . idx ] ?. id || null ;
26212683 overlayCanvas . style . cursor = 'ew-resize' ;
26222684 e . preventDefault ( ) ;
26232685 }
@@ -2643,10 +2705,11 @@ function render({ model, el }) {
26432705 } ) ;
26442706
26452707 overlayCanvas . addEventListener ( 'mousemove' , ( e ) => {
2646- if ( p . ovDrag ) return ; // handled by document mousemove during drag
2647- const st = p . state ; if ( ! st ) return ;
26482708 const rect = overlayCanvas . getBoundingClientRect ( ) ;
26492709 const mx = e . clientX - rect . left , my = e . clientY - rect . top ;
2710+ p . mouseX = mx ; p . mouseY = my ;
2711+ if ( p . ovDrag ) return ; // handled by document mousemove during drag
2712+ const st = p . state ; if ( ! st ) return ;
26502713
26512714 // Overlay widget cursor hint
26522715 const whit = _ovHitTest1d ( mx , my , p ) ;
@@ -2693,6 +2756,23 @@ function render({ model, el }) {
26932756 ? String ( st . x_labels [ idx ] ) : null ,
26942757 } ) ;
26952758 } ) ;
2759+
2760+ // Keyboard: registered_keys forwarded to Python; no built-in bar shortcuts.
2761+ overlayCanvas . addEventListener ( 'keydown' , ( e ) => {
2762+ const st = p . state ; if ( ! st ) return ;
2763+ const regKeys = st . registered_keys || [ ] ;
2764+ if ( regKeys . includes ( e . key ) || regKeys . includes ( '*' ) ) {
2765+ _emitEvent ( p . id , 'on_key' , null , {
2766+ key : e . key ,
2767+ last_widget_id : p . lastWidgetId || null ,
2768+ mouse_x : p . mouseX , mouse_y : p . mouseY ,
2769+ } ) ;
2770+ e . preventDefault ( ) ;
2771+ }
2772+ } ) ;
2773+ overlayCanvas . tabIndex = 0 ;
2774+ overlayCanvas . style . outline = 'none' ;
2775+ overlayCanvas . addEventListener ( 'mouseenter' , ( ) => overlayCanvas . focus ( ) ) ;
26962776 }
26972777
26982778 // ── generic redraw ────────────────────────────────────────────────────────
0 commit comments