index.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
  2. import _extends from "@babel/runtime/helpers/esm/extends";
  3. import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
  4. import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
  5. import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
  6. var _excluded = ["style", "className", "title", "eventKey", "warnKey", "disabled", "internalPopupClose", "children", "itemIcon", "expandIcon", "popupClassName", "popupOffset", "popupStyle", "onClick", "onMouseEnter", "onMouseLeave", "onTitleClick", "onTitleMouseEnter", "onTitleMouseLeave"],
  7. _excluded2 = ["active"];
  8. import * as React from 'react';
  9. import classNames from 'classnames';
  10. import Overflow from 'rc-overflow';
  11. import warning from "rc-util/es/warning";
  12. import SubMenuList from "./SubMenuList";
  13. import { parseChildren } from "../utils/commonUtil";
  14. import MenuContextProvider, { MenuContext } from "../context/MenuContext";
  15. import useMemoCallback from "../hooks/useMemoCallback";
  16. import PopupTrigger from "./PopupTrigger";
  17. import Icon from "../Icon";
  18. import useActive from "../hooks/useActive";
  19. import { warnItemProp } from "../utils/warnUtil";
  20. import useDirectionStyle from "../hooks/useDirectionStyle";
  21. import InlineSubMenuList from "./InlineSubMenuList";
  22. import { PathTrackerContext, PathUserContext, useFullPath, useMeasure } from "../context/PathContext";
  23. import { useMenuId } from "../context/IdContext";
  24. import PrivateContext from "../context/PrivateContext";
  25. var InternalSubMenu = /*#__PURE__*/React.forwardRef(function (props, ref) {
  26. var style = props.style,
  27. className = props.className,
  28. title = props.title,
  29. eventKey = props.eventKey,
  30. warnKey = props.warnKey,
  31. disabled = props.disabled,
  32. internalPopupClose = props.internalPopupClose,
  33. children = props.children,
  34. itemIcon = props.itemIcon,
  35. expandIcon = props.expandIcon,
  36. popupClassName = props.popupClassName,
  37. popupOffset = props.popupOffset,
  38. popupStyle = props.popupStyle,
  39. onClick = props.onClick,
  40. onMouseEnter = props.onMouseEnter,
  41. onMouseLeave = props.onMouseLeave,
  42. onTitleClick = props.onTitleClick,
  43. onTitleMouseEnter = props.onTitleMouseEnter,
  44. onTitleMouseLeave = props.onTitleMouseLeave,
  45. restProps = _objectWithoutProperties(props, _excluded);
  46. var domDataId = useMenuId(eventKey);
  47. var _React$useContext = React.useContext(MenuContext),
  48. prefixCls = _React$useContext.prefixCls,
  49. mode = _React$useContext.mode,
  50. openKeys = _React$useContext.openKeys,
  51. contextDisabled = _React$useContext.disabled,
  52. overflowDisabled = _React$useContext.overflowDisabled,
  53. activeKey = _React$useContext.activeKey,
  54. selectedKeys = _React$useContext.selectedKeys,
  55. contextItemIcon = _React$useContext.itemIcon,
  56. contextExpandIcon = _React$useContext.expandIcon,
  57. onItemClick = _React$useContext.onItemClick,
  58. onOpenChange = _React$useContext.onOpenChange,
  59. onActive = _React$useContext.onActive;
  60. var _React$useContext2 = React.useContext(PrivateContext),
  61. _internalRenderSubMenuItem = _React$useContext2._internalRenderSubMenuItem;
  62. var _React$useContext3 = React.useContext(PathUserContext),
  63. isSubPathKey = _React$useContext3.isSubPathKey;
  64. var connectedPath = useFullPath();
  65. var subMenuPrefixCls = "".concat(prefixCls, "-submenu");
  66. var mergedDisabled = contextDisabled || disabled;
  67. var elementRef = React.useRef();
  68. var popupRef = React.useRef();
  69. // ================================ Warn ================================
  70. if (process.env.NODE_ENV !== 'production' && warnKey) {
  71. warning(false, 'SubMenu should not leave undefined `key`.');
  72. }
  73. // ================================ Icon ================================
  74. var mergedItemIcon = itemIcon !== null && itemIcon !== void 0 ? itemIcon : contextItemIcon;
  75. var mergedExpandIcon = expandIcon !== null && expandIcon !== void 0 ? expandIcon : contextExpandIcon;
  76. // ================================ Open ================================
  77. var originOpen = openKeys.includes(eventKey);
  78. var open = !overflowDisabled && originOpen;
  79. // =============================== Select ===============================
  80. var childrenSelected = isSubPathKey(selectedKeys, eventKey);
  81. // =============================== Active ===============================
  82. var _useActive = useActive(eventKey, mergedDisabled, onTitleMouseEnter, onTitleMouseLeave),
  83. active = _useActive.active,
  84. activeProps = _objectWithoutProperties(_useActive, _excluded2);
  85. // Fallback of active check to avoid hover on menu title or disabled item
  86. var _React$useState = React.useState(false),
  87. _React$useState2 = _slicedToArray(_React$useState, 2),
  88. childrenActive = _React$useState2[0],
  89. setChildrenActive = _React$useState2[1];
  90. var triggerChildrenActive = function triggerChildrenActive(newActive) {
  91. if (!mergedDisabled) {
  92. setChildrenActive(newActive);
  93. }
  94. };
  95. var onInternalMouseEnter = function onInternalMouseEnter(domEvent) {
  96. triggerChildrenActive(true);
  97. onMouseEnter === null || onMouseEnter === void 0 || onMouseEnter({
  98. key: eventKey,
  99. domEvent: domEvent
  100. });
  101. };
  102. var onInternalMouseLeave = function onInternalMouseLeave(domEvent) {
  103. triggerChildrenActive(false);
  104. onMouseLeave === null || onMouseLeave === void 0 || onMouseLeave({
  105. key: eventKey,
  106. domEvent: domEvent
  107. });
  108. };
  109. var mergedActive = React.useMemo(function () {
  110. if (active) {
  111. return active;
  112. }
  113. if (mode !== 'inline') {
  114. return childrenActive || isSubPathKey([activeKey], eventKey);
  115. }
  116. return false;
  117. }, [mode, active, activeKey, childrenActive, eventKey, isSubPathKey]);
  118. // ========================== DirectionStyle ==========================
  119. var directionStyle = useDirectionStyle(connectedPath.length);
  120. // =============================== Events ===============================
  121. // >>>> Title click
  122. var onInternalTitleClick = function onInternalTitleClick(e) {
  123. // Skip if disabled
  124. if (mergedDisabled) {
  125. return;
  126. }
  127. onTitleClick === null || onTitleClick === void 0 || onTitleClick({
  128. key: eventKey,
  129. domEvent: e
  130. });
  131. // Trigger open by click when mode is `inline`
  132. if (mode === 'inline') {
  133. onOpenChange(eventKey, !originOpen);
  134. }
  135. };
  136. // >>>> Context for children click
  137. var onMergedItemClick = useMemoCallback(function (info) {
  138. onClick === null || onClick === void 0 || onClick(warnItemProp(info));
  139. onItemClick(info);
  140. });
  141. // >>>>> Visible change
  142. var onPopupVisibleChange = function onPopupVisibleChange(newVisible) {
  143. if (mode !== 'inline') {
  144. onOpenChange(eventKey, newVisible);
  145. }
  146. };
  147. /**
  148. * Used for accessibility. Helper will focus element without key board.
  149. * We should manually trigger an active
  150. */
  151. var onInternalFocus = function onInternalFocus() {
  152. onActive(eventKey);
  153. };
  154. // =============================== Render ===============================
  155. var popupId = domDataId && "".concat(domDataId, "-popup");
  156. var expandIconNode = React.useMemo(function () {
  157. return /*#__PURE__*/React.createElement(Icon, {
  158. icon: mode !== 'horizontal' ? mergedExpandIcon : undefined,
  159. props: _objectSpread(_objectSpread({}, props), {}, {
  160. isOpen: open,
  161. // [Legacy] Not sure why need this mark
  162. isSubMenu: true
  163. })
  164. }, /*#__PURE__*/React.createElement("i", {
  165. className: "".concat(subMenuPrefixCls, "-arrow")
  166. }));
  167. }, [mode, mergedExpandIcon, props, open, subMenuPrefixCls]);
  168. // >>>>> Title
  169. var titleNode = /*#__PURE__*/React.createElement("div", _extends({
  170. role: "menuitem",
  171. style: directionStyle,
  172. className: "".concat(subMenuPrefixCls, "-title"),
  173. tabIndex: mergedDisabled ? null : -1,
  174. ref: elementRef,
  175. title: typeof title === 'string' ? title : null,
  176. "data-menu-id": overflowDisabled && domDataId ? null : domDataId,
  177. "aria-expanded": open,
  178. "aria-haspopup": true,
  179. "aria-controls": popupId,
  180. "aria-disabled": mergedDisabled,
  181. onClick: onInternalTitleClick,
  182. onFocus: onInternalFocus
  183. }, activeProps), title, expandIconNode);
  184. // Cache mode if it change to `inline` which do not have popup motion
  185. var triggerModeRef = React.useRef(mode);
  186. if (mode !== 'inline' && connectedPath.length > 1) {
  187. triggerModeRef.current = 'vertical';
  188. } else {
  189. triggerModeRef.current = mode;
  190. }
  191. if (!overflowDisabled) {
  192. var triggerMode = triggerModeRef.current;
  193. // Still wrap with Trigger here since we need avoid react re-mount dom node
  194. // Which makes motion failed
  195. titleNode = /*#__PURE__*/React.createElement(PopupTrigger, {
  196. mode: triggerMode,
  197. prefixCls: subMenuPrefixCls,
  198. visible: !internalPopupClose && open && mode !== 'inline',
  199. popupClassName: popupClassName,
  200. popupOffset: popupOffset,
  201. popupStyle: popupStyle,
  202. popup: /*#__PURE__*/React.createElement(MenuContextProvider
  203. // Special handle of horizontal mode
  204. , {
  205. mode: triggerMode === 'horizontal' ? 'vertical' : triggerMode
  206. }, /*#__PURE__*/React.createElement(SubMenuList, {
  207. id: popupId,
  208. ref: popupRef
  209. }, children)),
  210. disabled: mergedDisabled,
  211. onVisibleChange: onPopupVisibleChange
  212. }, titleNode);
  213. }
  214. // >>>>> List node
  215. var listNode = /*#__PURE__*/React.createElement(Overflow.Item, _extends({
  216. ref: ref,
  217. role: "none"
  218. }, restProps, {
  219. component: "li",
  220. style: style,
  221. className: classNames(subMenuPrefixCls, "".concat(subMenuPrefixCls, "-").concat(mode), className, _defineProperty(_defineProperty(_defineProperty(_defineProperty({}, "".concat(subMenuPrefixCls, "-open"), open), "".concat(subMenuPrefixCls, "-active"), mergedActive), "".concat(subMenuPrefixCls, "-selected"), childrenSelected), "".concat(subMenuPrefixCls, "-disabled"), mergedDisabled)),
  222. onMouseEnter: onInternalMouseEnter,
  223. onMouseLeave: onInternalMouseLeave
  224. }), titleNode, !overflowDisabled && /*#__PURE__*/React.createElement(InlineSubMenuList, {
  225. id: popupId,
  226. open: open,
  227. keyPath: connectedPath
  228. }, children));
  229. if (_internalRenderSubMenuItem) {
  230. listNode = _internalRenderSubMenuItem(listNode, props, {
  231. selected: childrenSelected,
  232. active: mergedActive,
  233. open: open,
  234. disabled: mergedDisabled
  235. });
  236. }
  237. // >>>>> Render
  238. return /*#__PURE__*/React.createElement(MenuContextProvider, {
  239. onItemClick: onMergedItemClick,
  240. mode: mode === 'horizontal' ? 'vertical' : mode,
  241. itemIcon: mergedItemIcon,
  242. expandIcon: mergedExpandIcon
  243. }, listNode);
  244. });
  245. var SubMenu = /*#__PURE__*/React.forwardRef(function (props, ref) {
  246. var eventKey = props.eventKey,
  247. children = props.children;
  248. var connectedKeyPath = useFullPath(eventKey);
  249. var childList = parseChildren(children, connectedKeyPath);
  250. // ==================== Record KeyPath ====================
  251. var measure = useMeasure();
  252. // eslint-disable-next-line consistent-return
  253. React.useEffect(function () {
  254. if (measure) {
  255. measure.registerPath(eventKey, connectedKeyPath);
  256. return function () {
  257. measure.unregisterPath(eventKey, connectedKeyPath);
  258. };
  259. }
  260. }, [connectedKeyPath]);
  261. var renderNode;
  262. // ======================== Render ========================
  263. if (measure) {
  264. renderNode = childList;
  265. } else {
  266. renderNode = /*#__PURE__*/React.createElement(InternalSubMenu, _extends({
  267. ref: ref
  268. }, props), childList);
  269. }
  270. return /*#__PURE__*/React.createElement(PathTrackerContext.Provider, {
  271. value: connectedKeyPath
  272. }, renderNode);
  273. });
  274. if (process.env.NODE_ENV !== 'production') {
  275. SubMenu.displayName = 'SubMenu';
  276. }
  277. export default SubMenu;