From f788fe8f2940b6505f0c1b3bc172bb82ecfdc285 Mon Sep 17 00:00:00 2001 From: Mike Harvey <43474485+mikeharv@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:56:08 -0400 Subject: [PATCH 1/2] feat: announce toasts via shared ARIA live region --- packages/blockly/core/toast.ts | 22 ++++-------- packages/blockly/tests/mocha/toast_test.js | 40 +++++++++++++++------- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/packages/blockly/core/toast.ts b/packages/blockly/core/toast.ts index 72559279f57..12ffbb0d05d 100644 --- a/packages/blockly/core/toast.ts +++ b/packages/blockly/core/toast.ts @@ -45,7 +45,7 @@ export interface ToastOptions { * How prominently/interrupting the readout of the toast should be for * screenreaders. Corresponds to aria-live and defaults to polite. */ - assertiveness?: Toast.Assertiveness; + assertiveness?: aria.LiveRegionAssertiveness; } /** @@ -89,15 +89,13 @@ export class Toast { const { message, duration = 5, - assertiveness = Toast.Assertiveness.POLITE, + assertiveness = aria.LiveRegionAssertiveness.POLITE, } = options; const toast = document.createElement('div'); workspace.getInjectionDiv().appendChild(toast); toast.dataset.toastId = options.id; toast.className = CLASS_NAME; - aria.setRole(toast, aria.Role.STATUS); - aria.setState(toast, aria.State.LIVE, assertiveness); const messageElement = toast.appendChild(document.createElement('div')); messageElement.className = MESSAGE_CLASS_NAME; @@ -157,6 +155,11 @@ export class Toast { toast.addEventListener('mouseleave', setToastTimeout); setToastTimeout(); + aria.announceDynamicAriaState(message, { + assertiveness, + role: aria.Role.STATUS, + }); + return toast; } @@ -174,17 +177,6 @@ export class Toast { } } -/** - * Options for how aggressively toasts should be read out by screenreaders. - * Values correspond to those for aria-live. - */ -export namespace Toast { - export enum Assertiveness { - ASSERTIVE = 'assertive', - POLITE = 'polite', - } -} - Css.register(` .${CLASS_NAME} { font-size: 1.2rem; diff --git a/packages/blockly/tests/mocha/toast_test.js b/packages/blockly/tests/mocha/toast_test.js index afb7f7f6cb9..e69510b1776 100644 --- a/packages/blockly/tests/mocha/toast_test.js +++ b/packages/blockly/tests/mocha/toast_test.js @@ -14,6 +14,7 @@ suite('Toasts', function () { setup(function () { sharedTestSetup.call(this); this.workspace = Blockly.inject('blocklyDiv', {}); + this.liveRegion = document.getElementById('blocklyAriaAnnounce'); this.toastIsVisible = (message) => { const toast = this.workspace .getInjectionDiv() @@ -97,33 +98,48 @@ suite('Toasts', function () { clock.restore(); }); - test('default to polite assertiveness', function () { + test('toast announces message with status role and polite assertiveness', function () { const message = 'texas toast'; Blockly.Toast.show(this.workspace, {message, id: 'test'}); - const toast = this.workspace - .getInjectionDiv() - .querySelector('.blocklyToast'); + this.clock.tick(11); + + assert.include(this.liveRegion.textContent, message); assert.equal( - toast.getAttribute('aria-live'), - Blockly.Toast.Assertiveness.POLITE, + this.liveRegion.getAttribute('role'), + Blockly.utils.aria.Role.STATUS, + ); + assert.equal( + this.liveRegion.getAttribute('aria-live'), + Blockly.utils.aria.LiveRegionAssertiveness.POLITE, ); }); - + test('respects assertiveness option', function () { const message = 'texas toast'; Blockly.Toast.show(this.workspace, { message, id: 'test', - assertiveness: Blockly.Toast.Assertiveness.ASSERTIVE, + assertiveness: Blockly.utils.aria.LiveRegionAssertiveness.ASSERTIVE, }); + + this.clock.tick(11); + + assert.equal( + this.liveRegion.getAttribute('aria-live'), + Blockly.utils.aria.LiveRegionAssertiveness.ASSERTIVE, + ); + }); + + test('toast is not itself a live region', function () { + const message = 'texas toast'; + Blockly.Toast.show(this.workspace, {message, id: 'test'}); + const toast = this.workspace .getInjectionDiv() .querySelector('.blocklyToast'); - assert.equal( - toast.getAttribute('aria-live'), - Blockly.Toast.Assertiveness.ASSERTIVE, - ); + assert.isNull(toast.getAttribute('aria-live')); + assert.notEqual(toast.getAttribute('role'), Blockly.utils.aria.Role.STATUS); }); }); From 001c0311eca26106623c2520f51aca6eb058295f Mon Sep 17 00:00:00 2001 From: Mike Harvey <43474485+mikeharv@users.noreply.github.com> Date: Thu, 2 Apr 2026 09:41:14 -0400 Subject: [PATCH 2/2] chore: add extra space --- packages/blockly/tests/mocha/toast_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/blockly/tests/mocha/toast_test.js b/packages/blockly/tests/mocha/toast_test.js index e69510b1776..a1d03da94aa 100644 --- a/packages/blockly/tests/mocha/toast_test.js +++ b/packages/blockly/tests/mocha/toast_test.js @@ -114,7 +114,7 @@ suite('Toasts', function () { Blockly.utils.aria.LiveRegionAssertiveness.POLITE, ); }); - + test('respects assertiveness option', function () { const message = 'texas toast'; Blockly.Toast.show(this.workspace, {