Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion packages/blockly/core/block_flyout_inflater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,21 @@ export class BlockFlyoutInflater implements IFlyoutInflater {
// to correct the role and hidden state for it.
const focusableElement = block.getFocusableElement();
aria.clearState(focusableElement, aria.State.HIDDEN);
aria.setRole(focusableElement, aria.Role.LISTITEM);
aria.setRole(focusableElement, aria.Role.OPTION);

// Clickable icons in the flyout are owned by their parent block.
// This ensures that clickable icons are not included in the option
// count when screen readers assess the number of options in the
// flyout listbox.
const ownedIconIds = block
.getIcons()
.filter((icon) => icon.isClickableInFlyout?.(flyout.autoClose))
.map((icon) => icon.getFocusableElement().id)
.filter((id) => !!id);
if (ownedIconIds.length) {
aria.setState(focusableElement, aria.State.OWNS, ownedIconIds);
}

this.addBlockListeners(block);

return new FlyoutItem(block, BLOCK_TYPE);
Expand Down
4 changes: 2 additions & 2 deletions packages/blockly/core/flyout_base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -697,9 +697,9 @@ export abstract class Flyout
.trim();
aria.setState(this.getWorkspace().getCanvas(), aria.State.LABEL, ariaLabel);

// The block canvas is a list. The list items must be direct descendants of the list,
// The block canvas is a listbox. The options must be direct descendants of the listbox,
// and the flyout may or may not be a region, so we set the role on the block canvas rather than the svgGroup_.
aria.setRole(this.getWorkspace().getCanvas(), aria.Role.LIST);
aria.setRole(this.getWorkspace().getCanvas(), aria.Role.LISTBOX);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions packages/blockly/core/flyout_button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,10 @@ export class FlyoutButton
aria.setRole(svgText, aria.Role.PRESENTATION);

// We add the word "heading" or "button" to the label so that they give appropriate hints
// we can't use the corresponding roles because that overwrites the context of it being a list item.
// we can't use the corresponding roles because that overwrites the context of it being an option.
const ariaLabel = `${text}, ${this.isFlyoutLabel ? Msg['ARIA_LABEL_HEADING'] : Msg['ARIA_LABEL_BUTTON']}`;
aria.setState(this.getFocusableElement(), aria.State.LABEL, ariaLabel);
aria.setRole(this.getFocusableElement(), aria.Role.LISTITEM);
aria.setRole(this.getFocusableElement(), aria.Role.OPTION);

const fontSize = style.getComputedStyle(svgText, 'fontSize');
const fontWeight = style.getComputedStyle(svgText, 'fontWeight');
Expand Down
10 changes: 10 additions & 0 deletions packages/blockly/core/icons/icon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,16 @@ export abstract class Icon implements IIcon, IContextMenu {
protected recomputeAriaContext(): void {
const element = this.getFocusableElement();
if (!element) return;
const flyout = (
this.sourceBlock.workspace as WorkspaceSvg
).targetWorkspace?.getFlyout();
if (flyout && !this.isClickableInFlyout(flyout.autoClose)) {
// Icons that can't be used in the flyout are removed from the
// accessibility tree, like non-interactive fields.
aria.setState(element, aria.State.HIDDEN, true);
return;
}
aria.clearState(element, aria.State.HIDDEN);
aria.setRole(element, aria.Role.BUTTON);
const label = this.getAriaLabel() ?? Msg['ICON_LABEL_DEFAULT'];
aria.setState(element, aria.State.LABEL, label);
Expand Down
2 changes: 1 addition & 1 deletion packages/blockly/tests/mocha/aria_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ suite('ARIA', function () {
);
const block = this.workspace.getFlyout().getWorkspace().getTopBlocks()[0];
const role = Blockly.utils.aria.getRole(block.getFocusableElement());
assert.equal(role, Blockly.utils.aria.Role.LISTITEM);
assert.equal(role, Blockly.utils.aria.Role.OPTION);
});

test('Root workspace blocks indicate that in their labels', function () {
Expand Down
Loading