930 lines
47 KiB
JavaScript
930 lines
47 KiB
JavaScript
import { __assign, __rest, __spreadArray } from "tslib";
|
|
import * as React from 'react';
|
|
import { ContextualMenuItemType } from './ContextualMenu.types';
|
|
import { DirectionalHint } from '../../common/DirectionalHint';
|
|
import { FocusZone, FocusZoneDirection, FocusZoneTabbableElements } from '../../FocusZone';
|
|
import { divProperties, getNativeProps, shallowCompare, assign, classNamesFunction, css, getFirstFocusable, getLastFocusable, getRTL, KeyCodes, shouldWrapFocus, isIOS, isMac, memoizeFunction, getPropsWithDefaults, getDocument, FocusRects, composeComponentAs, } from '../../Utilities';
|
|
import { hasSubmenu, getIsChecked, isItemDisabled } from '../../utilities/contextualMenu/index';
|
|
import { Callout } from '../../Callout';
|
|
import { ContextualMenuItem } from './ContextualMenuItem';
|
|
import { ContextualMenuSplitButton, ContextualMenuButton, ContextualMenuAnchor, } from './ContextualMenuItemWrapper/index';
|
|
import { concatStyleSetsWithProps } from '../../Styling';
|
|
import { getItemStyles } from './ContextualMenu.classNames';
|
|
import { useTarget, usePrevious, useAsync, useWarnings, useId, useIsomorphicLayoutEffect, } from '@fluentui/react-hooks';
|
|
import { useResponsiveMode, ResponsiveMode } from '../../ResponsiveMode';
|
|
import { MenuContext } from '../../utilities/MenuContext/index';
|
|
var getClassNames = classNamesFunction();
|
|
var getContextualMenuItemClassNames = classNamesFunction();
|
|
// The default ContextualMenu properties have no items and beak, the default submenu direction is right and top.
|
|
var DEFAULT_PROPS = {
|
|
items: [],
|
|
shouldFocusOnMount: true,
|
|
gapSpace: 0,
|
|
directionalHint: DirectionalHint.bottomAutoEdge,
|
|
beakWidth: 16,
|
|
};
|
|
/* return number of menu items, excluding headers and dividers */
|
|
function getItemCount(items) {
|
|
var totalItemCount = 0;
|
|
for (var _i = 0, items_1 = items; _i < items_1.length; _i++) {
|
|
var item = items_1[_i];
|
|
if (item.itemType !== ContextualMenuItemType.Divider && item.itemType !== ContextualMenuItemType.Header) {
|
|
var itemCount = item.customOnRenderListLength ? item.customOnRenderListLength : 1;
|
|
totalItemCount += itemCount;
|
|
}
|
|
}
|
|
return totalItemCount;
|
|
}
|
|
export function getSubmenuItems(item, options) {
|
|
var target = options === null || options === void 0 ? void 0 : options.target;
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
var items = item.subMenuProps ? item.subMenuProps.items : item.items;
|
|
if (items) {
|
|
var overrideItems = [];
|
|
for (var _i = 0, items_2 = items; _i < items_2.length; _i++) {
|
|
var subItem = items_2[_i];
|
|
if (subItem.preferMenuTargetAsEventTarget) {
|
|
// For sub-items which need an overridden target, intercept `onClick`
|
|
var onClick = subItem.onClick, contextItem = __rest(subItem, ["onClick"]);
|
|
overrideItems.push(__assign(__assign({}, contextItem), { onClick: getOnClickWithOverrideTarget(onClick, target) }));
|
|
}
|
|
else {
|
|
overrideItems.push(subItem);
|
|
}
|
|
}
|
|
return overrideItems;
|
|
}
|
|
}
|
|
/**
|
|
* Returns true if a list of menu items can contain a checkbox
|
|
*/
|
|
export function canAnyMenuItemsCheck(items) {
|
|
return items.some(function (item) {
|
|
if (item.canCheck) {
|
|
return true;
|
|
}
|
|
// If the item is a section, check if any of the items in the section can check.
|
|
if (item.sectionProps && item.sectionProps.items.some(function (submenuItem) { return submenuItem.canCheck === true; })) {
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
}
|
|
var NavigationIdleDelay = 250; /* ms */
|
|
var COMPONENT_NAME = 'ContextualMenu';
|
|
var _getMenuItemStylesFunction = memoizeFunction(function () {
|
|
var styles = [];
|
|
for (var _i = 0; _i < arguments.length; _i++) {
|
|
styles[_i] = arguments[_i];
|
|
}
|
|
return function (styleProps) {
|
|
return concatStyleSetsWithProps.apply(void 0, __spreadArray([styleProps, getItemStyles], styles, false));
|
|
};
|
|
});
|
|
//#region Custom hooks
|
|
function useVisibility(props, targetWindow) {
|
|
var _a = props.hidden, hidden = _a === void 0 ? false : _a, onMenuDismissed = props.onMenuDismissed, onMenuOpened = props.onMenuOpened;
|
|
var previousHidden = usePrevious(hidden);
|
|
var onMenuOpenedRef = React.useRef(onMenuOpened);
|
|
var onMenuClosedRef = React.useRef(onMenuDismissed);
|
|
var propsRef = React.useRef(props);
|
|
onMenuOpenedRef.current = onMenuOpened;
|
|
onMenuClosedRef.current = onMenuDismissed;
|
|
propsRef.current = props;
|
|
React.useEffect(function () {
|
|
var _a, _b;
|
|
// Don't issue dismissed callbacks on initial mount
|
|
if (hidden && previousHidden === false) {
|
|
(_a = onMenuClosedRef.current) === null || _a === void 0 ? void 0 : _a.call(onMenuClosedRef, propsRef.current);
|
|
}
|
|
else if (!hidden && previousHidden !== false) {
|
|
(_b = onMenuOpenedRef.current) === null || _b === void 0 ? void 0 : _b.call(onMenuOpenedRef, propsRef.current);
|
|
}
|
|
}, [hidden, previousHidden]);
|
|
// Issue onDismissedCallback on unmount
|
|
React.useEffect(function () { return function () { var _a; return (_a = onMenuClosedRef.current) === null || _a === void 0 ? void 0 : _a.call(onMenuClosedRef, propsRef.current); }; }, []);
|
|
}
|
|
function useSubMenuState(_a, dismiss) {
|
|
var hidden = _a.hidden, items = _a.items, theme = _a.theme, className = _a.className, id = _a.id, menuTarget = _a.target;
|
|
var _b = React.useState(), expandedMenuItemKey = _b[0], setExpandedMenuItemKey = _b[1];
|
|
var _c = React.useState(), submenuTarget = _c[0], setSubmenuTarget = _c[1];
|
|
/** True if the menu was expanded by mouse click OR hover (as opposed to by keyboard) */
|
|
var _d = React.useState(), shouldFocusOnContainer = _d[0], setShouldFocusOnContainer = _d[1];
|
|
var subMenuId = useId(COMPONENT_NAME, id);
|
|
var closeSubMenu = React.useCallback(function () {
|
|
setShouldFocusOnContainer(undefined);
|
|
setExpandedMenuItemKey(undefined);
|
|
setSubmenuTarget(undefined);
|
|
}, []);
|
|
var openSubMenu = React.useCallback(function (_a, target, focusContainer) {
|
|
var submenuItemKey = _a.key;
|
|
if (expandedMenuItemKey === submenuItemKey) {
|
|
return;
|
|
}
|
|
target.focus();
|
|
setShouldFocusOnContainer(focusContainer);
|
|
setExpandedMenuItemKey(submenuItemKey);
|
|
setSubmenuTarget(target);
|
|
}, [expandedMenuItemKey]);
|
|
React.useEffect(function () {
|
|
if (hidden) {
|
|
closeSubMenu();
|
|
}
|
|
}, [hidden, closeSubMenu]);
|
|
var onSubMenuDismiss = useOnSubmenuDismiss(dismiss, closeSubMenu);
|
|
var getSubmenuProps = function () {
|
|
var item = findItemByKeyFromItems(expandedMenuItemKey, items);
|
|
var submenuProps = null;
|
|
if (item) {
|
|
submenuProps = {
|
|
items: getSubmenuItems(item, { target: menuTarget }),
|
|
target: submenuTarget,
|
|
onDismiss: onSubMenuDismiss,
|
|
isSubMenu: true,
|
|
id: subMenuId,
|
|
shouldFocusOnMount: true,
|
|
shouldFocusOnContainer: shouldFocusOnContainer,
|
|
directionalHint: getRTL(theme) ? DirectionalHint.leftTopEdge : DirectionalHint.rightTopEdge,
|
|
className: className,
|
|
gapSpace: 0,
|
|
isBeakVisible: false,
|
|
};
|
|
if (item.subMenuProps) {
|
|
assign(submenuProps, item.subMenuProps);
|
|
}
|
|
if (item.preferMenuTargetAsEventTarget) {
|
|
var onItemClick = item.onItemClick;
|
|
submenuProps.onItemClick = getOnClickWithOverrideTarget(onItemClick, menuTarget);
|
|
}
|
|
}
|
|
return submenuProps;
|
|
};
|
|
return [expandedMenuItemKey, openSubMenu, getSubmenuProps, onSubMenuDismiss];
|
|
}
|
|
function useShouldUpdateFocusOnMouseMove(_a) {
|
|
var delayUpdateFocusOnHover = _a.delayUpdateFocusOnHover, hidden = _a.hidden;
|
|
var shouldUpdateFocusOnMouseEvent = React.useRef(!delayUpdateFocusOnHover);
|
|
var gotMouseMove = React.useRef(false);
|
|
React.useEffect(function () {
|
|
shouldUpdateFocusOnMouseEvent.current = !delayUpdateFocusOnHover;
|
|
gotMouseMove.current = hidden ? false : !delayUpdateFocusOnHover && gotMouseMove.current;
|
|
}, [delayUpdateFocusOnHover, hidden]);
|
|
var onMenuFocusCapture = React.useCallback(function () {
|
|
if (delayUpdateFocusOnHover) {
|
|
shouldUpdateFocusOnMouseEvent.current = false;
|
|
}
|
|
}, [delayUpdateFocusOnHover]);
|
|
return [shouldUpdateFocusOnMouseEvent, gotMouseMove, onMenuFocusCapture];
|
|
}
|
|
function usePreviousActiveElement(_a, targetWindow, hostElement) {
|
|
var hidden = _a.hidden, onRestoreFocus = _a.onRestoreFocus;
|
|
var previousActiveElement = React.useRef(undefined);
|
|
var tryFocusPreviousActiveElement = React.useCallback(function (options) {
|
|
var _a, _b;
|
|
if (onRestoreFocus) {
|
|
onRestoreFocus(options);
|
|
}
|
|
else if (options === null || options === void 0 ? void 0 : options.documentContainsFocus) {
|
|
// Make sure that the focus method actually exists
|
|
// In some cases the object might exist but not be a real element.
|
|
// This is primarily for IE 11 and should be removed once IE 11 is no longer in use.
|
|
(_b = (_a = previousActiveElement.current) === null || _a === void 0 ? void 0 : _a.focus) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
}
|
|
}, [onRestoreFocus]);
|
|
useIsomorphicLayoutEffect(function () {
|
|
var _a, _b;
|
|
if (!hidden) {
|
|
var newElement = targetWindow === null || targetWindow === void 0 ? void 0 : targetWindow.document.activeElement;
|
|
if (!((_a = hostElement.current) === null || _a === void 0 ? void 0 : _a.contains(newElement)) && newElement.tagName !== 'BODY') {
|
|
previousActiveElement.current = newElement;
|
|
}
|
|
}
|
|
else if (previousActiveElement.current) {
|
|
tryFocusPreviousActiveElement({
|
|
originalElement: previousActiveElement.current,
|
|
containsFocus: true,
|
|
documentContainsFocus: ((_b = getDocument()) === null || _b === void 0 ? void 0 : _b.hasFocus()) || false,
|
|
});
|
|
previousActiveElement.current = undefined;
|
|
}
|
|
}, [hidden, targetWindow === null || targetWindow === void 0 ? void 0 : targetWindow.document.activeElement, tryFocusPreviousActiveElement, hostElement]);
|
|
return [tryFocusPreviousActiveElement];
|
|
}
|
|
function useKeyHandlers(_a, dismiss, hostElement, openSubMenu) {
|
|
var theme = _a.theme, isSubMenu = _a.isSubMenu, _b = _a.focusZoneProps, _c = _b === void 0 ? {} : _b, checkForNoWrap = _c.checkForNoWrap, _d = _c.direction, focusZoneDirection = _d === void 0 ? FocusZoneDirection.vertical : _d;
|
|
/** True if the most recent keydown event was for alt (option) or meta (command). */
|
|
var lastKeyDownWasAltOrMeta = React.useRef(undefined);
|
|
/**
|
|
* Calls `shouldHandleKey` to determine whether the keyboard event should be handled;
|
|
* if so, stops event propagation and dismisses menu(s).
|
|
* @param ev - The keyboard event.
|
|
* @param shouldHandleKey - Returns whether we should handle this keyboard event.
|
|
* @param dismissAllMenus - If true, dismiss all menus. Otherwise, dismiss only the current menu.
|
|
* Only does anything if `shouldHandleKey` returns true.
|
|
* @returns Whether the event was handled.
|
|
*/
|
|
var keyHandler = function (ev, shouldHandleKey, dismissAllMenus) {
|
|
var handled = false;
|
|
if (shouldHandleKey(ev)) {
|
|
dismiss(ev, dismissAllMenus);
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
handled = true;
|
|
}
|
|
return handled;
|
|
};
|
|
/**
|
|
* Checks if the submenu should be closed
|
|
*/
|
|
var shouldCloseSubMenu = function (ev) {
|
|
var submenuCloseKey = getRTL(theme) ? KeyCodes.right : KeyCodes.left;
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
if (ev.which !== submenuCloseKey || !isSubMenu) {
|
|
return false;
|
|
}
|
|
return !!(focusZoneDirection === FocusZoneDirection.vertical ||
|
|
(checkForNoWrap && !shouldWrapFocus(ev.target, 'data-no-horizontal-wrap')));
|
|
};
|
|
var shouldHandleKeyDown = function (ev) {
|
|
return (
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
ev.which === KeyCodes.escape || shouldCloseSubMenu(ev) || (ev.which === KeyCodes.up && (ev.altKey || ev.metaKey)));
|
|
};
|
|
var onKeyDown = function (ev) {
|
|
// Take note if we are processing an alt (option) or meta (command) keydown.
|
|
// See comment in shouldHandleKeyUp for reasoning.
|
|
lastKeyDownWasAltOrMeta.current = isAltOrMeta(ev);
|
|
// On Mac, pressing escape dismisses all levels of native context menus
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
var dismissAllMenus = ev.which === KeyCodes.escape && (isMac() || isIOS());
|
|
return keyHandler(ev, shouldHandleKeyDown, dismissAllMenus);
|
|
};
|
|
/**
|
|
* We close the menu on key up only if ALL of the following are true:
|
|
* - Most recent key down was alt or meta (command)
|
|
* - The alt/meta key down was NOT followed by some other key (such as down/up arrow to
|
|
* expand/collapse the menu)
|
|
* - We're not on a Mac (or iOS)
|
|
*
|
|
* This is because on Windows, pressing alt moves focus to the application menu bar or similar,
|
|
* closing any open context menus. There is not a similar behavior on Macs.
|
|
*/
|
|
var shouldHandleKeyUp = function (ev) {
|
|
var keyPressIsAltOrMetaAlone = lastKeyDownWasAltOrMeta.current && isAltOrMeta(ev);
|
|
lastKeyDownWasAltOrMeta.current = false;
|
|
return !!keyPressIsAltOrMetaAlone && !(isIOS() || isMac());
|
|
};
|
|
var onKeyUp = function (ev) {
|
|
return keyHandler(ev, shouldHandleKeyUp, true /* dismissAllMenus */);
|
|
};
|
|
var onMenuKeyDown = function (ev) {
|
|
// Mark as handled if onKeyDown returns true (for handling collapse cases)
|
|
// or if we are attempting to expand a submenu
|
|
var handled = onKeyDown(ev);
|
|
if (handled || !hostElement.current) {
|
|
return;
|
|
}
|
|
// If we have a modifier key being pressed, we do not want to move focus.
|
|
// Otherwise, handle up and down keys.
|
|
var hasModifier = !!(ev.altKey || ev.metaKey);
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
var isUp = ev.which === KeyCodes.up;
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
var isDown = ev.which === KeyCodes.down;
|
|
if (!hasModifier && (isUp || isDown)) {
|
|
var elementToFocus = isUp
|
|
? getLastFocusable(hostElement.current, hostElement.current.lastChild, true)
|
|
: getFirstFocusable(hostElement.current, hostElement.current.firstChild, true);
|
|
if (elementToFocus) {
|
|
elementToFocus.focus();
|
|
ev.preventDefault();
|
|
ev.stopPropagation();
|
|
}
|
|
}
|
|
};
|
|
var onItemKeyDown = function (item, ev) {
|
|
var openKey = getRTL(theme) ? KeyCodes.left : KeyCodes.right;
|
|
if (!item.disabled &&
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
(ev.which === openKey || ev.which === KeyCodes.enter || (ev.which === KeyCodes.down && (ev.altKey || ev.metaKey)))) {
|
|
openSubMenu(item, ev.currentTarget);
|
|
ev.preventDefault();
|
|
}
|
|
};
|
|
return [onKeyDown, onKeyUp, onMenuKeyDown, onItemKeyDown];
|
|
}
|
|
function useScrollHandler(asyncTracker) {
|
|
var isScrollIdle = React.useRef(true);
|
|
var scrollIdleTimeoutId = React.useRef(undefined);
|
|
/**
|
|
* Scroll handler for the callout to make sure the mouse events
|
|
* for updating focus are not interacting during scroll
|
|
*/
|
|
var onScroll = function () {
|
|
if (!isScrollIdle.current && scrollIdleTimeoutId.current !== undefined) {
|
|
asyncTracker.clearTimeout(scrollIdleTimeoutId.current);
|
|
scrollIdleTimeoutId.current = undefined;
|
|
}
|
|
else {
|
|
isScrollIdle.current = false;
|
|
}
|
|
scrollIdleTimeoutId.current = asyncTracker.setTimeout(function () {
|
|
isScrollIdle.current = true;
|
|
}, NavigationIdleDelay);
|
|
};
|
|
return [onScroll, isScrollIdle];
|
|
}
|
|
function useOnSubmenuDismiss(dismiss, closeSubMenu) {
|
|
var isMountedRef = React.useRef(false);
|
|
React.useEffect(function () {
|
|
isMountedRef.current = true;
|
|
return function () {
|
|
isMountedRef.current = false;
|
|
};
|
|
}, []);
|
|
/**
|
|
* This function is called ASYNCHRONOUSLY, and so there is a chance it is called
|
|
* after the component is unmounted. The isMountedRef is added to prevent
|
|
* from calling setState() after unmount. Do NOT copy this pattern in synchronous
|
|
* code.
|
|
*/
|
|
var onSubMenuDismiss = function (ev, dismissAll) {
|
|
if (dismissAll) {
|
|
dismiss(ev, dismissAll);
|
|
}
|
|
else if (isMountedRef.current) {
|
|
closeSubMenu();
|
|
}
|
|
};
|
|
return onSubMenuDismiss;
|
|
}
|
|
function useSubmenuEnterTimer(_a, asyncTracker) {
|
|
var _b = _a.subMenuHoverDelay, subMenuHoverDelay = _b === void 0 ? NavigationIdleDelay : _b;
|
|
var enterTimerRef = React.useRef(undefined);
|
|
var cancelSubMenuTimer = function () {
|
|
if (enterTimerRef.current !== undefined) {
|
|
asyncTracker.clearTimeout(enterTimerRef.current);
|
|
enterTimerRef.current = undefined;
|
|
}
|
|
};
|
|
var startSubmenuTimer = function (onTimerExpired) {
|
|
enterTimerRef.current = asyncTracker.setTimeout(function () {
|
|
onTimerExpired();
|
|
cancelSubMenuTimer();
|
|
}, subMenuHoverDelay);
|
|
};
|
|
return [cancelSubMenuTimer, startSubmenuTimer, enterTimerRef];
|
|
}
|
|
function useMouseHandlers(props,
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
isScrollIdle, subMenuEntryTimer, targetWindow,
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
shouldUpdateFocusOnMouseEvent,
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
gotMouseMove, expandedMenuItemKey, hostElement, startSubmenuTimer, cancelSubMenuTimer, openSubMenu, onSubMenuDismiss, dismiss) {
|
|
var menuTarget = props.target;
|
|
var onItemMouseEnterBase = function (item, ev, target) {
|
|
if (shouldUpdateFocusOnMouseEvent.current) {
|
|
gotMouseMove.current = true;
|
|
}
|
|
if (shouldIgnoreMouseEvent()) {
|
|
return;
|
|
}
|
|
updateFocusOnMouseEvent(item, ev, target);
|
|
};
|
|
var onItemMouseMoveBase = function (item, ev, target) {
|
|
var targetElement = ev.currentTarget;
|
|
// Always do this check to make sure we record a mouseMove if needed (even if we are timed out)
|
|
if (shouldUpdateFocusOnMouseEvent.current) {
|
|
gotMouseMove.current = true;
|
|
}
|
|
else {
|
|
return;
|
|
}
|
|
if (!isScrollIdle.current ||
|
|
subMenuEntryTimer.current !== undefined ||
|
|
targetElement === (targetWindow === null || targetWindow === void 0 ? void 0 : targetWindow.document.activeElement)) {
|
|
return;
|
|
}
|
|
updateFocusOnMouseEvent(item, ev, target);
|
|
};
|
|
var shouldIgnoreMouseEvent = function () {
|
|
return !isScrollIdle.current || !gotMouseMove.current;
|
|
};
|
|
var onMouseItemLeave = function (item, ev) {
|
|
var _a;
|
|
if (shouldIgnoreMouseEvent()) {
|
|
return;
|
|
}
|
|
cancelSubMenuTimer();
|
|
if (expandedMenuItemKey !== undefined) {
|
|
return;
|
|
}
|
|
/**
|
|
* IE11 focus() method forces parents to scroll to top of element.
|
|
* Edge and IE expose a setActive() function for focusable divs that
|
|
* sets the page focus but does not scroll the parent element.
|
|
*/
|
|
if (hostElement.current.setActive) {
|
|
try {
|
|
hostElement.current.setActive();
|
|
}
|
|
catch (e) {
|
|
/* no-op */
|
|
}
|
|
}
|
|
else {
|
|
(_a = hostElement.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
}
|
|
};
|
|
/**
|
|
* Handles updating focus when mouseEnter or mouseMove fire.
|
|
* As part of updating focus, This function will also update
|
|
* the expand/collapse state accordingly.
|
|
*/
|
|
var updateFocusOnMouseEvent = function (item, ev, target) {
|
|
var targetElement = target ? target : ev.currentTarget;
|
|
if (item.key === expandedMenuItemKey) {
|
|
return;
|
|
}
|
|
cancelSubMenuTimer();
|
|
// If the menu is not expanded we can update focus without any delay
|
|
if (expandedMenuItemKey === undefined) {
|
|
targetElement.focus();
|
|
}
|
|
// Delay updating expanding/dismissing the submenu
|
|
// and only set focus if we have not already done so
|
|
if (hasSubmenu(item)) {
|
|
ev.stopPropagation();
|
|
startSubmenuTimer(function () {
|
|
targetElement.focus();
|
|
openSubMenu(item, targetElement, true);
|
|
});
|
|
}
|
|
else {
|
|
startSubmenuTimer(function () {
|
|
onSubMenuDismiss(ev);
|
|
targetElement.focus();
|
|
});
|
|
}
|
|
};
|
|
var onItemClick = function (item, ev) {
|
|
onItemClickBase(item, ev, ev.currentTarget);
|
|
};
|
|
var onItemClickBase = function (item, ev, target) {
|
|
var items = getSubmenuItems(item, { target: menuTarget });
|
|
// Cancel an async menu item hover timeout action from being taken and instead
|
|
// just trigger the click event instead.
|
|
cancelSubMenuTimer();
|
|
if (!hasSubmenu(item) && (!items || !items.length)) {
|
|
// This is an item without a menu. Click it.
|
|
executeItemClick(item, ev);
|
|
}
|
|
else {
|
|
if (item.key !== expandedMenuItemKey) {
|
|
// This has a collapsed sub menu. Expand it.
|
|
// focus on the container by default when the menu is opened with a click event
|
|
// this differentiates from a keyboard interaction triggering the click event
|
|
var shouldFocusOnContainer = typeof props.shouldFocusOnContainer === 'boolean'
|
|
? props.shouldFocusOnContainer
|
|
: ev.nativeEvent.pointerType === 'mouse';
|
|
openSubMenu(item, target, shouldFocusOnContainer);
|
|
}
|
|
}
|
|
ev.stopPropagation();
|
|
ev.preventDefault();
|
|
};
|
|
var onAnchorClick = function (item, ev) {
|
|
executeItemClick(item, ev);
|
|
ev.stopPropagation();
|
|
};
|
|
var executeItemClick = function (item, ev) {
|
|
if (item.disabled || item.isDisabled) {
|
|
return;
|
|
}
|
|
if (item.preferMenuTargetAsEventTarget) {
|
|
overrideTarget(ev, menuTarget);
|
|
}
|
|
var shouldDismiss = false;
|
|
if (item.onClick) {
|
|
shouldDismiss = !!item.onClick(ev, item);
|
|
}
|
|
else if (props.onItemClick) {
|
|
shouldDismiss = !!props.onItemClick(ev, item);
|
|
}
|
|
if (shouldDismiss || !ev.defaultPrevented) {
|
|
dismiss(ev, true);
|
|
}
|
|
};
|
|
return [
|
|
onItemMouseEnterBase,
|
|
onItemMouseMoveBase,
|
|
onMouseItemLeave,
|
|
onItemClick,
|
|
onAnchorClick,
|
|
executeItemClick,
|
|
onItemClickBase,
|
|
];
|
|
}
|
|
//#endregion
|
|
export var ContextualMenuBase = React.memo(React.forwardRef(function (propsWithoutDefaults, forwardedRef) {
|
|
var _a;
|
|
var _b = getPropsWithDefaults(DEFAULT_PROPS, propsWithoutDefaults), ref = _b.ref, props = __rest(_b, ["ref"]);
|
|
var hostElement = React.useRef(null);
|
|
var asyncTracker = useAsync();
|
|
var menuId = useId(COMPONENT_NAME, props.id);
|
|
useWarnings({
|
|
name: COMPONENT_NAME,
|
|
props: props,
|
|
deprecations: {
|
|
getMenuClassNames: 'styles',
|
|
},
|
|
});
|
|
var dismiss = function (ev, dismissAll) { var _a; return (_a = props.onDismiss) === null || _a === void 0 ? void 0 : _a.call(props, ev, dismissAll); };
|
|
var _c = useTarget(props.target, hostElement), targetRef = _c[0], targetWindow = _c[1];
|
|
var tryFocusPreviousActiveElement = usePreviousActiveElement(props, targetWindow, hostElement)[0];
|
|
var _d = useSubMenuState(props, dismiss), expandedMenuItemKey = _d[0], openSubMenu = _d[1], getSubmenuProps = _d[2], onSubMenuDismiss = _d[3];
|
|
var _e = useShouldUpdateFocusOnMouseMove(props), shouldUpdateFocusOnMouseEvent = _e[0], gotMouseMove = _e[1], onMenuFocusCapture = _e[2];
|
|
var _f = useScrollHandler(asyncTracker), onScroll = _f[0], isScrollIdle = _f[1];
|
|
var _g = useSubmenuEnterTimer(props, asyncTracker), cancelSubMenuTimer = _g[0], startSubmenuTimer = _g[1], subMenuEntryTimer = _g[2];
|
|
var responsiveMode = useResponsiveMode(hostElement, props.responsiveMode);
|
|
useVisibility(props, targetWindow);
|
|
var _h = useKeyHandlers(props, dismiss, hostElement, openSubMenu), onKeyDown = _h[0], onKeyUp = _h[1], onMenuKeyDown = _h[2], onItemKeyDown = _h[3];
|
|
var _j = useMouseHandlers(props, isScrollIdle, subMenuEntryTimer, targetWindow, shouldUpdateFocusOnMouseEvent, gotMouseMove, expandedMenuItemKey, hostElement, startSubmenuTimer, cancelSubMenuTimer, openSubMenu, onSubMenuDismiss, dismiss), onItemMouseEnterBase = _j[0], onItemMouseMoveBase = _j[1], onMouseItemLeave = _j[2], onItemClick = _j[3], onAnchorClick = _j[4], executeItemClick = _j[5], onItemClickBase = _j[6];
|
|
//#region Render helpers
|
|
var onDefaultRenderMenuList = function (menuListProps,
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
menuClassNames, defaultRender) {
|
|
var indexCorrection = 0;
|
|
var items = menuListProps.items, totalItemCount = menuListProps.totalItemCount, hasCheckmarks = menuListProps.hasCheckmarks, hasIcons = menuListProps.hasIcons;
|
|
return (React.createElement("ul", { className: menuClassNames.list, onKeyDown: onKeyDown, onKeyUp: onKeyUp, role: 'presentation' }, items.map(function (item, index) {
|
|
var menuItem = renderMenuItem(item, index, indexCorrection, totalItemCount, hasCheckmarks, hasIcons, menuClassNames);
|
|
if (item.itemType !== ContextualMenuItemType.Divider && item.itemType !== ContextualMenuItemType.Header) {
|
|
var indexIncrease = item.customOnRenderListLength ? item.customOnRenderListLength : 1;
|
|
indexCorrection += indexIncrease;
|
|
}
|
|
return menuItem;
|
|
})));
|
|
};
|
|
var renderFocusZone = function (children, adjustedFocusZoneProps) {
|
|
var _a = props.focusZoneAs, ChildrenRenderer = _a === void 0 ? FocusZone : _a;
|
|
return React.createElement(ChildrenRenderer, __assign({}, adjustedFocusZoneProps), children);
|
|
};
|
|
/**
|
|
* !!!IMPORTANT!!! Avoid mutating `item: IContextualMenuItem` argument. It will
|
|
* cause the menu items to always re-render because the component update is based on shallow comparison.
|
|
*/
|
|
var renderMenuItem = function (item, index, focusableElementIndex, totalItemCount, hasCheckmarks, hasIcons,
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
menuClassNames) {
|
|
var _a;
|
|
var renderedItems = [];
|
|
var iconProps = item.iconProps || { iconName: 'None' };
|
|
var getItemClassNames = item.getItemClassNames, // eslint-disable-line @typescript-eslint/no-deprecated
|
|
itemProps = item.itemProps;
|
|
var styles = itemProps ? itemProps.styles : undefined;
|
|
// We only send a dividerClassName when the item to be rendered is a divider.
|
|
// For all other cases, the default divider style is used.
|
|
var dividerClassName = item.itemType === ContextualMenuItemType.Divider ? item.className : undefined;
|
|
var subMenuIconClassName = item.submenuIconProps ? item.submenuIconProps.className : '';
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
var itemClassNames;
|
|
// IContextualMenuItem#getItemClassNames for backwards compatibility
|
|
// otherwise uses mergeStyles for class names.
|
|
if (getItemClassNames) {
|
|
itemClassNames = getItemClassNames(props.theme, isItemDisabled(item), expandedMenuItemKey === item.key, !!getIsChecked(item), !!item.href, iconProps.iconName !== 'None', item.className, dividerClassName, iconProps.className, subMenuIconClassName, item.primaryDisabled);
|
|
}
|
|
else {
|
|
var itemStyleProps = {
|
|
theme: props.theme,
|
|
disabled: isItemDisabled(item),
|
|
expanded: expandedMenuItemKey === item.key,
|
|
checked: !!getIsChecked(item),
|
|
isAnchorLink: !!item.href,
|
|
knownIcon: iconProps.iconName !== 'None',
|
|
itemClassName: item.className,
|
|
dividerClassName: dividerClassName,
|
|
iconClassName: iconProps.className,
|
|
subMenuClassName: subMenuIconClassName,
|
|
primaryDisabled: item.primaryDisabled,
|
|
};
|
|
// We need to generate default styles then override if styles are provided
|
|
// since the ContextualMenu currently handles item classNames.
|
|
itemClassNames = getContextualMenuItemClassNames(_getMenuItemStylesFunction((_a = menuClassNames.subComponentStyles) === null || _a === void 0 ? void 0 : _a.menuItem, styles), itemStyleProps);
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
if (item.text === '-' || item.name === '-') {
|
|
item.itemType = ContextualMenuItemType.Divider;
|
|
}
|
|
switch (item.itemType) {
|
|
case ContextualMenuItemType.Divider:
|
|
renderedItems.push(renderSeparator(index, itemClassNames));
|
|
break;
|
|
case ContextualMenuItemType.Header:
|
|
renderedItems.push(renderSeparator(index, itemClassNames));
|
|
var headerItem = renderHeaderMenuItem(item, itemClassNames, menuClassNames, index, hasCheckmarks, hasIcons);
|
|
renderedItems.push(renderListItem(headerItem, item.key || index, itemClassNames, item.title));
|
|
break;
|
|
case ContextualMenuItemType.Section:
|
|
renderedItems.push(renderSectionItem(item, itemClassNames, menuClassNames, index, hasCheckmarks, hasIcons));
|
|
break;
|
|
default:
|
|
var defaultRenderNormalItem = function () {
|
|
return renderNormalItem(item, itemClassNames, index, focusableElementIndex, totalItemCount, hasCheckmarks, hasIcons);
|
|
};
|
|
var menuItem = props.onRenderContextualMenuItem
|
|
? props.onRenderContextualMenuItem(item, defaultRenderNormalItem)
|
|
: defaultRenderNormalItem();
|
|
renderedItems.push(renderListItem(menuItem, item.key || index, itemClassNames, item.title));
|
|
break;
|
|
}
|
|
// Since multiple nodes *could* be rendered, wrap them all in a fragment with this item's key.
|
|
// This ensures the reconciler handles multi-item output per-node correctly and does not re-mount content.
|
|
return React.createElement(React.Fragment, { key: item.key }, renderedItems);
|
|
};
|
|
var defaultMenuItemRenderer = function (item,
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
menuClassNames) {
|
|
var index = item.index, focusableElementIndex = item.focusableElementIndex, totalItemCount = item.totalItemCount, hasCheckmarks = item.hasCheckmarks, hasIcons = item.hasIcons;
|
|
return renderMenuItem(item, index, focusableElementIndex, totalItemCount, hasCheckmarks, hasIcons, menuClassNames);
|
|
};
|
|
var renderSectionItem = function (sectionItem,
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
itemClassNames,
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
menuClassNames, index, hasCheckmarks, hasIcons) {
|
|
var sectionProps = sectionItem.sectionProps;
|
|
if (!sectionProps) {
|
|
return;
|
|
}
|
|
var headerItem;
|
|
var groupProps;
|
|
if (sectionProps.title) {
|
|
var headerContextualMenuItem = undefined;
|
|
var ariaLabelledby = '';
|
|
if (typeof sectionProps.title === 'string') {
|
|
// Since title is a user-facing string, it needs to be stripped
|
|
// of whitespace in order to build a valid element ID
|
|
var id_1 = menuId + sectionProps.title.replace(/\s/g, '');
|
|
headerContextualMenuItem = {
|
|
key: "section-".concat(sectionProps.title, "-title"),
|
|
itemType: ContextualMenuItemType.Header,
|
|
text: sectionProps.title,
|
|
id: id_1,
|
|
};
|
|
ariaLabelledby = id_1;
|
|
}
|
|
else {
|
|
var id_2 = sectionProps.title.id || menuId + sectionProps.title.key.replace(/\s/g, '');
|
|
headerContextualMenuItem = __assign(__assign({}, sectionProps.title), { id: id_2 });
|
|
ariaLabelledby = id_2;
|
|
}
|
|
if (headerContextualMenuItem) {
|
|
groupProps = {
|
|
role: 'group',
|
|
'aria-labelledby': ariaLabelledby,
|
|
};
|
|
headerItem = renderHeaderMenuItem(headerContextualMenuItem, itemClassNames, menuClassNames, index, hasCheckmarks, hasIcons);
|
|
}
|
|
}
|
|
if (sectionProps.items && sectionProps.items.length > 0) {
|
|
var correctedIndex_1 = 0;
|
|
return (React.createElement("li", { role: "presentation", key: sectionProps.key || sectionItem.key || "section-".concat(index) },
|
|
React.createElement("div", __assign({}, groupProps),
|
|
React.createElement("ul", { className: menuClassNames.list, role: "presentation" },
|
|
sectionProps.topDivider && renderSeparator(index, itemClassNames, true, true),
|
|
headerItem && renderListItem(headerItem, sectionItem.key || index, itemClassNames, sectionItem.title),
|
|
sectionProps.items.map(function (contextualMenuItem, itemsIndex) {
|
|
var menuItem = renderMenuItem(contextualMenuItem, itemsIndex, correctedIndex_1, getItemCount(sectionProps.items), hasCheckmarks, hasIcons, menuClassNames);
|
|
if (contextualMenuItem.itemType !== ContextualMenuItemType.Divider &&
|
|
contextualMenuItem.itemType !== ContextualMenuItemType.Header) {
|
|
var indexIncrease = contextualMenuItem.customOnRenderListLength
|
|
? contextualMenuItem.customOnRenderListLength
|
|
: 1;
|
|
correctedIndex_1 += indexIncrease;
|
|
}
|
|
return menuItem;
|
|
}),
|
|
sectionProps.bottomDivider && renderSeparator(index, itemClassNames, false, true)))));
|
|
}
|
|
};
|
|
var renderListItem = function (content, key, classNames, // eslint-disable-line @typescript-eslint/no-deprecated
|
|
title) {
|
|
return (React.createElement("li", { role: "presentation", title: title, key: key, className: classNames.item }, content));
|
|
};
|
|
var renderSeparator = function (index, classNames, // eslint-disable-line @typescript-eslint/no-deprecated
|
|
top, fromSection) {
|
|
if (fromSection || index > 0) {
|
|
return (React.createElement("li", { role: "separator", key: 'separator-' + index + (top === undefined ? '' : top ? '-top' : '-bottom'), className: classNames.divider, "aria-hidden": "true" }));
|
|
}
|
|
return null;
|
|
};
|
|
var renderNormalItem = function (item, classNames, // eslint-disable-line @typescript-eslint/no-deprecated
|
|
index, focusableElementIndex, totalItemCount, hasCheckmarks, hasIcons) {
|
|
if (item.onRender) {
|
|
return item.onRender(__assign({ 'aria-posinset': focusableElementIndex + 1, 'aria-setsize': totalItemCount }, item), dismiss);
|
|
}
|
|
var contextualMenuItemAs = props.contextualMenuItemAs;
|
|
var commonProps = {
|
|
item: item,
|
|
classNames: classNames,
|
|
index: index,
|
|
focusableElementIndex: focusableElementIndex,
|
|
totalItemCount: totalItemCount,
|
|
hasCheckmarks: hasCheckmarks,
|
|
hasIcons: hasIcons,
|
|
contextualMenuItemAs: contextualMenuItemAs,
|
|
onItemMouseEnter: onItemMouseEnterBase,
|
|
onItemMouseLeave: onMouseItemLeave,
|
|
onItemMouseMove: onItemMouseMoveBase,
|
|
onItemMouseDown: onItemMouseDown,
|
|
executeItemClick: executeItemClick,
|
|
onItemKeyDown: onItemKeyDown,
|
|
expandedMenuItemKey: expandedMenuItemKey,
|
|
openSubMenu: openSubMenu,
|
|
dismissSubMenu: onSubMenuDismiss,
|
|
dismissMenu: dismiss,
|
|
};
|
|
if (item.href) {
|
|
var ContextualMenuAnchorAs = ContextualMenuAnchor;
|
|
if (item.contextualMenuItemWrapperAs) {
|
|
ContextualMenuAnchorAs = composeComponentAs(item.contextualMenuItemWrapperAs, ContextualMenuAnchorAs);
|
|
}
|
|
return React.createElement(ContextualMenuAnchorAs, __assign({}, commonProps, { onItemClick: onAnchorClick }));
|
|
}
|
|
if (item.split && hasSubmenu(item)) {
|
|
var ContextualMenuSplitButtonAs = ContextualMenuSplitButton;
|
|
if (item.contextualMenuItemWrapperAs) {
|
|
ContextualMenuSplitButtonAs = composeComponentAs(item.contextualMenuItemWrapperAs, ContextualMenuSplitButtonAs);
|
|
}
|
|
return (React.createElement(ContextualMenuSplitButtonAs, __assign({}, commonProps, { onItemClick: onItemClick, onItemClickBase: onItemClickBase, onTap: cancelSubMenuTimer })));
|
|
}
|
|
var ContextualMenuButtonAs = ContextualMenuButton;
|
|
if (item.contextualMenuItemWrapperAs) {
|
|
ContextualMenuButtonAs = composeComponentAs(item.contextualMenuItemWrapperAs, ContextualMenuButtonAs);
|
|
}
|
|
return React.createElement(ContextualMenuButtonAs, __assign({}, commonProps, { onItemClick: onItemClick, onItemClickBase: onItemClickBase }));
|
|
};
|
|
var renderHeaderMenuItem = function (item,
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
itemClassNames,
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
menuClassNames, index, hasCheckmarks, hasIcons) {
|
|
var ChildrenRenderer = ContextualMenuItem;
|
|
if (item.contextualMenuItemAs) {
|
|
ChildrenRenderer = composeComponentAs(item.contextualMenuItemAs, ChildrenRenderer);
|
|
}
|
|
if (props.contextualMenuItemAs) {
|
|
ChildrenRenderer = composeComponentAs(props.contextualMenuItemAs, ChildrenRenderer);
|
|
}
|
|
var itemProps = item.itemProps, id = item.id;
|
|
var divHtmlProperties = itemProps && getNativeProps(itemProps, divProperties);
|
|
return (
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
React.createElement("div", __assign({ id: id, className: menuClassNames.header }, divHtmlProperties, { style: item.style }),
|
|
React.createElement(ChildrenRenderer, __assign({ item: item, classNames: itemClassNames, index: index, onCheckmarkClick: hasCheckmarks ? onItemClick : undefined, hasIcons: hasIcons }, itemProps))));
|
|
};
|
|
//#endregion
|
|
//#region Main render
|
|
var isBeakVisible = props.isBeakVisible;
|
|
var items = props.items, labelElementId = props.labelElementId, id = props.id, className = props.className, beakWidth = props.beakWidth, directionalHint = props.directionalHint, directionalHintForRTL = props.directionalHintForRTL, alignTargetEdge = props.alignTargetEdge, gapSpace = props.gapSpace, coverTarget = props.coverTarget, ariaLabel = props.ariaLabel, doNotLayer = props.doNotLayer, target = props.target, bounds = props.bounds, useTargetWidth = props.useTargetWidth, useTargetAsMinWidth = props.useTargetAsMinWidth, directionalHintFixed = props.directionalHintFixed, shouldFocusOnMount = props.shouldFocusOnMount, shouldFocusOnContainer = props.shouldFocusOnContainer, title = props.title, styles = props.styles, theme = props.theme, calloutProps = props.calloutProps, _k = props.onRenderSubMenu, onRenderSubMenu = _k === void 0 ? onDefaultRenderSubMenu : _k, _l = props.onRenderMenuList, onRenderMenuList = _l === void 0 ? function (menuListProps, defaultRender) { return onDefaultRenderMenuList(menuListProps, classNames, defaultRender); } : _l, focusZoneProps = props.focusZoneProps,
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
getMenuClassNames = props.getMenuClassNames;
|
|
var classNames = getMenuClassNames
|
|
? getMenuClassNames(theme, className)
|
|
: getClassNames(styles, {
|
|
theme: theme,
|
|
className: className,
|
|
});
|
|
var hasIcons = itemsHaveIcons(items);
|
|
function itemsHaveIcons(contextualMenuItems) {
|
|
for (var _i = 0, contextualMenuItems_1 = contextualMenuItems; _i < contextualMenuItems_1.length; _i++) {
|
|
var item = contextualMenuItems_1[_i];
|
|
if (item.iconProps) {
|
|
return true;
|
|
}
|
|
if (item.itemType === ContextualMenuItemType.Section &&
|
|
item.sectionProps &&
|
|
itemsHaveIcons(item.sectionProps.items)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
var adjustedFocusZoneProps = __assign(__assign({ direction: FocusZoneDirection.vertical, handleTabKey: FocusZoneTabbableElements.all, isCircularNavigation: true, 'data-tabster': '{"uncontrolled": {}, "focusable": { "excludeFromMover": true }}' }, focusZoneProps), { className: css(classNames.root, (_a = props.focusZoneProps) === null || _a === void 0 ? void 0 : _a.className) });
|
|
var hasCheckmarks = canAnyMenuItemsCheck(items);
|
|
var submenuProps = expandedMenuItemKey && props.hidden !== true ? getSubmenuProps() : null;
|
|
isBeakVisible = isBeakVisible === undefined ? responsiveMode <= 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 = targetRef.current;
|
|
if ((useTargetWidth || useTargetAsMinWidth) && targetAsHtmlElement && targetAsHtmlElement.offsetWidth) {
|
|
var targetBoundingRect = targetAsHtmlElement.getBoundingClientRect();
|
|
var targetWidth = targetBoundingRect.width - 2; /* Accounts for 1px border */
|
|
if (useTargetWidth) {
|
|
contextMenuStyle = {
|
|
width: targetWidth,
|
|
};
|
|
}
|
|
else if (useTargetAsMinWidth) {
|
|
contextMenuStyle = {
|
|
minWidth: targetWidth,
|
|
};
|
|
}
|
|
}
|
|
// The menu should only return if items were provided, if no items were provided then it should not appear.
|
|
if (items && items.length > 0) {
|
|
var totalItemCount_1 = getItemCount(items);
|
|
var calloutStyles_1 = classNames.subComponentStyles
|
|
? classNames.subComponentStyles.callout
|
|
: undefined;
|
|
return (React.createElement(MenuContext.Consumer, null, function (menuContext) { return (React.createElement(Callout, __assign({ styles: calloutStyles_1, onRestoreFocus: tryFocusPreviousActiveElement }, calloutProps, { target: target || menuContext.target, isBeakVisible: isBeakVisible, beakWidth: beakWidth, directionalHint: directionalHint, directionalHintForRTL: directionalHintForRTL, gapSpace: gapSpace, coverTarget: coverTarget, doNotLayer: doNotLayer, className: css('ms-ContextualMenu-Callout', calloutProps && calloutProps.className), setInitialFocus: shouldFocusOnMount, onDismiss: props.onDismiss || menuContext.onDismiss, onScroll: onScroll, bounds: bounds, directionalHintFixed: directionalHintFixed, alignTargetEdge: alignTargetEdge, hidden: props.hidden || menuContext.hidden, ref: forwardedRef }),
|
|
React.createElement("div", { style: contextMenuStyle, ref: hostElement, id: id, className: classNames.container, tabIndex: shouldFocusOnContainer ? 0 : -1, onKeyDown: onMenuKeyDown, onKeyUp: onKeyUp, onFocusCapture: onMenuFocusCapture, "aria-label": ariaLabel, "aria-labelledby": labelElementId, role: 'menu' },
|
|
title && React.createElement("div", { className: classNames.title },
|
|
" ",
|
|
title,
|
|
" "),
|
|
items && items.length
|
|
? renderFocusZone(onRenderMenuList({
|
|
ariaLabel: ariaLabel,
|
|
items: items,
|
|
totalItemCount: totalItemCount_1,
|
|
hasCheckmarks: hasCheckmarks,
|
|
hasIcons: hasIcons,
|
|
defaultMenuItemRenderer: function (item) {
|
|
return defaultMenuItemRenderer(item, classNames);
|
|
},
|
|
labelElementId: labelElementId,
|
|
}, function (menuListProps, defaultRender) { return onDefaultRenderMenuList(menuListProps, classNames, defaultRender); }), adjustedFocusZoneProps)
|
|
: null,
|
|
submenuProps && onRenderSubMenu(submenuProps, onDefaultRenderSubMenu)),
|
|
React.createElement(FocusRects, null))); }));
|
|
}
|
|
else {
|
|
return null;
|
|
}
|
|
//#endregion
|
|
}), function (prevProps, newProps) {
|
|
if (!newProps.shouldUpdateWhenHidden && prevProps.hidden && newProps.hidden) {
|
|
// Do not update when hidden.
|
|
return true;
|
|
}
|
|
return shallowCompare(prevProps, newProps);
|
|
});
|
|
ContextualMenuBase.displayName = 'ContextualMenuBase';
|
|
/**
|
|
* Returns true if the key for the event is alt (Mac option) or meta (Mac command).
|
|
*/
|
|
function isAltOrMeta(ev) {
|
|
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
return ev.which === KeyCodes.alt || ev.key === 'Meta';
|
|
}
|
|
function onItemMouseDown(item, ev) {
|
|
var _a;
|
|
(_a = item.onMouseDown) === null || _a === void 0 ? void 0 : _a.call(item, item, ev);
|
|
}
|
|
function onDefaultRenderSubMenu(subMenuProps, defaultRender) {
|
|
throw Error('ContextualMenuBase: onRenderSubMenu callback is null or undefined. ' +
|
|
'Please ensure to set `onRenderSubMenu` property either manually or with `styled` helper.');
|
|
}
|
|
/**
|
|
* Returns the item that matches a given key if any.
|
|
* @param key - The key of the item to match
|
|
* @param items - The items to look for the key
|
|
*/
|
|
function findItemByKeyFromItems(key, items) {
|
|
for (var _i = 0, items_3 = items; _i < items_3.length; _i++) {
|
|
var item = items_3[_i];
|
|
if (item.itemType === ContextualMenuItemType.Section && item.sectionProps) {
|
|
var match = findItemByKeyFromItems(key, item.sectionProps.items);
|
|
if (match) {
|
|
return match;
|
|
}
|
|
}
|
|
else if (item.key && item.key === key) {
|
|
return item;
|
|
}
|
|
}
|
|
}
|
|
function getOnClickWithOverrideTarget(onClick, target) {
|
|
return onClick
|
|
? function (ev, item) {
|
|
overrideTarget(ev, target);
|
|
return onClick(ev, item);
|
|
}
|
|
: onClick;
|
|
}
|
|
function overrideTarget(ev, target) {
|
|
if (ev && target) {
|
|
ev.persist();
|
|
if (target instanceof Event) {
|
|
ev.target = target.target;
|
|
}
|
|
else if (target instanceof Element) {
|
|
ev.target = target;
|
|
}
|
|
}
|
|
}
|
|
//# sourceMappingURL=ContextualMenu.base.js.map
|