-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Fixes focus issue for contextual menus #4744
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
Changes from all commits
9f877be
2947a4d
3e99a25
8432116
f29bfd4
fde5885
19e4d27
fd759fb
2205afc
a6a7095
c929768
34c3bb2
3ef3590
c871515
0fe31a7
222a46b
0a8c819
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "changes": [ | ||
| { | ||
| "packageName": "office-ui-fabric-react", | ||
| "comment": "Fixes issue where focus isn't displayed correctly on the contextual mneu", | ||
| "type": "minor" | ||
| } | ||
| ], | ||
| "packageName": "office-ui-fabric-react", | ||
| "email": "[email protected]" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,6 +36,7 @@ import { ContextualMenuSplitButton } from './ContextualMenuSplitButton'; | |
|
|
||
| export interface IContextualMenuState { | ||
| expandedMenuItemKey?: string; | ||
| expandedByMouseClick?: boolean; | ||
| dismissedMenuItemKey?: string; | ||
| contextualMenuItems?: IContextualMenuItem[]; | ||
| contextualMenuTarget?: Element; | ||
|
|
@@ -193,6 +194,7 @@ export class ContextualMenu extends BaseComponent<IContextualMenuProps, IContext | |
| useTargetAsMinWidth, | ||
| directionalHintFixed, | ||
| shouldFocusOnMount, | ||
| shouldFocusOnContainer, | ||
| title, | ||
| theme, | ||
| calloutProps, | ||
|
|
@@ -276,14 +278,14 @@ export class ContextualMenu extends BaseComponent<IContextualMenuProps, IContext | |
| hidden={ this.props.hidden } | ||
| > | ||
| <div | ||
| role='menu' | ||
| role={ shouldFocusOnContainer ? 'menu' : undefined } | ||
| aria-label={ ariaLabel } | ||
| aria-labelledby={ labelElementId } | ||
| style={ contextMenuStyle } | ||
| ref={ (host: HTMLDivElement) => this._host = host } | ||
| id={ id } | ||
| className={ this._classNames.container } | ||
| tabIndex={ 0 } | ||
| tabIndex={ shouldFocusOnContainer ? 0 : undefined } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For my understanding, this is the part of the code where we prevent focus being added to the container for the contextual menu?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. |
||
| onKeyDown={ this._onMenuKeyDown } | ||
| > | ||
| { title && <div className={ this._classNames.title } role='heading' aria-level={ 1 }> { title } </div> } | ||
|
|
@@ -295,7 +297,6 @@ export class ContextualMenu extends BaseComponent<IContextualMenuProps, IContext | |
| handleTabKey={ FocusZoneTabbableElements.all } | ||
| > | ||
| <ul | ||
| role='presentation' | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we getting rid of the role? At the very least shouldn't it be something like: role = { shouldFocusOnContainer ? 'presentation' : undefined } to match with your changes above.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This role='presentation' was useless and it's presence didn't impact screen reader behavior at all. |
||
| className={ this._classNames.list } | ||
| onKeyDown={ this._onKeyDown } | ||
| > | ||
|
|
@@ -814,6 +815,9 @@ export class ContextualMenu extends BaseComponent<IContextualMenuProps, IContext | |
| ev.stopPropagation(); | ||
| this._enterTimerId = this._async.setTimeout(() => { | ||
| targetElement.focus(); | ||
| this.setState({ | ||
| expandedByMouseClick: true | ||
| }); | ||
| this._onItemSubMenuExpand(item, targetElement); | ||
| this._enterTimerId = undefined; | ||
| }, NavigationIdleDelay); | ||
|
|
@@ -849,6 +853,12 @@ export class ContextualMenu extends BaseComponent<IContextualMenuProps, IContext | |
| if (item.key === this.state.expandedMenuItemKey) { // This has an expanded sub menu. collapse it. | ||
| this._onSubMenuDismiss(ev); | ||
| } else { // This has a collapsed sub menu. Expand it. | ||
| this.setState({ | ||
| // When Edge + Narrator are used together (regardless of if the button is in a form or not), pressing | ||
| // "Enter" fires this method and not _onMenuKeyDown. Checking ev.nativeEvent.detail differentiates | ||
| // between a real click event and a keypress event. | ||
| expandedByMouseClick: (ev.nativeEvent.detail !== 0) | ||
| }); | ||
| this._onItemSubMenuExpand(item, target); | ||
| } | ||
| } | ||
|
|
@@ -878,9 +888,17 @@ export class ContextualMenu extends BaseComponent<IContextualMenuProps, IContext | |
| } | ||
|
|
||
| private _onItemKeyDown = (item: any, ev: React.KeyboardEvent<HTMLElement>): void => { | ||
| const openKey = getRTL() ? KeyCodes.left : KeyCodes.right; | ||
| let openKey; | ||
| if (item.split) { | ||
| openKey = getRTL() ? KeyCodes.left : KeyCodes.right; | ||
| } else { | ||
| openKey = KeyCodes.enter; | ||
| } | ||
|
|
||
| if (ev.which === openKey && !item.disabled) { | ||
| this.setState({ | ||
| expandedByMouseClick: false | ||
| }); | ||
| this._onItemSubMenuExpand(item, ev.currentTarget as HTMLElement); | ||
| ev.preventDefault(); | ||
| } | ||
|
|
@@ -924,6 +942,7 @@ export class ContextualMenu extends BaseComponent<IContextualMenuProps, IContext | |
| isSubMenu: true, | ||
| id: this.state.subMenuId, | ||
| shouldFocusOnMount: true, | ||
| shouldFocusOnContainer: this.state.expandedByMouseClick, | ||
| directionalHint: getRTL() ? DirectionalHint.leftTopEdge : DirectionalHint.rightTopEdge, | ||
| className: this.props.className, | ||
| gapSpace: 0, | ||
|
|
||
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.
Why should the role change based on whether or not it is focusable? What gets labeled as a
menuin the other case?Uh oh!
There was an error while loading. Please reload this page.
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.
So when focus is NOT on the container (and is instead on the first menu item), adding role='menu' to the container stopped Narrator from reading the aria-label on the menu. Role='group' was fine but so was no role at all for narration behavior. Currently nothing gets labeled as a menu in the case where focus is on the first menu item (and narration works optimally). Is it a big accessibility no-no to not have role='menu' somewhere on a menu?