Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Announcer: Part 1 #2362

Merged
merged 46 commits into from
Jan 16, 2025
Merged

Announcer: Part 1 #2362

merged 46 commits into from
Jan 16, 2025

Conversation

marcysutton
Copy link
Member

@marcysutton marcysutton commented Nov 15, 2024

The initial implementation for a live region component! I'm getting this draft PR up so I can test with the remote URL.

Issue: https://khanacademy.atlassian.net/browse/WB-1768

Outstanding questions/work areas:

  • Testing in more ATs, particularly in webapp and on mobile devices.
  • More integration with React: usage for JSX, debouncing with custom duration in continuously re-rendering components like the Video Player with Clarifications, etc.

Test Plan

Play with the Story with screen readers turned on
1. VoiceOver on OSX
2. VoiceOver on iOS
3. NVDA on Windows
4. JAWS on Windows

Copy link

changeset-bot bot commented Nov 15, 2024

🦋 Changeset detected

Latest commit: 6844066

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@khanacademy/wonder-blocks-announcer Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Contributor

github-actions bot commented Nov 15, 2024

Size Change: +1.99 kB (+2.02%)

Total Size: 100 kB

Filename Size Change
packages/wonder-blocks-announcer/dist/es/index.js 1.99 kB +1.99 kB (new file) 🆕
ℹ️ View Unchanged
Filename Size
packages/wonder-blocks-accordion/dist/es/index.js 3.77 kB
packages/wonder-blocks-banner/dist/es/index.js 1.53 kB
packages/wonder-blocks-birthday-picker/dist/es/index.js 1.77 kB
packages/wonder-blocks-breadcrumbs/dist/es/index.js 887 B
packages/wonder-blocks-button/dist/es/index.js 4.12 kB
packages/wonder-blocks-cell/dist/es/index.js 2.01 kB
packages/wonder-blocks-clickable/dist/es/index.js 3.06 kB
packages/wonder-blocks-core/dist/es/index.js 2.9 kB
packages/wonder-blocks-data/dist/es/index.js 6.24 kB
packages/wonder-blocks-dropdown/dist/es/index.js 19.1 kB
packages/wonder-blocks-form/dist/es/index.js 6.2 kB
packages/wonder-blocks-grid/dist/es/index.js 1.36 kB
packages/wonder-blocks-icon-button/dist/es/index.js 2.95 kB
packages/wonder-blocks-icon/dist/es/index.js 871 B
packages/wonder-blocks-labeled-field/dist/es/index.js 1.94 kB
packages/wonder-blocks-layout/dist/es/index.js 1.82 kB
packages/wonder-blocks-link/dist/es/index.js 2.28 kB
packages/wonder-blocks-modal/dist/es/index.js 5.42 kB
packages/wonder-blocks-pill/dist/es/index.js 1.65 kB
packages/wonder-blocks-popover/dist/es/index.js 4.85 kB
packages/wonder-blocks-progress-spinner/dist/es/index.js 1.52 kB
packages/wonder-blocks-search-field/dist/es/index.js 1.36 kB
packages/wonder-blocks-switch/dist/es/index.js 1.92 kB
packages/wonder-blocks-testing-core/dist/es/index.js 3.74 kB
packages/wonder-blocks-testing/dist/es/index.js 1.07 kB
packages/wonder-blocks-theming/dist/es/index.js 693 B
packages/wonder-blocks-timing/dist/es/index.js 1.8 kB
packages/wonder-blocks-tokens/dist/es/index.js 2.36 kB
packages/wonder-blocks-toolbar/dist/es/index.js 905 B
packages/wonder-blocks-tooltip/dist/es/index.js 6.99 kB
packages/wonder-blocks-typography/dist/es/index.js 1.23 kB

compressed-size-action

Copy link
Contributor

github-actions bot commented Nov 15, 2024

A new build was pushed to Chromatic! 🚀

https://5e1bf4b385e3fb0020b7073c-mnnpmtuceh.chromatic.com/

Chromatic results:

Metric Total
Captured snapshots 383
Tests with visual changes 0
Total stories 529
Inherited (not captured) snapshots [TurboSnap] 0
Tests on the build 383

@marcysutton marcysutton marked this pull request as ready for review November 20, 2024 21:28
@khan-actions-bot
Copy link
Contributor

khan-actions-bot commented Nov 20, 2024

Gerald

