Skip to content

Commit 993fa90

Browse files
fix: Treat the flyout as a listbox with options (#10059)
1 parent a233ac1 commit 993fa90

5 files changed

Lines changed: 30 additions & 6 deletions

File tree

packages/blockly/core/block_flyout_inflater.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,21 @@ export class BlockFlyoutInflater implements IFlyoutInflater {
8080
// to correct the role and hidden state for it.
8181
const focusableElement = block.getFocusableElement();
8282
aria.clearState(focusableElement, aria.State.HIDDEN);
83-
aria.setRole(focusableElement, aria.Role.LISTITEM);
83+
aria.setRole(focusableElement, aria.Role.OPTION);
84+
85+
// Clickable icons in the flyout are owned by their parent block.
86+
// This ensures that clickable icons are not included in the option
87+
// count when screen readers assess the number of options in the
88+
// flyout listbox.
89+
const ownedIconIds = block
90+
.getIcons()
91+
.filter((icon) => icon.isClickableInFlyout?.(flyout.autoClose))
92+
.map((icon) => icon.getFocusableElement().id)
93+
.filter((id) => !!id);
94+
if (ownedIconIds.length) {
95+
aria.setState(focusableElement, aria.State.OWNS, ownedIconIds);
96+
}
97+
8498
this.addBlockListeners(block);
8599

86100
return new FlyoutItem(block, BLOCK_TYPE);

packages/blockly/core/flyout_base.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -697,9 +697,9 @@ export abstract class Flyout
697697
.trim();
698698
aria.setState(this.getWorkspace().getCanvas(), aria.State.LABEL, ariaLabel);
699699

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

705705
/**

packages/blockly/core/flyout_button.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,10 +177,10 @@ export class FlyoutButton
177177
aria.setRole(svgText, aria.Role.PRESENTATION);
178178

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

185185
const fontSize = style.getComputedStyle(svgText, 'fontSize');
186186
const fontWeight = style.getComputedStyle(svgText, 'fontWeight');

packages/blockly/core/icons/icon.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,16 @@ export abstract class Icon implements IIcon, IContextMenu {
231231
protected recomputeAriaContext(): void {
232232
const element = this.getFocusableElement();
233233
if (!element) return;
234+
const flyout = (
235+
this.sourceBlock.workspace as WorkspaceSvg
236+
).targetWorkspace?.getFlyout();
237+
if (flyout && !this.isClickableInFlyout(flyout.autoClose)) {
238+
// Icons that can't be used in the flyout are removed from the
239+
// accessibility tree, like non-interactive fields.
240+
aria.setState(element, aria.State.HIDDEN, true);
241+
return;
242+
}
243+
aria.clearState(element, aria.State.HIDDEN);
234244
aria.setRole(element, aria.Role.BUTTON);
235245
const label = this.getAriaLabel() ?? Msg['ICON_LABEL_DEFAULT'];
236246
aria.setState(element, aria.State.LABEL, label);

packages/blockly/tests/mocha/aria_test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ suite('ARIA', function () {
348348
);
349349
const block = this.workspace.getFlyout().getWorkspace().getTopBlocks()[0];
350350
const role = Blockly.utils.aria.getRole(block.getFocusableElement());
351-
assert.equal(role, Blockly.utils.aria.Role.LISTITEM);
351+
assert.equal(role, Blockly.utils.aria.Role.OPTION);
352352
});
353353

354354
test('Root workspace blocks indicate that in their labels', function () {

0 commit comments

Comments
 (0)