Skip to content

Commit d05ab8f

Browse files
committed
fix: restore focus on toast dismiss
1 parent 8cf371a commit d05ab8f

4 files changed

Lines changed: 71 additions & 1 deletion

File tree

packages/blockly/core/focus_manager.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,14 @@ export class FocusManager {
302302
return this.focusedNode;
303303
}
304304

305+
/**
306+
* Returns the IFocusableNode that held focus immediately before the current
307+
* focused node, or null if there is no such node.
308+
*/
309+
getPreviouslyFocusedNode(): IFocusableNode | null {
310+
return this.previouslyFocusedNode;
311+
}
312+
305313
/**
306314
* Focuses the specific IFocusableTree. This either means restoring active
307315
* focus to the tree's passively focused node, or focusing the tree's root

packages/blockly/core/toast.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66

77
import * as Css from './css.js';
8+
import {getFocusManager} from './focus_manager.js';
89
import {Msg} from './msg.js';
910
import * as aria from './utils/aria.js';
1011
import * as dom from './utils/dom.js';
@@ -139,8 +140,17 @@ export class Toast {
139140
closeIcon,
140141
);
141142
closeButton.addEventListener('click', () => {
143+
const focusManager = getFocusManager();
144+
const nodeToRestore = focusManager.getPreviouslyFocusedNode();
142145
toast.remove();
143-
workspace.markFocused();
146+
if (nodeToRestore?.canBeFocused()) {
147+
focusManager.focusNode(nodeToRestore);
148+
} else {
149+
// No prior focus (e.g. a toast closed with the mouse before any
150+
// Blockly node was focused). Park focus back on the workspace
151+
// via FocusManager instead of dropping it on the document body.
152+
focusManager.focusTree(workspace);
153+
}
144154
});
145155

146156
let timeout: ReturnType<typeof setTimeout>;

packages/blockly/tests/mocha/focus_manager_test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,25 @@ suite('FocusManager', function () {
418418
});
419419
});
420420

421+
suite('getPreviouslyFocusedNode()', function () {
422+
test('by default returns null', function () {
423+
assert.isNull(this.focusManager.getPreviouslyFocusedNode());
424+
});
425+
426+
test('after losing focus to untracked element returns prior node', function () {
427+
this.focusManager.registerTree(this.testFocusableTree1);
428+
this.focusManager.focusNode(this.testFocusableTree1Node1);
429+
430+
document.body.focus();
431+
432+
assert.isNull(this.focusManager.getFocusedNode());
433+
assert.strictEqual(
434+
this.focusManager.getPreviouslyFocusedNode(),
435+
this.testFocusableTree1Node1,
436+
);
437+
});
438+
});
439+
421440
suite('focusTree()', function () {
422441
test('for not registered tree throws', function () {
423442
const errorMsgRegex = /Attempted to focus unregistered tree.+?/;

packages/blockly/tests/mocha/toast_test.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,4 +139,37 @@ suite('Toasts', function () {
139139
assert.isNull(toast.getAttribute('aria-live'));
140140
assert.notEqual(toast.getAttribute('role'), Blockly.utils.aria.Role.STATUS);
141141
});
142+
143+
suite('dismiss focus', function () {
144+
function closeToast(workspace) {
145+
const closeButton = workspace
146+
.getInjectionDiv()
147+
.querySelector('.blocklyToastCloseButton');
148+
closeButton.focus();
149+
closeButton.click();
150+
}
151+
152+
test('restores previously focused node on click', function () {
153+
const block = this.workspace.newBlock('text_print');
154+
block.initSvg();
155+
block.render();
156+
Blockly.getFocusManager().focusNode(block);
157+
Blockly.Toast.show(this.workspace, {message: 'texas toast'});
158+
159+
closeToast(this.workspace);
160+
assert.isFalse(this.toastIsVisible('texas toast'));
161+
assert.strictEqual(Blockly.getFocusManager().getFocusedNode(), block);
162+
});
163+
164+
test('falls back to workspace focus when nothing was previously focused', function () {
165+
Blockly.Toast.show(this.workspace, {message: 'texas toast'});
166+
167+
closeToast(this.workspace);
168+
assert.isFalse(this.toastIsVisible('texas toast'));
169+
assert.strictEqual(
170+
Blockly.getFocusManager().getFocusedNode(),
171+
this.workspace.getWorkspaceFocusTarget(),
172+
);
173+
});
174+
});
142175
});

0 commit comments

Comments
 (0)