Coachmark accessibility#5155
Coachmark accessibility#5155leddie24 merged 30 commits intomicrosoft:masterfrom leddie24:coachmarkAccessibility
Conversation
…c-react into coachmarkAccessibility # Conflicts: # packages/office-ui-fabric-react/src/components/TeachingBubble/TeachingBubbleContent.tsx
…ange from FocusZone to FocusTrapZone.
|
looks like snapshots are failing |
…c-react into coachmarkAccessibility # Conflicts: # packages/office-ui-fabric-react/src/components/ColorPicker/ColorPicker.tsx # packages/office-ui-fabric-react/src/components/ColorPicker/ColorRectangle/ColorRectangle.tsx # packages/office-ui-fabric-react/src/components/ColorPicker/ColorSlider/ColorSlider.tsx
|
|
||
| return ( | ||
| <div className={css('ms-Beak', classNames.root)}> | ||
| <div className={css('ms-Beak', classNames.root)} aria-hidden="true"> |
There was a problem hiding this comment.
Is there a strict need for aria-hidden? I'm curious since this element doesn't have an explicit role
There was a problem hiding this comment.
Not sure here. Is aria-hidden only required if an element has a role attribute? I haven't done a lot of accessibility work before, so advice is appreciated here. It looks like narrator focuses on the Beak and reads aloud as "image" if it's in scan mode, which I don't think is desirable.
There was a problem hiding this comment.
Aria-hidden hides all the subelements so it won't try to read the svg. Seems reasonable.
There was a problem hiding this comment.
Can you try role='presentation' and see if that solves the issue? aria-hidden is kinda like a last resort hack.
There was a problem hiding this comment.
Sure, updated to role="presentation"
There was a problem hiding this comment.
You likely need it on the svg itself.
There was a problem hiding this comment.
Instead of the root element, or both?
There was a problem hiding this comment.
I thought it needed to be on the element and wasn't inherited. Give it a try w/ narrator+edge
There was a problem hiding this comment.
I think we can leave it on the root element only. We have a FocusTrapZone on Coachmark anyways, so we can't tab out to the Beak element.
| } | ||
|
|
||
| private _onKeyDown = (e: KeyboardEvent): void => { | ||
| // Open coachmark if user presses ALT + C (arbitrary keypress for now, will change later) |
There was a problem hiding this comment.
This answers a question I had.. how a user selects or activates the coachmark. It appears there is not a clearly defined role for this in ARIA. I'm curious what this could be changed to? Is there a standard of behavior for coachmarks that you know of?
There was a problem hiding this comment.
Can you elaborate here? Not entirely sure what your ask is. Are you asking if we can/should customize the shortcut to open Coachmark? @cschlechty , can you chime in here if you have a chance please?
There was a problem hiding this comment.
No, I'm asking how a user using Narrator actually enters and highlights the Coachmark using keyboard navigation. Tabbing seems to go past it. In the ARIA standard, I couldn't find a specified way to highlight a non-modal dialog.
Alt+C highlights it, but I'm curious how a user using assistive technology would know this.. is it standardized anywhere?
There was a problem hiding this comment.
It's not a stand control or pattern. We're inventing it, and pushing its usage instructions via embedded usage string. @leddie24 be sure this is included in the examples.
There was a problem hiding this comment.
Is that embedded usage string in this PR? I'm assuming it informs users about hitting ALT+C. I couldn't find it.. thanks
There was a problem hiding this comment.
It'll be included in the prop for the control.
There was a problem hiding this comment.
So it will only be set from outside the control? The default selection behavior is embedded in the control, so I'm wondering if the default notification on usage should be as well. (Similar to how "A coachmark has appeared" is.)
There was a problem hiding this comment.
"A coachmark has appeared" and the usage instructions need to be localize-able, so they'll need to be passed in.
There was a problem hiding this comment.
I understand that, but some are defaulted to English within the component and others are not. I feel like the navigation info is probably the most important one to be present (if any will be.)
| }, this.props.delayBeforeMouseOpen!); | ||
|
|
||
| // SetTimeout is hacky solution. ARIA text doesn't read aloud unless it is appended or changed after the component | ||
| // is mounted |
There was a problem hiding this comment.
What would be the fix for this? Is this an issue with ARIA spec or with Narrator?
There was a problem hiding this comment.
It looks like it's behaving correctly now? I removed the hacky solution and just had the text render directly inside of the <div>
There was a problem hiding this comment.
False alarm, I tested this incorrectly. I switched to using setTimeout and appending a textNode. See here:
…out hacky solution for ARIA text; seems to be working fine now in narrator.
…office-ui-fabric-react into coachmarkAccessibility
| {ariaAlertText && ( | ||
| <div | ||
| className={classNames.ariaContainer} | ||
| aria-live="assertive" |
There was a problem hiding this comment.
If you used role="alert" will it read? We could then remove the timeout, or is the timeout to focus the dialog content?
There was a problem hiding this comment.
I reverted the change back to a setTimeout and appending a textNode. It doesn't seem to work unless it's one of these hacky methods:
| > | ||
| {isCollapsed && | ||
| ariaLabelledBy && ( | ||
| <h1 id={ariaLabelledBy} className={classNames.ariaContainer}> |
There was a problem hiding this comment.
why H1? I thought we decided to not use a header
There was a problem hiding this comment.
Changed to a <p> element
| className={classNames.entityHost} | ||
| tabIndex={-1} | ||
| data-is-focusable={true} | ||
| onClick={this._onFocusHandler} |
…c-react into coachmarkAccessibility
…file for fabric-website. Set default true for isPositionForced.
|
@JasonGore @cschlechty , is there anything else needed on this PR to get it merged? Thanks again for looking. |
| <FocusZone> | ||
| <div className={classNames.entityHost} data-is-focusable={true} onFocus={this._onFocusHandler}> | ||
| <div className={classNames.entityInnerHost} ref={this._entityInnerHostElement}> | ||
| <FocusTrapZone isClickableOutsideFocusTrap={true} forceFocusInsideTrap={false}> |
There was a problem hiding this comment.
What's the point of having this focustrap zone if you can click out of it and it doesn't force focus within it?
There was a problem hiding this comment.
Removed the forceFocusInsideTrap prop. I think I inadvertently added this and forgot to remove it. I think we still want to have a clickable region outside of the trap though.
| )} | ||
| {isCollapsed && | ||
| ariaDescribedBy && ( | ||
| <p id={ariaDescribedBy} className={classNames.ariaContainer}> |
There was a problem hiding this comment.
You can move this up to the other isCollapsed section so you don't need to evaluate twice. You might need to make it an array if it complains about needing a single parent but I don't believe you'll need to do that.
There was a problem hiding this comment.
Fixed in latest commit
| if (this.props.ariaAlertText) { | ||
| this._async.setTimeout(() => { | ||
| if (this.props.ariaAlertText && this._ariaAlertContainer.current) { | ||
| const alertText = document.createTextNode(this.props.ariaAlertText); |
There was a problem hiding this comment.
Why are you creating a new node instead of doing something like setState({ alertText: this.props.alertText}) and then in the container you can populate the alert text. That should work the same I believe.
There was a problem hiding this comment.
Fixed in latest commit
| role={'dialog'} | ||
| tabIndex={-1} | ||
| aria-labelledby={ariaLabelledBy} | ||
| aria-describedby={ariaDescribedBy} |
There was a problem hiding this comment.
these don't appear in the dom if they're undefined, correct?
There was a problem hiding this comment.
Correct, although they do appear in snapshot output
…xt state to Coachmark.
|
Spoke with Eddie and he said he'd address the merge conflict. |
…c-react into coachmarkAccessibility
| */ | ||
| ariaAlertText?: string; | ||
|
|
||
| /** |
There was a problem hiding this comment.
@JasonGore Any suggestions here? I'm not quite sure how make the interface for teachingBubbleRef look nicer.
|
|
||
| /** | ||
| * Ref for TeachingBubble | ||
| * @todo Fix interface here |
There was a problem hiding this comment.
What needs to be fixed about this interface?
| teachingBubbleRef?: { | ||
| (component: ITeachingBubble | null): void; | ||
| current: ITeachingBubble | null; | ||
| value: ITeachingBubble | null; |
There was a problem hiding this comment.
This seems like a weird flattened mixing of componentRef and and createRef, did you mean to do this?
There was a problem hiding this comment.
Yes, not entirely sure what the right interface is. If I set it to teachingBubbleRef?: (component: ITeachingBubble | null) => void; then I don't have access to this.props.teachingBubbleRef.current in Coachmark.tsx (line 487). Do you know what's the correct way to implement this?
|
@JasonGore just ping me when you're ready for me to add my approval |
| (): void => { | ||
| // Need setTimeout to trigger narrator | ||
| this._async.setTimeout(() => { | ||
| if (this.props.teachingBubbleRef && this.props.teachingBubbleRef) { |
| this.forceUpdate(); | ||
| } | ||
|
|
||
| document.addEventListener('keydown', this._onKeyDown, true); |
There was a problem hiding this comment.
BaseComponent provides _events that should be used for these.. it automatically takes care of a lot of housekeeping for you. For example in KeytipLayer.base.tsx:
this._events.on(window, 'keydown', this._onKeyDown, true);
JasonGore
left a comment
There was a problem hiding this comment.
Just a couple of minor things that should probably be changed, but otherwise approved
…isteners to use _events
|
@micahgodbolt this should be good to go for your approval, made changes as requested from @JasonGore . |
…5656) * Cherry pick Coachmark: Positioning fixes (#4995) e87289b * Cherry pick TeachingBubble/Coachmark: Fix TeachingBubble content wrapping to next line unnecessarily (#5121) 72bab3c * Cherrry pick Fix import paths to use relative paths in product code (#5338) e2076e1 * Cherry pick Coachmark accessibility #5155 7a36bb9 * Revert formatting back to 5.0 in Coachmark.tsx * Fix unnecessary ; and change " to ' * Fix merge conflicts for Coachmark.Basic.Example.tsx * " to ' * Fix imports for TeachingBubble.tsx * Update TeachinBubbleContent, update TeachingBubble snapshot * Update snapshot. Fix TeachingBubble.types * Fix import for RefObject * Fix some inadvertent formatting from prettier.
Pull request checklist
[ ] Addresses an existing issue: Fixes #0000
[ ] Include a change request file using
$ npm run changeDescription of changes
There's some inadvertent changes from formatting due to Prettier; I'm not sure how to reset this, and pulling from master doesn't seem to change it results for me.
This PR adds accessibility features to Coachmark and TeachingBubble:
Remaining To Do's
Focus areas to test
(optional)
Microsoft Reviewers: Open in CodeFlow