office-ui-fabric-react
Version:
Reusable React components for building experiences for Office 365.
388 lines (386 loc) • 20.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var React = require("react");
var ContextualMenu_Props_1 = require("./ContextualMenu.Props");
var DirectionalHint_1 = require("../../common/DirectionalHint");
var FocusZone_1 = require("../../FocusZone");
var Utilities_1 = require("../../Utilities");
var withResponsiveMode_1 = require("../../utilities/decorators/withResponsiveMode");
var Callout_1 = require("../../Callout");
var Icon_1 = require("../../Icon");
var stylesImport = require("./ContextualMenu.scss");
var styles = stylesImport;
var ContextualMenuType;
(function (ContextualMenuType) {
ContextualMenuType[ContextualMenuType["vertical"] = 0] = "vertical";
ContextualMenuType[ContextualMenuType["horizontal"] = 1] = "horizontal";
})(ContextualMenuType || (ContextualMenuType = {}));
var HorizontalAlignmentHint;
(function (HorizontalAlignmentHint) {
HorizontalAlignmentHint[HorizontalAlignmentHint["auto"] = 0] = "auto";
HorizontalAlignmentHint[HorizontalAlignmentHint["left"] = 1] = "left";
HorizontalAlignmentHint[HorizontalAlignmentHint["center"] = 2] = "center";
HorizontalAlignmentHint[HorizontalAlignmentHint["right"] = 3] = "right";
})(HorizontalAlignmentHint || (HorizontalAlignmentHint = {}));
var VerticalAlignmentHint;
(function (VerticalAlignmentHint) {
VerticalAlignmentHint[VerticalAlignmentHint["top"] = 0] = "top";
VerticalAlignmentHint[VerticalAlignmentHint["center"] = 1] = "center";
VerticalAlignmentHint[VerticalAlignmentHint["bottom"] = 2] = "bottom";
})(VerticalAlignmentHint || (VerticalAlignmentHint = {}));
function hasSubmenuItems(item) {
var submenuItems = getSubmenuItems(item);
return !!(submenuItems && submenuItems.length);
}
exports.hasSubmenuItems = hasSubmenuItems;
function getSubmenuItems(item) {
return item.subMenuProps ? item.subMenuProps.items : item.items;
}
exports.getSubmenuItems = getSubmenuItems;
var ContextualMenu = ContextualMenu_1 = (function (_super) {
tslib_1.__extends(ContextualMenu, _super);
function ContextualMenu(props) {
var _this = _super.call(this, props) || this;
_this.state = {
contextualMenuItems: null,
subMenuId: Utilities_1.getId('ContextualMenu')
};
_this._isFocusingPreviousElement = false;
_this._enterTimerId = 0;
return _this;
}
ContextualMenu.prototype.dismiss = function (ev, dismissAll) {
var onDismiss = this.props.onDismiss;
if (onDismiss) {
onDismiss(ev, dismissAll);
}
};
ContextualMenu.prototype.componentWillUpdate = function (newProps) {
if (newProps.targetElement !== this.props.targetElement || newProps.target !== this.props.target) {
var newTarget = newProps.targetElement ? newProps.targetElement : newProps.target;
this._setTargetWindowAndElement(newTarget);
}
};
// Invoked once, both on the client and server, immediately before the initial rendering occurs.
ContextualMenu.prototype.componentWillMount = function () {
var target = this.props.targetElement ? this.props.targetElement : this.props.target;
this._setTargetWindowAndElement(target);
this._previousActiveElement = this._targetWindow ? this._targetWindow.document.activeElement : null;
};
// Invoked once, only on the client (not on the server), immediately after the initial rendering occurs.
ContextualMenu.prototype.componentDidMount = function () {
this._events.on(this._targetWindow, 'resize', this.dismiss);
if (this.props.onMenuOpened) {
this.props.onMenuOpened(this.props);
}
};
// Invoked immediately before a component is unmounted from the DOM.
ContextualMenu.prototype.componentWillUnmount = function () {
var _this = this;
if (this._isFocusingPreviousElement && this._previousActiveElement) {
// This slight delay is required so that we can unwind the stack, let react try to mess with focus, and then
// apply the correct focus. Without the setTimeout, we end up focusing the correct thing, and then React wants
// to reset the focus back to the thing it thinks should have been focused.
setTimeout(function () { return _this._previousActiveElement.focus(); }, 0);
}
this._events.dispose();
this._async.dispose();
};
ContextualMenu.prototype.render = function () {
var _this = this;
var _a = this.props, className = _a.className, items = _a.items, isBeakVisible = _a.isBeakVisible, labelElementId = _a.labelElementId, targetElement = _a.targetElement, id = _a.id, targetPoint = _a.targetPoint, useTargetPoint = _a.useTargetPoint, beakWidth = _a.beakWidth, directionalHint = _a.directionalHint, gapSpace = _a.gapSpace, coverTarget = _a.coverTarget, ariaLabel = _a.ariaLabel, doNotLayer = _a.doNotLayer, arrowDirection = _a.arrowDirection, target = _a.target, bounds = _a.bounds, useTargetWidth = _a.useTargetWidth, directionalHintFixed = _a.directionalHintFixed, shouldFocusOnMount = _a.shouldFocusOnMount, calloutProps = _a.calloutProps;
var hasIcons = !!(items && items.some(function (item) { return !!item.icon || !!item.iconProps; }));
var hasCheckmarks = !!(items && items.some(function (item) { return !!item.canCheck; }));
var submenuProps = this.state.expandedMenuItemKey ? this._getSubmenuProps() : null;
isBeakVisible = isBeakVisible === undefined ? this.props.responsiveMode <= withResponsiveMode_1.ResponsiveMode.medium : isBeakVisible;
/**
* When useTargetWidth is true, get the width of the target element and apply it for the context menu container
*/
var contextMenuStyle;
var targetAsHtmlElement = this._target;
if (useTargetWidth && targetAsHtmlElement && targetAsHtmlElement.offsetWidth) {
var contextMenuWidth = targetAsHtmlElement.offsetWidth;
contextMenuStyle = {
width: contextMenuWidth
};
}
// The menu should only return if items were provided, if no items were provided then it should not appear.
if (items && items.length > 0) {
return (React.createElement(Callout_1.Callout, tslib_1.__assign({}, calloutProps, { target: target, targetElement: targetElement, targetPoint: targetPoint, useTargetPoint: useTargetPoint, isBeakVisible: isBeakVisible, beakWidth: beakWidth, directionalHint: directionalHint, gapSpace: gapSpace, coverTarget: coverTarget, doNotLayer: doNotLayer, className: 'ms-ContextualMenu-Callout', setInitialFocus: shouldFocusOnMount, onDismiss: this.props.onDismiss, bounds: bounds, directionalHintFixed: directionalHintFixed }),
React.createElement("div", { style: contextMenuStyle, ref: function (host) { return _this._host = host; }, id: id, className: Utilities_1.css('ms-ContextualMenu-container', className) },
(items && items.length) ? (React.createElement(FocusZone_1.FocusZone, { className: Utilities_1.css('ms-ContextualMenu is-open', styles.root), direction: arrowDirection, ref: function (focusZone) { return _this._focusZone = focusZone; }, isCircularNavigation: true },
React.createElement("ul", { role: 'menu', "aria-label": ariaLabel, "aria-labelledby": labelElementId, className: Utilities_1.css('ms-ContextualMenu-list is-open', styles.list), onKeyDown: this._onKeyDown }, items.map(function (item, index) { return (_this._renderMenuItem(item, index, hasCheckmarks, hasIcons)); })))) : (null),
submenuProps && React.createElement(ContextualMenu_1, tslib_1.__assign({}, submenuProps)))));
}
else {
return null;
}
};
ContextualMenu.prototype._renderMenuItem = function (item, index, hasCheckmarks, hasIcons) {
var renderedItems = [];
if (item.name === '-') {
item.itemType = ContextualMenu_Props_1.ContextualMenuItemType.Divider;
}
switch (item.itemType) {
case ContextualMenu_Props_1.ContextualMenuItemType.Divider:
renderedItems.push(this._renderSeparator(index, item.className));
break;
case ContextualMenu_Props_1.ContextualMenuItemType.Header:
renderedItems.push(this._renderSeparator(index));
var headerItem = this._renderHeaderMenuItem(item, index, hasCheckmarks, hasIcons);
renderedItems.push(this._renderListItem(headerItem, item.key || index, item.className, item.title));
break;
default:
var menuItem = this._renderNormalItem(item, index, hasCheckmarks, hasIcons);
renderedItems.push(this._renderListItem(menuItem, item.key || index, item.className, item.title));
break;
}
return renderedItems;
};
ContextualMenu.prototype._renderListItem = function (content, key, className, title) {
return React.createElement("li", { role: 'presentation', title: title, key: key, className: Utilities_1.css('ms-ContextualMenu-item', styles.item, className) }, content);
};
ContextualMenu.prototype._renderSeparator = function (index, className) {
if (index > 0) {
return React.createElement("li", { role: 'separator', key: 'separator-' + index, className: Utilities_1.css('ms-ContextualMenu-divider', styles.divider, className) });
}
return null;
};
ContextualMenu.prototype._renderNormalItem = function (item, index, hasCheckmarks, hasIcons) {
if (item.onRender) {
return [item.onRender(item)];
}
if (item.href) {
return this._renderAnchorMenuItem(item, index, hasCheckmarks, hasIcons);
}
return this._renderButtonItem(item, index, hasCheckmarks, hasIcons);
};
ContextualMenu.prototype._renderHeaderMenuItem = function (item, index, hasCheckmarks, hasIcons) {
return (React.createElement("div", { className: Utilities_1.css('ms-ContextualMenu-header', styles.header), style: item.style }, this._renderMenuItemChildren(item, index, hasCheckmarks, hasIcons)));
};
ContextualMenu.prototype._renderAnchorMenuItem = function (item, index, hasCheckmarks, hasIcons) {
return (React.createElement("div", null,
React.createElement("a", tslib_1.__assign({}, Utilities_1.getNativeProps(item, Utilities_1.anchorProperties), { href: item.href, className: Utilities_1.css('ms-ContextualMenu-link', styles.link, (item.isDisabled || item.disabled) && 'is-disabled'), role: 'menuitem', style: item.style, onClick: this._onAnchorClick.bind(this, item) }), this._renderMenuItemChildren(item, index, hasCheckmarks, hasIcons))));
};
ContextualMenu.prototype._renderButtonItem = function (item, index, hasCheckmarks, hasIcons) {
var _this = this;
var _a = this.state, expandedMenuItemKey = _a.expandedMenuItemKey, subMenuId = _a.subMenuId;
var ariaLabel = '';
if (item.ariaLabel) {
ariaLabel = item.ariaLabel;
}
else if (item.name) {
ariaLabel = item.name;
}
var itemButtonProperties = {
className: Utilities_1.css('ms-ContextualMenu-link', styles.link, (_b = {},
_b['is-expanded ' + styles.isExpanded] = (expandedMenuItemKey === item.key),
_b)),
onClick: this._onItemClick.bind(this, item),
onKeyDown: hasSubmenuItems(item) ? this._onItemKeyDown.bind(this, item) : null,
onMouseEnter: this._onItemMouseEnter.bind(this, item),
onMouseLeave: this._onMouseLeave,
onMouseDown: function (ev) { return _this._onItemMouseDown(item, ev); },
disabled: item.isDisabled || item.disabled,
href: item.href,
title: item.title,
'aria-label': ariaLabel,
'aria-haspopup': hasSubmenuItems(item) ? true : null,
'aria-owns': item.key === expandedMenuItemKey ? subMenuId : null,
role: 'menuitem',
style: item.style,
};
return React.createElement('button', Utilities_1.assign({}, Utilities_1.getNativeProps(item, Utilities_1.buttonProperties), itemButtonProperties), this._renderMenuItemChildren(item, index, hasCheckmarks, hasIcons));
var _b;
};
ContextualMenu.prototype._renderMenuItemChildren = function (item, index, hasCheckmarks, hasIcons) {
var isItemChecked = item.isChecked || item.checked;
return (React.createElement("div", { className: Utilities_1.css('ms-ContextualMenu-linkContent', styles.linkContent) },
(hasCheckmarks) ? (React.createElement(Icon_1.Icon, { iconName: isItemChecked ? 'CheckMark' : 'CustomIcon', className: Utilities_1.css('ms-ContextualMenu-icon', styles.icon), onClick: this._onItemClick.bind(this, item) })) : (null),
(hasIcons) ? (this._renderIcon(item)) : (null),
React.createElement("span", { className: Utilities_1.css('ms-ContextualMenu-itemText', styles.itemText) }, item.name),
hasSubmenuItems(item) ? (React.createElement(Icon_1.Icon, tslib_1.__assign({ iconName: Utilities_1.getRTL() ? 'ChevronLeft' : 'ChevronRight' }, item.submenuIconProps, { className: Utilities_1.css('ms-ContextualMenu-submenuIcon', styles.submenuIcon, item.submenuIconProps ? item.submenuIconProps.className : '') }))) : (null)));
};
ContextualMenu.prototype._renderIcon = function (item) {
// Only present to allow continued use of item.icon which is deprecated.
var iconProps = item.iconProps ? item.iconProps : {
iconName: item.icon
};
// Use the default icon color for the known icon names
var iconColorClassName = iconProps.iconName === 'None' ? '' : ('ms-ContextualMenu-iconColor ' + styles.iconColor);
var iconClassName = Utilities_1.css('ms-ContextualMenu-icon', styles.icon, iconColorClassName, iconProps.className);
return React.createElement(Icon_1.Icon, tslib_1.__assign({}, iconProps, { className: iconClassName }));
};
ContextualMenu.prototype._onKeyDown = function (ev) {
var submenuCloseKey = Utilities_1.getRTL() ? 39 /* right */ : 37 /* left */;
if (ev.which === 27 /* escape */
|| ev.which === 9 /* tab */
|| (ev.which === submenuCloseKey && this.props.isSubMenu && this.props.arrowDirection === FocusZone_1.FocusZoneDirection.vertical)) {
// When a user presses escape, we will try to refocus the previous focused element.
this._isFocusingPreviousElement = true;
ev.preventDefault();
ev.stopPropagation();
this.dismiss(ev);
}
};
ContextualMenu.prototype._onItemMouseEnter = function (item, ev) {
var _this = this;
var targetElement = ev.currentTarget;
if (item.key !== this.state.expandedMenuItemKey) {
if (hasSubmenuItems(item)) {
this._enterTimerId = this._async.setTimeout(function () { return _this._onItemSubMenuExpand(item, targetElement); }, 500);
}
else {
this._enterTimerId = this._async.setTimeout(function () { return _this._onSubMenuDismiss(ev); }, 500);
}
}
};
ContextualMenu.prototype._onMouseLeave = function (ev) {
this._async.clearTimeout(this._enterTimerId);
};
ContextualMenu.prototype._onItemMouseDown = function (item, ev) {
if (item.onMouseDown) {
item.onMouseDown(item, ev);
}
};
ContextualMenu.prototype._onItemClick = function (item, ev) {
var items = getSubmenuItems(item);
if (!items || !items.length) {
this._executeItemClick(item, ev);
}
else {
if (item.key === this.state.expandedMenuItemKey) {
this._onSubMenuDismiss(ev);
}
else {
this._onItemSubMenuExpand(item, ev.currentTarget);
}
}
ev.stopPropagation();
ev.preventDefault();
};
ContextualMenu.prototype._onAnchorClick = function (item, ev) {
this._executeItemClick(item, ev);
ev.stopPropagation();
};
ContextualMenu.prototype._executeItemClick = function (item, ev) {
if (item.onClick) {
item.onClick(ev, item);
}
else if (this.props.onItemClick) {
this.props.onItemClick(ev, item);
}
this.dismiss(ev, true);
};
ContextualMenu.prototype._onItemKeyDown = function (item, ev) {
var openKey = Utilities_1.getRTL() ? 37 /* left */ : 39 /* right */;
if (ev.which === openKey) {
this._onItemSubMenuExpand(item, ev.currentTarget);
ev.preventDefault();
}
};
ContextualMenu.prototype._onItemSubMenuExpand = function (item, target) {
if (this.state.expandedMenuItemKey !== item.key) {
if (this.state.expandedMenuItemKey) {
this._onSubMenuDismiss();
}
this.setState({
expandedMenuItemKey: item.key,
submenuTarget: target
});
}
};
ContextualMenu.prototype._getSubmenuProps = function () {
var _a = this.state, submenuTarget = _a.submenuTarget, expandedMenuItemKey = _a.expandedMenuItemKey;
var item = this._findItemByKey(expandedMenuItemKey);
var submenuProps = null;
if (item) {
submenuProps = {
items: getSubmenuItems(item),
target: submenuTarget,
onDismiss: this._onSubMenuDismiss,
isSubMenu: true,
id: this.state.subMenuId,
shouldFocusOnMount: true,
directionalHint: Utilities_1.getRTL() ? DirectionalHint_1.DirectionalHint.leftTopEdge : DirectionalHint_1.DirectionalHint.rightTopEdge,
className: this.props.className,
gapSpace: 0
};
if (item.subMenuProps) {
Utilities_1.assign(submenuProps, item.subMenuProps);
}
}
return submenuProps;
};
ContextualMenu.prototype._findItemByKey = function (key) {
var items = this.props.items;
for (var _i = 0, items_1 = items; _i < items_1.length; _i++) {
var item = items_1[_i];
if (item.key && item.key === key) {
return item;
}
}
};
ContextualMenu.prototype._onSubMenuDismiss = function (ev, dismissAll) {
if (dismissAll) {
this.dismiss(ev, dismissAll);
}
else {
this.setState({
dismissedMenuItemKey: this.state.expandedMenuItemKey,
expandedMenuItemKey: null,
submenuTarget: null
});
}
};
ContextualMenu.prototype._setTargetWindowAndElement = function (target) {
if (target) {
if (typeof target === 'string') {
var currentDoc = Utilities_1.getDocument();
this._target = currentDoc ? currentDoc.querySelector(target) : null;
this._targetWindow = Utilities_1.getWindow();
}
else if (target.stopPropagation) {
this._target = target;
this._targetWindow = Utilities_1.getWindow(target.toElement);
}
else {
var targetElement = target;
this._target = target;
this._targetWindow = Utilities_1.getWindow(targetElement);
}
}
else {
this._targetWindow = Utilities_1.getWindow();
}
};
return ContextualMenu;
}(Utilities_1.BaseComponent));
// The default ContextualMenu properities have no items and beak, the default submenu direction is right and top.
ContextualMenu.defaultProps = {
items: [],
shouldFocusOnMount: true,
gapSpace: 0,
directionalHint: DirectionalHint_1.DirectionalHint.bottomAutoEdge,
beakWidth: 16,
arrowDirection: FocusZone_1.FocusZoneDirection.vertical,
};
tslib_1.__decorate([
Utilities_1.autobind
], ContextualMenu.prototype, "dismiss", null);
tslib_1.__decorate([
Utilities_1.autobind
], ContextualMenu.prototype, "_onKeyDown", null);
tslib_1.__decorate([
Utilities_1.autobind
], ContextualMenu.prototype, "_onMouseLeave", null);
tslib_1.__decorate([
Utilities_1.autobind
], ContextualMenu.prototype, "_onSubMenuDismiss", null);
ContextualMenu = ContextualMenu_1 = tslib_1.__decorate([
withResponsiveMode_1.withResponsiveMode
], ContextualMenu);
exports.ContextualMenu = ContextualMenu;
var ContextualMenu_1;
//# sourceMappingURL=ContextualMenu.js.map