You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
420 lines
16 KiB
420 lines
16 KiB
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
|
|
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
|
|
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
|
|
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
|
|
import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
|
|
var _excluded = ["prefixCls", "style", "className", "tabIndex", "children", "direction", "id", "mode", "inlineCollapsed", "disabled", "disabledOverflow", "subMenuOpenDelay", "subMenuCloseDelay", "forceSubMenuRender", "defaultOpenKeys", "openKeys", "activeKey", "defaultActiveFirst", "selectable", "multiple", "defaultSelectedKeys", "selectedKeys", "onSelect", "onDeselect", "inlineIndent", "motion", "defaultMotions", "triggerSubMenuAction", "builtinPlacements", "itemIcon", "expandIcon", "overflowedIndicator", "overflowedIndicatorPopupClassName", "getPopupContainer", "onClick", "onOpenChange", "onKeyDown", "openAnimation", "openTransitionName", "_internalRenderMenuItem", "_internalRenderSubMenuItem"];
|
|
import * as React from 'react';
|
|
import classNames from 'classnames';
|
|
import shallowEqual from 'shallowequal';
|
|
import useMergedState from "rc-util/es/hooks/useMergedState";
|
|
import warning from "rc-util/es/warning";
|
|
import Overflow from 'rc-overflow';
|
|
import MenuItem from './MenuItem';
|
|
import { parseChildren } from './utils/nodeUtil';
|
|
import MenuContextProvider from './context/MenuContext';
|
|
import useMemoCallback from './hooks/useMemoCallback';
|
|
import { warnItemProp } from './utils/warnUtil';
|
|
import SubMenu from './SubMenu';
|
|
import useAccessibility from './hooks/useAccessibility';
|
|
import useUUID from './hooks/useUUID';
|
|
import { PathRegisterContext, PathUserContext } from './context/PathContext';
|
|
import useKeyRecords, { OVERFLOW_KEY } from './hooks/useKeyRecords';
|
|
import { IdContext } from './context/IdContext';
|
|
import PrivateContext from './context/PrivateContext';
|
|
/**
|
|
* Menu modify after refactor:
|
|
* ## Add
|
|
* - disabled
|
|
*
|
|
* ## Remove
|
|
* - openTransitionName
|
|
* - openAnimation
|
|
* - onDestroy
|
|
* - siderCollapsed: Seems antd do not use this prop (Need test in antd)
|
|
* - collapsedWidth: Seems this logic should be handle by antd Layout.Sider
|
|
*/
|
|
// optimize for render
|
|
|
|
var EMPTY_LIST = [];
|
|
|
|
var Menu = function Menu(props) {
|
|
var _childList$, _classNames;
|
|
|
|
var _props$prefixCls = props.prefixCls,
|
|
prefixCls = _props$prefixCls === void 0 ? 'rc-menu' : _props$prefixCls,
|
|
style = props.style,
|
|
className = props.className,
|
|
_props$tabIndex = props.tabIndex,
|
|
tabIndex = _props$tabIndex === void 0 ? 0 : _props$tabIndex,
|
|
children = props.children,
|
|
direction = props.direction,
|
|
id = props.id,
|
|
_props$mode = props.mode,
|
|
mode = _props$mode === void 0 ? 'vertical' : _props$mode,
|
|
inlineCollapsed = props.inlineCollapsed,
|
|
disabled = props.disabled,
|
|
disabledOverflow = props.disabledOverflow,
|
|
_props$subMenuOpenDel = props.subMenuOpenDelay,
|
|
subMenuOpenDelay = _props$subMenuOpenDel === void 0 ? 0.1 : _props$subMenuOpenDel,
|
|
_props$subMenuCloseDe = props.subMenuCloseDelay,
|
|
subMenuCloseDelay = _props$subMenuCloseDe === void 0 ? 0.1 : _props$subMenuCloseDe,
|
|
forceSubMenuRender = props.forceSubMenuRender,
|
|
defaultOpenKeys = props.defaultOpenKeys,
|
|
openKeys = props.openKeys,
|
|
activeKey = props.activeKey,
|
|
defaultActiveFirst = props.defaultActiveFirst,
|
|
_props$selectable = props.selectable,
|
|
selectable = _props$selectable === void 0 ? true : _props$selectable,
|
|
_props$multiple = props.multiple,
|
|
multiple = _props$multiple === void 0 ? false : _props$multiple,
|
|
defaultSelectedKeys = props.defaultSelectedKeys,
|
|
selectedKeys = props.selectedKeys,
|
|
onSelect = props.onSelect,
|
|
onDeselect = props.onDeselect,
|
|
_props$inlineIndent = props.inlineIndent,
|
|
inlineIndent = _props$inlineIndent === void 0 ? 24 : _props$inlineIndent,
|
|
motion = props.motion,
|
|
defaultMotions = props.defaultMotions,
|
|
_props$triggerSubMenu = props.triggerSubMenuAction,
|
|
triggerSubMenuAction = _props$triggerSubMenu === void 0 ? 'hover' : _props$triggerSubMenu,
|
|
builtinPlacements = props.builtinPlacements,
|
|
itemIcon = props.itemIcon,
|
|
expandIcon = props.expandIcon,
|
|
_props$overflowedIndi = props.overflowedIndicator,
|
|
overflowedIndicator = _props$overflowedIndi === void 0 ? '...' : _props$overflowedIndi,
|
|
overflowedIndicatorPopupClassName = props.overflowedIndicatorPopupClassName,
|
|
getPopupContainer = props.getPopupContainer,
|
|
onClick = props.onClick,
|
|
onOpenChange = props.onOpenChange,
|
|
onKeyDown = props.onKeyDown,
|
|
openAnimation = props.openAnimation,
|
|
openTransitionName = props.openTransitionName,
|
|
_internalRenderMenuItem = props._internalRenderMenuItem,
|
|
_internalRenderSubMenuItem = props._internalRenderSubMenuItem,
|
|
restProps = _objectWithoutProperties(props, _excluded);
|
|
|
|
var childList = parseChildren(children, EMPTY_LIST);
|
|
|
|
var _React$useState = React.useState(false),
|
|
_React$useState2 = _slicedToArray(_React$useState, 2),
|
|
mounted = _React$useState2[0],
|
|
setMounted = _React$useState2[1];
|
|
|
|
var containerRef = React.useRef();
|
|
var uuid = useUUID(id);
|
|
var isRtl = direction === 'rtl'; // ========================= Warn =========================
|
|
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
warning(!openAnimation && !openTransitionName, '`openAnimation` and `openTransitionName` is removed. Please use `motion` or `defaultMotion` instead.');
|
|
} // ========================= Mode =========================
|
|
|
|
|
|
var _React$useMemo = React.useMemo(function () {
|
|
if ((mode === 'inline' || mode === 'vertical') && inlineCollapsed) {
|
|
return ['vertical', inlineCollapsed];
|
|
}
|
|
|
|
return [mode, false];
|
|
}, [mode, inlineCollapsed]),
|
|
_React$useMemo2 = _slicedToArray(_React$useMemo, 2),
|
|
mergedMode = _React$useMemo2[0],
|
|
mergedInlineCollapsed = _React$useMemo2[1]; // ====================== Responsive ======================
|
|
|
|
|
|
var _React$useState3 = React.useState(0),
|
|
_React$useState4 = _slicedToArray(_React$useState3, 2),
|
|
lastVisibleIndex = _React$useState4[0],
|
|
setLastVisibleIndex = _React$useState4[1];
|
|
|
|
var allVisible = lastVisibleIndex >= childList.length - 1 || mergedMode !== 'horizontal' || disabledOverflow; // ========================= Open =========================
|
|
|
|
var _useMergedState = useMergedState(defaultOpenKeys, {
|
|
value: openKeys,
|
|
postState: function postState(keys) {
|
|
return keys || EMPTY_LIST;
|
|
}
|
|
}),
|
|
_useMergedState2 = _slicedToArray(_useMergedState, 2),
|
|
mergedOpenKeys = _useMergedState2[0],
|
|
setMergedOpenKeys = _useMergedState2[1];
|
|
|
|
var triggerOpenKeys = function triggerOpenKeys(keys) {
|
|
setMergedOpenKeys(keys);
|
|
onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(keys);
|
|
}; // >>>>> Cache & Reset open keys when inlineCollapsed changed
|
|
|
|
|
|
var _React$useState5 = React.useState(mergedOpenKeys),
|
|
_React$useState6 = _slicedToArray(_React$useState5, 2),
|
|
inlineCacheOpenKeys = _React$useState6[0],
|
|
setInlineCacheOpenKeys = _React$useState6[1];
|
|
|
|
var isInlineMode = mergedMode === 'inline';
|
|
var mountRef = React.useRef(false); // Cache
|
|
|
|
React.useEffect(function () {
|
|
if (isInlineMode) {
|
|
setInlineCacheOpenKeys(mergedOpenKeys);
|
|
}
|
|
}, [mergedOpenKeys]); // Restore
|
|
|
|
React.useEffect(function () {
|
|
if (!mountRef.current) {
|
|
mountRef.current = true;
|
|
return;
|
|
}
|
|
|
|
if (isInlineMode) {
|
|
setMergedOpenKeys(inlineCacheOpenKeys);
|
|
} else {
|
|
// Trigger open event in case its in control
|
|
triggerOpenKeys(EMPTY_LIST);
|
|
}
|
|
}, [isInlineMode]); // ========================= Path =========================
|
|
|
|
var _useKeyRecords = useKeyRecords(),
|
|
registerPath = _useKeyRecords.registerPath,
|
|
unregisterPath = _useKeyRecords.unregisterPath,
|
|
refreshOverflowKeys = _useKeyRecords.refreshOverflowKeys,
|
|
isSubPathKey = _useKeyRecords.isSubPathKey,
|
|
getKeyPath = _useKeyRecords.getKeyPath,
|
|
getKeys = _useKeyRecords.getKeys,
|
|
getSubPathKeys = _useKeyRecords.getSubPathKeys;
|
|
|
|
var registerPathContext = React.useMemo(function () {
|
|
return {
|
|
registerPath: registerPath,
|
|
unregisterPath: unregisterPath
|
|
};
|
|
}, [registerPath, unregisterPath]);
|
|
var pathUserContext = React.useMemo(function () {
|
|
return {
|
|
isSubPathKey: isSubPathKey
|
|
};
|
|
}, [isSubPathKey]);
|
|
React.useEffect(function () {
|
|
refreshOverflowKeys(allVisible ? EMPTY_LIST : childList.slice(lastVisibleIndex + 1).map(function (child) {
|
|
return child.key;
|
|
}));
|
|
}, [lastVisibleIndex, allVisible]); // ======================== Active ========================
|
|
|
|
var _useMergedState3 = useMergedState(activeKey || defaultActiveFirst && ((_childList$ = childList[0]) === null || _childList$ === void 0 ? void 0 : _childList$.key), {
|
|
value: activeKey
|
|
}),
|
|
_useMergedState4 = _slicedToArray(_useMergedState3, 2),
|
|
mergedActiveKey = _useMergedState4[0],
|
|
setMergedActiveKey = _useMergedState4[1];
|
|
|
|
var onActive = useMemoCallback(function (key) {
|
|
setMergedActiveKey(key);
|
|
});
|
|
var onInactive = useMemoCallback(function () {
|
|
setMergedActiveKey(undefined);
|
|
}); // ======================== Select ========================
|
|
// >>>>> Select keys
|
|
|
|
var _useMergedState5 = useMergedState(defaultSelectedKeys || [], {
|
|
value: selectedKeys,
|
|
// Legacy convert key to array
|
|
postState: function postState(keys) {
|
|
if (Array.isArray(keys)) {
|
|
return keys;
|
|
}
|
|
|
|
if (keys === null || keys === undefined) {
|
|
return EMPTY_LIST;
|
|
}
|
|
|
|
return [keys];
|
|
}
|
|
}),
|
|
_useMergedState6 = _slicedToArray(_useMergedState5, 2),
|
|
mergedSelectKeys = _useMergedState6[0],
|
|
setMergedSelectKeys = _useMergedState6[1]; // >>>>> Trigger select
|
|
|
|
|
|
var triggerSelection = function triggerSelection(info) {
|
|
if (selectable) {
|
|
// Insert or Remove
|
|
var targetKey = info.key;
|
|
var exist = mergedSelectKeys.includes(targetKey);
|
|
var newSelectKeys;
|
|
|
|
if (multiple) {
|
|
if (exist) {
|
|
newSelectKeys = mergedSelectKeys.filter(function (key) {
|
|
return key !== targetKey;
|
|
});
|
|
} else {
|
|
newSelectKeys = [].concat(_toConsumableArray(mergedSelectKeys), [targetKey]);
|
|
}
|
|
} else {
|
|
newSelectKeys = [targetKey];
|
|
}
|
|
|
|
setMergedSelectKeys(newSelectKeys); // Trigger event
|
|
|
|
var selectInfo = _objectSpread(_objectSpread({}, info), {}, {
|
|
selectedKeys: newSelectKeys
|
|
});
|
|
|
|
if (exist) {
|
|
onDeselect === null || onDeselect === void 0 ? void 0 : onDeselect(selectInfo);
|
|
} else {
|
|
onSelect === null || onSelect === void 0 ? void 0 : onSelect(selectInfo);
|
|
}
|
|
} // Whatever selectable, always close it
|
|
|
|
|
|
if (!multiple && mergedOpenKeys.length && mergedMode !== 'inline') {
|
|
triggerOpenKeys(EMPTY_LIST);
|
|
}
|
|
}; // ========================= Open =========================
|
|
|
|
/**
|
|
* Click for item. SubMenu do not have selection status
|
|
*/
|
|
|
|
|
|
var onInternalClick = useMemoCallback(function (info) {
|
|
onClick === null || onClick === void 0 ? void 0 : onClick(warnItemProp(info));
|
|
triggerSelection(info);
|
|
});
|
|
var onInternalOpenChange = useMemoCallback(function (key, open) {
|
|
var newOpenKeys = mergedOpenKeys.filter(function (k) {
|
|
return k !== key;
|
|
});
|
|
|
|
if (open) {
|
|
newOpenKeys.push(key);
|
|
} else if (mergedMode !== 'inline') {
|
|
// We need find all related popup to close
|
|
var subPathKeys = getSubPathKeys(key);
|
|
newOpenKeys = newOpenKeys.filter(function (k) {
|
|
return !subPathKeys.has(k);
|
|
});
|
|
}
|
|
|
|
if (!shallowEqual(mergedOpenKeys, newOpenKeys)) {
|
|
triggerOpenKeys(newOpenKeys);
|
|
}
|
|
});
|
|
var getInternalPopupContainer = useMemoCallback(getPopupContainer); // ==================== Accessibility =====================
|
|
|
|
var triggerAccessibilityOpen = function triggerAccessibilityOpen(key, open) {
|
|
var nextOpen = open !== null && open !== void 0 ? open : !mergedOpenKeys.includes(key);
|
|
onInternalOpenChange(key, nextOpen);
|
|
};
|
|
|
|
var onInternalKeyDown = useAccessibility(mergedMode, mergedActiveKey, isRtl, uuid, containerRef, getKeys, getKeyPath, setMergedActiveKey, triggerAccessibilityOpen, onKeyDown); // ======================== Effect ========================
|
|
|
|
React.useEffect(function () {
|
|
setMounted(true);
|
|
}, []); // ======================= Context ========================
|
|
|
|
var privateContext = React.useMemo(function () {
|
|
return {
|
|
_internalRenderMenuItem: _internalRenderMenuItem,
|
|
_internalRenderSubMenuItem: _internalRenderSubMenuItem
|
|
};
|
|
}, [_internalRenderMenuItem, _internalRenderSubMenuItem]); // ======================== Render ========================
|
|
// >>>>> Children
|
|
|
|
var wrappedChildList = mergedMode !== 'horizontal' || disabledOverflow ? childList : // Need wrap for overflow dropdown that do not response for open
|
|
childList.map(function (child, index) {
|
|
return (
|
|
/*#__PURE__*/
|
|
// Always wrap provider to avoid sub node re-mount
|
|
React.createElement(MenuContextProvider, {
|
|
key: child.key,
|
|
overflowDisabled: index > lastVisibleIndex
|
|
}, child)
|
|
);
|
|
}); // >>>>> Container
|
|
|
|
var container = /*#__PURE__*/React.createElement(Overflow, _extends({
|
|
id: id,
|
|
ref: containerRef,
|
|
prefixCls: "".concat(prefixCls, "-overflow"),
|
|
component: "ul",
|
|
itemComponent: MenuItem,
|
|
className: classNames(prefixCls, "".concat(prefixCls, "-root"), "".concat(prefixCls, "-").concat(mergedMode), className, (_classNames = {}, _defineProperty(_classNames, "".concat(prefixCls, "-inline-collapsed"), mergedInlineCollapsed), _defineProperty(_classNames, "".concat(prefixCls, "-rtl"), isRtl), _classNames)),
|
|
dir: direction,
|
|
style: style,
|
|
role: "menu",
|
|
tabIndex: tabIndex,
|
|
data: wrappedChildList,
|
|
renderRawItem: function renderRawItem(node) {
|
|
return node;
|
|
},
|
|
renderRawRest: function renderRawRest(omitItems) {
|
|
// We use origin list since wrapped list use context to prevent open
|
|
var len = omitItems.length;
|
|
var originOmitItems = len ? childList.slice(-len) : null;
|
|
return /*#__PURE__*/React.createElement(SubMenu, {
|
|
eventKey: OVERFLOW_KEY,
|
|
title: overflowedIndicator,
|
|
disabled: allVisible,
|
|
internalPopupClose: len === 0,
|
|
popupClassName: overflowedIndicatorPopupClassName
|
|
}, originOmitItems);
|
|
},
|
|
maxCount: mergedMode !== 'horizontal' || disabledOverflow ? Overflow.INVALIDATE : Overflow.RESPONSIVE,
|
|
ssr: "full",
|
|
"data-menu-list": true,
|
|
onVisibleChange: function onVisibleChange(newLastIndex) {
|
|
setLastVisibleIndex(newLastIndex);
|
|
},
|
|
onKeyDown: onInternalKeyDown
|
|
}, restProps)); // >>>>> Render
|
|
|
|
return /*#__PURE__*/React.createElement(PrivateContext.Provider, {
|
|
value: privateContext
|
|
}, /*#__PURE__*/React.createElement(IdContext.Provider, {
|
|
value: uuid
|
|
}, /*#__PURE__*/React.createElement(MenuContextProvider, {
|
|
prefixCls: prefixCls,
|
|
mode: mergedMode,
|
|
openKeys: mergedOpenKeys,
|
|
rtl: isRtl // Disabled
|
|
,
|
|
disabled: disabled // Motion
|
|
,
|
|
motion: mounted ? motion : null,
|
|
defaultMotions: mounted ? defaultMotions : null // Active
|
|
,
|
|
activeKey: mergedActiveKey,
|
|
onActive: onActive,
|
|
onInactive: onInactive // Selection
|
|
,
|
|
selectedKeys: mergedSelectKeys // Level
|
|
,
|
|
inlineIndent: inlineIndent // Popup
|
|
,
|
|
subMenuOpenDelay: subMenuOpenDelay,
|
|
subMenuCloseDelay: subMenuCloseDelay,
|
|
forceSubMenuRender: forceSubMenuRender,
|
|
builtinPlacements: builtinPlacements,
|
|
triggerSubMenuAction: triggerSubMenuAction,
|
|
getPopupContainer: getInternalPopupContainer // Icon
|
|
,
|
|
itemIcon: itemIcon,
|
|
expandIcon: expandIcon // Events
|
|
,
|
|
onItemClick: onInternalClick,
|
|
onOpenChange: onInternalOpenChange
|
|
}, /*#__PURE__*/React.createElement(PathUserContext.Provider, {
|
|
value: pathUserContext
|
|
}, container), /*#__PURE__*/React.createElement("div", {
|
|
style: {
|
|
display: 'none'
|
|
},
|
|
"aria-hidden": true
|
|
}, /*#__PURE__*/React.createElement(PathRegisterContext.Provider, {
|
|
value: registerPathContext
|
|
}, childList)))));
|
|
};
|
|
|
|
export default Menu; |