-
Notifications
You must be signed in to change notification settings - Fork 10
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
base: main
Are you sure you want to change the base?
Announcer: Part 1 #2362
Conversation
🦋 Changeset detectedLatest commit: 50f263b The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
Size Change: +1.99 kB (+2.04%) Total Size: 99.3 kB
ℹ️ View Unchanged
|
A new build was pushed to Chromatic! 🚀https://5e1bf4b385e3fb0020b7073c-nlcjdakqus.chromatic.com/ Chromatic results:
|
GeraldRequired Reviewers
Don't want to be involved in this pull request? Comment |
npm Snapshot: Published🎉 Good news!! We've packaged up the latest commit from this PR (ce3ccdd) 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 |
There was a problem hiding this 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/src/components/send-message-button.tsx
Outdated
Show resolved
Hide resolved
setTimeout(() => { | ||
return announcer?.announce(message, level, removalDelay); | ||
}, 100); |
There was a problem hiding this comment.
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 need to use this timeout? I wonder if there's another way to handle this case, but I don't think I have full context on that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was for Safari and VoiceOver, but I can play around with it again to see if we really need it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this does seem important for Safari in particular! I made it configurable so it can be manipulated at runtime if necessary (especially for tests).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ended up removing this with the move to async! It wasn't needed in my testing for Safari/Voiceover.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An initial timeout might still be needed to get reliable announcements! The async code does seem to be messing with Voiceover in both Chrome and Safari, especially on the first couple of messages. When I test in Storybook, polite messages are getting dropped almost entirely and only some assertive messages are making it through. So I'll figure out how to work it back in..probably in the announce
method of the Announcer class. I hate to say it, but it might make sense to get rid of the double announcer element approach and simplify things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did some more testing and added the initial timeout back in for Safari. I also included an option to set the length to 0 so that tests can skip it and work properly. I kept the double announcer stuff in as there wasn't a compelling reason to remove when I tested it.
There was a problem hiding this 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)", |
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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:
My next steps are to test this more in webapp and see if it works as I expected! |
There was a problem hiding this 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!!
} from "./util/dom"; | ||
import {alternateIndex, debounce} from "./util/util"; | ||
|
||
const REMOVAL_TIMEOUT_DELAY = 5000; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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/src/__tests__/Announcer.test.ts
Outdated
Show resolved
Hide resolved
packages/wonder-blocks-announcer/src/__tests__/components/announce-message-button.tsx
Outdated
Show resolved
Hide resolved
packages/wonder-blocks-announcer/src/__tests__/util/util.test.ts
Outdated
Show resolved
Hide resolved
packages/wonder-blocks-announcer/src/__tests__/util/dom.test.ts
Outdated
Show resolved
Hide resolved
packages/wonder-blocks-announcer/src/__tests__/util/dom.test.ts
Outdated
Show resolved
Hide resolved
createDuplicateRegions( | ||
aWrapper, | ||
"assertive", | ||
this.regionFactory.count, | ||
this.dictionary, | ||
); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
There was a problem hiding this 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 😄
* @param {number} wait Length of time to wait before calling callback again | ||
* @returns {string} idRef of targeted live region element | ||
*/ | ||
export function debounce(callback: (...args: any[]) => string, wait: number) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed we also have another debounce utility for dropdowns! The return type is a bit different though. No changes needed now but thought I'd mention it in case we want to consolidate utility functions across WB later on :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call out! This one needs to return a promise with a string and a utility method for updating the wait time so it's pretty specific internally.
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 @jandrade I also fixed that styling issue in Storybook that you were seeing with the Combobox. I added an |
We don't need this yet!
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.
4957581
to
7eacfef
Compare
There was a problem hiding this 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)", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
debounceThreshold, | ||
}: AnnounceMessageProps) => { | ||
return ( | ||
<Button |
There was a problem hiding this comment.
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
message: "Here is some example text.", | ||
level: "polite", | ||
}, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can disable this story for chromatic since we don't need visual regression tests for it! https://www.chromatic.com/docs/disable-snapshots/#with-storybook
@marcysutton I've been testing the latest changes in The issue is that the button is pressed, the message is queued to be announced, but because the Screen.Recording.2024-12-20.at.6.12.57.PM.movI'll try to put a new PR with my current progress. |
The initial implementation for a live region component! I'm getting this draft PR up so I can test with the remote URL.
Jira Issue: https://khanacademy.atlassian.net/browse/WB-1768
Outstanding questions/work areas: