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
22 changes: 7 additions & 15 deletions packages/blockly/core/toast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -157,6 +155,11 @@ export class Toast {
toast.addEventListener('mouseleave', setToastTimeout);
setToastTimeout();

aria.announceDynamicAriaState(message, {
assertiveness,
role: aria.Role.STATUS,
});

return toast;
}

Expand All @@ -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;
Expand Down
38 changes: 27 additions & 11 deletions packages/blockly/tests/mocha/toast_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -97,16 +98,20 @@ 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,
);
});

Expand All @@ -115,15 +120,26 @@ suite('Toasts', function () {
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);
});
});
Loading