Skip to content

Commit 4157270

Browse files
committed
Revert "Fix: Revert focus prs (RaspberryPiFoundation#8933)"
This reverts commit c644fe3.
1 parent e14c457 commit 4157270

File tree

13 files changed

+296
-15
lines changed

13 files changed

+296
-15
lines changed

core/block_svg.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ import {IContextMenu} from './interfaces/i_contextmenu.js';
4444
import type {ICopyable} from './interfaces/i_copyable.js';
4545
import {IDeletable} from './interfaces/i_deletable.js';
4646
import type {IDragStrategy, IDraggable} from './interfaces/i_draggable.js';
47+
import type {IFocusableNode} from './interfaces/i_focusable_node.js';
48+
import type {IFocusableTree} from './interfaces/i_focusable_tree.js';
4749
import {IIcon} from './interfaces/i_icon.js';
4850
import * as internalConstants from './internal_constants.js';
4951
import {MarkerManager} from './marker_manager.js';
@@ -76,7 +78,8 @@ export class BlockSvg
7678
IContextMenu,
7779
ICopyable<BlockCopyData>,
7880
IDraggable,
79-
IDeletable
81+
IDeletable,
82+
IFocusableNode
8083
{
8184
/**
8285
* Constant for identifying rows that are to be rendered inline.
@@ -210,6 +213,7 @@ export class BlockSvg
210213

211214
// Expose this block's ID on its top-level SVG group.
212215
this.svgGroup.setAttribute('data-id', this.id);
216+
svgPath.id = this.id;
213217

214218
this.doInit_();
215219
}
@@ -1827,4 +1831,26 @@ export class BlockSvg
18271831
);
18281832
}
18291833
}
1834+
1835+
/** See IFocusableNode.getFocusableElement. */
1836+
getFocusableElement(): HTMLElement | SVGElement {
1837+
return this.pathObject.svgPath;
1838+
}
1839+
1840+
/** See IFocusableNode.getFocusableTree. */
1841+
getFocusableTree(): IFocusableTree {
1842+
return this.workspace;
1843+
}
1844+
1845+
/** See IFocusableNode.onNodeFocus. */
1846+
onNodeFocus(): void {
1847+
common.setSelected(this);
1848+
}
1849+
1850+
/** See IFocusableNode.onNodeBlur. */
1851+
onNodeBlur(): void {
1852+
if (common.getSelected() === this) {
1853+
common.setSelected(null);
1854+
}
1855+
}
18301856
}

