451 lines
16 KiB
JavaScript
451 lines
16 KiB
JavaScript
"use strict";
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
value: true
|
|
});
|
|
exports.default = void 0;
|
|
var _clsx = require("clsx");
|
|
var _overflow = _interopRequireDefault(require("@rc-component/overflow"));
|
|
var _useControlledState = _interopRequireDefault(require("@rc-component/util/lib/hooks/useControlledState"));
|
|
var _useId = _interopRequireDefault(require("@rc-component/util/lib/hooks/useId"));
|
|
var _isEqual = _interopRequireDefault(require("@rc-component/util/lib/isEqual"));
|
|
var _warning = _interopRequireDefault(require("@rc-component/util/lib/warning"));
|
|
var _react = _interopRequireWildcard(require("react"));
|
|
var React = _react;
|
|
var _reactDom = require("react-dom");
|
|
var _IdContext = require("./context/IdContext");
|
|
var _MenuContext = _interopRequireDefault(require("./context/MenuContext"));
|
|
var _PathContext = require("./context/PathContext");
|
|
var _PrivateContext = _interopRequireDefault(require("./context/PrivateContext"));
|
|
var _useAccessibility = require("./hooks/useAccessibility");
|
|
var _useKeyRecords = _interopRequireWildcard(require("./hooks/useKeyRecords"));
|
|
var _useMemoCallback = _interopRequireDefault(require("./hooks/useMemoCallback"));
|
|
var _MenuItem = _interopRequireDefault(require("./MenuItem"));
|
|
var _SubMenu = _interopRequireDefault(require("./SubMenu"));
|
|
var _nodeUtil = require("./utils/nodeUtil");
|
|
var _warnUtil = require("./utils/warnUtil");
|
|
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
|
|
/**
|
|
* 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
|
|
const EMPTY_LIST = [];
|
|
const Menu = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
const {
|
|
prefixCls = 'rc-menu',
|
|
rootClassName,
|
|
style,
|
|
className,
|
|
styles,
|
|
classNames: menuClassNames,
|
|
tabIndex = 0,
|
|
items,
|
|
children,
|
|
direction,
|
|
id,
|
|
// Mode
|
|
mode = 'vertical',
|
|
inlineCollapsed,
|
|
// Disabled
|
|
disabled,
|
|
disabledOverflow,
|
|
// Open
|
|
subMenuOpenDelay = 0.1,
|
|
subMenuCloseDelay = 0.1,
|
|
forceSubMenuRender,
|
|
defaultOpenKeys,
|
|
openKeys,
|
|
// Active
|
|
activeKey,
|
|
defaultActiveFirst,
|
|
// Selection
|
|
selectable = true,
|
|
multiple = false,
|
|
defaultSelectedKeys,
|
|
selectedKeys,
|
|
onSelect,
|
|
onDeselect,
|
|
// Level
|
|
inlineIndent = 24,
|
|
// Motion
|
|
motion,
|
|
defaultMotions,
|
|
// Popup
|
|
triggerSubMenuAction = 'hover',
|
|
builtinPlacements,
|
|
// Icon
|
|
itemIcon,
|
|
expandIcon,
|
|
overflowedIndicator = '...',
|
|
overflowedIndicatorPopupClassName,
|
|
// Function
|
|
getPopupContainer,
|
|
// Events
|
|
onClick,
|
|
onOpenChange,
|
|
onKeyDown,
|
|
// Deprecated
|
|
openAnimation,
|
|
openTransitionName,
|
|
// Internal
|
|
_internalRenderMenuItem,
|
|
_internalRenderSubMenuItem,
|
|
_internalComponents,
|
|
popupRender,
|
|
...restProps
|
|
} = props;
|
|
const [childList, measureChildList] = React.useMemo(() => [(0, _nodeUtil.parseItems)(children, items, EMPTY_LIST, _internalComponents, prefixCls), (0, _nodeUtil.parseItems)(children, items, EMPTY_LIST, {}, prefixCls)], [children, items, _internalComponents]);
|
|
const [mounted, setMounted] = React.useState(false);
|
|
const containerRef = React.useRef();
|
|
const uuid = (0, _useId.default)(id ? `rc-menu-uuid-${id}` : 'rc-menu-uuid');
|
|
const isRtl = direction === 'rtl';
|
|
|
|
// ========================= Warn =========================
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
(0, _warning.default)(!openAnimation && !openTransitionName, '`openAnimation` and `openTransitionName` is removed. Please use `motion` or `defaultMotion` instead.');
|
|
}
|
|
|
|
// ========================= Open =========================
|
|
const [innerOpenKeys, setMergedOpenKeys] = (0, _useControlledState.default)(defaultOpenKeys, openKeys);
|
|
const mergedOpenKeys = innerOpenKeys || EMPTY_LIST;
|
|
|
|
// React 18 will merge mouse event which means we open key will not sync
|
|
// ref: https://github.com/ant-design/ant-design/issues/38818
|
|
const triggerOpenKeys = (keys, forceFlush = false) => {
|
|
function doUpdate() {
|
|
setMergedOpenKeys(keys);
|
|
onOpenChange?.(keys);
|
|
}
|
|
if (forceFlush) {
|
|
(0, _reactDom.flushSync)(doUpdate);
|
|
} else {
|
|
doUpdate();
|
|
}
|
|
};
|
|
|
|
// >>>>> Cache & Reset open keys when inlineCollapsed changed
|
|
const [inlineCacheOpenKeys, setInlineCacheOpenKeys] = React.useState(mergedOpenKeys);
|
|
const mountRef = React.useRef(false);
|
|
|
|
// ========================= Mode =========================
|
|
const [mergedMode, mergedInlineCollapsed] = React.useMemo(() => {
|
|
if ((mode === 'inline' || mode === 'vertical') && inlineCollapsed) {
|
|
return ['vertical', inlineCollapsed];
|
|
}
|
|
return [mode, false];
|
|
}, [mode, inlineCollapsed]);
|
|
const isInlineMode = mergedMode === 'inline';
|
|
const [internalMode, setInternalMode] = React.useState(mergedMode);
|
|
const [internalInlineCollapsed, setInternalInlineCollapsed] = React.useState(mergedInlineCollapsed);
|
|
React.useEffect(() => {
|
|
setInternalMode(mergedMode);
|
|
setInternalInlineCollapsed(mergedInlineCollapsed);
|
|
if (!mountRef.current) {
|
|
return;
|
|
}
|
|
// Synchronously update MergedOpenKeys
|
|
if (isInlineMode) {
|
|
setMergedOpenKeys(inlineCacheOpenKeys);
|
|
} else {
|
|
// Trigger open event in case its in control
|
|
triggerOpenKeys(EMPTY_LIST);
|
|
}
|
|
}, [mergedMode, mergedInlineCollapsed]);
|
|
|
|
// ====================== Responsive ======================
|
|
const [lastVisibleIndex, setLastVisibleIndex] = React.useState(0);
|
|
const allVisible = lastVisibleIndex >= childList.length - 1 || internalMode !== 'horizontal' || disabledOverflow;
|
|
|
|
// Cache
|
|
React.useEffect(() => {
|
|
if (isInlineMode) {
|
|
setInlineCacheOpenKeys(mergedOpenKeys);
|
|
}
|
|
}, [mergedOpenKeys]);
|
|
React.useEffect(() => {
|
|
mountRef.current = true;
|
|
return () => {
|
|
mountRef.current = false;
|
|
};
|
|
}, []);
|
|
|
|
// ========================= Path =========================
|
|
const {
|
|
registerPath,
|
|
unregisterPath,
|
|
refreshOverflowKeys,
|
|
isSubPathKey,
|
|
getKeyPath,
|
|
getKeys,
|
|
getSubPathKeys
|
|
} = (0, _useKeyRecords.default)();
|
|
const registerPathContext = React.useMemo(() => ({
|
|
registerPath,
|
|
unregisterPath
|
|
}), [registerPath, unregisterPath]);
|
|
const pathUserContext = React.useMemo(() => ({
|
|
isSubPathKey
|
|
}), [isSubPathKey]);
|
|
React.useEffect(() => {
|
|
refreshOverflowKeys(allVisible ? EMPTY_LIST : childList.slice(lastVisibleIndex + 1).map(child => child.key));
|
|
}, [lastVisibleIndex, allVisible]);
|
|
|
|
// ======================== Active ========================
|
|
const [mergedActiveKey, setMergedActiveKey] = (0, _useControlledState.default)(activeKey || defaultActiveFirst && childList[0]?.key, activeKey);
|
|
const onActive = (0, _useMemoCallback.default)(key => {
|
|
setMergedActiveKey(key);
|
|
});
|
|
const onInactive = (0, _useMemoCallback.default)(() => {
|
|
setMergedActiveKey(undefined);
|
|
});
|
|
(0, _react.useImperativeHandle)(ref, () => {
|
|
return {
|
|
list: containerRef.current,
|
|
focus: options => {
|
|
const keys = getKeys();
|
|
const {
|
|
elements,
|
|
key2element,
|
|
element2key
|
|
} = (0, _useAccessibility.refreshElements)(keys, uuid);
|
|
const focusableElements = (0, _useAccessibility.getFocusableElements)(containerRef.current, elements);
|
|
let shouldFocusKey;
|
|
if (mergedActiveKey && keys.includes(mergedActiveKey)) {
|
|
shouldFocusKey = mergedActiveKey;
|
|
} else {
|
|
shouldFocusKey = focusableElements[0] ? element2key.get(focusableElements[0]) : childList.find(node => !node.props.disabled)?.key;
|
|
}
|
|
const elementToFocus = key2element.get(shouldFocusKey);
|
|
if (shouldFocusKey && elementToFocus) {
|
|
elementToFocus?.focus?.(options);
|
|
}
|
|
},
|
|
findItem: ({
|
|
key: itemKey
|
|
}) => {
|
|
const keys = getKeys();
|
|
const {
|
|
key2element
|
|
} = (0, _useAccessibility.refreshElements)(keys, uuid);
|
|
return key2element.get(itemKey) || null;
|
|
}
|
|
};
|
|
});
|
|
|
|
// ======================== Select ========================
|
|
// >>>>> Select keys
|
|
const [internalSelectKeys, setMergedSelectKeys] = (0, _useControlledState.default)(defaultSelectedKeys || [], selectedKeys);
|
|
const mergedSelectKeys = React.useMemo(() => {
|
|
if (Array.isArray(internalSelectKeys)) {
|
|
return internalSelectKeys;
|
|
}
|
|
if (internalSelectKeys === null || internalSelectKeys === undefined) {
|
|
return EMPTY_LIST;
|
|
}
|
|
return [internalSelectKeys];
|
|
}, [internalSelectKeys]);
|
|
|
|
// >>>>> Trigger select
|
|
const triggerSelection = info => {
|
|
if (selectable) {
|
|
// Insert or Remove
|
|
const {
|
|
key: targetKey
|
|
} = info;
|
|
const exist = mergedSelectKeys.includes(targetKey);
|
|
let newSelectKeys;
|
|
if (multiple) {
|
|
if (exist) {
|
|
newSelectKeys = mergedSelectKeys.filter(key => key !== targetKey);
|
|
} else {
|
|
newSelectKeys = [...mergedSelectKeys, targetKey];
|
|
}
|
|
} else {
|
|
newSelectKeys = [targetKey];
|
|
}
|
|
setMergedSelectKeys(newSelectKeys);
|
|
|
|
// Trigger event
|
|
const selectInfo = {
|
|
...info,
|
|
selectedKeys: newSelectKeys
|
|
};
|
|
if (exist) {
|
|
onDeselect?.(selectInfo);
|
|
} else {
|
|
onSelect?.(selectInfo);
|
|
}
|
|
}
|
|
|
|
// Whatever selectable, always close it
|
|
if (!multiple && mergedOpenKeys.length && internalMode !== 'inline') {
|
|
triggerOpenKeys(EMPTY_LIST);
|
|
}
|
|
};
|
|
|
|
// ========================= Open =========================
|
|
/**
|
|
* Click for item. SubMenu do not have selection status
|
|
*/
|
|
const onInternalClick = (0, _useMemoCallback.default)(info => {
|
|
onClick?.((0, _warnUtil.warnItemProp)(info));
|
|
triggerSelection(info);
|
|
});
|
|
const onInternalOpenChange = (0, _useMemoCallback.default)((key, open) => {
|
|
let newOpenKeys = mergedOpenKeys.filter(k => k !== key);
|
|
if (open) {
|
|
newOpenKeys.push(key);
|
|
} else if (internalMode !== 'inline') {
|
|
// We need find all related popup to close
|
|
const subPathKeys = getSubPathKeys(key);
|
|
newOpenKeys = newOpenKeys.filter(k => !subPathKeys.has(k));
|
|
}
|
|
if (!(0, _isEqual.default)(mergedOpenKeys, newOpenKeys, true)) {
|
|
triggerOpenKeys(newOpenKeys, true);
|
|
}
|
|
});
|
|
|
|
// ==================== Accessibility =====================
|
|
const triggerAccessibilityOpen = (key, open) => {
|
|
const nextOpen = open ?? !mergedOpenKeys.includes(key);
|
|
onInternalOpenChange(key, nextOpen);
|
|
};
|
|
const onInternalKeyDown = (0, _useAccessibility.useAccessibility)(internalMode, mergedActiveKey, isRtl, uuid, containerRef, getKeys, getKeyPath, setMergedActiveKey, triggerAccessibilityOpen, onKeyDown);
|
|
|
|
// ======================== Effect ========================
|
|
React.useEffect(() => {
|
|
setMounted(true);
|
|
}, []);
|
|
|
|
// ======================= Context ========================
|
|
const privateContext = React.useMemo(() => ({
|
|
_internalRenderMenuItem,
|
|
_internalRenderSubMenuItem
|
|
}), [_internalRenderMenuItem, _internalRenderSubMenuItem]);
|
|
|
|
// ======================== Render ========================
|
|
|
|
// >>>>> Children
|
|
const wrappedChildList = internalMode !== 'horizontal' || disabledOverflow ? childList :
|
|
// Need wrap for overflow dropdown that do not response for open
|
|
childList.map((child, index) =>
|
|
/*#__PURE__*/
|
|
// Always wrap provider to avoid sub node re-mount
|
|
React.createElement(_MenuContext.default, {
|
|
key: child.key,
|
|
overflowDisabled: index > lastVisibleIndex,
|
|
classNames: menuClassNames,
|
|
styles: styles
|
|
}, child));
|
|
|
|
// >>>>> Container
|
|
const container = /*#__PURE__*/React.createElement(_overflow.default, _extends({
|
|
id: id,
|
|
ref: containerRef,
|
|
prefixCls: `${prefixCls}-overflow`,
|
|
component: "ul",
|
|
itemComponent: _MenuItem.default,
|
|
className: (0, _clsx.clsx)(prefixCls, `${prefixCls}-root`, `${prefixCls}-${internalMode}`, className, {
|
|
[`${prefixCls}-inline-collapsed`]: internalInlineCollapsed,
|
|
[`${prefixCls}-rtl`]: isRtl
|
|
}, rootClassName),
|
|
dir: direction,
|
|
style: style,
|
|
role: "menu",
|
|
tabIndex: tabIndex,
|
|
data: wrappedChildList,
|
|
renderRawItem: node => node,
|
|
renderRawRest: omitItems => {
|
|
// We use origin list since wrapped list use context to prevent open
|
|
const len = omitItems.length;
|
|
const originOmitItems = len ? childList.slice(-len) : null;
|
|
return /*#__PURE__*/React.createElement(_SubMenu.default, {
|
|
eventKey: _useKeyRecords.OVERFLOW_KEY,
|
|
title: overflowedIndicator,
|
|
disabled: allVisible,
|
|
internalPopupClose: len === 0,
|
|
popupClassName: overflowedIndicatorPopupClassName
|
|
}, originOmitItems);
|
|
},
|
|
maxCount: internalMode !== 'horizontal' || disabledOverflow ? _overflow.default.INVALIDATE : _overflow.default.RESPONSIVE,
|
|
ssr: "full",
|
|
"data-menu-list": true,
|
|
onVisibleChange: newLastIndex => {
|
|
setLastVisibleIndex(newLastIndex);
|
|
},
|
|
onKeyDown: onInternalKeyDown
|
|
}, restProps));
|
|
|
|
// >>>>> Render
|
|
return /*#__PURE__*/React.createElement(_PrivateContext.default.Provider, {
|
|
value: privateContext
|
|
}, /*#__PURE__*/React.createElement(_IdContext.IdContext.Provider, {
|
|
value: uuid
|
|
}, /*#__PURE__*/React.createElement(_MenuContext.default, {
|
|
prefixCls: prefixCls,
|
|
rootClassName: rootClassName,
|
|
classNames: menuClassNames,
|
|
styles: styles,
|
|
mode: internalMode,
|
|
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: getPopupContainer
|
|
// Icon
|
|
,
|
|
itemIcon: itemIcon,
|
|
expandIcon: expandIcon
|
|
// Events
|
|
,
|
|
onItemClick: onInternalClick,
|
|
onOpenChange: onInternalOpenChange,
|
|
popupRender: popupRender
|
|
}, /*#__PURE__*/React.createElement(_PathContext.PathUserContext.Provider, {
|
|
value: pathUserContext
|
|
}, container), /*#__PURE__*/React.createElement("div", {
|
|
style: {
|
|
display: 'none'
|
|
},
|
|
"aria-hidden": true
|
|
}, /*#__PURE__*/React.createElement(_PathContext.PathRegisterContext.Provider, {
|
|
value: registerPathContext
|
|
}, measureChildList)))));
|
|
});
|
|
var _default = exports.default = Menu; |