-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Focus Zone: Allow Tab to Skip Selection #4061
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 17 commits
9efa1f3
65d118b
06282e1
29ca036
e493f66
6726d5b
a2b3691
3a53eb4
7b0413f
bb3ff0b
9da77fd
e478938
ef7dd1e
a8b146a
cffed1e
e2a1c59
def63ae
7cfcf8b
787fa4a
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": "Focus Zone: Add support for tab to skip selection elements", | ||
| "type": "minor" | ||
| } | ||
| ], | ||
| "packageName": "office-ui-fabric-react", | ||
| "email": "chiechan@microsoft.com" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| import * as React from 'react'; | ||
| import { | ||
| FocusZoneDirection, | ||
| FocusZoneTabbableElements, | ||
| IFocusZone, | ||
| IFocusZoneProps | ||
| } from './FocusZone.types'; | ||
|
|
@@ -63,17 +64,26 @@ 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 _processingTabKey: boolean; | ||
|
|
||
| constructor(props: IFocusZoneProps) { | ||
| super(props); | ||
|
|
||
| this._warnDeprecations({ rootProps: undefined }); | ||
| this._warnDeprecations({ | ||
| rootProps: undefined, | ||
| 'allowTabKey': 'handleTabKey' | ||
| }); | ||
|
|
||
|
|
||
| this._id = getId('FocusZone'); | ||
|
|
||
| this._focusAlignment = { | ||
| left: 0, | ||
| top: 0 | ||
| }; | ||
|
|
||
| this._processingTabKey = false; | ||
| } | ||
|
|
||
| public componentDidMount() { | ||
|
|
@@ -361,21 +371,19 @@ export class FocusZone extends BaseComponent<IFocusZoneProps, {}> implements IFo | |
| return; | ||
|
|
||
| case KeyCodes.tab: | ||
| if (this.props.allowTabKey) { | ||
| if (this.props.allowTabKey || | ||
| this.props.handleTabKey === FocusZoneTabbableElements.all || | ||
| (this.props.handleTabKey === FocusZoneTabbableElements.inputOnly && this._isElementInput(ev.target as HTMLElement))) { | ||
| let focusChanged = false; | ||
| this._processingTabKey = true; | ||
| if (direction === FocusZoneDirection.vertical || | ||
| !this._shouldWrapFocus(this._activeElement as HTMLElement, NO_HORIZONTAL_WRAP)) { | ||
| if (ev.shiftKey) { | ||
| this._moveFocusUp(); | ||
| } else { | ||
| this._moveFocusDown(); | ||
| } | ||
| break; | ||
| focusChanged = ev.shiftKey ? this._moveFocusUp() : this._moveFocusDown(); | ||
| } else if (direction === FocusZoneDirection.horizontal || direction === FocusZoneDirection.bidirectional) { | ||
| if (ev.shiftKey) { | ||
| this._moveFocusLeft(); | ||
| } else { | ||
| this._moveFocusRight(); | ||
| } | ||
| focusChanged = ev.shiftKey ? this._moveFocusLeft() : this._moveFocusRight(); | ||
| } | ||
| this._processingTabKey = false; | ||
| if (focusChanged) { | ||
| break; | ||
|
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 is the code now only breaking if focusChanged? |
||
| } | ||
| } | ||
|
|
@@ -646,7 +654,7 @@ export class FocusZone extends BaseComponent<IFocusZoneProps, {}> implements IFo | |
| return distance; | ||
| }, | ||
| undefined /*ev*/, | ||
| (shouldWrap || !getRTL()) | ||
| shouldWrap | ||
| )) { | ||
| this._setFocusAlignment(this._activeElement as HTMLElement, true, false); | ||
| return true; | ||
|
|
@@ -674,7 +682,7 @@ export class FocusZone extends BaseComponent<IFocusZoneProps, {}> implements IFo | |
| return distance; | ||
| }, | ||
| undefined /*ev*/, | ||
| (shouldWrap || getRTL()) | ||
| shouldWrap | ||
| )) { | ||
| this._setFocusAlignment(this._activeElement as HTMLElement, true, false); | ||
| return true; | ||
|
|
@@ -787,7 +795,9 @@ export class FocusZone extends BaseComponent<IFocusZoneProps, {}> implements IFo | |
| } | ||
|
|
||
| private _shouldInputLoseFocus(element: HTMLInputElement, isForward?: boolean) { | ||
| if (element && | ||
| // If a tab was used, we want to focus on the next element. | ||
| if (!this._processingTabKey && | ||
| element && | ||
| element.type && | ||
| ALLOWED_INPUT_TYPES.indexOf(element.type.toLowerCase()) > -1) { | ||
| const selectionStart = element.selectionStart; | ||
|
|
@@ -799,9 +809,11 @@ export class FocusZone extends BaseComponent<IFocusZoneProps, {}> implements IFo | |
| // 1. There is range selected. | ||
| // 2. When selection start is larger than 0 and it is backward. | ||
| // 3. when selection start is not the end of lenght and it is forward. | ||
| // 4. We press any of the arrow keys when we're in a mode that supports tab (only losing focus if we hit tab) | ||
| if (isRangeSelected || | ||
| (selectionStart > 0 && !isForward) || | ||
| (selectionStart !== inputValue.length && isForward)) { | ||
| (selectionStart !== inputValue.length && isForward) || | ||
| !!this.props.handleTabKey) { | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -108,9 +108,18 @@ export interface IFocusZoneProps extends React.HTMLAttributes<HTMLElement | Focu | |
| * Allows tab key to be handled to tab through a list of items in the focus zone, | ||
| * 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. | ||
| * @deprecated Use 'handleTabKey' instead. | ||
| * | ||
| */ | ||
| allowTabKey?: boolean; | ||
|
|
||
| /** | ||
| * Allows tab key to be handled to tab through a list of items in the focus zone, | ||
| * 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. | ||
| */ | ||
| handleTabKey?: FocusZoneTabbableElements; | ||
|
|
||
| /** | ||
| * Whether the to check for data-no-horizontal-wrap or data-no-vertical-wrap attributes | ||
| * when determining how to move focus | ||
|
|
@@ -119,6 +128,18 @@ export interface IFocusZoneProps extends React.HTMLAttributes<HTMLElement | Focu | |
| checkForNoWrap?: boolean; | ||
| } | ||
|
|
||
| export const enum FocusZoneTabbableElements { | ||
|
|
||
| /** Tabbing is not allowed */ | ||
| none = 0, | ||
|
|
||
| /** All tabbing action is allowed */ | ||
| all = 1, | ||
|
|
||
| /** Tabbing is allowed only on input elements */ | ||
| inputOnly = 2 | ||
| } | ||
|
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. If you update the prop name to something like |
||
|
|
||
| export enum FocusZoneDirection { | ||
| /** Only react to up/down arrows. */ | ||
| vertical = 0, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| :global { | ||
| .ms-FocusZoneTabbableExample .ms-Row { | ||
| display: block; | ||
| margin: 5px; | ||
| } | ||
|
|
||
| .ms-FocusZoneTabbableExample-textField { | ||
| display: inline-block; | ||
| width: 300px; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| /* tslint:disable:no-unused-variable */ | ||
| import * as React from 'react'; | ||
| /* tslint:enable:no-unused-variable */ | ||
|
|
||
| import { DefaultButton } from 'office-ui-fabric-react/lib/Button'; | ||
| import { FocusZone, FocusZoneDirection, FocusZoneTabbableElements } from 'office-ui-fabric-react/lib/FocusZone'; | ||
| import { TextField } from 'office-ui-fabric-react/lib/TextField'; | ||
| import './FocusZone.Tabbable.Example.scss'; | ||
|
|
||
| export const FocusZoneTabbableExample = () => ( | ||
| <div className='ms-FocusZoneTabbableExample'> | ||
| <div className='ms-Row'> | ||
| <FocusZone direction={ FocusZoneDirection.horizontal } handleTabKey={ FocusZoneTabbableElements.all } isCircularNavigation={ true }> | ||
| <span>Circular Tabbable FocusZone: </span> | ||
| <DefaultButton>Button 1</DefaultButton> | ||
| <DefaultButton>Button 2</DefaultButton> | ||
| <TextField value='FocusZone TextField' className='ms-FocusZoneTabbableExample-textField' /> | ||
| <DefaultButton>Button 3</DefaultButton> | ||
| </FocusZone> | ||
| </div> | ||
| <div className='ms-Row'> | ||
| <FocusZone direction={ FocusZoneDirection.horizontal } handleTabKey={ FocusZoneTabbableElements.inputOnly } isCircularNavigation={ false }> | ||
| <span>Input Only FocusZone: </span> | ||
| <DefaultButton>Button 1</DefaultButton> | ||
| <DefaultButton>Button 2</DefaultButton> | ||
| <TextField value='FocusZone TextField' className='ms-FocusZoneTabbableExample-textField' /> | ||
| <DefaultButton>Button 3</DefaultButton> | ||
| </FocusZone> | ||
| </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.
Put this onto multiple lines