-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Removing ability to focus with keyboarding on menu buttons for split buttons and and spin boxes #4222
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
Conversation
…only on the container
|
I recently rejected this in screener as it is breaking our Fabric focus. |
jspurlin
left a comment
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.
@betrue-final-final how is this breaking focus in fabric?
|
@jspurlin Before I didn't have the correct styling for the disabled split button (we were still outlining it), but I've since made the correct changes. |
|
@chang47 it looks like the bundle maxSize needs to be updated from 44.7 to 44.9 |
jspurlin
left a comment
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.
@chang47 Can you include some gifs showing the old/new behavior?
|
@jspurlin I added the before and after and I also updated the bundle size. |
| ] | ||
| } } | ||
| /> | ||
| </div> <div> |
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.
weird spacing
| onKeyDown={ this._onSplitContainerItemKeyDown.bind(this, item) } | ||
| onClick={ this._executeItemClick.bind(this, item) } | ||
| tabIndex={ 0 } | ||
| aria-hidden={ true } |
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 aria-hidden is incorrect, it should not be set on the 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.
Also, shouldn't this element have data-is-focusable="true"?
| aria-hidden={ true } | ||
| className={ classNames.splitContainer } | ||
| > | ||
| <span aria-hidden={ true }> |
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.
since the flex classname is on the div rather than the span, that just means that the span will flex to fit, but there's only one element so it will always be able to fit. Should this span have been deleted? The contents of the span should likely still be flexed.
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 put it back, because I wanted to have a way to have aria-hidden for all the child elements, but seeing as each of the pieces in the child element is uniquely created here, I don't see the harm in adding aria-hidden to each of the individual pieces.
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.
Is there any reason why we aren't just putting the span back to the way it was before this change (the more scoped we make the change the better, and making this change doesn't seem necessary)?
| if (ev.which === KeyCodes.enter) { | ||
| this._executeItemClick(item, ev); | ||
| } else { | ||
| this._onItemKeyDown(item, ev); |
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 pretty sure you don't need this else as the event will continue to propagate to the next event handler.
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 an interesting point to discuss, if we press enter, on a button, that's the equivalent of pressing the onClick event, but should we also try to handle the onKeyDown event too? @jspurlin what do you think?
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.
@chang47 you are correct, in this case (handling enter) we want to tie that with executeItemClick and not also fire onItemKeyDown
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.
In the if statement you should prevent default and stop propagation, since it will propagate up to the keyhandler that's on the root unless you stop propagation.
joschect
left a comment
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.
Approved with suggestions.
|
|
||
| return <BaseButton { ...splitButtonProps } onMouseDown={ this._onMouseDown } />; | ||
|
|
||
| return <BaseButton {...splitButtonProps} onMouseDown={ this._onMouseDown } tabIndex={ -1 } />; |
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.
Adding tabIndex is an antipattern here. If this is hosted in a focusZone, it will become focusable again.
Plus, if something should not be focusable, it shouldn't be rendered as a button.
My preference here is to convert the menu click target to a DIV or SPAN which can soak clicks and touches, but not receive focus, and not attempt to render as a BaseButton.
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.
Setting the tabIndex=-1 and data-is-focusable=false ensures that the button will not be keyboard focusable when it is or is not inside of a FocusZone.
As for if the individual portions of the splitButton should be buttons, I'm working with Narrator on defining a splitButton so that they will always be able to tell that it is in fact a split button. One approach may be a button with x, y, z markup and the fact that it contains two buttons... We could change them to be divs or spans if we want, the only downside of that is now we lose the consistency when the splitButton is rendered in a ContextualMenu. We we want to update that as well then we'll lose the ability to use the default render functionality
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 being said, I just noticed the span wrapping the splitButton contents here does not have aria-hidden="true" (which it should to align with how splitButtons are represented in ContextualMenus)
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.
Chatted with @dzearing and we should go ahead and simplify this to not be created with two buttons
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.
Talking with @jspurlin offline, we were discussing the idea of just removing the wrapping span. now that there is a single hittarget, there is no need for the wrapping span, which would reduce dom (and bundle size, and work from focus zone).
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.
@dzearing I've spent a couple of hours trying to make the fix, but there were some not so simple styles that have to be made. Considering that the PR is to add focus on split button containers and some other components, I think that's outside of the scope of this specific issue. However, I have logged a bug for this issue at VSTS: 2209061 and will take a 2nd crack at it some other time.
|
@dzearing I made the changes we discussed offline. Specifically here's what was added extra:
|
| 'aria-haspopup': true, | ||
| 'aria-expanded': this._isExpanded | ||
| 'aria-expanded': this._isExpanded, | ||
| 'data-is-focusable': !isPrimaryButtonDisabled |
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.
Shouldn't this always not be focusable, even if the primary action is disabled? You want the interaction with a control to always be consistent.
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 working with the assumption that if the primary button is disabled, we don't want to focus on the whole container and want to focus on the menu button. Would that not be the case?
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.
No, that would not be the case. Focus should always only go on the container and the splitButton should always behave consistently for keyboard users
| if (onMenuClick) { | ||
| onMenuClick(ev, this); | ||
| } | ||
|
|
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 seems strange that this calls onMenuClick (considering that this is keydown)... If this want to call onMenuClick, why isn't this calling _onMenuClick?
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 want to trigger the primary action when we hit the enter button if we're focusing on the html element. This works for buttons, unfortunately, the container is a div so pressing enter won't trigger the onClick event.
I can move the code in onMenuClick and call it in both places, but they would both also reference onClick events. What do you think?
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 think as long as _onToggleMenu is updating the focus correctly this is fine
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 resolved, we'll keep this code here and add the focus change to onToggleMenu
| this.props.menuTriggerKeyCode !== null && | ||
| ev.which === (this.props.menuTriggerKeyCode === undefined ? KeyCodes.down : this.props.menuTriggerKeyCode)) { | ||
| this._isValidMenuOpenKey(ev)) { | ||
| this._onToggleMenu(); |
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.
as part of _onToggleMenu could we set focus to the container before setting the state to toggle the menu? This would allow focus to return to the whole split button once the menu is dismissed
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 that be what we want to do? By doing this we would force the focus to go back to the menu in every scenario.
From my understanding, we want focus to go back to the previous focused element, wIth the suggested change, we always focus on the button when we close the menu.
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.
The focus should always only be on the container (even if it was fired against the split button portion). We should never be setting focus on the individual portions of the splitButton
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.
Added the changes here. Now we only don't focus the split button if everything is disabled.
| } as IContextualMenuItem; | ||
|
|
||
| return React.createElement('button', | ||
| assign({}, getNativeProps(itemProps, buttonProperties), { |
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.
Have splitButtons in menus been updated to behave the same way that splitButtons outside of menus behave? I think they will need to be updated at least to put focus on the container before the menu expands (so that focus will go back to the container when the menu collapses)
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.
Style wise, it has the same hover affect, because I did use the getFocusStyle, unfortunately, I introduced a regression somewhere with the styles and it's not looking right. I'm looking back at how to fix it.
As for putting the focus back, you're right that's something we should have. I'll add that in.
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.
The base button changes should apply to the contextual menu for free. the only possible change might be when we open a sub menu, but I don't think that'll be an issue as we have to focus on the split button in the contextual menu to open it.
In the end, after we close all the sub-menu we should put our focus back to the button that opened the contextual menu anyways.
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 retract my earlier statement, like we discussed offline there was a case where if we keyboard on the menu and then hover over a split button menu and hit escape we would focus on the split button and not whole div. I've fixed that.
| buttonProps, | ||
| { | ||
| onClick: undefined, | ||
| tabIndex: -1, |
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.
Tabindex -1 still seems like a bad idea
Problem:
Currently, in our spin buttons and our split buttons, we can keyboard to the menu button for split buttons and the increment/decrement button on the spin button if it's in a focus zone.
In terms of accessibility, they are an extra set of controls that are not needed. Instead focus should just be solely set on the the whole component.
Solution:
Spin button: added data-is-focusable=false for the the icon buttons so that we won't be able to focus on them while in a focus zone.
Split button:
Contextual Menu Split Button:
Validation:
Spin button: focus no longer goes to the icon buttons in both focus zones and in normal situations when keyboarding.
Split Button: Same as spin button.
Contextual Menu: Same as spin button.
Pictures: Coming soon...
New changes with the contextual menu:

Old contextual menu split button:

New changes with a normal split button:

Old split button:
