File tree Expand file tree Collapse file tree
packages/scratch-gui/src/containers Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -240,6 +240,20 @@ class Blocks extends React.Component {
240240 }
241241 componentWillUnmount ( ) {
242242 this . detachVM ( ) ;
243+ // Hide any open field editor and move Blockly focus to the workspace
244+ // root before disposing. Without this, BlockSvg.dispose() detects the
245+ // focused element is inside a block and schedules a stale
246+ // setTimeout(() => focusTree(workspace)), which fires after the
247+ // workspace is unregistered and throws
248+ // "Attempted to focus unregistered tree" (scratch-blocks#3460).
249+ //
250+ // focusNode(workspace) — not focusTree(workspace) — is used here
251+ // because focusTree would restore focus to whatever was previously
252+ // focused in this workspace (likely the same block about to be
253+ // disposed). focusNode pins focus to the workspace root directly,
254+ // ensuring no block is focused when dispose() runs.
255+ this . ScratchBlocks . WidgetDiv . hide ( ) ;
256+ this . ScratchBlocks . getFocusManager ( ) . focusNode ( this . workspace ) ;
243257 this . workspace . dispose ( ) ;
244258 clearTimeout ( this . toolboxUpdateTimeout ) ;
245259
Original file line number Diff line number Diff line change @@ -26,6 +26,21 @@ class CustomProcedures extends React.Component {
2626 }
2727 componentWillUnmount ( ) {
2828 if ( this . workspace ) {
29+ // When BlockSvg.dispose() runs with the focused element inside the
30+ // block, it schedules setTimeout(() => focusTree(workspace)) to
31+ // return focus somewhere sensible. For procedures_declaration
32+ // (no parent block), that setTimeout fires *after* the workspace
33+ // is unregistered, throwing "Attempted to focus unregistered tree".
34+ //
35+ // Fix: hide any open field editor (releases ephemeral focus) then
36+ // move Blockly focus to the main workspace before disposing.
37+ // BlockSvg.dispose() then sees the focused element is not inside
38+ // any dialog block and skips the stale focusTree setTimeout.
39+ ScratchBlocks . WidgetDiv . hide ( ) ;
40+ const mainWorkspace = ScratchBlocks . getMainWorkspace ( ) ;
41+ if ( mainWorkspace ) {
42+ ScratchBlocks . getFocusManager ( ) . focusTree ( mainWorkspace ) ;
43+ }
2944 this . workspace . dispose ( ) ;
3045 }
3146 }
You can’t perform that action at this time.
0 commit comments