core/flyout_base.ts

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@ import * as eventUtils from './events/utils.js';
2121
import {FlyoutItem} from './flyout_item.js';
2222
import {FlyoutMetricsManager} from './flyout_metrics_manager.js';
2323
import {FlyoutSeparator, SeparatorAxis} from './flyout_separator.js';
24+
import {getFocusManager} from './focus_manager.js';
2425
import {IAutoHideable} from './interfaces/i_autohideable.js';
2526
import type {IFlyout} from './interfaces/i_flyout.js';
2627
import type {IFlyoutInflater} from './interfaces/i_flyout_inflater.js';
28+
import {IFocusableNode} from './interfaces/i_focusable_node.js';
29+
import {IFocusableTree} from './interfaces/i_focusable_tree.js';
2730
import type {Options} from './options.js';
2831
import * as registry from './registry.js';
2932
import * as renderManagement from './render_management.js';
@@ -43,7 +46,7 @@ import {WorkspaceSvg} from './workspace_svg.js';
4346
*/
4447
export abstract class Flyout
4548
extends DeleteArea
46-
implements IAutoHideable, IFlyout
49+
implements IAutoHideable, IFlyout, IFocusableNode
4750
{
4851
/**
4952
* Position the flyout.
@@ -303,6 +306,7 @@ export abstract class Flyout
303306
// hide/show code will set up proper visibility and size later.
304307
this.svgGroup_ = dom.createSvgElement(tagName, {
305308
'class': 'blocklyFlyout',
309+
'tabindex': '0',
306310
});
307311
this.svgGroup_.style.display = 'none';
308312
this.svgBackground_ = dom.createSvgElement(
@@ -317,6 +321,9 @@ export abstract class Flyout
317321
this.workspace_
318322
.getThemeManager()
319323
.subscribe(this.svgBackground_, 'flyoutOpacity', 'fill-opacity');
324+
325+
getFocusManager().registerTree(this);
326+
320327
return this.svgGroup_;
321328
}
322329

@@ -398,6 +405,7 @@ export abstract class Flyout
398405
if (this.svgGroup_) {
399406
dom.removeNode(this.svgGroup_);
400407
}
408+
getFocusManager().unregisterTree(this);
401409
}
402410

403411
/**
@@ -961,4 +969,63 @@ export abstract class Flyout
961969

962970
return null;
963971
}
972+
973+
/** See IFocusableNode.getFocusableElement. */
974+
getFocusableElement(): HTMLElement | SVGElement {
975+
if (!this.svgGroup_) throw new Error('Flyout DOM is not yet created.');
976+
return this.svgGroup_;
977+
}
978+
979+
/** See IFocusableNode.getFocusableTree. */
980+
getFocusableTree(): IFocusableTree {
981+
return this;
982+
}
983+
984+
/** See IFocusableNode.onNodeFocus. */
985+
onNodeFocus(): void {}
986+
987+
/** See IFocusableNode.onNodeBlur. */
988+
onNodeBlur(): void {}
989+
990+
/** See IFocusableTree.getRootFocusableNode. */
991+
getRootFocusableNode(): IFocusableNode {
992+
return this;
993+
}
994+
995+
/** See IFocusableTree.getRestoredFocusableNode. */
996+
getRestoredFocusableNode(
997+
_previousNode: IFocusableNode | null,
998+
): IFocusableNode | null {
999+
return null;
1000+
}
1001+
1002+
/** See IFocusableTree.getNestedTrees. */
1003+
getNestedTrees(): Array<IFocusableTree> {
1004+
return [this.workspace_];
1005+
}
1006+
1007+
/** See IFocusableTree.lookUpFocusableNode. */
1008+
lookUpFocusableNode(_id: string): IFocusableNode | null {
1009+
// No focusable node needs to be returned since the flyout's subtree is a
1010+
// workspace that will manage its own focusable state.
1011+
return null;
1012+
}
1013+
1014+
/** See IFocusableTree.onTreeFocus. */
1015+
onTreeFocus(
1016+
_node: IFocusableNode,
1017+
_previousTree: IFocusableTree | null,
1018+
): void {}
1019+
1020+
/** See IFocusableTree.onTreeBlur. */
1021+
onTreeBlur(nextTree: IFocusableTree | null): void {
1022+
const toolbox = this.targetWorkspace.getToolbox();
1023+
// If focus is moving to either the toolbox or the flyout's workspace, do
1024+
// not close the flyout. For anything else, do close it since the flyout is
1025+
// no longer focused.
1026+
if (toolbox && nextTree === toolbox) return;
1027+
if (nextTree == this.workspace_) return;
1028+
if (toolbox) toolbox.clearSelection();
1029+
this.autoHide(false);
1030+
}
9641031
}

core/inject.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,11 @@ import * as common from './common.js';
1313
import * as Css from './css.js';
1414
import * as dropDownDiv from './dropdowndiv.js';
1515
import {Grid} from './grid.js';
16-
import {Msg} from './msg.js';
1716
import {Options} from './options.js';
1817
import {ScrollbarPair} from './scrollbar_pair.js';
1918
import {ShortcutRegistry} from './shortcut_registry.js';
2019
import * as Tooltip from './tooltip.js';
2120
import * as Touch from './touch.js';
22-
import * as aria from './utils/aria.js';
2321
import * as dom from './utils/dom.js';
2422
import {Svg} from './utils/svg.js';
2523
import * as WidgetDiv from './widgetdiv.js';
@@ -56,8 +54,6 @@ export function inject(
5654
if (opt_options?.rtl) {
5755
dom.addClass(subContainer, 'blocklyRTL');
5856
}
59-
subContainer.tabIndex = 0;
60-
aria.setState(subContainer, aria.State.LABEL, Msg['WORKSPACE_ARIA_LABEL']);
6157

6258
containerElement!.appendChild(subContainer);
6359
const svg = createDom(subContainer, options);
@@ -126,7 +122,6 @@ function createDom(container: HTMLElement, options: Options): SVGElement {
126122
'xmlns:xlink': dom.XLINK_NS,
127123
'version': '1.1',
128124
'class': 'blocklySvg',
129-
'tabindex': '0',
130125
},
131126
container,
132127
);

