Menu.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. import _extends from "@babel/runtime/helpers/esm/extends";
  2. import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
  3. import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
  4. import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
  5. import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
  6. import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
  7. var _excluded = ["prefixCls", "rootClassName", "style", "className", "tabIndex", "items", "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", "_internalComponents"];
  8. import classNames from 'classnames';
  9. import Overflow from 'rc-overflow';
  10. import useMergedState from "rc-util/es/hooks/useMergedState";
  11. import isEqual from "rc-util/es/isEqual";
  12. import warning from "rc-util/es/warning";
  13. import * as React from 'react';
  14. import { useImperativeHandle } from 'react';
  15. import { flushSync } from 'react-dom';
  16. import { IdContext } from "./context/IdContext";
  17. import MenuContextProvider from "./context/MenuContext";
  18. import { PathRegisterContext, PathUserContext } from "./context/PathContext";
  19. import PrivateContext from "./context/PrivateContext";
  20. import { getFocusableElements, refreshElements, useAccessibility } from "./hooks/useAccessibility";
  21. import useKeyRecords, { OVERFLOW_KEY } from "./hooks/useKeyRecords";
  22. import useMemoCallback from "./hooks/useMemoCallback";
  23. import useUUID from "./hooks/useUUID";
  24. import MenuItem from "./MenuItem";
  25. import SubMenu from "./SubMenu";
  26. import { parseItems } from "./utils/nodeUtil";
  27. import { warnItemProp } from "./utils/warnUtil";
  28. /**
  29. * Menu modify after refactor:
  30. * ## Add
  31. * - disabled
  32. *
  33. * ## Remove
  34. * - openTransitionName
  35. * - openAnimation
  36. * - onDestroy
  37. * - siderCollapsed: Seems antd do not use this prop (Need test in antd)
  38. * - collapsedWidth: Seems this logic should be handle by antd Layout.Sider
  39. */
  40. // optimize for render
  41. var EMPTY_LIST = [];
  42. var Menu = /*#__PURE__*/React.forwardRef(function (props, ref) {
  43. var _childList$;
  44. var _ref = props,
  45. _ref$prefixCls = _ref.prefixCls,
  46. prefixCls = _ref$prefixCls === void 0 ? 'rc-menu' : _ref$prefixCls,
  47. rootClassName = _ref.rootClassName,
  48. style = _ref.style,
  49. className = _ref.className,
  50. _ref$tabIndex = _ref.tabIndex,
  51. tabIndex = _ref$tabIndex === void 0 ? 0 : _ref$tabIndex,
  52. items = _ref.items,
  53. children = _ref.children,
  54. direction = _ref.direction,
  55. id = _ref.id,
  56. _ref$mode = _ref.mode,
  57. mode = _ref$mode === void 0 ? 'vertical' : _ref$mode,
  58. inlineCollapsed = _ref.inlineCollapsed,
  59. disabled = _ref.disabled,
  60. disabledOverflow = _ref.disabledOverflow,
  61. _ref$subMenuOpenDelay = _ref.subMenuOpenDelay,
  62. subMenuOpenDelay = _ref$subMenuOpenDelay === void 0 ? 0.1 : _ref$subMenuOpenDelay,
  63. _ref$subMenuCloseDela = _ref.subMenuCloseDelay,
  64. subMenuCloseDelay = _ref$subMenuCloseDela === void 0 ? 0.1 : _ref$subMenuCloseDela,
  65. forceSubMenuRender = _ref.forceSubMenuRender,
  66. defaultOpenKeys = _ref.defaultOpenKeys,
  67. openKeys = _ref.openKeys,
  68. activeKey = _ref.activeKey,
  69. defaultActiveFirst = _ref.defaultActiveFirst,
  70. _ref$selectable = _ref.selectable,
  71. selectable = _ref$selectable === void 0 ? true : _ref$selectable,
  72. _ref$multiple = _ref.multiple,
  73. multiple = _ref$multiple === void 0 ? false : _ref$multiple,
  74. defaultSelectedKeys = _ref.defaultSelectedKeys,
  75. selectedKeys = _ref.selectedKeys,
  76. onSelect = _ref.onSelect,
  77. onDeselect = _ref.onDeselect,
  78. _ref$inlineIndent = _ref.inlineIndent,
  79. inlineIndent = _ref$inlineIndent === void 0 ? 24 : _ref$inlineIndent,
  80. motion = _ref.motion,
  81. defaultMotions = _ref.defaultMotions,
  82. _ref$triggerSubMenuAc = _ref.triggerSubMenuAction,
  83. triggerSubMenuAction = _ref$triggerSubMenuAc === void 0 ? 'hover' : _ref$triggerSubMenuAc,
  84. builtinPlacements = _ref.builtinPlacements,
  85. itemIcon = _ref.itemIcon,
  86. expandIcon = _ref.expandIcon,
  87. _ref$overflowedIndica = _ref.overflowedIndicator,
  88. overflowedIndicator = _ref$overflowedIndica === void 0 ? '...' : _ref$overflowedIndica,
  89. overflowedIndicatorPopupClassName = _ref.overflowedIndicatorPopupClassName,
  90. getPopupContainer = _ref.getPopupContainer,
  91. onClick = _ref.onClick,
  92. onOpenChange = _ref.onOpenChange,
  93. onKeyDown = _ref.onKeyDown,
  94. openAnimation = _ref.openAnimation,
  95. openTransitionName = _ref.openTransitionName,
  96. _internalRenderMenuItem = _ref._internalRenderMenuItem,
  97. _internalRenderSubMenuItem = _ref._internalRenderSubMenuItem,
  98. _internalComponents = _ref._internalComponents,
  99. restProps = _objectWithoutProperties(_ref, _excluded);
  100. var _React$useMemo = React.useMemo(function () {
  101. return [parseItems(children, items, EMPTY_LIST, _internalComponents, prefixCls), parseItems(children, items, EMPTY_LIST, {}, prefixCls)];
  102. }, [children, items, _internalComponents]),
  103. _React$useMemo2 = _slicedToArray(_React$useMemo, 2),
  104. childList = _React$useMemo2[0],
  105. measureChildList = _React$useMemo2[1];
  106. var _React$useState = React.useState(false),
  107. _React$useState2 = _slicedToArray(_React$useState, 2),
  108. mounted = _React$useState2[0],
  109. setMounted = _React$useState2[1];
  110. var containerRef = React.useRef();
  111. var uuid = useUUID(id);
  112. var isRtl = direction === 'rtl';
  113. // ========================= Warn =========================
  114. if (process.env.NODE_ENV !== 'production') {
  115. warning(!openAnimation && !openTransitionName, '`openAnimation` and `openTransitionName` is removed. Please use `motion` or `defaultMotion` instead.');
  116. }
  117. // ========================= Open =========================
  118. var _useMergedState = useMergedState(defaultOpenKeys, {
  119. value: openKeys,
  120. postState: function postState(keys) {
  121. return keys || EMPTY_LIST;
  122. }
  123. }),
  124. _useMergedState2 = _slicedToArray(_useMergedState, 2),
  125. mergedOpenKeys = _useMergedState2[0],
  126. setMergedOpenKeys = _useMergedState2[1];
  127. // React 18 will merge mouse event which means we open key will not sync
  128. // ref: https://github.com/ant-design/ant-design/issues/38818
  129. var triggerOpenKeys = function triggerOpenKeys(keys) {
  130. var forceFlush = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  131. function doUpdate() {
  132. setMergedOpenKeys(keys);
  133. onOpenChange === null || onOpenChange === void 0 || onOpenChange(keys);
  134. }
  135. if (forceFlush) {
  136. flushSync(doUpdate);
  137. } else {
  138. doUpdate();
  139. }
  140. };
  141. // >>>>> Cache & Reset open keys when inlineCollapsed changed
  142. var _React$useState3 = React.useState(mergedOpenKeys),
  143. _React$useState4 = _slicedToArray(_React$useState3, 2),
  144. inlineCacheOpenKeys = _React$useState4[0],
  145. setInlineCacheOpenKeys = _React$useState4[1];
  146. var mountRef = React.useRef(false);
  147. // ========================= Mode =========================
  148. var _React$useMemo3 = React.useMemo(function () {
  149. if ((mode === 'inline' || mode === 'vertical') && inlineCollapsed) {
  150. return ['vertical', inlineCollapsed];
  151. }
  152. return [mode, false];
  153. }, [mode, inlineCollapsed]),
  154. _React$useMemo4 = _slicedToArray(_React$useMemo3, 2),
  155. mergedMode = _React$useMemo4[0],
  156. mergedInlineCollapsed = _React$useMemo4[1];
  157. var isInlineMode = mergedMode === 'inline';
  158. var _React$useState5 = React.useState(mergedMode),
  159. _React$useState6 = _slicedToArray(_React$useState5, 2),
  160. internalMode = _React$useState6[0],
  161. setInternalMode = _React$useState6[1];
  162. var _React$useState7 = React.useState(mergedInlineCollapsed),
  163. _React$useState8 = _slicedToArray(_React$useState7, 2),
  164. internalInlineCollapsed = _React$useState8[0],
  165. setInternalInlineCollapsed = _React$useState8[1];
  166. React.useEffect(function () {
  167. setInternalMode(mergedMode);
  168. setInternalInlineCollapsed(mergedInlineCollapsed);
  169. if (!mountRef.current) {
  170. return;
  171. }
  172. // Synchronously update MergedOpenKeys
  173. if (isInlineMode) {
  174. setMergedOpenKeys(inlineCacheOpenKeys);
  175. } else {
  176. // Trigger open event in case its in control
  177. triggerOpenKeys(EMPTY_LIST);
  178. }
  179. }, [mergedMode, mergedInlineCollapsed]);
  180. // ====================== Responsive ======================
  181. var _React$useState9 = React.useState(0),
  182. _React$useState10 = _slicedToArray(_React$useState9, 2),
  183. lastVisibleIndex = _React$useState10[0],
  184. setLastVisibleIndex = _React$useState10[1];
  185. var allVisible = lastVisibleIndex >= childList.length - 1 || internalMode !== 'horizontal' || disabledOverflow;
  186. // Cache
  187. React.useEffect(function () {
  188. if (isInlineMode) {
  189. setInlineCacheOpenKeys(mergedOpenKeys);
  190. }
  191. }, [mergedOpenKeys]);
  192. React.useEffect(function () {
  193. mountRef.current = true;
  194. return function () {
  195. mountRef.current = false;
  196. };
  197. }, []);
  198. // ========================= Path =========================
  199. var _useKeyRecords = useKeyRecords(),
  200. registerPath = _useKeyRecords.registerPath,
  201. unregisterPath = _useKeyRecords.unregisterPath,
  202. refreshOverflowKeys = _useKeyRecords.refreshOverflowKeys,
  203. isSubPathKey = _useKeyRecords.isSubPathKey,
  204. getKeyPath = _useKeyRecords.getKeyPath,
  205. getKeys = _useKeyRecords.getKeys,
  206. getSubPathKeys = _useKeyRecords.getSubPathKeys;
  207. var registerPathContext = React.useMemo(function () {
  208. return {
  209. registerPath: registerPath,
  210. unregisterPath: unregisterPath
  211. };
  212. }, [registerPath, unregisterPath]);
  213. var pathUserContext = React.useMemo(function () {
  214. return {
  215. isSubPathKey: isSubPathKey
  216. };
  217. }, [isSubPathKey]);
  218. React.useEffect(function () {
  219. refreshOverflowKeys(allVisible ? EMPTY_LIST : childList.slice(lastVisibleIndex + 1).map(function (child) {
  220. return child.key;
  221. }));
  222. }, [lastVisibleIndex, allVisible]);
  223. // ======================== Active ========================
  224. var _useMergedState3 = useMergedState(activeKey || defaultActiveFirst && ((_childList$ = childList[0]) === null || _childList$ === void 0 ? void 0 : _childList$.key), {
  225. value: activeKey
  226. }),
  227. _useMergedState4 = _slicedToArray(_useMergedState3, 2),
  228. mergedActiveKey = _useMergedState4[0],
  229. setMergedActiveKey = _useMergedState4[1];
  230. var onActive = useMemoCallback(function (key) {
  231. setMergedActiveKey(key);
  232. });
  233. var onInactive = useMemoCallback(function () {
  234. setMergedActiveKey(undefined);
  235. });
  236. useImperativeHandle(ref, function () {
  237. return {
  238. list: containerRef.current,
  239. focus: function focus(options) {
  240. var _childList$find;
  241. var keys = getKeys();
  242. var _refreshElements = refreshElements(keys, uuid),
  243. elements = _refreshElements.elements,
  244. key2element = _refreshElements.key2element,
  245. element2key = _refreshElements.element2key;
  246. var focusableElements = getFocusableElements(containerRef.current, elements);
  247. var shouldFocusKey = mergedActiveKey !== null && mergedActiveKey !== void 0 ? mergedActiveKey : focusableElements[0] ? element2key.get(focusableElements[0]) : (_childList$find = childList.find(function (node) {
  248. return !node.props.disabled;
  249. })) === null || _childList$find === void 0 ? void 0 : _childList$find.key;
  250. var elementToFocus = key2element.get(shouldFocusKey);
  251. if (shouldFocusKey && elementToFocus) {
  252. var _elementToFocus$focus;
  253. elementToFocus === null || elementToFocus === void 0 || (_elementToFocus$focus = elementToFocus.focus) === null || _elementToFocus$focus === void 0 || _elementToFocus$focus.call(elementToFocus, options);
  254. }
  255. }
  256. };
  257. });
  258. // ======================== Select ========================
  259. // >>>>> Select keys
  260. var _useMergedState5 = useMergedState(defaultSelectedKeys || [], {
  261. value: selectedKeys,
  262. // Legacy convert key to array
  263. postState: function postState(keys) {
  264. if (Array.isArray(keys)) {
  265. return keys;
  266. }
  267. if (keys === null || keys === undefined) {
  268. return EMPTY_LIST;
  269. }
  270. return [keys];
  271. }
  272. }),
  273. _useMergedState6 = _slicedToArray(_useMergedState5, 2),
  274. mergedSelectKeys = _useMergedState6[0],
  275. setMergedSelectKeys = _useMergedState6[1];
  276. // >>>>> Trigger select
  277. var triggerSelection = function triggerSelection(info) {
  278. if (selectable) {
  279. // Insert or Remove
  280. var targetKey = info.key;
  281. var exist = mergedSelectKeys.includes(targetKey);
  282. var newSelectKeys;
  283. if (multiple) {
  284. if (exist) {
  285. newSelectKeys = mergedSelectKeys.filter(function (key) {
  286. return key !== targetKey;
  287. });
  288. } else {
  289. newSelectKeys = [].concat(_toConsumableArray(mergedSelectKeys), [targetKey]);
  290. }
  291. } else {
  292. newSelectKeys = [targetKey];
  293. }
  294. setMergedSelectKeys(newSelectKeys);
  295. // Trigger event
  296. var selectInfo = _objectSpread(_objectSpread({}, info), {}, {
  297. selectedKeys: newSelectKeys
  298. });
  299. if (exist) {
  300. onDeselect === null || onDeselect === void 0 || onDeselect(selectInfo);
  301. } else {
  302. onSelect === null || onSelect === void 0 || onSelect(selectInfo);
  303. }
  304. }
  305. // Whatever selectable, always close it
  306. if (!multiple && mergedOpenKeys.length && internalMode !== 'inline') {
  307. triggerOpenKeys(EMPTY_LIST);
  308. }
  309. };
  310. // ========================= Open =========================
  311. /**
  312. * Click for item. SubMenu do not have selection status
  313. */
  314. var onInternalClick = useMemoCallback(function (info) {
  315. onClick === null || onClick === void 0 || onClick(warnItemProp(info));
  316. triggerSelection(info);
  317. });
  318. var onInternalOpenChange = useMemoCallback(function (key, open) {
  319. var newOpenKeys = mergedOpenKeys.filter(function (k) {
  320. return k !== key;
  321. });
  322. if (open) {
  323. newOpenKeys.push(key);
  324. } else if (internalMode !== 'inline') {
  325. // We need find all related popup to close
  326. var subPathKeys = getSubPathKeys(key);
  327. newOpenKeys = newOpenKeys.filter(function (k) {
  328. return !subPathKeys.has(k);
  329. });
  330. }
  331. if (!isEqual(mergedOpenKeys, newOpenKeys, true)) {
  332. triggerOpenKeys(newOpenKeys, true);
  333. }
  334. });
  335. // ==================== Accessibility =====================
  336. var triggerAccessibilityOpen = function triggerAccessibilityOpen(key, open) {
  337. var nextOpen = open !== null && open !== void 0 ? open : !mergedOpenKeys.includes(key);
  338. onInternalOpenChange(key, nextOpen);
  339. };
  340. var onInternalKeyDown = useAccessibility(internalMode, mergedActiveKey, isRtl, uuid, containerRef, getKeys, getKeyPath, setMergedActiveKey, triggerAccessibilityOpen, onKeyDown);
  341. // ======================== Effect ========================
  342. React.useEffect(function () {
  343. setMounted(true);
  344. }, []);
  345. // ======================= Context ========================
  346. var privateContext = React.useMemo(function () {
  347. return {
  348. _internalRenderMenuItem: _internalRenderMenuItem,
  349. _internalRenderSubMenuItem: _internalRenderSubMenuItem
  350. };
  351. }, [_internalRenderMenuItem, _internalRenderSubMenuItem]);
  352. // ======================== Render ========================
  353. // >>>>> Children
  354. var wrappedChildList = internalMode !== 'horizontal' || disabledOverflow ? childList :
  355. // Need wrap for overflow dropdown that do not response for open
  356. childList.map(function (child, index) {
  357. return (
  358. /*#__PURE__*/
  359. // Always wrap provider to avoid sub node re-mount
  360. React.createElement(MenuContextProvider, {
  361. key: child.key,
  362. overflowDisabled: index > lastVisibleIndex
  363. }, child)
  364. );
  365. });
  366. // >>>>> Container
  367. var container = /*#__PURE__*/React.createElement(Overflow, _extends({
  368. id: id,
  369. ref: containerRef,
  370. prefixCls: "".concat(prefixCls, "-overflow"),
  371. component: "ul",
  372. itemComponent: MenuItem,
  373. className: classNames(prefixCls, "".concat(prefixCls, "-root"), "".concat(prefixCls, "-").concat(internalMode), className, _defineProperty(_defineProperty({}, "".concat(prefixCls, "-inline-collapsed"), internalInlineCollapsed), "".concat(prefixCls, "-rtl"), isRtl), rootClassName),
  374. dir: direction,
  375. style: style,
  376. role: "menu",
  377. tabIndex: tabIndex,
  378. data: wrappedChildList,
  379. renderRawItem: function renderRawItem(node) {
  380. return node;
  381. },
  382. renderRawRest: function renderRawRest(omitItems) {
  383. // We use origin list since wrapped list use context to prevent open
  384. var len = omitItems.length;
  385. var originOmitItems = len ? childList.slice(-len) : null;
  386. return /*#__PURE__*/React.createElement(SubMenu, {
  387. eventKey: OVERFLOW_KEY,
  388. title: overflowedIndicator,
  389. disabled: allVisible,
  390. internalPopupClose: len === 0,
  391. popupClassName: overflowedIndicatorPopupClassName
  392. }, originOmitItems);
  393. },
  394. maxCount: internalMode !== 'horizontal' || disabledOverflow ? Overflow.INVALIDATE : Overflow.RESPONSIVE,
  395. ssr: "full",
  396. "data-menu-list": true,
  397. onVisibleChange: function onVisibleChange(newLastIndex) {
  398. setLastVisibleIndex(newLastIndex);
  399. },
  400. onKeyDown: onInternalKeyDown
  401. }, restProps));
  402. // >>>>> Render
  403. return /*#__PURE__*/React.createElement(PrivateContext.Provider, {
  404. value: privateContext
  405. }, /*#__PURE__*/React.createElement(IdContext.Provider, {
  406. value: uuid
  407. }, /*#__PURE__*/React.createElement(MenuContextProvider, {
  408. prefixCls: prefixCls,
  409. rootClassName: rootClassName,
  410. mode: internalMode,
  411. openKeys: mergedOpenKeys,
  412. rtl: isRtl
  413. // Disabled
  414. ,
  415. disabled: disabled
  416. // Motion
  417. ,
  418. motion: mounted ? motion : null,
  419. defaultMotions: mounted ? defaultMotions : null
  420. // Active
  421. ,
  422. activeKey: mergedActiveKey,
  423. onActive: onActive,
  424. onInactive: onInactive
  425. // Selection
  426. ,
  427. selectedKeys: mergedSelectKeys
  428. // Level
  429. ,
  430. inlineIndent: inlineIndent
  431. // Popup
  432. ,
  433. subMenuOpenDelay: subMenuOpenDelay,
  434. subMenuCloseDelay: subMenuCloseDelay,
  435. forceSubMenuRender: forceSubMenuRender,
  436. builtinPlacements: builtinPlacements,
  437. triggerSubMenuAction: triggerSubMenuAction,
  438. getPopupContainer: getPopupContainer
  439. // Icon
  440. ,
  441. itemIcon: itemIcon,
  442. expandIcon: expandIcon
  443. // Events
  444. ,
  445. onItemClick: onInternalClick,
  446. onOpenChange: onInternalOpenChange
  447. }, /*#__PURE__*/React.createElement(PathUserContext.Provider, {
  448. value: pathUserContext
  449. }, container), /*#__PURE__*/React.createElement("div", {
  450. style: {
  451. display: 'none'
  452. },
  453. "aria-hidden": true
  454. }, /*#__PURE__*/React.createElement(PathRegisterContext.Provider, {
  455. value: registerPathContext
  456. }, measureChildList)))));
  457. });
  458. export default Menu;