diff --git a/common/changes/office-ui-fabric-react/focus2_2018-03-09-17-45.json b/common/changes/office-ui-fabric-react/focus2_2018-03-09-17-45.json new file mode 100644 index 00000000000000..6a9d7581ea5811 --- /dev/null +++ b/common/changes/office-ui-fabric-react/focus2_2018-03-09-17-45.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "office-ui-fabric-react", + "comment": "Removed focusability on buttons for split buttons and spin buttons", + "type": "patch" + } + ], + "packageName": "office-ui-fabric-react", + "email": "chiechan@microsoft.com" +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/Button/BaseButton.tsx b/packages/office-ui-fabric-react/src/components/Button/BaseButton.tsx index 4d94e15a693146..0237c56493ecc5 100644 --- a/packages/office-ui-fabric-react/src/components/Button/BaseButton.tsx +++ b/packages/office-ui-fabric-react/src/components/Button/BaseButton.tsx @@ -163,7 +163,7 @@ export class BaseButton extends BaseComponent { + if (this._splitButtonContainer.value) { + this._splitButtonContainer.value.focus(); + } + const { menuProps } = this.props; const currentMenuProps = this.state.menuProps; currentMenuProps ? this._dismissMenu() : this._openMenu(); } @@ -432,7 +438,14 @@ export class BaseButton extends BaseComponent ; - + return ; } private _onMouseDown = (ev: React.MouseEvent) => { @@ -506,7 +519,23 @@ export class BaseButton extends BaseComponent) => { + if (ev.which === KeyCodes.enter) { + if (this._buttonElement.value) { + this._buttonElement.value.click(); + ev.preventDefault(); + ev.stopPropagation(); + } + } else { + this._onMenuKeyDown(ev); + } + } + private _onMenuKeyDown = (ev: React.KeyboardEvent) => { + if (this.props.disabled) { + return; + } + if (this.props.onKeyDown) { this.props.onKeyDown(ev); } @@ -518,13 +547,26 @@ export class BaseButton extends BaseComponent): boolean { + if (this.props.menuTriggerKeyCode) { + return ev.which === this.props.menuTriggerKeyCode; + } else { + return ev.which === KeyCodes.down && (ev.altKey || ev.metaKey); + } + } + private _onMenuClick = (ev: React.MouseEvent) => { const { onMenuClick } = this.props; if (onMenuClick) { diff --git a/packages/office-ui-fabric-react/src/components/Button/Button.test.tsx b/packages/office-ui-fabric-react/src/components/Button/Button.test.tsx index df1c3edab4bbbf..9908bc87ff259f 100644 --- a/packages/office-ui-fabric-react/src/components/Button/Button.test.tsx +++ b/packages/office-ui-fabric-react/src/components/Button/Button.test.tsx @@ -491,7 +491,7 @@ describe('Button', () => { expect(didClick).toEqual(true); }); - it('Pressing down on SplitButton triggers menu', () => { + it('Pressing alt + down on SplitButton triggers menu', () => { const renderedDOM: HTMLElement = renderIntoDocument( { ReactTestUtils.Simulate.keyDown(menuButtonElement, { - which: KeyCodes.down + which: KeyCodes.down, + altKey: true }); expect(renderedDOM.getAttribute('aria-expanded')).toEqual('true'); }); diff --git a/packages/office-ui-fabric-react/src/components/Button/SplitButton/SplitButton.styles.ts b/packages/office-ui-fabric-react/src/components/Button/SplitButton/SplitButton.styles.ts index 5ad66a78c19cf8..c596d1ee0fde60 100644 --- a/packages/office-ui-fabric-react/src/components/Button/SplitButton/SplitButton.styles.ts +++ b/packages/office-ui-fabric-react/src/components/Button/SplitButton/SplitButton.styles.ts @@ -19,22 +19,21 @@ export const getStyles = memoizeFunction(( }; const splitButtonStyles: IButtonStyles = { - splitButtonContainer: { - position: 'relative', - display: 'inline-block', - border: '1px solid transparent' - }, + splitButtonContainer: [ + getFocusStyle(theme, 0, 'relative', buttonHighContrastFocus), + { + display: 'inline-block' + } + ], splitButtonContainerFocused: { outline: 'none!important', - border: '1px solid' }, - splitButtonMenuButton: [ - getFocusStyle(theme, -1, 'relative', buttonHighContrastFocus), + splitButtonMenuButton: { padding: 6, height: 'auto', boxSizing: 'border-box', - border: '1px solid transparent', + border: 0, borderRadius: 0, outline: 'transparent', userSelect: 'none', @@ -45,8 +44,7 @@ export const getStyles = memoizeFunction(( verticalAlign: 'top', width: 32, marginLeft: -1 - } - ], + }, splitButtonDivider: { position: 'absolute', @@ -72,6 +70,11 @@ export const getStyles = memoizeFunction(( justifyContent: 'center', alignItems: 'center' }, + + splitButtonContainerDisabled: { + outline: 'none', + border: 'none' + } }; return concatStyleSets(splitButtonStyles, customStyles)!; diff --git a/packages/office-ui-fabric-react/src/components/Button/examples/Button.Split.Example.tsx b/packages/office-ui-fabric-react/src/components/Button/examples/Button.Split.Example.tsx index 36049fc90a03c7..3ab322885eda6c 100644 --- a/packages/office-ui-fabric-react/src/components/Button/examples/Button.Split.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/Button/examples/Button.Split.Example.tsx @@ -105,6 +105,33 @@ export class ButtonSplitExample extends React.Component { } } /> +
+ + +
); } diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.classNames.ts b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.classNames.ts index d568a25ea8df2c..0bd0d9a741f381 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.classNames.ts +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.classNames.ts @@ -198,6 +198,13 @@ export const getItemClassNames = memoizeFunction(( 'ms-ContextualMenu-itemText', styles.label ], - splitContainer: styles.splitButtonFlexContainer, + splitContainer: [ + styles.splitButtonFlexContainer, + !disabled && !checked && [{ + selectors: { + '.ms-Fabric.is-focusVisible &:focus, .ms-Fabric.is-focusVisible &:focus:hover': styles.rootFocused, + } + }] + ], }); }); \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.styles.ts b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.styles.ts index 1a2b87105d7a1f..25db1e745942f7 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.styles.ts +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.styles.ts @@ -168,13 +168,14 @@ export const getMenuItemStyles = memoizeFunction(( flexShrink: '0', fontSize: FontSizes.mini }, - splitButtonFlexContainer: { - display: 'flex', - height: ContextualMenuItemHeight, - flexWrap: 'nowrap', - justifyContent: 'center', - alignItems: 'center' - }, + splitButtonFlexContainer: [ + getFocusStyle(theme), { + display: 'flex', + height: ContextualMenuItemHeight, + flexWrap: 'nowrap', + justifyContent: 'center', + alignItems: 'center', + }], splitButtonSeparator: {} }; diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.tsx index 49009fea857bed..4f531f196a895e 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.tsx @@ -94,6 +94,8 @@ export class ContextualMenu extends BaseComponent; + private _adjustedFocusZoneProps: IFocusZoneProps; constructor(props: IContextualMenuProps) { @@ -139,6 +141,7 @@ export class ContextualMenu extends BaseComponent + this._splitButtonContainers.set(item.key, el) + } + role={ 'button' } aria-labelledby={ item.ariaLabel } + className={ classNames.splitContainer } aria-disabled={ this._isItemDisabled(item) } aria-haspopup={ true } aria-describedby={ item.ariaDescription } aria-checked={ item.isChecked || item.checked } aria-posinset={ focusableElementIndex + 1 } aria-setsize={ totalItemCount } + onKeyDown={ this._onSplitContainerItemKeyDown.bind(this, item) } + onClick={ this._executeItemClick.bind(this, item) } + tabIndex={ 0 } + data-is-focusable={ true } > { this._renderSplitPrimaryButton(item, classNames, index, hasCheckmarks!, hasIcons!) } { this._renderSplitDivider(item) } { this._renderSplitIconButton(item, classNames, index) } - + ); } @@ -618,7 +630,6 @@ export class ContextualMenu extends BaseComponent) { + if (ev.which === KeyCodes.enter) { + this._executeItemClick(item, ev); + ev.preventDefault(); + ev.stopPropagation(); + } else { + this._onItemKeyDown(item, ev); + } + } + private _renderSplitIconButton(item: IContextualMenuItem, classNames: IMenuItemClassNames, index: number) { const { contextualMenuItemAs: ChildrenRenderer = ContextualMenuItem } = this.props; const itemProps = { @@ -648,11 +670,11 @@ export class ContextualMenu extends BaseComponent this._onItemMouseDown(item, ev), - onMouseMove: this._onItemMouseMove.bind(this, item) + onMouseMove: this._onItemMouseMove.bind(this, item), + 'data-is-focusable': false }), ); @@ -849,7 +871,7 @@ export class ContextualMenu extends BaseComponent) { + private _executeItemClick(item: IContextualMenuItem, ev: React.MouseEvent | React.KeyboardEvent) { if (item.disabled || item.isDisabled) { return; } @@ -863,10 +885,10 @@ export class ContextualMenu extends BaseComponent) { const openKey = getRTL() ? KeyCodes.left : KeyCodes.right; - if (ev.which === openKey) { + if (ev.which === openKey && !item.disabled) { this._onItemSubMenuExpand(item, ev.currentTarget as HTMLElement); ev.preventDefault(); } @@ -878,6 +900,12 @@ export class ContextualMenu extends BaseComponent, IWith * menu item. * Returning true will dismiss the menu even if ev.preventDefault() was called. */ - onItemClick?: (ev?: React.MouseEvent, item?: IContextualMenuItem) => boolean | void; + onItemClick?: (ev?: React.MouseEvent | React.KeyboardEvent, item?: IContextualMenuItem) => boolean | void; /** * CSS class to apply to the context menu. @@ -329,7 +329,7 @@ export interface IContextualMenuItem { * Callback issued when the menu item is invoked. If ev.preventDefault() is called in onClick, click will not close menu. * Returning true will dismiss the menu even if ev.preventDefault() was called. */ - onClick?: (ev?: React.MouseEvent, item?: IContextualMenuItem) => boolean | void; + onClick?: (ev?: React.MouseEvent | React.KeyboardEvent, item?: IContextualMenuItem) => boolean | void; /** * An optional URL to navigate to upon selection diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/examples/ContextualMenu.Checkmarks.Example.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/examples/ContextualMenu.Checkmarks.Example.tsx index 3e9d34033dc2a9..cda93290150428 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/examples/ContextualMenu.Checkmarks.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/examples/ContextualMenu.Checkmarks.Example.tsx @@ -7,7 +7,7 @@ export interface IContextualMenuMultiselectExampleState { selection?: { [key: string]: boolean }; } -const keys: string[] = ['newItem', 'share', 'mobile', 'enablePrint', 'enableMusic', 'newSub', 'emailMessage', 'calendarEvent']; +const keys: string[] = ['newItem', 'share', 'mobile', 'enablePrint', 'enableMusic', 'newSub', 'emailMessage', 'calendarEvent', 'disabledNewSub', 'disabledEmailMessage', 'disabledCalendarEvent']; export class ContextualMenuCheckmarksExample extends React.Component<{}, IContextualMenuMultiselectExampleState> { @@ -102,6 +102,36 @@ export class ContextualMenuCheckmarksExample extends React.Component<{}, IContex split: true, onClick: this._onToggleSelect, }, + { + key: keys[8], + iconProps: { + iconName: 'MusicInCollectionFill' + }, + subMenuProps: { + items: [ + { + key: keys[6], + name: 'Email message', + canCheck: true, + isChecked: selection![keys[9]], + onClick: this._onToggleSelect + }, + { + key: keys[7], + name: 'Calendar event', + canCheck: true, + isChecked: selection![keys[10]], + onClick: this._onToggleSelect + } + ], + }, + name: 'Split Button', + canCheck: true, + isChecked: selection![keys[8]], + split: true, + onClick: this._onToggleSelect, + disabled: true + }, ] } } diff --git a/packages/office-ui-fabric-react/src/components/FocusZone/examples/FocusZone.Tabbable.Example.tsx b/packages/office-ui-fabric-react/src/components/FocusZone/examples/FocusZone.Tabbable.Example.tsx index 14d6d2308c0ce8..2fb2a8a02e843f 100644 --- a/packages/office-ui-fabric-react/src/components/FocusZone/examples/FocusZone.Tabbable.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/FocusZone/examples/FocusZone.Tabbable.Example.tsx @@ -2,11 +2,15 @@ import * as React from 'react'; /* tslint:enable:no-unused-variable */ -import { DefaultButton } from 'office-ui-fabric-react/lib/Button'; +import { DefaultButton, BaseButton } 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'; +const alertClicked = (): void => { + alert('Clicked'); +}; + export const FocusZoneTabbableExample = () => (
@@ -16,6 +20,26 @@ export const FocusZoneTabbableExample = () => ( Button 2 Button 3 +
diff --git a/packages/office-ui-fabric-react/src/components/SpinButton/SpinButton.tsx b/packages/office-ui-fabric-react/src/components/SpinButton/SpinButton.tsx index f73fbec1ec4911..433bc0b8e33160 100644 --- a/packages/office-ui-fabric-react/src/components/SpinButton/SpinButton.tsx +++ b/packages/office-ui-fabric-react/src/components/SpinButton/SpinButton.tsx @@ -227,6 +227,7 @@ export class SpinButton extends BaseComponent
diff --git a/packages/office-ui-fabric-react/src/components/SpinButton/__snapshots__/SpinButton.test.tsx.snap b/packages/office-ui-fabric-react/src/components/SpinButton/__snapshots__/SpinButton.test.tsx.snap index 5195dc0f5eaa6a..2e4db82ba4d660 100644 --- a/packages/office-ui-fabric-react/src/components/SpinButton/__snapshots__/SpinButton.test.tsx.snap +++ b/packages/office-ui-fabric-react/src/components/SpinButton/__snapshots__/SpinButton.test.tsx.snap @@ -213,7 +213,7 @@ exports[`SpinButton renders SpinButton correctly 1`] = ` background-color: Highlight; color: HighlightText; } - data-is-focusable={true} + data-is-focusable={false} disabled={undefined} onMouseDown={[Function]} onMouseLeave={[Function]} @@ -326,7 +326,7 @@ exports[`SpinButton renders SpinButton correctly 1`] = ` background-color: Highlight; color: HighlightText; } - data-is-focusable={true} + data-is-focusable={false} disabled={undefined} onMouseDown={[Function]} onMouseLeave={[Function]}