From 2301c6227f9b1c2eb6541ba115b59f6dbe982015 Mon Sep 17 00:00:00 2001 From: "REDMOND\\chiechan" Date: Thu, 19 Apr 2018 10:30:59 -0700 Subject: [PATCH 01/19] moved split button out of the contextual menu --- .../src/stories/ContextualMenu.stories.tsx | 47 ++ .../ContextualMenu/ContextualMenu.tsx | 179 ++--- .../ContextualMenuSplitButton.test.tsx | 49 ++ .../ContextualMenuSplitButton.tsx | 226 ++++++ .../ContextualMenuSplitButton.types.ts | 94 +++ .../ContextualMenuSplitButton.test.tsx.snap | 678 ++++++++++++++++++ .../contextualMenu/contextualMenuUtility.ts | 4 + 7 files changed, 1149 insertions(+), 128 deletions(-) create mode 100644 packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.test.tsx create mode 100644 packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx create mode 100644 packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.types.ts create mode 100644 packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap diff --git a/apps/vr-tests/src/stories/ContextualMenu.stories.tsx b/apps/vr-tests/src/stories/ContextualMenu.stories.tsx index 4f8dda89f37f24..5e6f6cff36cff8 100644 --- a/apps/vr-tests/src/stories/ContextualMenu.stories.tsx +++ b/apps/vr-tests/src/stories/ContextualMenu.stories.tsx @@ -178,6 +178,48 @@ const itemsWithHeaders = [ } ]; +const itemsWithSplitButtonSubmenu = [ + { + key: 'share', + split: true, + onClick: () => { }, + subMenuProps: { + items: [ + { + key: 'sharetotwitter', + name: 'Share to Twitter', + }, + { + key: 'sharetofacebook', + name: 'Share to Facebook', + }, + { + key: 'sharetoemail', + split: true, + onClick: () => { }, + name: 'Share to Email', + subMenuProps: { + items: [ + { + key: 'sharetooutlook_1', + name: 'Share to Outlook', + title: 'Share to Outlook', + }, + { + key: 'sharetogmail_1', + name: 'Share to Gmail', + title: 'Share to Gmail', + } + ], + }, + }, + ], + }, + name: 'Share' + } +]; + + storiesOf('ContextualMenu', module) .addDecorator(FabricDecorator) .addDecorator(story => ( @@ -214,4 +256,9 @@ storiesOf('ContextualMenu', module) + )) + .add('With split button submenu', () => ( + )); \ No newline at end of file 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 bf64647c86c86c..4693d2794c7fef 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.tsx @@ -6,8 +6,7 @@ import { IMenuItemClassNames, IContextualMenuClassNames, getContextualMenuClassNames, - getItemClassNames, - getSplitButtonVerticalDividerClassNames + getItemClassNames } from './ContextualMenu.classNames'; import { BaseComponent, @@ -27,14 +26,12 @@ import { css, shouldWrapFocus } from '../../Utilities'; -import { hasSubmenu, getIsChecked } from '../../utilities/contextualMenu/index'; +import { hasSubmenu, getIsChecked, isItemDisabled } from '../../utilities/contextualMenu/index'; import { withResponsiveMode, ResponsiveMode } from '../../utilities/decorators/withResponsiveMode'; import { Callout } from '../../Callout'; import { IIconProps } from '../../Icon'; -import { - VerticalDivider -} from '../../Divider'; import { ContextualMenuItem } from './ContextualMenuItem'; +import { ContextualMenuSplitButton } from './ContextualMenuSplitButton'; export interface IContextualMenuState { expandedMenuItemKey?: string; @@ -94,8 +91,6 @@ export class ContextualMenu extends BaseComponent; - private _adjustedFocusZoneProps: IFocusZoneProps; constructor(props: IContextualMenuProps) { @@ -114,7 +109,6 @@ export class ContextualMenu extends BaseComponent { @@ -363,7 +357,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 } - onMouseEnter={ this._onItemMouseEnter.bind(this, { ...item, subMenuProps: null, items: null }) } - onMouseLeave={ this._onMouseItemLeave.bind(this, { ...item, subMenuProps: null, items: null }) } - onMouseMove={ this._onItemMouseMove.bind(this, { ...item, subMenuProps: null, items: null }) } - 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) } - - ); - } - - private _renderSplitPrimaryButton(item: IContextualMenuItem, classNames: IMenuItemClassNames, index: number, hasCheckmarks: boolean, hasIcons: boolean) { - - const isChecked: boolean | null | undefined = getIsChecked(item); - const canCheck: boolean = isChecked !== null; - const defaultRole = canCheck ? 'menuitemcheckbox' : 'menuitem'; - const { contextualMenuItemAs: ChildrenRenderer = ContextualMenuItem } = this.props; - - const itemProps = { - key: item.key, - disabled: this._isItemDisabled(item) || item.primaryDisabled, - name: item.name, - className: classNames.splitPrimary, - role: item.role || defaultRole, - canCheck: item.canCheck, - isChecked: item.isChecked, - checked: item.checked, - icon: item.icon, - iconProps: item.iconProps, - 'data-is-focusable': false, - 'aria-hidden': true - } as IContextualMenuItem; - return React.createElement('button', - getNativeProps(itemProps, buttonProperties), - , - ); - } - - private _onSplitContainerItemKeyDown(item: any, ev: React.KeyboardEvent) { - 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 = { - onClick: this._onSplitItemClick.bind(this, item), - disabled: this._isItemDisabled(item), - className: classNames.splitMenu, - subMenuProps: item.subMenuProps, - submenuIconProps: item.submenuIconProps, - split: true, - } as IContextualMenuItem; - - return React.createElement('button', - assign({}, getNativeProps(itemProps, buttonProperties), { - onMouseEnter: this._onItemMouseEnter.bind(this, item), - onMouseLeave: this._onMouseItemLeave.bind(this, item), - onMouseDown: (ev: any) => this._onItemMouseDown(item, ev), - onMouseMove: this._onItemMouseMove.bind(this, item), - 'data-is-focusable': false, - 'aria-hidden': true - }), - + ); } - private _renderSplitDivider(item: IContextualMenuItem) { - const getDividerClassnames = item.getSplitButtonVerticalDividerClassNames || getSplitButtonVerticalDividerClassNames; - return ; - } - private _getIconProps(item: IContextualMenuItem): IIconProps { const iconProps: IIconProps = item.iconProps ? item.iconProps : { iconName: item.icon @@ -782,14 +704,22 @@ export class ContextualMenu extends BaseComponent) { + this._onItemMouseEnterBase(item, ev, ev.target as HTMLElement); + } + + private _onItemMouseEnterBase = (item: any, ev: React.MouseEvent, target: HTMLElement): void => { if (!this._isScrollIdle) { return; } - this._updateFocusOnMouseEvent(item, ev); + this._updateFocusOnMouseEvent(item, ev, target); } private _onItemMouseMove(item: any, ev: React.MouseEvent) { + this._onItemMouseMoveBase(item, ev, ev.target as HTMLElement); + } + + private _onItemMouseMoveBase = (item: any, ev: React.MouseEvent, target: HTMLElement): void => { const targetElement = ev.currentTarget as HTMLElement; @@ -799,9 +729,8 @@ export class ContextualMenu extends BaseComponent): void => { if (!this._isScrollIdle) { return; @@ -833,8 +762,8 @@ export class ContextualMenu extends BaseComponent) { - const targetElement = ev.currentTarget as HTMLElement; + private _updateFocusOnMouseEvent(item: IContextualMenuItem, ev: React.MouseEvent, target: HTMLElement) { + const targetElement = target; const { subMenuHoverDelay: timeoutDuration = this._navigationIdleDelay } = this.props; if (item.key === this.state.expandedMenuItemKey) { @@ -857,9 +786,7 @@ export class ContextualMenu extends BaseComponent { targetElement.focus(); - const splitButtonContainer = this._splitButtonContainers.get(item.key); - this._onItemSubMenuExpand(item, - ((item.split && splitButtonContainer) ? splitButtonContainer : targetElement) as HTMLElement); + this._onItemSubMenuExpand(item, targetElement); this._enterTimerId = undefined; }, this._navigationIdleDelay); } else { @@ -871,26 +798,25 @@ export class ContextualMenu extends BaseComponent) { - if (item.onMouseDown) { + private _onItemMouseDown = (item: IContextualMenuItem, ev: React.MouseEvent): void => { + if (item.onMouseDown && this._enterTimerId === undefined) { item.onMouseDown(item, ev); } } - private _onItemClick(item: IContextualMenuItem, ev: React.MouseEvent) { + private _onItemClick = (item: IContextualMenuItem, ev: React.MouseEvent | React.KeyboardEvent): void => { this._onItemClickBase(item, ev, ev.currentTarget as HTMLElement); } - private _onSplitItemClick(item: IContextualMenuItem, ev: React.MouseEvent) { - const splitButtonContainer = this._splitButtonContainers.get(item.key); - // get the whole splitButton container to base the menu off of - this._onItemClickBase(item, ev, - (splitButtonContainer ? splitButtonContainer : ev.currentTarget) as HTMLElement); - } - - private _onItemClickBase(item: IContextualMenuItem, ev: React.MouseEvent, target: HTMLElement) { + private _onItemClickBase = (item: IContextualMenuItem, ev: React.MouseEvent | React.KeyboardEvent, target: HTMLElement): void => { const items = getSubmenuItems(item); + // Cancel a async menu item hover timeout action from being taken and instead + // just trigger the click event instead. + if (this._enterTimerId !== undefined) { + this._async.clearTimeout(this._enterTimerId); + this._enterTimerId = undefined; + } if (!hasSubmenu(item) && (!items || !items.length)) { // This is an item without a menu. Click it. this._executeItemClick(item, ev); } else { @@ -910,10 +836,11 @@ export class ContextualMenu extends BaseComponent | React.KeyboardEvent) { + private _executeItemClick = (item: IContextualMenuItem, ev: React.MouseEvent | React.KeyboardEvent): void => { if (item.disabled || item.isDisabled) { return; } + let dismiss = false; if (item.onClick) { dismiss = !!item.onClick(ev, item); @@ -924,7 +851,7 @@ export class ContextualMenu extends BaseComponent) { + private _onItemKeyDown = (item: any, ev: React.KeyboardEvent): void => { const openKey = getRTL() ? KeyCodes.left : KeyCodes.right; if (ev.which === openKey && !item.disabled) { @@ -1032,10 +959,6 @@ export class ContextualMenu extends BaseComponent { + describe('creates a normal split button', () => { + let menuItem: IContextualMenuItem; + let menuClassNames: IMenuItemClassNames; + + let wrapper: ShallowWrapper; + beforeEach(() => { + menuItem = { key: '123' }; + menuClassNames = getMenuItemClassNames(); + wrapper = shallow( + + ); + }); + + it('renders the contextual menu split button correctly', () => { + expect(wrapper).toMatchSnapshot(); + }); + }); +}); + +function getMenuItemClassNames(): IMenuItemClassNames { + return { + item: 'item', + divider: '---', + root: 'root', + linkContent: 'linkContent', + icon: 'icon', + checkmarkIcon: 'checkmarkIcon', + subMenuIcon: 'subMenuIcon', + label: 'label', + splitContainer: 'splitContainer', + splitPrimary: 'splitPrimary', + splitMenu: 'splitMenu', + linkContentMenu: 'linkContentMenu', + }; +} diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx new file mode 100644 index 00000000000000..477509e7a25756 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx @@ -0,0 +1,226 @@ +import * as React from 'react'; +import { + BaseComponent, + assign, + buttonProperties, + getNativeProps, + KeyCodes +} from '../../Utilities'; +import { IContextualMenuItem } from '../../ContextualMenu'; +import { + IMenuItemClassNames, + getSplitButtonVerticalDividerClassNames +} from './ContextualMenu.classNames'; +import { ContextualMenuItem } from './ContextualMenuItem'; + +import { getIsChecked, isItemDisabled } from '../../utilities/contextualMenu/index'; +import { VerticalDivider } from '../../Divider'; +import { IContextualMenuSplitButtonProps } from './ContextualMenuSplitButton.types'; + +export interface IContextualMenuSplitButtonState { } + +const TouchIdleDelay = 500; /* ms */ + +export class ContextualMenuSplitButton extends BaseComponent { + private _processingTouch: boolean; + private _lastTouchTimeoutId: number | undefined; + private _splitButton: HTMLDivElement; + + public componentDidMount() { + if (this._splitButton && 'onpointerdown' in this._splitButton) { + this._events.on(this._splitButton, 'pointerdown', this._onPointerDown, true); + } + } + + public render(): JSX.Element | null { + const { + item, + classNames, + index, + focusableElementIndex, + totalItemCount, + hasCheckmarks, + hasIcons, + onItemMouseLeave, + onItemMouseMove + } = this.props; + + return ( +
this._splitButton = splitButton } + role={ 'button' } + aria-labelledby={ item.ariaLabel } + className={ classNames.splitContainer } + aria-disabled={ isItemDisabled(item) } + aria-haspopup={ true } + aria-describedby={ item.ariaDescription } + aria-checked={ item.isChecked || item.checked } + aria-posinset={ focusableElementIndex + 1 } + aria-setsize={ totalItemCount } + onMouseEnter={ this._onItemMouseEnter.bind(this, { ...item, subMenuProps: null, items: null }) } + onMouseLeave={ onItemMouseLeave ? onItemMouseLeave.bind(this, { ...item, subMenuProps: null, items: null }) : undefined } + onMouseMove={ onItemMouseMove ? this._onItemMouseMove.bind(this, { ...item, subMenuProps: null, items: null }) : undefined } + onKeyDown={ this._onItemKeyDown.bind(this, item) } + onClick={ this._executeItemClick.bind(this, item) } + onTouchStart={ this._onTouchStart } + tabIndex={ 0 } + data-is-focusable={ true } + > + { this._renderSplitPrimaryButton(item, classNames, index, hasCheckmarks!, hasIcons!) } + { this._renderSplitDivider(item) } + { this._renderSplitIconButton(item, classNames, index) } +
+ ); + } + + private _renderSplitPrimaryButton(item: IContextualMenuItem, classNames: IMenuItemClassNames, index: number, hasCheckmarks: boolean, hasIcons: boolean) { + const isChecked: boolean | null | undefined = getIsChecked(item); + const canCheck: boolean = isChecked !== null; + const defaultRole = canCheck ? 'menuitemcheckbox' : 'menuitem'; + const { + contextualMenuItemAs: ChildrenRenderer = ContextualMenuItem, + onItemClick + } = this.props; + + const itemProps = { + key: item.key, + disabled: isItemDisabled(item) || item.primaryDisabled, + name: item.name, + className: classNames.splitPrimary, + role: item.role || defaultRole, + canCheck: item.canCheck, + isChecked: item.isChecked, + checked: item.checked, + icon: item.icon, + iconProps: item.iconProps, + 'data-is-focusable': false, + 'aria-hidden': true + } as IContextualMenuItem; + return React.createElement('button', + getNativeProps(itemProps, buttonProperties), + , + ); + } + + private _renderSplitDivider(item: IContextualMenuItem) { + const getDividerClassnames = item.getSplitButtonVerticalDividerClassNames || getSplitButtonVerticalDividerClassNames; + return ; + } + + private _renderSplitIconButton(item: IContextualMenuItem, classNames: IMenuItemClassNames, index: number) { + const { + contextualMenuItemAs: ChildrenRenderer = ContextualMenuItem, + onItemMouseEnter, + onItemMouseLeave, + onItemMouseDown, + onItemMouseMove + } = this.props; + + // With the introduction of touch support for split buttons. We would now open sub-menus by touching anywhere + // on the split button but we can now longer trigger the primary action. This is correct from an accessibility + // stand point, however we're missing the next part which is having a primary action as an option to the sub menu + // of the split button. This should be enforced by being dynamically added into our list of sub menu items. + // This is logged on Issue #4532 + const itemProps = { + onClick: this._onSplitItemClick.bind(this, item), + disabled: isItemDisabled(item), + className: classNames.splitMenu, + subMenuProps: item.subMenuProps, + submenuIconProps: item.submenuIconProps, + split: true, + } as IContextualMenuItem; + + return React.createElement('button', + assign({}, getNativeProps(itemProps, buttonProperties), { + onMouseEnter: onItemMouseEnter ? onItemMouseEnter.bind(this, item, ) : undefined, + onMouseLeave: onItemMouseLeave ? onItemMouseLeave.bind(this, item) : undefined, + onMouseDown: (ev: any) => onItemMouseDown ? onItemMouseDown(item, ev) : undefined, + onMouseMove: onItemMouseMove ? this._onItemMouseMove.bind(this, item) : undefined, + 'data-is-focusable': false, + 'aria-hidden': true + }), + + ); + } + + private _onTouchStart = (): void => { + if (this._splitButton && !('onpointerdown' in this._splitButton)) { + this._handleTouchAndPointerEvent(); + } + } + + private _onPointerDown = (ev: PointerEvent): void => { + if (ev.pointerType === 'touch') { + this._handleTouchAndPointerEvent(); + + ev.preventDefault(); + ev.stopImmediatePropagation(); + } + } + + private _handleTouchAndPointerEvent() { + // If we already have an existing timeout from a previous touch/pointer event + // cancel that timeout so we can set a new one. + if (this._lastTouchTimeoutId !== undefined) { + this._async.clearTimeout(this._lastTouchTimeoutId); + this._lastTouchTimeoutId = undefined; + } + this._processingTouch = true; + + this._lastTouchTimeoutId = this._async.setTimeout(() => { + this._processingTouch = false; + this._lastTouchTimeoutId = undefined; + }, TouchIdleDelay); + } + + private _onItemMouseEnter = (item: IContextualMenuItem, ev: React.MouseEvent): void => { + const { onItemMouseEnter } = this.props; + if (onItemMouseEnter) { + onItemMouseEnter(item, ev, this._splitButton); + } + } + + private _onItemMouseMove = (item: IContextualMenuItem, ev: React.MouseEvent): void => { + const { onItemMouseMove } = this.props; + if (onItemMouseMove) { + onItemMouseMove(item, ev, this._splitButton); + } + } + + private _onSplitItemClick = (item: IContextualMenuItem, ev: React.MouseEvent): void => { + const { onItemClickBase } = this.props; + if (onItemClickBase) { + onItemClickBase(item, ev, (this._splitButton ? this._splitButton : ev.currentTarget) as HTMLElement); + } + } + + private _executeItemClick = (item: IContextualMenuItem, ev: React.MouseEvent | React.KeyboardEvent): void => { + const { + onItemClick, + executeItemClick + } = this.props; + + if (item.disabled || item.isDisabled) { + return; + } + + if (this._processingTouch && onItemClick) { + return onItemClick(item, ev); + } + + if (executeItemClick) { + executeItemClick(item, ev); + } + } + + private _onItemKeyDown = (item: any, ev: React.KeyboardEvent): void => { + const { onItemKeyDown } = this.props; + if (ev.which === KeyCodes.enter) { + this._executeItemClick(item, ev); + ev.preventDefault(); + ev.stopPropagation(); + } else if (onItemKeyDown) { + onItemKeyDown(item, ev); + } + } +} diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.types.ts b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.types.ts new file mode 100644 index 00000000000000..a957632f8a59ec --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.types.ts @@ -0,0 +1,94 @@ + +import { IContextualMenuItem } from '../../ContextualMenu'; +import { IMenuItemClassNames } from './ContextualMenu.classNames'; +import { IContextualMenuItemProps } from './ContextualMenuItem.types'; +import { ContextualMenuSplitButton } from './ContextualMenuSplitButton'; + +export interface IContextualMenuSplitButtonProps extends React.Props { + /** + * Optional callback to access the ContextualmenuSplitButton interface. Use this instead of ref for accessing + * the public methods and properties of the component. + */ + componentRef?: (component: ContextualMenuSplitButton | null) => void; + + /** + * The item that is used to render the split button. + */ + item: IContextualMenuItem; + + /** + * CSS class to apply to the context menu. + */ + classNames: IMenuItemClassNames; + + /** + * The index number of the split button among all items in the contextual menu including things like dividers and headers. + */ + index: number; + + /** + * The index number of the split button among all items in the contextual menu excluding dividers and headers. + */ + focusableElementIndex: number; + + /** + * The total number of items in the contextual menu. + */ + totalItemCount: number; + + /** + * Whether or not if the item for the split button uses checkmarks. + */ + hasCheckmarks?: boolean; + + /** + * Whether or not the item for the split button uses icons. + */ + hasIcons?: boolean; + + /** + * Method to override the render of the individual menu items. + * @default ContextualMenuItem + */ + contextualMenuItemAs?: React.ComponentClass | React.StatelessComponent; + + /** + * Callback for when the user's mouse enters the split button. + */ + onItemMouseEnter?: (item: IContextualMenuItem, ev: React.MouseEvent, target: HTMLElement) => boolean | void; + + /** + * Callback for when the user's mouse leaves the split button. + */ + onItemMouseLeave?: (item: IContextualMenuItem, ev: React.MouseEvent) => void; + + /** + * Callback for when the user's mouse moves in the split button. + */ + onItemMouseMove?: (item: IContextualMenuItem, ev: React.MouseEvent, target: HTMLElement) => void; + + /** + * Callback for the mousedown event on the icon button in the split menu. + */ + onItemMouseDown?: (item: IContextualMenuItem, ev: React.MouseEvent) => void; + + /** + * Callback for when the click event on the primary button. + */ + executeItemClick?: (item: IContextualMenuItem, ev: React.MouseEvent | React.KeyboardEvent) => void; + + /** + * Callback for when the click event on the icon button from the split button. + */ + onItemClick?: (item: IContextualMenuItem, ev: React.MouseEvent | React.KeyboardEvent) => void; + + /** + * Callback for when the click event on the icon button which also takes in a specific HTMLElement that will be focused. + */ + onItemClickBase?: (item: IContextualMenuItem, ev: React.MouseEvent | React.KeyboardEvent, target: HTMLElement) => void; + + /** + * Callback for keyboard events on the split button. + */ + onItemKeyDown?: (item: IContextualMenuItem, ev: React.KeyboardEvent) => void; +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap b/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap new file mode 100644 index 00000000000000..74797273abdcd4 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap @@ -0,0 +1,678 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ContextualMenuSplitButton creates a normal split button renders the contextual menu split button correctly 1`] = ` +ShallowWrapper { + "length": 1, + Symbol(enzyme.__root__): [Circular], + Symbol(enzyme.__unrendered__): , + Symbol(enzyme.__renderer__): Object { + "batchedUpdates": [Function], + "getNode": [Function], + "render": [Function], + "simulateEvent": [Function], + "unmount": [Function], + }, + Symbol(enzyme.__node__): Object { + "instance": null, + "key": undefined, + "nodeType": "host", + "props": Object { + "aria-checked": undefined, + "aria-describedby": undefined, + "aria-disabled": false, + "aria-haspopup": true, + "aria-labelledby": undefined, + "aria-posinset": 1, + "aria-setsize": 1, + "children": Array [ + , + , + , + ], + "className": "splitContainer", + "data-is-focusable": true, + "onClick": [Function], + "onKeyDown": [Function], + "onMouseEnter": [Function], + "onMouseLeave": undefined, + "onMouseMove": undefined, + "onTouchStart": [Function], + "role": "button", + "tabIndex": 0, + }, + "ref": [Function], + "rendered": Array [ + Object { + "instance": null, + "key": undefined, + "nodeType": "host", + "props": Object { + "aria-hidden": true, + "checked": undefined, + "children": , + "className": "splitPrimary", + "data-is-focusable": false, + "disabled": undefined, + "icon": undefined, + "name": undefined, + "role": "menuitem", + }, + "ref": null, + "rendered": Object { + "instance": null, + "key": undefined, + "nodeType": "function", + "props": Object { + "classNames": Object { + "checkmarkIcon": "checkmarkIcon", + "divider": "---", + "icon": "icon", + "item": "item", + "label": "label", + "linkContent": "linkContent", + "linkContentMenu": "linkContentMenu", + "root": "root", + "splitContainer": "splitContainer", + "splitMenu": "splitMenu", + "splitPrimary": "splitPrimary", + "subMenuIcon": "subMenuIcon", + }, + "data-is-focusable": false, + "hasIcons": undefined, + "index": 0, + "item": Object { + "aria-hidden": true, + "canCheck": undefined, + "checked": undefined, + "className": "splitPrimary", + "data-is-focusable": false, + "disabled": undefined, + "icon": undefined, + "iconProps": undefined, + "isChecked": undefined, + "key": "123", + "name": undefined, + "role": "menuitem", + }, + "onCheckmarkClick": undefined, + }, + "ref": null, + "rendered": null, + "type": [Function], + }, + "type": "button", + }, + Object { + "instance": null, + "key": undefined, + "nodeType": "function", + "props": Object { + "getClassNames": [Function], + }, + "ref": null, + "rendered": null, + "type": [Function], + }, + Object { + "instance": null, + "key": undefined, + "nodeType": "host", + "props": Object { + "aria-hidden": true, + "children": , + "className": "splitMenu", + "data-is-focusable": false, + "disabled": false, + "onClick": [Function], + "onMouseDown": [Function], + "onMouseEnter": undefined, + "onMouseLeave": undefined, + "onMouseMove": undefined, + }, + "ref": null, + "rendered": Object { + "instance": null, + "key": undefined, + "nodeType": "function", + "props": Object { + "classNames": Object { + "checkmarkIcon": "checkmarkIcon", + "divider": "---", + "icon": "icon", + "item": "item", + "label": "label", + "linkContent": "linkContent", + "linkContentMenu": "linkContentMenu", + "root": "root", + "splitContainer": "splitContainer", + "splitMenu": "splitMenu", + "splitPrimary": "splitPrimary", + "subMenuIcon": "subMenuIcon", + }, + "hasIcons": false, + "index": 0, + "item": Object { + "className": "splitMenu", + "disabled": false, + "onClick": [Function], + "split": true, + "subMenuProps": undefined, + "submenuIconProps": undefined, + }, + }, + "ref": null, + "rendered": null, + "type": [Function], + }, + "type": "button", + }, + ], + "type": "div", + }, + Symbol(enzyme.__nodes__): Array [ + Object { + "instance": null, + "key": undefined, + "nodeType": "host", + "props": Object { + "aria-checked": undefined, + "aria-describedby": undefined, + "aria-disabled": false, + "aria-haspopup": true, + "aria-labelledby": undefined, + "aria-posinset": 1, + "aria-setsize": 1, + "children": Array [ + , + , + , + ], + "className": "splitContainer", + "data-is-focusable": true, + "onClick": [Function], + "onKeyDown": [Function], + "onMouseEnter": [Function], + "onMouseLeave": undefined, + "onMouseMove": undefined, + "onTouchStart": [Function], + "role": "button", + "tabIndex": 0, + }, + "ref": [Function], + "rendered": Array [ + Object { + "instance": null, + "key": undefined, + "nodeType": "host", + "props": Object { + "aria-hidden": true, + "checked": undefined, + "children": , + "className": "splitPrimary", + "data-is-focusable": false, + "disabled": undefined, + "icon": undefined, + "name": undefined, + "role": "menuitem", + }, + "ref": null, + "rendered": Object { + "instance": null, + "key": undefined, + "nodeType": "function", + "props": Object { + "classNames": Object { + "checkmarkIcon": "checkmarkIcon", + "divider": "---", + "icon": "icon", + "item": "item", + "label": "label", + "linkContent": "linkContent", + "linkContentMenu": "linkContentMenu", + "root": "root", + "splitContainer": "splitContainer", + "splitMenu": "splitMenu", + "splitPrimary": "splitPrimary", + "subMenuIcon": "subMenuIcon", + }, + "data-is-focusable": false, + "hasIcons": undefined, + "index": 0, + "item": Object { + "aria-hidden": true, + "canCheck": undefined, + "checked": undefined, + "className": "splitPrimary", + "data-is-focusable": false, + "disabled": undefined, + "icon": undefined, + "iconProps": undefined, + "isChecked": undefined, + "key": "123", + "name": undefined, + "role": "menuitem", + }, + "onCheckmarkClick": undefined, + }, + "ref": null, + "rendered": null, + "type": [Function], + }, + "type": "button", + }, + Object { + "instance": null, + "key": undefined, + "nodeType": "function", + "props": Object { + "getClassNames": [Function], + }, + "ref": null, + "rendered": null, + "type": [Function], + }, + Object { + "instance": null, + "key": undefined, + "nodeType": "host", + "props": Object { + "aria-hidden": true, + "children": , + "className": "splitMenu", + "data-is-focusable": false, + "disabled": false, + "onClick": [Function], + "onMouseDown": [Function], + "onMouseEnter": undefined, + "onMouseLeave": undefined, + "onMouseMove": undefined, + }, + "ref": null, + "rendered": Object { + "instance": null, + "key": undefined, + "nodeType": "function", + "props": Object { + "classNames": Object { + "checkmarkIcon": "checkmarkIcon", + "divider": "---", + "icon": "icon", + "item": "item", + "label": "label", + "linkContent": "linkContent", + "linkContentMenu": "linkContentMenu", + "root": "root", + "splitContainer": "splitContainer", + "splitMenu": "splitMenu", + "splitPrimary": "splitPrimary", + "subMenuIcon": "subMenuIcon", + }, + "hasIcons": false, + "index": 0, + "item": Object { + "className": "splitMenu", + "disabled": false, + "onClick": [Function], + "split": true, + "subMenuProps": undefined, + "submenuIconProps": undefined, + }, + }, + "ref": null, + "rendered": null, + "type": [Function], + }, + "type": "button", + }, + ], + "type": "div", + }, + ], + Symbol(enzyme.__options__): Object { + "adapter": ReactSixteenAdapter { + "options": Object { + "enableComponentDidUpdateOnSetState": true, + }, + }, + }, +} +`; diff --git a/packages/office-ui-fabric-react/src/utilities/contextualMenu/contextualMenuUtility.ts b/packages/office-ui-fabric-react/src/utilities/contextualMenu/contextualMenuUtility.ts index e296244f73c737..d5a5cdfdbfad70 100644 --- a/packages/office-ui-fabric-react/src/utilities/contextualMenu/contextualMenuUtility.ts +++ b/packages/office-ui-fabric-react/src/utilities/contextualMenu/contextualMenuUtility.ts @@ -28,3 +28,7 @@ export function getIsChecked(item: IContextualMenuItem): boolean | null { export function hasSubmenu(item: IContextualMenuItem) { return !!(item.subMenuProps || item.items); } + +export function isItemDisabled(item: IContextualMenuItem): boolean { + return !!(item.isDisabled || item.disabled); +} \ No newline at end of file From 922d51d90ad562a80a7cfcbad70bdc4b812aa7b5 Mon Sep 17 00:00:00 2001 From: "REDMOND\\chiechan" Date: Thu, 19 Apr 2018 10:32:24 -0700 Subject: [PATCH 02/19] Removed all references to touch --- .../ContextualMenuSplitButton.tsx | 46 ------------------- 1 file changed, 46 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx index 477509e7a25756..e506d9472d1092 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx @@ -26,12 +26,6 @@ export class ContextualMenuSplitButton extends BaseComponent @@ -116,11 +109,6 @@ export class ContextualMenuSplitButton extends BaseComponent { - if (this._splitButton && !('onpointerdown' in this._splitButton)) { - this._handleTouchAndPointerEvent(); - } - } - - private _onPointerDown = (ev: PointerEvent): void => { - if (ev.pointerType === 'touch') { - this._handleTouchAndPointerEvent(); - - ev.preventDefault(); - ev.stopImmediatePropagation(); - } - } - - private _handleTouchAndPointerEvent() { - // If we already have an existing timeout from a previous touch/pointer event - // cancel that timeout so we can set a new one. - if (this._lastTouchTimeoutId !== undefined) { - this._async.clearTimeout(this._lastTouchTimeoutId); - this._lastTouchTimeoutId = undefined; - } - this._processingTouch = true; - - this._lastTouchTimeoutId = this._async.setTimeout(() => { - this._processingTouch = false; - this._lastTouchTimeoutId = undefined; - }, TouchIdleDelay); - } - private _onItemMouseEnter = (item: IContextualMenuItem, ev: React.MouseEvent): void => { const { onItemMouseEnter } = this.props; if (onItemMouseEnter) { @@ -204,10 +162,6 @@ export class ContextualMenuSplitButton extends BaseComponent Date: Thu, 19 Apr 2018 10:33:56 -0700 Subject: [PATCH 03/19] added change files --- ...lMenuSplitButtonWithoutTouch_2018-04-19-17-33.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/office-ui-fabric-react/jspurlin-contextualMenuSplitButtonWithoutTouch_2018-04-19-17-33.json diff --git a/common/changes/office-ui-fabric-react/jspurlin-contextualMenuSplitButtonWithoutTouch_2018-04-19-17-33.json b/common/changes/office-ui-fabric-react/jspurlin-contextualMenuSplitButtonWithoutTouch_2018-04-19-17-33.json new file mode 100644 index 00000000000000..cc5f972b8ce07b --- /dev/null +++ b/common/changes/office-ui-fabric-react/jspurlin-contextualMenuSplitButtonWithoutTouch_2018-04-19-17-33.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "office-ui-fabric-react", + "comment": "Contextual Menu: moved out the split button to be its own component, ContextualMenuSplitButton", + "type": "minor" + } + ], + "packageName": "office-ui-fabric-react", + "email": "chiechan@microsoft.com" +} \ No newline at end of file From e58ba9b00a5790e9ea32fcdb4cbdde2362f371bb Mon Sep 17 00:00:00 2001 From: "REDMOND\\chiechan" Date: Thu, 19 Apr 2018 14:23:10 -0700 Subject: [PATCH 04/19] solved style and updated test --- .../src/components/ContextualMenu/ContextualMenu.tsx | 6 +++--- .../components/ContextualMenu/ContextualMenuSplitButton.tsx | 5 ----- .../__snapshots__/ContextualMenuSplitButton.test.tsx.snap | 2 -- 3 files changed, 3 insertions(+), 10 deletions(-) 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 4693d2794c7fef..d9591aa661bd42 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.tsx @@ -762,8 +762,8 @@ export class ContextualMenu extends BaseComponent, target: HTMLElement) { - const targetElement = target; + private _updateFocusOnMouseEvent(item: IContextualMenuItem, ev: React.MouseEvent, target?: HTMLElement) { + const targetElement = target ? target : ev.currentTarget as HTMLElement; const { subMenuHoverDelay: timeoutDuration = this._navigationIdleDelay } = this.props; if (item.key === this.state.expandedMenuItemKey) { @@ -799,7 +799,7 @@ export class ContextualMenu extends BaseComponent): void => { - if (item.onMouseDown && this._enterTimerId === undefined) { + if (item.onMouseDown) { item.onMouseDown(item, ev); } } diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx index e506d9472d1092..ee145104739751 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx @@ -19,11 +19,7 @@ import { IContextualMenuSplitButtonProps } from './ContextualMenuSplitButton.typ export interface IContextualMenuSplitButtonState { } -const TouchIdleDelay = 500; /* ms */ - export class ContextualMenuSplitButton extends BaseComponent { - private _processingTouch: boolean; - private _lastTouchTimeoutId: number | undefined; private _splitButton: HTMLDivElement; public render(): JSX.Element | null { @@ -154,7 +150,6 @@ export class ContextualMenuSplitButton extends BaseComponent | React.KeyboardEvent): void => { const { - onItemClick, executeItemClick } = this.props; diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap b/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap index 74797273abdcd4..1e28ecd4856fb2 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap @@ -152,7 +152,6 @@ ShallowWrapper { "onMouseEnter": [Function], "onMouseLeave": undefined, "onMouseMove": undefined, - "onTouchStart": [Function], "role": "button", "tabIndex": 0, }, @@ -467,7 +466,6 @@ ShallowWrapper { "onMouseEnter": [Function], "onMouseLeave": undefined, "onMouseMove": undefined, - "onTouchStart": [Function], "role": "button", "tabIndex": 0, }, From 47e4cbdc1d26c27053fe4df8174a53d078465575 Mon Sep 17 00:00:00 2001 From: "REDMOND\\chiechan" Date: Thu, 19 Apr 2018 16:22:52 -0700 Subject: [PATCH 05/19] fixed problem with submenu appearing in the wrong target button --- .../src/components/ContextualMenu/ContextualMenu.tsx | 2 +- .../src/components/ContextualMenu/ContextualMenuSplitButton.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 d9591aa661bd42..7d65d4182ac3e1 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.tsx @@ -703,7 +703,7 @@ export class ContextualMenu extends BaseComponent { this._isScrollIdle = true; }, this._navigationIdleDelay); } - private _onItemMouseEnter(item: any, ev: React.MouseEvent) { + private _onItemMouseEnter = (item: any, ev: React.MouseEvent): void => { this._onItemMouseEnterBase(item, ev, ev.target as HTMLElement); } diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx index ee145104739751..8cc6ec6135aea8 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx @@ -116,7 +116,7 @@ export class ContextualMenuSplitButton extends BaseComponent onItemMouseDown ? onItemMouseDown(item, ev) : undefined, onMouseMove: onItemMouseMove ? this._onItemMouseMove.bind(this, item) : undefined, From 4638505acbc994a18a174f7b68e70f0a69d153c1 Mon Sep 17 00:00:00 2001 From: "REDMOND\\chiechan" Date: Thu, 19 Apr 2018 16:38:38 -0700 Subject: [PATCH 06/19] fixed style issue --- .../src/components/ContextualMenu/ContextualMenuSplitButton.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx index 8cc6ec6135aea8..5195c874e5bd7a 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx @@ -99,7 +99,6 @@ export class ContextualMenuSplitButton extends BaseComponent Date: Thu, 19 Apr 2018 17:22:23 -0700 Subject: [PATCH 07/19] removed unnecessary binds --- .../ContextualMenuSplitButton.tsx | 61 ++++++++++--------- .../ContextualMenuSplitButton.test.tsx.snap | 20 +++--- 2 files changed, 42 insertions(+), 39 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx index 5195c874e5bd7a..7991c459fe7c1f 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx @@ -31,8 +31,7 @@ export class ContextualMenuSplitButton extends BaseComponent @@ -85,9 +84,10 @@ export class ContextualMenuSplitButton extends BaseComponent, + return ( + ); } @@ -100,8 +100,7 @@ export class ContextualMenuSplitButton extends BaseComponent onItemMouseDown ? onItemMouseDown(item, ev) : undefined, - onMouseMove: onItemMouseMove ? this._onItemMouseMove.bind(this, item) : undefined, - 'data-is-focusable': false, - 'aria-hidden': true - }), - + const buttonProps = assign({}, getNativeProps(itemProps, buttonProperties), { + onMouseEnter: this._onItemMouseEnter, + onMouseLeave: onItemMouseLeave ? onItemMouseLeave.bind(this, item) : undefined, + onMouseDown: (ev: any) => onItemMouseDown ? onItemMouseDown(item, ev) : undefined, + onMouseMove: this._onItemMouseMove.bind(this, item), + 'data-is-focusable': false, + 'aria-hidden': true + }); + + return ( + ); } - private _onItemMouseEnter = (item: IContextualMenuItem, ev: React.MouseEvent): void => { - const { onItemMouseEnter } = this.props; + private _onItemMouseEnter = (ev: React.MouseEvent): void => { + const { item, onItemMouseEnter } = this.props; if (onItemMouseEnter) { onItemMouseEnter(item, ev, this._splitButton); } } - private _onItemMouseMove = (item: IContextualMenuItem, ev: React.MouseEvent): void => { - const { onItemMouseMove } = this.props; + private _onItemMouseMove = (ev: React.MouseEvent): void => { + const { item, onItemMouseMove } = this.props; if (onItemMouseMove) { onItemMouseMove(item, ev, this._splitButton); } @@ -147,9 +149,10 @@ export class ContextualMenuSplitButton extends BaseComponent | React.KeyboardEvent): void => { + private _executeItemClick = (ev: React.MouseEvent | React.KeyboardEvent): void => { const { - executeItemClick + executeItemClick, + item } = this.props; if (item.disabled || item.isDisabled) { @@ -161,10 +164,10 @@ export class ContextualMenuSplitButton extends BaseComponent): void => { - const { onItemKeyDown } = this.props; + private _onItemKeyDown = (ev: React.KeyboardEvent): void => { + const { item, onItemKeyDown } = this.props; if (ev.which === KeyCodes.enter) { - this._executeItemClick(item, ev); + this._executeItemClick(ev); ev.preventDefault(); ev.stopPropagation(); } else if (onItemKeyDown) { diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap b/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap index 1e28ecd4856fb2..5fe08a6dd930ee 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap @@ -109,9 +109,9 @@ ShallowWrapper { disabled={false} onClick={[Function]} onMouseDown={[Function]} - onMouseEnter={undefined} + onMouseEnter={[Function]} onMouseLeave={undefined} - onMouseMove={undefined} + onMouseMove={[Function]} > Date: Thu, 19 Apr 2018 18:55:38 -0700 Subject: [PATCH 08/19] reverted removing items in functions; set correct event target for anchor button menu focus --- .../ContextualMenu/ContextualMenu.tsx | 4 +- .../ContextualMenuSplitButton.tsx | 41 ++++++++++--------- 2 files changed, 23 insertions(+), 22 deletions(-) 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 7d65d4182ac3e1..4b0650cb669333 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.tsx @@ -704,7 +704,7 @@ export class ContextualMenu extends BaseComponent): void => { - this._onItemMouseEnterBase(item, ev, ev.target as HTMLElement); + this._onItemMouseEnterBase(item, ev, ev.currentTarget as HTMLElement); } private _onItemMouseEnterBase = (item: any, ev: React.MouseEvent, target: HTMLElement): void => { @@ -716,7 +716,7 @@ export class ContextualMenu extends BaseComponent) { - this._onItemMouseMoveBase(item, ev, ev.target as HTMLElement); + this._onItemMouseMoveBase(item, ev, ev.currentTarget as HTMLElement); } private _onItemMouseMoveBase = (item: any, ev: React.MouseEvent, target: HTMLElement): void => { diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx index 7991c459fe7c1f..754a61270506ce 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx @@ -31,7 +31,8 @@ export class ContextualMenuSplitButton extends BaseComponent @@ -100,7 +101,8 @@ export class ContextualMenuSplitButton extends BaseComponent onItemMouseDown ? onItemMouseDown(item, ev) : undefined, - onMouseMove: this._onItemMouseMove.bind(this, item), + onMouseMove: onItemMouseMove ? this._onItemMouseMove.bind(this, item) : undefined, 'data-is-focusable': false, 'aria-hidden': true - }); + }) return ( - ); } - private _onItemMouseEnter = (ev: React.MouseEvent): void => { - const { item, onItemMouseEnter } = this.props; + private _onItemMouseEnter = (item: IContextualMenuItem, ev: React.MouseEvent): void => { + const { onItemMouseEnter } = this.props; if (onItemMouseEnter) { onItemMouseEnter(item, ev, this._splitButton); } } - private _onItemMouseMove = (ev: React.MouseEvent): void => { - const { item, onItemMouseMove } = this.props; + private _onItemMouseMove = (item: IContextualMenuItem, ev: React.MouseEvent): void => { + const { onItemMouseMove } = this.props; if (onItemMouseMove) { onItemMouseMove(item, ev, this._splitButton); } @@ -149,10 +151,9 @@ export class ContextualMenuSplitButton extends BaseComponent | React.KeyboardEvent): void => { + private _executeItemClick = (item: IContextualMenuItem, ev: React.MouseEvent | React.KeyboardEvent): void => { const { - executeItemClick, - item + executeItemClick } = this.props; if (item.disabled || item.isDisabled) { @@ -164,10 +165,10 @@ export class ContextualMenuSplitButton extends BaseComponent): void => { - const { item, onItemKeyDown } = this.props; + private _onItemKeyDown = (item: any, ev: React.KeyboardEvent): void => { + const { onItemKeyDown } = this.props; if (ev.which === KeyCodes.enter) { - this._executeItemClick(ev); + this._executeItemClick(item, ev); ev.preventDefault(); ev.stopPropagation(); } else if (onItemKeyDown) { From ba5c45a6c2d545ac2108c4bcd96fa6357b9f1ab2 Mon Sep 17 00:00:00 2001 From: "REDMOND\\chiechan" Date: Thu, 19 Apr 2018 19:15:37 -0700 Subject: [PATCH 09/19] addressed style problems --- .../src/components/ContextualMenu/ContextualMenuSplitButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx index 754a61270506ce..70c7d40a1f1cfe 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx @@ -121,7 +121,7 @@ export class ContextualMenuSplitButton extends BaseComponent From 87aa9832710f4147a4a5640950e919abcdf8233f Mon Sep 17 00:00:00 2001 From: "REDMOND\\chiechan" Date: Thu, 19 Apr 2018 19:23:06 -0700 Subject: [PATCH 10/19] updated snapshot --- .../ContextualMenuSplitButton.test.tsx.snap | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap b/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap index 5fe08a6dd930ee..99a5739a7aa971 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap @@ -111,7 +111,7 @@ ShallowWrapper { onMouseDown={[Function]} onMouseEnter={[Function]} onMouseLeave={undefined} - onMouseMove={[Function]} + onMouseMove={undefined} > Date: Fri, 20 Apr 2018 10:12:09 -0700 Subject: [PATCH 11/19] updated bundle size --- scripts/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/package.json b/scripts/package.json index d5af64928894c6..1609236ec4e6e8 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -50,7 +50,7 @@ "bundlesize": [ { "path": "../apps/test-bundle-button/dist/test-bundle-button.min.js", - "maxSize": "46.5 kB" + "maxSize": "46.8 kB" } ] } \ No newline at end of file From f0aa3d733777bbb12c6f52833564ea312e9de14b Mon Sep 17 00:00:00 2001 From: "REDMOND\\chiechan" Date: Wed, 25 Apr 2018 16:30:59 -0700 Subject: [PATCH 12/19] updated button styles with suggested changes --- .../components/ContextualMenu/ContextualMenuSplitButton.tsx | 4 ++-- .../ContextualMenu/ContextualMenuSplitButton.types.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx index 70c7d40a1f1cfe..f52984eaa17dff 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx @@ -114,7 +114,7 @@ export class ContextualMenuSplitButton extends BaseComponent onItemMouseDown ? onItemMouseDown(item, ev) : undefined, @@ -124,7 +124,7 @@ export class ContextualMenuSplitButton extends BaseComponent + ); diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.types.ts b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.types.ts index a957632f8a59ec..1e7ebdb801f2c5 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.types.ts +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.types.ts @@ -4,7 +4,7 @@ import { IMenuItemClassNames } from './ContextualMenu.classNames'; import { IContextualMenuItemProps } from './ContextualMenuItem.types'; import { ContextualMenuSplitButton } from './ContextualMenuSplitButton'; -export interface IContextualMenuSplitButtonProps extends React.Props { +export interface IContextualMenuSplitButtonProps extends React.Props { /** * Optional callback to access the ContextualmenuSplitButton interface. Use this instead of ref for accessing * the public methods and properties of the component. From 8e16927a1ea9451ecf02b294084c5df64e31ec25 Mon Sep 17 00:00:00 2001 From: "REDMOND\\chiechan" Date: Thu, 26 Apr 2018 17:15:27 -0700 Subject: [PATCH 13/19] separated the commands to be different autobind --- .../ContextualMenuSplitButton.tsx | 58 +++++++++++++------ 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx index f52984eaa17dff..02a55ee199d408 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx @@ -47,11 +47,11 @@ export class ContextualMenuSplitButton extends BaseComponent @@ -106,7 +106,7 @@ export class ContextualMenuSplitButton extends BaseComponent onItemMouseDown ? onItemMouseDown(item, ev) : undefined, - onMouseMove: onItemMouseMove ? this._onItemMouseMove.bind(this, item) : undefined, + onMouseMove: this._onItemMouseMoveSecondary, 'data-is-focusable': false, 'aria-hidden': true }); @@ -130,29 +130,53 @@ export class ContextualMenuSplitButton extends BaseComponent): void => { - const { onItemMouseEnter } = this.props; + private _onItemMouseEnterPrimary = (ev: React.MouseEvent): void => { + const { + item, + onItemMouseEnter + } = this.props; + if (onItemMouseEnter) { + onItemMouseEnter({ ...item, subMenuProps: undefined, items: undefined }, ev, this._splitButton); + } + } + + private _onItemMouseEnterSecondary = (ev: React.MouseEvent): void => { + const { item, onItemMouseEnter } = this.props; if (onItemMouseEnter) { onItemMouseEnter(item, ev, this._splitButton); } } - private _onItemMouseMove = (item: IContextualMenuItem, ev: React.MouseEvent): void => { - const { onItemMouseMove } = this.props; + private _onItemMouseMovePrimary = (ev: React.MouseEvent): void => { + const { + item, + onItemMouseMove + } = this.props; + if (onItemMouseMove) { + onItemMouseMove({ ...item, subMenuProps: undefined, items: undefined }, ev, this._splitButton); + } + } + + private _onItemMouseMoveSecondary = (ev: React.MouseEvent): void => { + const { + item, + onItemMouseMove + } = this.props; if (onItemMouseMove) { onItemMouseMove(item, ev, this._splitButton); } } - private _onSplitItemClick = (item: IContextualMenuItem, ev: React.MouseEvent): void => { - const { onItemClickBase } = this.props; + private _onSplitItemClick = (ev: React.MouseEvent): void => { + const { item, onItemClickBase } = this.props; if (onItemClickBase) { onItemClickBase(item, ev, (this._splitButton ? this._splitButton : ev.currentTarget) as HTMLElement); } } - private _executeItemClick = (item: IContextualMenuItem, ev: React.MouseEvent | React.KeyboardEvent): void => { + private _executeItemClick = (ev: React.MouseEvent | React.KeyboardEvent): void => { const { + item, executeItemClick } = this.props; @@ -165,10 +189,10 @@ export class ContextualMenuSplitButton extends BaseComponent): void => { - const { onItemKeyDown } = this.props; + private _onItemKeyDown = (ev: React.KeyboardEvent): void => { + const { item, onItemKeyDown } = this.props; if (ev.which === KeyCodes.enter) { - this._executeItemClick(item, ev); + this._executeItemClick(ev); ev.preventDefault(); ev.stopPropagation(); } else if (onItemKeyDown) { From ff88ae231ebecefb3fd72660d7895809fc4e5f53 Mon Sep 17 00:00:00 2001 From: "REDMOND\\chiechan" Date: Thu, 26 Apr 2018 17:24:48 -0700 Subject: [PATCH 14/19] made target optional --- .../src/components/ContextualMenu/ContextualMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4b0650cb669333..ea3e97e1ec51af 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenu.tsx @@ -707,7 +707,7 @@ export class ContextualMenu extends BaseComponent, target: HTMLElement): void => { + private _onItemMouseEnterBase = (item: any, ev: React.MouseEvent, target?: HTMLElement): void => { if (!this._isScrollIdle) { return; } From c7a333a76152d0526fa150ee020a1481541c3b58 Mon Sep 17 00:00:00 2001 From: "REDMOND\\chiechan" Date: Thu, 26 Apr 2018 17:41:12 -0700 Subject: [PATCH 15/19] renamed actions to be a different name --- .../ContextualMenu/ContextualMenuSplitButton.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx index 02a55ee199d408..9c502c6fe35106 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx @@ -106,7 +106,7 @@ export class ContextualMenuSplitButton extends BaseComponent onItemMouseDown ? onItemMouseDown(item, ev) : undefined, - onMouseMove: this._onItemMouseMoveSecondary, + onMouseMove: this._onItemMouseMoveIcon, 'data-is-focusable': false, 'aria-hidden': true }); @@ -140,7 +140,7 @@ export class ContextualMenuSplitButton extends BaseComponent): void => { + private _onItemMouseEnterIcon = (ev: React.MouseEvent): void => { const { item, onItemMouseEnter } = this.props; if (onItemMouseEnter) { onItemMouseEnter(item, ev, this._splitButton); @@ -157,7 +157,7 @@ export class ContextualMenuSplitButton extends BaseComponent): void => { + private _onItemMouseMoveIcon = (ev: React.MouseEvent): void => { const { item, onItemMouseMove @@ -167,7 +167,7 @@ export class ContextualMenuSplitButton extends BaseComponent): void => { + private _onIconItemClick = (ev: React.MouseEvent): void => { const { item, onItemClickBase } = this.props; if (onItemClickBase) { onItemClickBase(item, ev, (this._splitButton ? this._splitButton : ev.currentTarget) as HTMLElement); From 364fa11c7e2e130608ea9212419a144ebf640627 Mon Sep 17 00:00:00 2001 From: "REDMOND\\chiechan" Date: Fri, 27 Apr 2018 07:23:22 -0700 Subject: [PATCH 16/19] removed unused prop fields --- .../components/ContextualMenu/ContextualMenuSplitButton.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx index 9c502c6fe35106..2b8d961dfdc6d5 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx @@ -31,8 +31,7 @@ export class ContextualMenuSplitButton extends BaseComponent Date: Fri, 27 Apr 2018 07:34:06 -0700 Subject: [PATCH 17/19] updated snapsthos --- .../ContextualMenuSplitButton.test.tsx.snap | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap b/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap index 99a5739a7aa971..5fe08a6dd930ee 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap @@ -111,7 +111,7 @@ ShallowWrapper { onMouseDown={[Function]} onMouseEnter={[Function]} onMouseLeave={undefined} - onMouseMove={undefined} + onMouseMove={[Function]} > Date: Fri, 27 Apr 2018 08:06:26 -0700 Subject: [PATCH 18/19] addressed comment to clean up code naming; bumped package size --- .../ContextualMenu/ContextualMenuSplitButton.tsx | 12 +++++++++--- scripts/package.json | 4 ++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx index 2b8d961dfdc6d5..a84a974c68e4aa 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx @@ -86,14 +86,20 @@ export class ContextualMenuSplitButton extends BaseComponent - + ); } private _renderSplitDivider(item: IContextualMenuItem) { - const getDividerClassnames = item.getSplitButtonVerticalDividerClassNames || getSplitButtonVerticalDividerClassNames; - return ; + const getDividerClassNames = item.getSplitButtonVerticalDividerClassNames || getSplitButtonVerticalDividerClassNames; + return ; } private _renderSplitIconButton(item: IContextualMenuItem, classNames: IMenuItemClassNames, index: number) { diff --git a/scripts/package.json b/scripts/package.json index d26a3559c52e17..a7bd473e66e63f 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -50,7 +50,7 @@ "bundlesize": [ { "path": "../apps/test-bundle-button/dist/test-bundle-button.min.js", - "maxSize": "46.9 kB" + "maxSize": "47.6 kB" } ] -} +} \ No newline at end of file From 883e727914bf0de71d43fe9e8e369bb43f93a8e6 Mon Sep 17 00:00:00 2001 From: "REDMOND\\chiechan" Date: Fri, 27 Apr 2018 08:38:55 -0700 Subject: [PATCH 19/19] fixd lint error with closing tags --- .../components/ContextualMenu/ContextualMenuSplitButton.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx index a84a974c68e4aa..98ed1091a7567f 100644 --- a/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx +++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/ContextualMenuSplitButton.tsx @@ -92,7 +92,8 @@ export class ContextualMenuSplitButton extends BaseComponent + hasIcons={ hasIcons } + /> ); }