Skip to content
Open
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
9 changes: 1 addition & 8 deletions packages/blockly/core/block_aria_composer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,6 @@ export function computeAriaLabel(
block: BlockSvg,
verbosity = Verbosity.STANDARD,
) {
if (block.isSimpleReporter()) {
// special case for full-block field blocks.
const field = block.getFullBlockField();
if (field) {
return field.computeAriaLabel(verbosity >= Verbosity.STANDARD);
}
}
return [
verbosity >= Verbosity.STANDARD && getBeginStackLabel(block),
getParentInputLabel(block),
Expand Down Expand Up @@ -271,7 +264,7 @@ function getParentInputLabel(block: BlockSvg) {
* @returns Text indicating that the block begins a stack, or undefined if it
* does not.
*/
function getBeginStackLabel(block: BlockSvg) {
export function getBeginStackLabel(block: BlockSvg) {
// Don't include the "begin stack" label for blocks that are moving
// or blocks in the flyout
if (block.isInFlyout || block.isDragging()) return undefined;
Expand Down
10 changes: 9 additions & 1 deletion packages/blockly/core/field_dropdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
*/
// Former goog.module ID: Blockly.FieldDropdown

import {computeAriaLabel} from './block_aria_composer.js';
import type {BlockSvg} from './block_svg.js';
import * as dropDownDiv from './dropdowndiv.js';
import {
Expand Down Expand Up @@ -940,7 +941,14 @@ export class FieldDropdown extends Field<string> {
if (!shouldCustomize) return false;

const focusableElement = this.getFocusableElement();
const label = this.computeAriaLabel(true);
let label = this.computeAriaLabel(true);
if (this.isFullBlockField()) {
// Full block fields get a more detailed label that includes the block's label
label = computeAriaLabel(this.getSourceBlock() as BlockSvg).replace(
this.computeAriaLabel(false),
label,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This admittedly feels a bit janky, but it's needed because getInputLabels will use includeTypeInfo = false for standard verbosity.

);
}

aria.setState(focusableElement, aria.State.LABEL, label);
aria.setState(focusableElement, aria.State.HASPOPUP, 'listbox');
Expand Down
32 changes: 30 additions & 2 deletions packages/blockly/core/field_input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// Unused import preserved for side-effects. Remove if unneeded.
import './events/events_block_change.js';

import {computeAriaLabel, getBeginStackLabel} from './block_aria_composer.js';
import {BlockSvg} from './block_svg.js';
import * as browserEvents from './browser_events.js';
import * as bumpObjects from './bump_objects.js';
Expand Down Expand Up @@ -855,8 +856,35 @@ export abstract class FieldInput<T extends InputTypes> extends Field<
const focusableElement = this.getFocusableElement();

let label = this.computeAriaLabel(true);
if (this.isCurrentlyEditable() && !this.getSourceBlock()?.isInFlyout) {
label = Msg['FIELD_LABEL_EDIT_PREFIX'].replace('%1', label);
const requiresEditableLabel =
this.isCurrentlyEditable() && !this.getSourceBlock()?.isInFlyout;

if (!this.isFullBlockField()) {
if (requiresEditableLabel) {
label = Msg['FIELD_LABEL_EDIT_PREFIX'].replace('%1', label);
}
} else {
// Full block fields get a more detailed label that includes the block's label
const fullBlockLabel = computeAriaLabel(
this.getSourceBlock() as BlockSvg,
).replace(this.computeAriaLabel(false), label);
if (requiresEditableLabel) {
const labels = fullBlockLabel.split(', ');
const beginStackLabel = getBeginStackLabel(
this.getSourceBlock() as BlockSvg,
);

// Insert "Edit" after "Begin stack" if found, otherwise at start.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is vibes based, but it felt odd to read/hear "Edit Begin stack, number:10..."

const beginStackLabelIndex =
beginStackLabel === undefined ? -1 : labels.indexOf(beginStackLabel);
const insertIndex =
beginStackLabelIndex === -1 ? 0 : beginStackLabelIndex + 1;
labels[insertIndex] = Msg['FIELD_LABEL_EDIT_PREFIX'].replace(
'%1',
labels[insertIndex] ?? '',
);
label = labels.join(', ');
}
}
aria.setState(focusableElement, aria.State.LABEL, label);
return true;
Expand Down
53 changes: 53 additions & 0 deletions packages/blockly/tests/mocha/field_dropdown_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -580,5 +580,58 @@ suite('Dropdown Fields', function () {
assert.include(label, 'Option 5');
});
});
suite('Full block fields', function () {
setup(function () {
this.workspace = Blockly.inject('blocklyDiv', {
renderer: 'zelos',
});
this.block = this.workspace.newBlock('variables_get');
this.block.initSvg();
this.block.render();
this.field = this.block.getField('VAR');
});

test('Top block ARIA label includes "Begin stack" label before dropdown field label', function () {
const labels = this.block
.getFocusableElement()
.getAttribute('aria-label')
.split(', ');

const expectedBeginStackLabel = 'Begin stack';
const expectedFieldLabel = "dropdown: Variable 'item'";
assert.include(labels, expectedBeginStackLabel);
assert.include(labels, expectedFieldLabel);
assert.isTrue(
labels.indexOf(expectedBeginStackLabel) <
labels.indexOf(expectedFieldLabel),
);
});

test('Child block ARIA label includes parent input custom label before dropdown field label', function () {
const parentBlock = this.workspace.newBlock('controls_repeat_ext');
parentBlock.initSvg();
parentBlock.render();

this.block.outputConnection.connect(
parentBlock.getInput('TIMES').connection,
);

this.block.getField('VAR').recomputeAriaContext();

const labels = this.block
.getFocusableElement()
.getAttribute('aria-label')
.split(', ');

const expectedInputLabel = 'number of times to repeat';
const expectedFieldLabel = "dropdown: Variable 'item'";
assert.include(labels, expectedInputLabel);
assert.include(labels, expectedFieldLabel);
assert.isTrue(
labels.indexOf(expectedInputLabel) <
labels.indexOf(expectedFieldLabel),
);
});
});
});
});
48 changes: 48 additions & 0 deletions packages/blockly/tests/mocha/field_number_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -551,5 +551,53 @@ suite('Number Fields', function () {
const updatedLabel = this.focusableElement.getAttribute('aria-label');
assert.isTrue(updatedLabel.includes('1'));
});
suite('Full block fields', function () {
setup(function () {
this.workspace = Blockly.inject('blocklyDiv', {
renderer: 'zelos',
});
this.block = this.workspace.newBlock('math_number');
this.field = this.block.getField('NUM');
this.block.initSvg();
this.block.render();
});
test('Top block ARIA label includes "Begin stack" label before expected field label', function () {
const labels = this.block
.getFocusableElement()
.getAttribute('aria-label')
.split(', ');

const expectedBeginStackLabel = 'Begin stack';
const expectedFieldLabel = 'Edit number: 0';
assert.include(labels, expectedBeginStackLabel);
assert.include(labels, expectedFieldLabel);
assert.isTrue(
labels.indexOf(expectedBeginStackLabel) <
labels.indexOf(expectedFieldLabel),
);
});
test('Child block ARIA label includes parent input custom label after "Edit" label and before field label', function () {
const parentBlock = this.workspace.newBlock('controls_repeat_ext');
parentBlock.initSvg();
parentBlock.render();
this.block.outputConnection.connect(
parentBlock.getInput('TIMES').connection,
);
this.block.getField('NUM').recomputeAriaContext();
const labels = this.block
.getFocusableElement()
.getAttribute('aria-label')
.split(', ');

const expectedInputLabel = 'Edit number of times to repeat';
const expectedFieldLabel = 'number: 0';
assert.include(labels, expectedInputLabel);
assert.include(labels, expectedFieldLabel);
assert.isTrue(
labels.indexOf(expectedInputLabel) <
labels.indexOf(expectedFieldLabel),
);
});
});
});
});
Loading