Skip to content

Commit 2756a47

Browse files
feat(workspace-search): focus found block on close or restore focus (#2576)
Uses Blockly 12 FocusManager API. This matches browser in find in page behaviour where Escape closes find and moves focus to the highlighted item. When there's no search performed or nothing ever matched we restore focus to the previous node. Where there was a match but then the search changes so it no longer matched we still focus that last match, because we've scrolled it into view which may have moved the previous focus position out of view. In this scenario browsers seem to focus the element but not mark it as focus:visible but we don't have a similar distinction.
1 parent 3fda886 commit 2756a47

2 files changed

Lines changed: 79 additions & 1 deletion

File tree

plugins/workspace-search/src/workspace_search.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ export class WorkspaceSearch implements Blockly.IPositionable {
4242
*/
4343
protected blocks: Blockly.BlockSvg[] = [];
4444

45+
/**
46+
* The last highlighted block, which we focus on close.
47+
*
48+
* This block is focused on close even if the most recent search found nothing
49+
* because we've centered on it and it's helpful for focus to be in sync with
50+
* the scroll position.
51+
*/
52+
private lastHighlighted: Blockly.BlockSvg | null = null;
53+
4554
/**
4655
* Index of the currently "selected" block in the blocks array.
4756
*/
@@ -436,6 +445,7 @@ export class WorkspaceSearch implements Blockly.IPositionable {
436445

437446
this.highlightCurrentSelection(currentBlock);
438447
this.workspace.centerOnBlock(currentBlock.id, false);
448+
this.lastHighlighted = currentBlock;
439449
}
440450

441451
/**
@@ -455,8 +465,14 @@ export class WorkspaceSearch implements Blockly.IPositionable {
455465
*/
456466
close() {
457467
this.setVisible(false);
458-
this.workspace.markFocused();
468+
const focusManager = Blockly.FocusManager.getFocusManager();
469+
if (this.lastHighlighted && !this.lastHighlighted.isDisposed()) {
470+
focusManager.focusNode(this.lastHighlighted);
471+
} else {
472+
focusManager.focusTree(this.workspace);
473+
}
459474
this.clearBlocks();
475+
this.lastHighlighted = null;
460476
}
461477

462478
/**

plugins/workspace-search/test/workspace_search_test.mocha.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ suite('WorkspaceSearch', function () {
6969
);
7070
this.workspace = Blockly.inject('blocklyDiv');
7171
this.workspaceSearch = new WorkspaceSearch(this.workspace);
72+
// See https://github.com/google/blockly-samples/issues/2528 for context.
73+
global.SVGElement = window.SVGElement;
7274
});
7375

7476
teardown(function () {
@@ -436,4 +438,64 @@ suite('WorkspaceSearch', function () {
436438
assertNoExtraCurrentStyling(this.blocks, this.blocks[1]);
437439
});
438440
});
441+
442+
suite('focus', function () {
443+
suiteSetup(function () {
444+
Blockly.defineBlocksWithJsonArray([
445+
{
446+
type: 'alpha_block',
447+
message0: 'alpha',
448+
},
449+
{
450+
type: 'beta_block',
451+
message0: 'beta',
452+
},
453+
]);
454+
});
455+
456+
setup(function () {
457+
this.alphaBlock = this.workspace.newBlock('alpha_block');
458+
this.betaBlock = this.workspace.newBlock('beta_block');
459+
this.workspaceSearch.init();
460+
// Check starting position
461+
this.focusManager = Blockly.FocusManager.getFocusManager();
462+
this.focusManager.focusTree(this.workspace);
463+
const originalBlock = /** @type {Blockly.BlockSvg} */ (
464+
this.focusManager.getFocusedNode()
465+
);
466+
assert.equal('alpha_block', originalBlock.type);
467+
});
468+
469+
/**
470+
* @param {string} expected The expected block type.
471+
*/
472+
function assertFocusedNodeType(expected) {
473+
const block = /** @type {Blockly.BlockSvg} */ (
474+
Blockly.FocusManager.getFocusManager().getFocusedNode()
475+
);
476+
assert.equal(expected, block.type);
477+
}
478+
479+
test('close with match focuses found block', function () {
480+
this.workspaceSearch.searchAndHighlight('beta', false);
481+
this.workspaceSearch.close();
482+
483+
assertFocusedNodeType('beta_block');
484+
});
485+
486+
test('close with no match restores focus', function () {
487+
this.workspaceSearch.searchAndHighlight('nothingMatchesThis', false);
488+
this.workspaceSearch.close();
489+
490+
assertFocusedNodeType('alpha_block');
491+
});
492+
493+
test('close with match followed by non-match still focuses last found block', function () {
494+
this.workspaceSearch.searchAndHighlight('beta', false);
495+
this.workspaceSearch.searchAndHighlight('nothingMatchesThis', false);
496+
this.workspaceSearch.close();
497+
498+
assertFocusedNodeType('beta_block');
499+
});
500+
});
439501
});

0 commit comments

Comments
 (0)