core/interfaces/i_flyout.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ import type {Coordinate} from '../utils/coordinate.js';
1212
import type {Svg} from '../utils/svg.js';
1313
import type {FlyoutDefinition} from '../utils/toolbox.js';
1414
import type {WorkspaceSvg} from '../workspace_svg.js';
15+
import {IFocusableTree} from './i_focusable_tree.js';
1516
import type {IRegistrable} from './i_registrable.js';
1617

1718
/**
1819
* Interface for a flyout.
1920
*/
20-
export interface IFlyout extends IRegistrable {
21+
export interface IFlyout extends IRegistrable, IFocusableTree {
2122
/** Whether the flyout is laid out horizontally or not. */
2223
horizontalLayout: boolean;
2324

core/interfaces/i_toolbox.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@
99
import type {ToolboxInfo} from '../utils/toolbox.js';
1010
import type {WorkspaceSvg} from '../workspace_svg.js';
1111
import type {IFlyout} from './i_flyout.js';
12+
import type {IFocusableTree} from './i_focusable_tree.js';
1213
import type {IRegistrable} from './i_registrable.js';
1314
import type {IToolboxItem} from './i_toolbox_item.js';
1415

1516
/**
1617
* Interface for a toolbox.
1718
*/
18-
export interface IToolbox extends IRegistrable {
19+
export interface IToolbox extends IRegistrable, IFocusableTree {
1920
/** Initializes the toolbox. */
2021
init(): void;
2122

core/interfaces/i_toolbox_item.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66

77
// Former goog.module ID: Blockly.IToolboxItem
88

9+
import type {IFocusableNode} from './i_focusable_node.js';
10+
911
/**
1012
* Interface for an item in the toolbox.
1113
*/
12-
export interface IToolboxItem {
14+
export interface IToolboxItem extends IFocusableNode {
1315
/**
1416
* Initializes the toolbox item.
1517
* This includes creating the DOM and updating the state of any items based

core/renderers/common/path_object.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export class PathObject implements IPathObject {
6262
/** The primary path of the block. */
6363
this.svgPath = dom.createSvgElement(
6464
Svg.PATH,
65-
{'class': 'blocklyPath'},
65+
{'class': 'blocklyPath', 'tabindex': '-1'},
6666
this.svgRoot,
6767
);
6868

core/toolbox/category.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,8 @@ export class ToolboxCategory
225225
*/
226226
protected createContainer_(): HTMLDivElement {
227227
const container = document.createElement('div');
228+
container.tabIndex = -1;
229+
container.id = this.getId();
228230
const className = this.cssConfig_['container'];
229231
if (className) {
230232
dom.addClass(container, className);

core/toolbox/separator.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ export class ToolboxSeparator extends ToolboxItem {
5454
*/
5555
protected createDom_(): HTMLDivElement {
5656
const container = document.createElement('div');
57+
container.tabIndex = -1;
58+
container.id = this.getId();
5759
const className = this.cssConfig_['container'];
5860
if (className) {
5961
dom.addClass(container, className);

core/toolbox/toolbox.ts

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,14 @@ import {DeleteArea} from '../delete_area.js';
2222
import '../events/events_toolbox_item_select.js';
2323
import {EventType} from '../events/type.js';
2424
import * as eventUtils from '../events/utils.js';
25+
import {getFocusManager} from '../focus_manager.js';
2526
import type {IAutoHideable} from '../interfaces/i_autohideable.js';
2627
import type {ICollapsibleToolboxItem} from '../interfaces/i_collapsible_toolbox_item.js';
2728
import {isDeletable} from '../interfaces/i_deletable.js';
2829
import type {IDraggable} from '../interfaces/i_draggable.js';
2930
import type {IFlyout} from '../interfaces/i_flyout.js';
31+
import type {IFocusableNode} from '../interfaces/i_focusable_node.js';
32+
import type {IFocusableTree} from '../interfaces/i_focusable_tree.js';
3033
import type {IKeyboardAccessible} from '../interfaces/i_keyboard_accessible.js';
3134
import type {ISelectableToolboxItem} from '../interfaces/i_selectable_toolbox_item.js';
3235
import {isSelectableToolboxItem} from '../interfaces/i_selectable_toolbox_item.js';
@@ -51,7 +54,12 @@ import {CollapsibleToolboxCategory} from './collapsible_category.js';
5154
*/
5255
export class Toolbox
5356
extends DeleteArea
54-
implements IAutoHideable, IKeyboardAccessible, IStyleable, IToolbox
57+
implements
58+
IAutoHideable,
59+
IKeyboardAccessible,
60+
IStyleable,
61+
IToolbox,
62+
IFocusableNode
5563
{
5664
/**
5765
* The unique ID for this component that is used to register with the
@@ -163,6 +171,7 @@ export class Toolbox
163171
ComponentManager.Capability.DRAG_TARGET,
164172
],
165173
});
174+
getFocusManager().registerTree(this);
166175
}
167176

168177
/**
@@ -177,7 +186,6 @@ export class Toolbox
177186
const container = this.createContainer_();
178187

179188
this.contentsDiv_ = this.createContentsContainer_();
180-
this.contentsDiv_.tabIndex = 0;
181189
aria.setRole(this.contentsDiv_, aria.Role.TREE);
182190
container.appendChild(this.contentsDiv_);
183191

@@ -194,6 +202,7 @@ export class Toolbox
194202
*/
195203
protected createContainer_(): HTMLDivElement {
196204
const toolboxContainer = document.createElement('div');
205+
toolboxContainer.tabIndex = 0;
197206
toolboxContainer.setAttribute('layout', this.isHorizontal() ? 'h' : 'v');
198207
dom.addClass(toolboxContainer, 'blocklyToolbox');
199208
toolboxContainer.setAttribute('dir', this.RTL ? 'RTL' : 'LTR');
@@ -1077,7 +1086,71 @@ export class Toolbox
10771086
this.workspace_.getThemeManager().unsubscribe(this.HtmlDiv);
10781087
dom.removeNode(this.HtmlDiv);
10791088
}
1089+
1090+
getFocusManager().unregisterTree(this);
1091+
}
1092+
1093+
/** See IFocusableNode.getFocusableElement. */
1094+
getFocusableElement(): HTMLElement | SVGElement {
1095+
if (!this.HtmlDiv) throw Error('Toolbox DOM has not yet been created.');
1096+
return this.HtmlDiv;
1097+
}
1098+
1099+
/** See IFocusableNode.getFocusableTree. */
1100+
getFocusableTree(): IFocusableTree {
1101+
return this;
1102+
}
1103+
1104+
/** See IFocusableNode.onNodeFocus. */
1105+
onNodeFocus(): void {}
1106+
1107+
/** See IFocusableNode.onNodeBlur. */
1108+
onNodeBlur(): void {}
1109+
1110+
/** See IFocusableTree.getRootFocusableNode. */
1111+
getRootFocusableNode(): IFocusableNode {
1112+
return this;
1113+
}
1114+
1115+
/** See IFocusableTree.getRestoredFocusableNode. */
1116+
getRestoredFocusableNode(
1117+
previousNode: IFocusableNode | null,
1118+
): IFocusableNode | null {
1119+
// Always try to select the first selectable toolbox item rather than the
1120+
// root of the toolbox.
1121+
if (!previousNode || previousNode === this) {
1122+
return this.getToolboxItems().find((item) => item.isSelectable()) ?? null;
1123+
}
1124+
return null;
10801125
}
1126+
1127+
/** See IFocusableTree.getNestedTrees. */
1128+
getNestedTrees(): Array<IFocusableTree> {
1129+
return [];
1130+
}
1131+
1132+
/** See IFocusableTree.lookUpFocusableNode. */
1133+
lookUpFocusableNode(id: string): IFocusableNode | null {
1134+
return this.getToolboxItemById(id) as IFocusableNode;
1135+
}
1136+
1137+
/** See IFocusableTree.onTreeFocus. */
1138+
onTreeFocus(
1139+
node: IFocusableNode,
1140+
_previousTree: IFocusableTree | null,
1141+
): void {
1142+
if (node !== this) {
1143+
// Only select the item if it isn't already selected so as to not toggle.
1144+
if (this.getSelectedItem() !== node) {
1145+
this.setSelectedItem(node as IToolboxItem);
1146+
}
1147+
} else {
1148+
this.clearSelection();
1149+
}
1150+
}
1151+
1152+
/** See IFocusableTree.onTreeBlur. */
1153+
onTreeBlur(_nextTree: IFocusableTree | null): void {}
10811154
}
10821155

10831156
/** CSS for Toolbox. See css.js for use. */

0 commit comments

Comments
 (0)