Required Reviewers
  • @Khan/wonder-blocks for changes to .changeset/thirty-ducks-type.md, .storybook/preview.tsx, __docs__/wonder-blocks-announcer/announcer.stories.tsx, packages/wonder-blocks-announcer/package.json, packages/wonder-blocks-announcer/tsconfig-build.json, static/sb-styles/preview.css, packages/wonder-blocks-announcer/src/announce-message.ts, packages/wonder-blocks-announcer/src/announcer.ts, packages/wonder-blocks-announcer/src/clear-messages.ts, packages/wonder-blocks-announcer/src/index.ts, packages/wonder-blocks-announcer/types/announcer.types.ts, packages/wonder-blocks-announcer/src/__tests__/announce-message.test.tsx, packages/wonder-blocks-announcer/src/__tests__/announcer.test.ts, packages/wonder-blocks-announcer/src/__tests__/clear-messages.test.tsx, packages/wonder-blocks-announcer/src/util/dom.ts, packages/wonder-blocks-announcer/src/util/util.ts, packages/wonder-blocks-announcer/src/__tests__/components/announce-message-button.tsx, packages/wonder-blocks-announcer/src/__tests__/util/dom.test.ts, packages/wonder-blocks-announcer/src/__tests__/util/test-utilities.ts, packages/wonder-blocks-announcer/src/__tests__/util/util.test.ts

Don't want to be involved in this pull request? Comment #removeme and we won't notify you of further changes.

@khan-actions-bot khan-actions-bot requested a review from a team November 20, 2024 21:28
Copy link
Contributor

github-actions bot commented Nov 20, 2024

npm Snapshot: Published

🎉 Good news!! We've packaged up the latest commit from this PR (7b5f690) and published all packages with changesets to npm.

You can install the packages in webapp by running:

./services/static/dev/tools/deploy_wonder_blocks.js --tag="PR2362"

Packages can also be installed manually by running:

yarn add @khanacademy/wonder-blocks-<package-name>@PR2362

Copy link
Member

@jandrade jandrade left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking really good! Giving my initial review with some suggestions on how to structure the files to be more consistent with the rest of the repo and asking some questions to get a bit more context on certain parts. This is great progress 👏

packages/wonder-blocks-announcer/CHANGELOG.md Outdated Show resolved Hide resolved
packages/wonder-blocks-announcer/src/index.ts Outdated Show resolved Hide resolved
packages/wonder-blocks-announcer/src/index.ts Outdated Show resolved Hide resolved
packages/wonder-blocks-announcer/src/index.ts Outdated Show resolved Hide resolved
packages/wonder-blocks-announcer/src/index.ts Outdated Show resolved Hide resolved
packages/wonder-blocks-announcer/src/index.ts Outdated Show resolved Hide resolved
packages/wonder-blocks-announcer/src/index.ts Outdated Show resolved Hide resolved
packages/wonder-blocks-announcer/src/index.ts Outdated Show resolved Hide resolved
packages/wonder-blocks-announcer/src/index.ts Show resolved Hide resolved
@khan-actions-bot khan-actions-bot requested a review from a team November 22, 2024 17:28
Copy link
Member

@beaesguerra beaesguerra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work so far Marcy! Left some feedback and questions 😄

