{
+ private _splitButton: HTMLDivElement;
+
+ public render(): JSX.Element | null {
+ const {
+ item,
+ classNames,
+ index,
+ focusableElementIndex,
+ totalItemCount,
+ hasCheckmarks,
+ hasIcons,
+ onItemMouseLeave
+ } = 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._onItemMouseEnterPrimary }
+ onMouseLeave={ onItemMouseLeave ? onItemMouseLeave.bind(this, { ...item, subMenuProps: null, items: null }) : undefined }
+ onMouseMove={ this._onItemMouseMovePrimary }
+ onKeyDown={ this._onItemKeyDown }
+ onClick={ this._executeItemClick }
+ 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 (
+
+ );
+ }
+
+ private _renderSplitDivider(item: IContextualMenuItem) {
+ const getDividerClassNames = item.getSplitButtonVerticalDividerClassNames || getSplitButtonVerticalDividerClassNames;
+ return ;
+ }
+
+ private _renderSplitIconButton(item: IContextualMenuItem, classNames: IMenuItemClassNames, index: number) {
+ const {
+ contextualMenuItemAs: ChildrenRenderer = ContextualMenuItem,
+ onItemMouseLeave,
+ onItemMouseDown
+ } = this.props;
+
+ const itemProps = {
+ onClick: this._onIconItemClick,
+ disabled: isItemDisabled(item),
+ className: classNames.splitMenu,
+ subMenuProps: item.subMenuProps,
+ submenuIconProps: item.submenuIconProps,
+ split: true,
+ } as IContextualMenuItem;
+
+ const buttonProps = assign({}, getNativeProps(itemProps, buttonProperties), {
+ onMouseEnter: this._onItemMouseEnterIcon,
+ onMouseLeave: onItemMouseLeave ? onItemMouseLeave.bind(this, item) : undefined,
+ onMouseDown: (ev: any) => onItemMouseDown ? onItemMouseDown(item, ev) : undefined,
+ onMouseMove: this._onItemMouseMoveIcon,
+ 'data-is-focusable': false,
+ 'aria-hidden': true
+ });
+
+ return (
+
+ );
+ }
+
+ private _onItemMouseEnterPrimary = (ev: React.MouseEvent): void => {
+ const {
+ item,
+ onItemMouseEnter
+ } = this.props;
+ if (onItemMouseEnter) {
+ onItemMouseEnter({ ...item, subMenuProps: undefined, items: undefined }, ev, this._splitButton);
+ }
+ }
+
+ private _onItemMouseEnterIcon = (ev: React.MouseEvent): void => {
+ const { item, onItemMouseEnter } = this.props;
+ if (onItemMouseEnter) {
+ onItemMouseEnter(item, ev, this._splitButton);
+ }
+ }
+
+ private _onItemMouseMovePrimary = (ev: React.MouseEvent): void => {
+ const {
+ item,
+ onItemMouseMove
+ } = this.props;
+ if (onItemMouseMove) {
+ onItemMouseMove({ ...item, subMenuProps: undefined, items: undefined }, ev, this._splitButton);
+ }
+ }
+
+ private _onItemMouseMoveIcon = (ev: React.MouseEvent): void => {
+ const {
+ item,
+ onItemMouseMove
+ } = this.props;
+ if (onItemMouseMove) {
+ onItemMouseMove(item, ev, this._splitButton);
+ }
+ }
+
+ private _onIconItemClick = (ev: React.MouseEvent): void => {
+ const { item, onItemClickBase } = this.props;
+ if (onItemClickBase) {
+ onItemClickBase(item, ev, (this._splitButton ? this._splitButton : ev.currentTarget) as HTMLElement);
+ }
+ }
+
+ private _executeItemClick = (ev: React.MouseEvent | React.KeyboardEvent): void => {
+ const {
+ item,
+ executeItemClick
+ } = this.props;
+
+ if (item.disabled || item.isDisabled) {
+ return;
+ }
+
+ if (executeItemClick) {
+ executeItemClick(item, ev);
+ }
+ }
+
+ private _onItemKeyDown = (ev: React.KeyboardEvent): void => {
+ const { item, onItemKeyDown } = this.props;
+ if (ev.which === KeyCodes.enter) {
+ this._executeItemClick(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..1e7ebdb801f2c5
--- /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..5fe08a6dd930ee
--- /dev/null
+++ b/packages/office-ui-fabric-react/src/components/ContextualMenu/__snapshots__/ContextualMenuSplitButton.test.tsx.snap
@@ -0,0 +1,676 @@
+// 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": [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": [Function],
+ "onMouseLeave": undefined,
+ "onMouseMove": [Function],
+ },
+ "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": [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": [Function],
+ "onMouseLeave": undefined,
+ "onMouseMove": [Function],
+ },
+ "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 434710ccbe961e..d4309c63e9423b 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): boolean {
return !!(item.subMenuProps || item.items);
}
+
+export function isItemDisabled(item: IContextualMenuItem): boolean {
+ return !!(item.isDisabled || item.disabled);
+}
\ No newline at end of file
diff --git a/scripts/package.json b/scripts/package.json
index 54abbbccd466c3..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": "47 kB"
+ "maxSize": "47.6 kB"
}
]
}
\ No newline at end of file