-
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: announcer
Are you sure you want to change the base?
Announcer: Part 1 #2362
Conversation
🦋 Changeset detectedLatest commit: 30edef8 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.57 kB (+1.56%) Total Size: 102 kB
ℹ️ View Unchanged
|
A new build was pushed to Chromatic! 🚀https://5e1bf4b385e3fb0020b7073c-bpmevxlvta.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 (2332fac) 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).
|
||
private constructor() { | ||
if (typeof document !== "undefined") { | ||
const topLevelId = `wbAnnounce`; |
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: This is a good candidate for a constant at the file level so this ID can be shared across the file. wdyt?
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.
That's what I had at first, but it added cognitive load when working with the API. It makes the file a lot more scannable to locate this ID closer to the code it's used 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.
fair enough!
Object.assign(this.node.style, srOnly); | ||
|
||
const aWrapper = this.createRegionWrapper("assertive"); | ||
this.createDuplicateRegions(aWrapper, "assertive"); |
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: Could you please elaborate why we need to create duplicate regions here? I was looking at the story in the browser and I'm not sure what should expect from 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's an architectural decision to try and prevent messages from being dropped by AT, something I picked up back in the Angular.js days. Coupled with the debounce logic I'm working on, I think I'll keep it in for now to ensure unique messages are appended and always picked up by AT.
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.
ah ok, that makes sense thanks! Do you think you could add a short comment explaining this context? so other folks could understand the why of this approach. I'm sure my future self will appreciate 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.
+1 me (and future me) too! Especially around alternating index. It would be great to document why we do this and how the live regions are structured!
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. I added some comments to this and the types!
/** | ||
* Internal class to manage screen reader announcements. | ||
*/ | ||
class Announcer { |
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: I'd recommend moving this class into a separate file as well. Sometimes we use an internal/
folder for things like this, but up to you to decide what folder is best.
Side question: Do you think that Announcer
can be tested independently? or is it better to rely on the exposed APIs for unit testing (the ones that you already created)?
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 question about testing! It seems redundant to test the class itself, at least the parts that affect the DOM. But perhaps some unit testing for the singleton and some of the utility methods would be a good idea?
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.
totally agree! if you see that adding unit tests to the class add value, then go for it. I wouldn't want to add more tests just for the sake of increasing coverage 😄.
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: Related to some of the comments left in this file. For consistency, we recommend splitting this file into separate files and keep this index.ts
file as a barrel file. To clarify, I'm 100% with you about avoiding barrel files, but in the WB case, it really helps to make more clear and explicit what's going to be public API and what's internal/private. You can check other packages to see how we usually use this approach.
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.
Makes sense to me! I made some updates so I can keep working on unit tests and the debounce functionality.
We don't need this yet!
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!
} | ||
/** | ||
* Recover in the event regions get lost | ||
* This happens in Storybook when saving a file: |
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.
Would this also happen in webapp with HMR?
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, potentially! I'll do some testing on it.
*/ | ||
createRegionWrapper(level: PolitenessLevel) { | ||
const wrapper = document.createElement("div"); | ||
wrapper.id = `wbAWrap-${level}`; |
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.
For curiosity, does the A
in wbAWrap
stand for Announcer or assertive? I'm assuming "Announcer", but just wanted to double check since we also have aIndex
and pIndex
!
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, it was a prefix for Wonder Blocks Announcer! The full id for these ends up like: wbAWrap-polite
. It's pretty arbitrary though, so I could change it. I debated on whether to keep these wrapper elements at all, as they aren't really being used for anything.
removalDelay?: number, | ||
): string { | ||
if (!this.node) { | ||
return ""; |
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.
If node isn't set at this point, could we trigger the initialization steps so that it can continue to announce the message?
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, great idea! I renamed the HMR method to "reattachNodes" and called it from this part of the code.
Why didn't Prettier do this automatically? I do not know...
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:
sendMessage
to ensure clearing messages works. This doesn't seem super critical since messages get cleared after 5000ms (and this is configurable), but I want to make sure there aren't any bugs. The Jest test for it passes but the ID returned is empty in Storybook 🤷♀️