angular / components

Component infrastructure and Material Design components for Angular
https://material.angular.io
MIT License
24.32k stars 6.73k forks source link

Queue messages sent to LiveAnnouncer #17852

Open yurii-shylov opened 4 years ago

yurii-shylov commented 4 years ago

Feature Description

LiveAnnouncer should be able to queue multiple messages and announce them one by one instead of announcing only the lates message.

Use Case

See https://stackblitz.com/edit/angular-b4jomd, it has a button which announces two messages "Table Foo was updated" and "Table Bar was updated" on click. It illustrates two use cases:

1) Simultaneous announcements. Current implementation of LiveAnnouncer uses a single DOM element for the announcement, so second message overrides the first one and screen reader users hear only the second message.

2) Consequent announcement of the same message. Current implementation will announce only distinct messages, i.e. clicking the button multiple times will produce only one message for the screen reader. That's not always convenient. For example, if you have a table on the page and you do some polling which updates the table 5 times, we need to be able to announce every of those updates so that screen reader users know what's happening on the page.

Implementation

We already have an implementation of a queued LiveAnnouncer which was used in GCP for the last 5 months, it alowed to fix a lot of accessibility problems on pages with heavy UIs. I will convert it to a PR here, but I'd like to have some discussion first. At the very least I'd like to know whether it should replace the existing LiveAnnouncer implementation or we should allow users to switch between two implementations

jelbourn commented 4 years ago

In general I think this could be a good improvement. It would be great to see a specific proposal/design doc for updating the existing LiveAnnouncer, since we would want to avoid any breaking changes.

yurii-shylov commented 4 years ago

How current LiveAnnouncer works

In constructor LiveAnnouncer creates an aria-live <div>. Whenever user calls announce('Foo') it erases the content of that <div> and after a 100ms delay sets 'Foo' to it. Screen readers detect that change and read 'Foo' to the user.

If announce('Foo') and announce('Bar') are called approximatelly at the same time, second message overrides the value of that single <div> element. It means that screen reader will only read 'Bar'.

What QueuedLiveAnnouncer would change

There will be a TaskQueue class with a single method enqueue(task: () => void, politeness: AriaLivePoliteness, delay = 0): void. All tasks will be added to a single queue, they will be sorted by politeness only (so 'assertive' messages will always get announced before 'polite' ones, even if they were scheduled later). delay will regulate the delay before task gets executed (similar to the 100ms described above). So calling enqueue(foo, 100); enqueu(bar, 100) will execute task foo in ~100ms from now and task bar in ~200ms from now.

In constructor QueuedLiveAnnouncer creates only the TaskQueue. Whenever user calls announce('Foo') it creates a new <div> element with appropriate aria-live and attaches it to the DOM. It places a task to the queue with a 100ms delay; the task sets 'Foo' to the previously created <div> and schedules a removal of that <div> in 1000ms (originally we had 30s, but we've tested 1s in various screen readers, it's safe to use and allows to clean up DOM sooner). Screen readers detect new <div> and read 'Foo' to the user.

If announce('Foo') and announce('Bar') are called approximatelly at the same time, both divs will also be attached to the DOM at the same time, but the innex test for them will be set with a guaranteed difference of at least 100ms. This allows screen readers to properly read both 'Foo' and 'Bar' one by one. Unlike regular LiveAnnounce it also allows to read 'Foo' more than once because different divs are used for them.

Potential breakages

I see two potential problems:

  1. Some projects might actually depend on the fact, that currently LiveAnnouncer announces only distinct messages. They might want the behavior that calling announce('Foo'); announce('Foo'); announce('Foo'); announce('Bar'); produces only 2 messages.
  2. Since QueuedLiveAnnouncer adds a new DOM element for every message it incurs slightly worse performance than having a single element for all the messages.
jelbourn commented 4 years ago
yurii-shylov commented 4 years ago
jelbourn commented 4 years ago

I like the idea of using LiveAnnouncerDefaultOptions for this.

angular-robot[bot] commented 2 years ago

Just a heads up that we kicked off a community voting process for your feature request. There are 20 days until the voting process ends.

Find more details about Angular's feature request process in our documentation.

angular-robot[bot] commented 2 years ago

Thank you for submitting your feature request! Looks like during the polling process it didn't collect a sufficient number of votes to move to the next stage.

We want to keep Angular rich and ergonomic and at the same time be mindful about its scope and learning journey. If you think your request could live outside Angular's scope, we'd encourage you to collaborate with the community on publishing it as an open source package.

You can find more details about the feature request process in our documentation.