-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Focus Zone: Allow Tab to Skip Selection #4061
Conversation
@@ -58,6 +58,9 @@ export class FocusZone extends BaseComponent<IFocusZoneProps, {}> implements IFo | |||
private _focusAlignment: IPoint; | |||
private _isInnerZone: boolean; | |||
|
|||
/** Used to allow us to move to next focusable element even when we're focusing on a input element when pressing tab */ | |||
private _tabPressActive: boolean; |
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.
maybe a better name is _processingTabKey or something like that since this flag is for when the tab key press is being processed
focusChanged = ev.shiftKey ? this._moveFocusLeft() : this._moveFocusRight(); | ||
} | ||
this._tabPressActive = false; | ||
if (focusChanged) { | ||
break; |
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 is the code now only breaking if focusChanged?
@@ -487,7 +488,7 @@ export class FocusZone extends BaseComponent<IFocusZoneProps, {}> implements IFo | |||
} | |||
|
|||
if (this._isElementInput(element)) { | |||
if (!this._shouldInputLoseFocus(element as HTMLInputElement, isForward)) { | |||
if (!this._tabPressActive && !this._shouldInputLoseFocus(element as HTMLInputElement, isForward)) { |
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.
Consider moving this check into shouldInputLoseFocus since it's directly related to if the input should lose focus when tabbing
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.
A few minor changes are needed but looks good overall
Pull the latest changes from master, this PR will need to be updated with the changes made in #3962 |
@@ -109,7 +109,7 @@ export interface IFocusZoneProps extends React.HTMLAttributes<HTMLElement | Focu | |||
* an unfortunate side effect is that users will not be able to tab out of the focus zone | |||
* and have to hit escape or some other key. | |||
*/ | |||
allowTabKey?: boolean; | |||
tabPermission?: FocusZoneTabbableElements; |
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.
Let's go ahead and deprecate allowTabKey
instead of removing it. Remove all references to it (setting the value to tabPermission
if it is passed in), put at @deprecated
in the jsdoc comment, and add a _warnDeprecations
for that prop referencing tabPermission
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.
Consider updating the prop name, maybe handleTabKey
@@ -119,6 +119,18 @@ export interface IFocusZoneProps extends React.HTMLAttributes<HTMLElement | Focu | |||
checkForNoWrap?: boolean; | |||
} | |||
|
|||
export enum FocusZoneTabbableElements { |
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.
export const enum ...
it will help keep size down.
@@ -788,6 +790,10 @@ export class FocusZone extends BaseComponent<IFocusZoneProps, {}> implements IFo | |||
} | |||
|
|||
private _shouldInputLoseFocus(element: HTMLInputElement, isForward?: boolean) { | |||
if (this.props.tabPermission === FocusZoneTabbableElements.inputOnly && !this._processingTabKey) { |
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.
You don't know that the element is an input at this point, this check should be moved down to the if statement below.
@@ -787,7 +790,13 @@ export class FocusZone extends BaseComponent<IFocusZoneProps, {}> implements IFo | |||
} | |||
|
|||
private _shouldInputLoseFocus(element: HTMLInputElement, isForward?: boolean) { | |||
if (element && | |||
if (this.props.tabPermission === FocusZoneTabbableElements.inputOnly && !this._processingTabKey) { |
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.
You don't know if the element is an input element at this point. This needs to be moved down to the if statement below.
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 believe we should. The element we pass in is of type: HTMLInputElement
However, I think it would make more sense to add it with the rest of the cases, so I'll move it down.
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.
Inputs can be of different types, that's why the check below is set up the way that it is
|
||
// allowTabKeyOnInput is true and allowTabKey is false | ||
inputOnly = 2 | ||
} |
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 you update the prop name to something like handleTabKey
the enum name and string values should be updated... for the values maybe never = 0, always= 1, inputOnly = 2
@@ -1048,7 +1048,7 @@ describe('FocusZone', () => { | |||
const tabDownListener = jest.fn(); | |||
const component = ReactTestUtils.renderIntoDocument( | |||
<div { ...{ onFocusCapture: _onFocus, onKeyDown: tabDownListener } }> | |||
<FocusZone {...{ allowTabKey: true, isCircularNavigation: true }}> | |||
<FocusZone {...{ tabPermission: FocusZoneTabbableElements.all, isCircularNavigation: 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.
You should add a new test case for inputOnly
@@ -361,21 +367,18 @@ export class FocusZone extends BaseComponent<IFocusZoneProps, {}> implements IFo | |||
return; | |||
|
|||
case KeyCodes.tab: | |||
if (this.props.allowTabKey) { | |||
if (this.props.tabPermission === FocusZoneTabbableElements.all | |||
|| (this.props.tabPermission === FocusZoneTabbableElements.inputOnly && this._isElementInput(ev.target as HTMLElement))) { |
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.
put the || at the end of the previous line, it makes the if statement more readable
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.
some minor updates need to be made
@@ -1132,7 +1132,7 @@ describe('FocusZone', () => { | |||
<div { ...{ onFocusCapture: _onFocus, onKeyDown: tabDownListener } }> | |||
<FocusZone> | |||
<button className='a'>a</button> | |||
</FocusZone> | |||
</FocusZone>g |
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.
Did you mean to have this 'g' there?
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.
Looks good
if (this.props.allowTabKey) { | ||
if (this.props.allowTabKey || | ||
this.props.handleTabKey === FocusZoneTabbableElements.all || | ||
(this.props.handleTabKey === FocusZoneTabbableElements.inputOnly && this._isElementInput(ev.target as HTMLElement))) { |
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.
Put this onto multiple lines
* master: (28 commits) Scrollable pane sort stickies (microsoft#4111) Allow ScrollablePane to accept native properties. (microsoft#4095) Sticky (microsoft#4091) Applying package updates. [ColorRectangle, Sticky] Fixed null root refs (microsoft#4099) DatePicker: order of callbacks for onSelectDate and onAfterMenuDismiss (microsoft#4092) Applying package updates. Alhenry fix split button props (microsoft#4090) Fixing ComboBox styling by reverting button classname move (microsoft#4088) Update CODEOWNERS Undoing terrible change. [DetailsList] Fixed focus test (microsoft#4087) Added icons package screener test (microsoft#4082) ContextualMenu: Fix ContextualMenuUtility imports (microsoft#4085) [ContextualMenu] Made disabled buttons focusable (microsoft#4074) Convert Check to mergeStyles (microsoft#3880) [DetailsList] Add public focusIndex function (microsoft#3852) ComboBox button should have data-is-focusable="false" (microsoft#4070) Applying package updates. Focus Zone: Allow Tab to Skip Selection (microsoft#4061) ...
* added return for focus zone when focus change fail * add tab override for focus zone * added support for skipping selection with tab * add change files * added tabbable focus zone example * got rid of unnecessary return * used member instead of passing varaables down * changed tab field name; moved tab location * removed duplicate component; fixed lint problems * added actual tabbable example scss to fix build crash * Adding FocusZoneTabbableElements enum * add new input only mode that allows skipping of focus on tab for input elements * cleaned things up and added better comments * updated pr for everything except test * added unit test * optimized if statement; cleaned up test * separated ampersand for if statement * update all lint probelms
Problem:
If tabbing is enabled, we have a problem where we can't tab through input html elements and we would be stuck on the input button
Solution:
The reason for this problem is, because when we're trying to switch focus on an input element, we would stop the focus. For arrow keys that would be the correct thing to do, however if we're tabbing, we want to ignore that and focus on the next element.
I also changed the way we handle tab to be more like arrow keys where we only stop if we change focus otherwise we'll let the tab event to continue to propagate.
Validation:
I added a new example component that allows tabbing and circular navigation along with an text input so that we can more easily test to see if have a regression in the focus zone where we would get stuck an an input element.
Here is what it is now:
Here is what it was before where it get stuck: