mirror of
https://github.com/google/blockly.git
synced 2026-05-22 20:10:11 +02:00
fix: Block-level ARIA labels no longer include clickable image descri… (#9844)
* fix: Block-level ARIA labels no longer include clickable image descriptions * fixed merge conflicts, added/updated tests
This commit is contained in:
@@ -366,9 +366,11 @@ export abstract class Field<T = any>
|
||||
* checkboxes represent their checked/non-checked status (i.e. value) through
|
||||
* a separate ARIA property.
|
||||
*
|
||||
* It's not expected that this method, under normal operations, returns an empty
|
||||
* string. If the field's value is empty then it will return a localized
|
||||
* placeholder indicating that its value is empty.
|
||||
* If the field's value is empty then it will return a localized placeholder
|
||||
* indicating that its value is empty. If this method returns an empty string,
|
||||
* the output will be ignored when composing the block-level ARIA label. Make
|
||||
* sure you want your label hidden from screenreaders before returning an
|
||||
* empty string.
|
||||
*
|
||||
* @param includeTypeInfo Whether to include the field's type information in
|
||||
* the returned label, if available.
|
||||
|
||||
@@ -315,6 +315,36 @@ export class FieldImage extends Field<string> {
|
||||
return this.altText || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a descriptive ARIA label to represent this field with configurable
|
||||
* verbosity.
|
||||
*
|
||||
* A 'verbose' label includes type information, if available, whereas a
|
||||
* non-verbose label only contains the field's value.
|
||||
*
|
||||
* Note that this will always return the latest representation of the field's
|
||||
* label which may differ from any previously set ARIA label for the field
|
||||
* itself. Implementations are largely responsible for ensuring that the
|
||||
* field's ARIA label is set correctly at relevant moments in the field's
|
||||
* lifecycle (such as when its value changes).
|
||||
*
|
||||
* Finally, it is never guaranteed that implementations use the label returned
|
||||
* by this method for their actual ARIA label. Some implementations may rely
|
||||
* on other contexts to convey information like the field's value. Example:
|
||||
* checkboxes represent their checked/non-checked status (i.e. value) through
|
||||
* a separate ARIA property.
|
||||
*
|
||||
* Returns an empty string on clickable images (buttons), as we do not want to
|
||||
* include image buttons on the block-level ARIA label. When the button is
|
||||
* focused the label is set in recomputeAriaContext below.
|
||||
*
|
||||
* @param includeTypeInfo Whether to include the field's type information in
|
||||
* the returned label, if available.
|
||||
*/
|
||||
override computeAriaLabel(includeTypeInfo: boolean): string {
|
||||
return this.isClickable() ? '' : super.computeAriaLabel(includeTypeInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Customizes label and sets additional aria state.
|
||||
*/
|
||||
@@ -333,6 +363,11 @@ export class FieldImage extends Field<string> {
|
||||
aria.clearState(focusableElement, aria.State.LABEL);
|
||||
return false;
|
||||
}
|
||||
// For clickable images we need to set the label to the alt text here as
|
||||
// we have overridden the computeAriaLabel to return an empty string. This
|
||||
// will set it at the element level.
|
||||
const label = this.getAriaValue() || '';
|
||||
aria.setState(focusableElement, aria.State.LABEL, label);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,7 +379,7 @@ suite('Image Fields', function () {
|
||||
});
|
||||
});
|
||||
suite('Image with click handler', function () {
|
||||
test('Field has field type name in ARIA label', function () {
|
||||
test('Field has alt text ARIA label', function () {
|
||||
const block = this.workspace.newBlock('test_images_clickhandler');
|
||||
const field = block.getField('IMAGE');
|
||||
block.initSvg();
|
||||
@@ -387,9 +387,9 @@ suite('Image Fields', function () {
|
||||
|
||||
const focusableElement = field.getFocusableElement();
|
||||
const fieldLabel = focusableElement.getAttribute('aria-label');
|
||||
assert.include(fieldLabel, 'image:');
|
||||
assert.include(fieldLabel, 'image with click handler');
|
||||
});
|
||||
test('Focusable element has role of button', function () {
|
||||
test('Focusable element has role of button', function () {
|
||||
const block = this.workspace.newBlock('test_images_clickhandler');
|
||||
const field = block.getField('IMAGE');
|
||||
block.initSvg();
|
||||
@@ -399,6 +399,17 @@ suite('Image Fields', function () {
|
||||
const role = focusableElement.getAttribute('role');
|
||||
assert.equal(role, 'button');
|
||||
});
|
||||
test('Block omits image button from ARIA label', function () {
|
||||
const block = this.workspace.newBlock('test_images_clickhandler');
|
||||
const field = block.getField('IMAGE');
|
||||
block.initSvg();
|
||||
block.render();
|
||||
|
||||
const blockFocusableElement = block.getFocusableElement();
|
||||
const blockLabel = blockFocusableElement.getAttribute('aria-label');
|
||||
assert.notInclude(blockLabel, 'Image:');
|
||||
assert.notInclude(blockLabel, 'image with click handler');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user