timeoutDelay: {
control: "number",
type: "number",
description: "(milliseconds)",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if there's a way for us to use the function docs for the storybook docs! Normally we're able to get the prop docs automatically from setting component in this block, though this is different since these docs are for functions rather than components!

cc: @jandrade in case you have come across similar things before!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there isn't a way to do this, it would be helpful to add a description for the different options so it shows up in the docs! This can help developers know when to use what level or when to use debounceThreshold. Same for documenting the clear-messages utility!

image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good ideas on docs! I will plan to tackle this in a future PR.

packages/wonder-blocks-announcer/src/send-message.ts Outdated Show resolved Hide resolved
packages/wonder-blocks-announcer/types/Announcer.types.ts Outdated Show resolved Hide resolved
packages/wonder-blocks-announcer/src/Announcer.ts Outdated Show resolved Hide resolved
packages/wonder-blocks-announcer/src/Announcer.ts Outdated Show resolved Hide resolved
packages/wonder-blocks-announcer/src/Announcer.ts Outdated Show resolved Hide resolved
packages/wonder-blocks-announcer/src/Announcer.ts Outdated Show resolved Hide resolved
packages/wonder-blocks-announcer/src/Announcer.ts Outdated Show resolved Hide resolved
@khan-actions-bot khan-actions-bot requested a review from a team November 22, 2024 21:56
@marcysutton
Copy link
Member Author

marcysutton commented Nov 27, 2024

@jandrade @beaesguerra this is ready for another review! I added quite a few more tests. Yay!

In testing the debounce logic more thoroughly, I realized the first iteration was going to be unpredictable and hard to use. So I refactored it a bit. I'm currently debugging in ATs and working with Storybook though, as this might have broken things. 😬

Some code highlights:

  1. I added a debounceThreshold parameter to allow callers to specify how long to wait before announcing another message. This is intended for repeatedly-rendering components like the ClarificationNotifications component for the video player which are re-rendered every 1/2 second. This seemed like a reasonable way to handle it without having to compare strings. (We might still need to do that so it's not annoying to screen reader users, but I want to try this approach first.)
  2. The debounce function now returns the first message rather than the last, so it will be announced immediately (other implementations use the last callback rather than the first).
  3. I got rid of the removalDelay parameter as it wasn't that useful to begin with, and it made the entire thing super confusing. Elements are still removed after a period time, I just changed it to not be configurable rather than trying to maintain removalDelay and debounceThreshold which both use setTimeouts.
  • The removalDelay is internally set to 5000 ms + the default debounce wait time of 250ms. If a custom debounceThreshold is passed in (say 2000ms), the internal removalDelay would updated to a longer duration for removal to avoid a race condition (2000ms + 5000ms).

My next steps are to test this more in webapp and see if it works as I expected!

Copy link
Member

@jandrade jandrade left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this new iteration. I'm adding some extra suggestions around the code (specially around tests). I'll keep digging into it and will integrate with the Combobox component. Thanks for all this progress!!

packages/wonder-blocks-announcer/src/Announcer.ts Outdated Show resolved Hide resolved
} from "./util/dom";
import {alternateIndex, debounce} from "./util/util";

const REMOVAL_TIMEOUT_DELAY = 5000;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: Is there a specific reason why we have to wait for 5 seconds? do you think this would need to be configured by the consumer at some point?

Copy link
Member Author

@marcysutton marcysutton Dec 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's admittedly a bit of a magic number... long enough to let the platform accessibility API read out a message of unknown length before it's removed, and also longer than the default initial timeout. Initially I made this configurable by the user but I removed the option due to added complexity to maintain.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idea: What if we determined the delay dynamically based on the number of words in the announcement multiplied by something like 500ms? That way long messages are accounted for! This might also account for translations that might be longer, though we would be assuming approximately how long each word is read!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's an interesting thought. I'm hesitant to go that route as screen reader speech rates are configurable, so it's a moving target. Perhaps we could mark that down for a future improvement if it comes up in production?

packages/wonder-blocks-announcer/types/Announcer.types.ts Outdated Show resolved Hide resolved
Comment on lines 76 to 81
createDuplicateRegions(
aWrapper,
"assertive",
this.regionFactory.count,
this.dictionary,
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thought: One thing that I noticed while playing with Storybook is that if I change the text in the Story and click the "submit" button, sometimes the announcement is not read by Voice Over. I'm not sure where to put this, but saw that sometimes was announced in one of the duplicate regions.

Copy link
Member Author

@marcysutton marcysutton Dec 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, that is odd. I noticed initial announcements aren't read if I change the politeness level to Assertive, but not for changing the text. The default Storybook frame also seems to impact reliability of messages, so I tend to test it in a standalone window. I think I'll opt for integration testing to observe these kinds of issues since I've spent so long on it already and the behavior is so inconsistent!

@khan-actions-bot khan-actions-bot requested a review from a team December 10, 2024 22:36
Copy link
Member

@beaesguerra beaesguerra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great progress Marcy! Left some questions and comments 😄

packages/wonder-blocks-announcer/src/util/util.ts Outdated Show resolved Hide resolved
packages/wonder-blocks-announcer/src/clear-messages.ts Outdated Show resolved Hide resolved
packages/wonder-blocks-announcer/src/announcer.ts Outdated Show resolved Hide resolved
@khan-actions-bot khan-actions-bot requested a review from a team December 12, 2024 19:56
@marcysutton
Copy link
Member Author

Alright, the debounce refactor is in! (it wasn't actually working when I tested it) I had to change it to allow a configurable debounce wait parameter with the simple announceMessage API. This means you can just call it with options without having to configure anything separately (helpful when it's being called in multiple places, so configurations don't interfere with each other).

@jandrade I also fixed that styling issue in Storybook that you were seeing with the Combobox. I added an addBodyClass decorator that can be used for anything...kind of a nice general improvement!

@marcysutton marcysutton changed the base branch from announcer to main December 12, 2024 22:44
Copy link
Member

@beaesguerra beaesguerra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking great, I tested the story out with VoiceOver + Safari, NVDA + Chrome/Firefox and the message is announced as I expected! Thanks for your hard work on this!

I had a few suggestions around stories for other scenarios and documentation (non-blocking). I'm looking forward to seeing the announcer get used in context!

(Will let Juan be the final approver though since he may have feedback/insights on how it was integrating it with combobox!)

timeoutDelay: {
control: "number",
type: "number",
description: "(milliseconds)",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there isn't a way to do this, it would be helpful to add a description for the different options so it shows up in the docs! This can help developers know when to use what level or when to use debounceThreshold. Same for documenting the clear-messages utility!

image

debounceThreshold,
}: AnnounceMessageProps) => {
return (
<Button
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(suggestion, no changes necessary) - I was curious about other scenarios that we could add to the story (or another story) so we can test different cases easily:

  • A button that triggers an announcement and another button that clears the specific announcement and/or all announcements
  • 1 button that triggers a polite message, another button that triggers an assertive message to see the behaviour for different announcement levels
  • buttons with different debounceThreshold values to show how that option changes the behaviour

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love these ideas. I will tackle them in a future PR also! Thanks Bea!

@jandrade
Copy link
Member

@marcysutton I've been testing the latest changes in Combobox and I'm still having some trouble announcing messages correctly when the dismiss/clear button is pressed.

The issue is that the button is pressed, the message is queued to be announced, but because the input[combobox] is focused again, then it causes the listbox to open and prioritizes the announcement for that (e.g. Expanded, completion selected). I'm attaching a screenshot here to illustrate this case:

Screen.Recording.2024-12-20.at.6.12.57.PM.mov

I'll try to put a new PR with my current progress.

@khan-actions-bot khan-actions-bot requested a review from a team January 10, 2025 18:32
@marcysutton marcysutton changed the base branch from main to feature/announcer January 10, 2025 18:40
Comment on lines +65 to +69
expect(politeRegion1).toHaveAttribute("aria-live", "polite");
expect(politeRegion1).toHaveAttribute("id", "wbARegion-polite0");
expect(politeRegion2).toHaveAttribute("aria-live", "polite");
expect(politeRegion2).toHaveAttribute("id", "wbARegion-polite1");
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Can we make these aria-atomic so that they read the whole message instead of browsers/srs trimming similar starting text? Or is that handled in a different way?

Copy link
Member Author

@marcysutton marcysutton Jan 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can definitely try it! I wasn't noticing differences with the current implementation. But I will caveat that I'm also planning to experiment with a queue of messages that get appended as children and removed on a staggered delay. So aria-atomic might not make sense in that implementation!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a note to look into this in a future PR, since I want to do some more cross-platform testing anyway!

Copy link
Member

@beaesguerra beaesguerra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me! Thanks for the continuous iterations, and summarizing the follow up work after this PR!

1. Keeps announce from being called too frequently. We can play with the timeout duration.
2. Makes the returned IDREF more reliable in a browser.
1. Keeps announce from being called too frequently. We can play with the timeout duration.
2. Makes the returned IDREF more reliable in a browser.
1. Return first result and debounce subsequent calls within the debounceThreshold
2. Remove removalDelay parameter to simplify API
3. Put debounce utility into a separate file and add tests
Debounce wasn't actually limiting execution in the wait period. Now it does, and the debounce duration is still configurable when calling announceMessage!
I was trying to avoid having to import the Announcer in this test to keep things isolated, but it's so specific to the Announcer that I decided it didn't matter that much. Specifying the Announcer instance for the scope instead of generic thisArg logic simplified things quite a bit as well.
@khan-actions-bot khan-actions-bot requested a review from a team January 16, 2025 22:31
@marcysutton marcysutton merged commit fe17b20 into feature/announcer Jan 16, 2025
14 checks passed
@marcysutton marcysutton deleted the announcer-pt1 branch January 16, 2025 22:34
Copy link

codecov bot commented Jan 16, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 0.00%. Comparing base (d8c91db) to head (6844066).
Report is 1 commits behind head on feature/announcer.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##   feature/announcer   #2362   +/-   ##
=========================================
=========================================

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d8c91db...6844066. Read the comment docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants