mirror of
https://github.com/google/blockly.git
synced 2026-05-01 17:40:11 +02:00
fix: Make live region coalesce messages (#9778)
* fix: Make live region coalesce messages * chore: Remove dead code * fix: Fix test
This commit is contained in:
@@ -33,6 +33,9 @@ export enum LiveRegionAssertiveness {
|
||||
POLITE = 'polite',
|
||||
}
|
||||
|
||||
let nextAnnouncementAssertiveness = LiveRegionAssertiveness.OFF;
|
||||
const queuedAnnouncements: string[] = [];
|
||||
|
||||
/**
|
||||
* Customization options that can be passed when using `announceDynamicAriaState`.
|
||||
*/
|
||||
@@ -386,6 +389,12 @@ export function announceDynamicAriaState(
|
||||
role = DEFAULT_LIVE_REGION_ROLE,
|
||||
} = options || {};
|
||||
|
||||
queuedAnnouncements.push(text);
|
||||
nextAnnouncementAssertiveness = mostAssertive(
|
||||
assertiveness,
|
||||
nextAnnouncementAssertiveness,
|
||||
);
|
||||
|
||||
// We use a short delay so rapid successive calls collapse into a single
|
||||
// announcement, and to ensure assistive technologies reliably detect the
|
||||
// DOM change.
|
||||
@@ -393,14 +402,38 @@ export function announceDynamicAriaState(
|
||||
ariaAnnounceTimeout = setTimeout(() => {
|
||||
// Clear previous content.
|
||||
ariaAnnouncementContainer.replaceChildren();
|
||||
setState(ariaAnnouncementContainer, State.LIVE, assertiveness);
|
||||
setState(
|
||||
ariaAnnouncementContainer,
|
||||
State.LIVE,
|
||||
nextAnnouncementAssertiveness,
|
||||
);
|
||||
setRole(ariaAnnouncementContainer, role);
|
||||
|
||||
const span = document.createElement('span');
|
||||
// The non-breaking space toggle ensures otherwise identical consecutive
|
||||
// messages are still announced.
|
||||
span.textContent = text + (addBreakingSpace ? '\u00A0' : '');
|
||||
span.textContent =
|
||||
queuedAnnouncements.join('\n') + (addBreakingSpace ? '\u00A0' : '');
|
||||
addBreakingSpace = !addBreakingSpace;
|
||||
ariaAnnouncementContainer.appendChild(span);
|
||||
queuedAnnouncements.length = 0;
|
||||
nextAnnouncementAssertiveness = LiveRegionAssertiveness.OFF;
|
||||
}, 10);
|
||||
}
|
||||
|
||||
/** Returns the maximally assertive of the given assertiveness levels. */
|
||||
function mostAssertive(a: LiveRegionAssertiveness, b: LiveRegionAssertiveness) {
|
||||
if (
|
||||
a === LiveRegionAssertiveness.ASSERTIVE ||
|
||||
b === LiveRegionAssertiveness.ASSERTIVE
|
||||
) {
|
||||
return LiveRegionAssertiveness.ASSERTIVE;
|
||||
} else if (
|
||||
a === LiveRegionAssertiveness.POLITE ||
|
||||
b === LiveRegionAssertiveness.POLITE
|
||||
) {
|
||||
return LiveRegionAssertiveness.POLITE;
|
||||
}
|
||||
|
||||
return LiveRegionAssertiveness.OFF;
|
||||
}
|
||||
|
||||
@@ -103,14 +103,33 @@ suite('ARIA', function () {
|
||||
assert.notEqual(first, second);
|
||||
});
|
||||
|
||||
test('last write wins when called rapidly', function () {
|
||||
test('Coalesces messages when called rapidly', function () {
|
||||
Blockly.utils.aria.announceDynamicAriaState('First message');
|
||||
Blockly.utils.aria.announceDynamicAriaState('Second message');
|
||||
Blockly.utils.aria.announceDynamicAriaState('Final message');
|
||||
|
||||
this.clock.tick(11);
|
||||
|
||||
assert.include(this.liveRegion.textContent, 'Final message');
|
||||
assert.include(
|
||||
this.liveRegion.textContent,
|
||||
'First message\nSecond message\nFinal message',
|
||||
);
|
||||
});
|
||||
|
||||
test('Uses maximal assertiveness when coalescing', function () {
|
||||
Blockly.utils.aria.announceDynamicAriaState('First message', {
|
||||
assertiveness: Blockly.utils.aria.LiveRegionAssertiveness.OFF,
|
||||
});
|
||||
Blockly.utils.aria.announceDynamicAriaState('Second message', {
|
||||
assertiveness: Blockly.utils.aria.LiveRegionAssertiveness.ASSERTIVE,
|
||||
});
|
||||
Blockly.utils.aria.announceDynamicAriaState('Final message', {
|
||||
assertiveness: Blockly.utils.aria.LiveRegionAssertiveness.POLITE,
|
||||
});
|
||||
|
||||
this.clock.tick(11);
|
||||
|
||||
assert.equal(this.liveRegion.getAttribute('aria-live'), 'assertive');
|
||||
});
|
||||
|
||||
test('assertive option sets aria-live assertive', function () {
|
||||
|
||||
@@ -1040,7 +1040,7 @@ suite('Keyboard-driven movement', function () {
|
||||
|
||||
Blockly.getFocusManager().focusNode(valueBlock);
|
||||
startMove(this.workspace);
|
||||
|
||||
this.clock.tick(10);
|
||||
this.moveAndAssert(
|
||||
moveRight,
|
||||
['moving', 'inside', this.getBlockLabel(parent)],
|
||||
@@ -1057,6 +1057,7 @@ suite('Keyboard-driven movement', function () {
|
||||
Blockly.getFocusManager().focusNode(loop);
|
||||
startMove(this.workspace);
|
||||
moveRight(this.workspace);
|
||||
this.clock.tick(10);
|
||||
|
||||
this.moveAndAssert(
|
||||
moveRight,
|
||||
@@ -1081,6 +1082,7 @@ suite('Keyboard-driven movement', function () {
|
||||
Blockly.getFocusManager().focusNode(ifBlock);
|
||||
startMove(this.workspace); // on workspace
|
||||
moveRight(this.workspace); // before block1
|
||||
this.clock.tick(10);
|
||||
this.moveAndAssert(
|
||||
moveRight,
|
||||
[
|
||||
@@ -1114,7 +1116,7 @@ suite('Keyboard-driven movement', function () {
|
||||
|
||||
Blockly.getFocusManager().focusNode(boolean);
|
||||
startMove(this.workspace);
|
||||
|
||||
this.clock.tick(10);
|
||||
this.moveAndAssert(
|
||||
moveRight,
|
||||
[
|
||||
@@ -1151,7 +1153,7 @@ suite('Keyboard-driven movement', function () {
|
||||
Blockly.getFocusManager().focusNode(text);
|
||||
startMove(this.workspace);
|
||||
moveRight(this.workspace); // First labeled input
|
||||
|
||||
this.clock.tick(10);
|
||||
this.moveAndAssert(
|
||||
moveRight,
|
||||
['moving', 'inside', this.getBlockLabel(textJoin), 'input 2'],
|
||||
|
||||
Reference in New Issue
Block a user