feat: Remove most block tree support. (#9412)

Also, use regions for identifiying toolbox, workspace, and flyout.
This commit is contained in:
Ben Henning
2025-10-13 12:37:21 -07:00
committed by GitHub
parent 40aa0d3328
commit c8a7fc66c4
4 changed files with 23 additions and 96 deletions

View File

@@ -300,42 +300,18 @@ export class BlockSvg
private computeAriaRole() {
if (this.isSimpleReporter()) {
aria.setRole(this.pathObject.svgPath, aria.Role.BUTTON);
} else {
// This isn't read out by VoiceOver and it will read in the wrong place
// as a duplicate in ChromeVox due to the other changes in this branch.
// aria.setState(
// this.pathObject.svgPath,
// aria.State.ROLEDESCRIPTION,
// 'block',
// );
} else if (this.workspace.isFlyout) {
aria.setRole(this.pathObject.svgPath, aria.Role.TREEITEM);
}
}
collectSiblingBlocks(surroundParent: BlockSvg | null): BlockSvg[] {
// NOTE TO DEVELOPERS: it's very important that these are NOT sorted. The
// returned list needs to be relatively stable for consistent block indexes
// read out to users via screen readers.
if (surroundParent) {
// Start from the first sibling and iterate in navigation order.
const firstSibling: BlockSvg = surroundParent.getChildren(false)[0];
const siblings: BlockSvg[] = [firstSibling];
let nextSibling: BlockSvg | null = firstSibling;
while ((nextSibling = nextSibling?.getNextBlock())) {
siblings.push(nextSibling);
}
return siblings;
} else {
// For top-level blocks, simply return those from the workspace.
return this.workspace.getTopBlocks(false);
aria.setState(
this.pathObject.svgPath,
aria.State.ROLEDESCRIPTION,
'block',
);
aria.setRole(this.pathObject.svgPath, aria.Role.FIGURE);
}
}
computeLevelInWorkspace(): number {
const surroundParent = this.getSurroundParent();
return surroundParent ? surroundParent.computeLevelInWorkspace() + 1 : 0;
}
/**
* Create and initialize the SVG representation of the block.
* May be called more than once.

View File

@@ -154,9 +154,6 @@ export class Toolbox
this.setVisible(true);
this.flyout.init(workspace);
aria.setRole(this.HtmlDiv, aria.Role.TREE);
aria.setState(this.HtmlDiv, aria.State.LABEL, Msg['TOOLBOX_ARIA_LABEL']);
this.render(this.toolboxDef_);
const themeManager = workspace.getThemeManager();
themeManager.subscribe(
@@ -208,6 +205,12 @@ export class Toolbox
toolboxContainer.setAttribute('layout', this.isHorizontal() ? 'h' : 'v');
dom.addClass(toolboxContainer, 'blocklyToolbox');
toolboxContainer.setAttribute('dir', this.RTL ? 'RTL' : 'LTR');
aria.setRole(toolboxContainer, aria.Role.REGION);
aria.setState(
toolboxContainer,
aria.State.LABEL,
Msg['TOOLBOX_ARIA_LABEL'],
);
return toolboxContainer;
}
@@ -222,6 +225,7 @@ export class Toolbox
if (this.isHorizontal()) {
contentsContainer.style.flexDirection = 'row';
}
aria.setRole(contentsContainer, aria.Role.TREE);
return contentsContainer;
}

View File

@@ -53,6 +53,7 @@ export enum Role {
TEXTBOX = 'textbox',
COMBOBOX = 'combobox',
SPINBUTTON = 'spinbutton',
REGION = 'region',
}
/**

View File

@@ -31,7 +31,6 @@ import {WorkspaceComment} from './comments/workspace_comment.js';
import * as common from './common.js';
import {ComponentManager} from './component_manager.js';
import {ConnectionDB} from './connection_db.js';
import {ConnectionType} from './connection_type.js';
import * as ContextMenu from './contextmenu.js';
import {
ContextMenuOption,
@@ -763,19 +762,15 @@ export class WorkspaceSvg
});
let ariaLabel = null;
if (injectionDiv) {
ariaLabel = Msg['WORKSPACE_ARIA_LABEL'];
} else if (this.isFlyout) {
if (this.isFlyout) {
ariaLabel = 'Flyout';
} else if (this.isMutator) {
ariaLabel = 'Mutator';
ariaLabel = 'Mutator Workspace';
} else {
// This case can happen in some test scenarios.
// TODO: Figure out when this can happen in non-test scenarios (if ever).
ariaLabel = 'Workspace';
ariaLabel = Msg['WORKSPACE_ARIA_LABEL'];
}
aria.setRole(this.svgGroup_, aria.Role.REGION);
aria.setState(this.svgGroup_, aria.State.LABEL, ariaLabel);
aria.setRole(this.svgGroup_, aria.Role.TREE);
// Note that a <g> alone does not receive mouse events--it must have a
// valid target inside it. If no background class is specified, as in the
@@ -803,7 +798,10 @@ export class WorkspaceSvg
this.svgBlockCanvas_ = this.layerManager.getBlockLayer();
this.svgBubbleCanvas_ = this.layerManager.getBubbleLayer();
if (!this.isFlyout) {
if (this.isFlyout) {
// Use the block canvas as the primary tree parent for flyout blocks.
aria.setRole(this.svgBlockCanvas_, aria.Role.TREE);
} else {
browserEvents.conditionalBind(
this.svgGroup_,
'pointerdown',
@@ -2959,61 +2957,9 @@ export class WorkspaceSvg
aria.setState(treeItemElem, aria.State.POSINSET, index + 1);
aria.setState(treeItemElem, aria.State.SETSIZE, focusableItems.length);
aria.setState(treeItemElem, aria.State.LEVEL, 1); // They are always top-level.
if (item instanceof BlockSvg) {
item
.getChildren(false)
.forEach((child) =>
this.recomputeAriaTreeItemDetailsRecursively(child),
);
}
});
} else {
// TODO: Do this efficiently (probably incrementally).
this.getTopBlocks(false).forEach((block) =>
this.recomputeAriaTreeItemDetailsRecursively(block),
);
}
}
private recomputeAriaTreeItemDetailsRecursively(block: BlockSvg) {
const elem = block.getFocusableElement();
const connection = block.currentConnectionCandidate;
let childPosition: number;
let parentsChildCount: number;
let hierarchyDepth: number;
if (connection) {
// If the block is being inserted into a new location, the position is hypothetical.
// TODO: Figure out how to deal with output connections.
let surroundParent: BlockSvg | null;
let siblingBlocks: BlockSvg[];
if (connection.type === ConnectionType.INPUT_VALUE) {
surroundParent = connection.sourceBlock_;
siblingBlocks = block.collectSiblingBlocks(surroundParent);
// The block is being added as a child since it's input.
// TODO: Figure out how to compute the correct position.
childPosition = 0;
} else {
surroundParent = connection.sourceBlock_.getSurroundParent();
siblingBlocks = block.collectSiblingBlocks(surroundParent);
// The block is being added after the connected block.
childPosition = siblingBlocks.indexOf(connection.sourceBlock_) + 1;
}
parentsChildCount = siblingBlocks.length + 1;
hierarchyDepth = surroundParent?.computeLevelInWorkspace() ?? 0;
} else {
const surroundParent = block.getSurroundParent();
const siblingBlocks = block.collectSiblingBlocks(surroundParent);
childPosition = siblingBlocks.indexOf(block);
parentsChildCount = siblingBlocks.length;
hierarchyDepth = block.computeLevelInWorkspace();
}
aria.setState(elem, aria.State.POSINSET, childPosition + 1);
aria.setState(elem, aria.State.SETSIZE, parentsChildCount);
aria.setState(elem, aria.State.LEVEL, hierarchyDepth + 1);
block
.getChildren(false)
.forEach((child) => this.recomputeAriaTreeItemDetailsRecursively(child));
}
}
/**