UNPKG

rsuite

Version:

A suite of react components

383 lines (371 loc) 15.1 kB
'use client'; "use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.default = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _react = _interopRequireWildcard(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _isNil = _interopRequireDefault(require("lodash/isNil")); var _MenuContext = _interopRequireWildcard(require("./MenuContext")); var _useMenu = _interopRequireDefault(require("./useMenu")); var _hooks = require("../hooks"); var _constants = require("../constants"); var _CustomProvider = require("../../CustomProvider"); var _utils = require("../utils"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(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 && {}.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; } var defaultOpenMenuOn = ['click']; function getMenuItemTarget(event) { var _event$currentTarget; var target = event.target; if (target.getAttribute('role') === 'menuitem') { return target; } return Array.from((_event$currentTarget = event.currentTarget) === null || _event$currentTarget === void 0 ? void 0 : _event$currentTarget.querySelectorAll('[role="menuitem"]')).find(function (item) { return item.contains(target); }); } /** * Headless ARIA `menu` * @private */ function Menu(_ref) { var _items$activeItemInde; var disabled = _ref.disabled, children = _ref.children, _ref$openMenuOn = _ref.openMenuOn, openMenuOn = _ref$openMenuOn === void 0 ? defaultOpenMenuOn : _ref$openMenuOn, _ref$defaultOpen = _ref.defaultOpen, defaultOpen = _ref$defaultOpen === void 0 ? false : _ref$defaultOpen, openProp = _ref.open, menuButtonText = _ref.menuButtonText, renderMenuButton = _ref.renderMenuButton, renderMenuPopup = _ref.renderMenuPopup, onToggleMenu = _ref.onToggleMenu; var buttonElementRef = (0, _react.useRef)(null); var menuElementRef = (0, _react.useRef)(null); var parentMenu = (0, _react.useContext)(_MenuContext.default); var isSubmenu = !!parentMenu; var menu = (0, _useMenu.default)({ open: defaultOpen }); var _menu$ = menu[0], openState = _menu$.open, items = _menu$.items, activeItemIndex = _menu$.activeItemIndex, dispatch = menu[1]; var openControlled = typeof openProp !== 'undefined'; var open = openControlled ? openProp : openState; var _useCustom = (0, _CustomProvider.useCustom)(), rtl = _useCustom.rtl; var activeItem = (0, _isNil.default)(activeItemIndex) ? null : (_items$activeItemInde = items[activeItemIndex]) === null || _items$activeItemInde === void 0 ? void 0 : _items$activeItemInde.element; var _useFocus = (0, _hooks.useFocus)(menuElementRef), grabFocus = _useFocus.grab; var openMenu = (0, _react.useCallback)(function (event) { dispatch({ type: _MenuContext.MenuActionTypes.OpenMenu }); if (!event.isTrusted) { dispatch({ type: _MenuContext.MenuActionTypes.MoveFocus, to: _MenuContext.MoveFocusTo.First }); } onToggleMenu === null || onToggleMenu === void 0 || onToggleMenu(true, event); grabFocus(); }, [dispatch, onToggleMenu, grabFocus]); var closeMenu = (0, _react.useCallback)(function (event, returnFocusToButton) { if (returnFocusToButton === void 0) { returnFocusToButton = true; } dispatch({ type: _MenuContext.MenuActionTypes.CloseMenu }); dispatch({ type: _MenuContext.MenuActionTypes.MoveFocus, to: _MenuContext.MoveFocusTo.None }); onToggleMenu === null || onToggleMenu === void 0 || onToggleMenu(false, event); if (returnFocusToButton) { var _buttonElementRef$cur; (_buttonElementRef$cur = buttonElementRef.current) === null || _buttonElementRef$cur === void 0 || _buttonElementRef$cur.focus({ preventScroll: true }); } }, [dispatch, onToggleMenu, buttonElementRef]); var toggleMenu = (0, _react.useCallback)(function (event) { if (!open) { openMenu(event); } else { closeMenu(event); } }, [open, openMenu, closeMenu]); (0, _hooks.useClickOutside)({ enabled: open, isOutside: function isOutside(event) { var _buttonElementRef$cur2, _menuElementRef$curre; return !((_buttonElementRef$cur2 = buttonElementRef.current) !== null && _buttonElementRef$cur2 !== void 0 && _buttonElementRef$cur2.contains(event.target)) && !((_menuElementRef$curre = menuElementRef.current) !== null && _menuElementRef$curre !== void 0 && _menuElementRef$curre.contains(event.target)); }, // fixme if clicking on a focusable element, don't move focus to menu button handle: function handle(event) { return closeMenu(event, !(0, _utils.isFocusableElement)(event.target)); } }); /** * Keyboard interaction on menu button * @see https://www.w3.org/TR/wai-aria-practices-1.2/#keyboard-interaction-13 */ var handleButtonKeydown = (0, _react.useCallback)(function (e) { switch (e.key) { // Open the menu case _constants.KEY_VALUES.ENTER: case _constants.KEY_VALUES.SPACE: e.preventDefault(); e.stopPropagation(); if (!open) { openMenu(e); dispatch({ type: _MenuContext.MenuActionTypes.MoveFocus, to: _MenuContext.MoveFocusTo.First }); } else { closeMenu(e); } break; // Open the menu (if closed) and move focus to first item // This is mostly useful after opening the menu with click case _constants.KEY_VALUES.DOWN: e.preventDefault(); e.stopPropagation(); if (!open) { openMenu(e); } dispatch({ type: _MenuContext.MenuActionTypes.MoveFocus, to: _MenuContext.MoveFocusTo.First }); break; } }, [open, openMenu, closeMenu, dispatch]); var handleButtonClick = (0, _react.useCallback)(function (event) { if (disabled) { return; } toggleMenu(event); }, [disabled, toggleMenu]); var handleButtonContextMenu = (0, _react.useCallback)(function (event) { // prevents default contextmenu event.preventDefault(); // Only opens menu on right click. Left click can close the menu opened by a right click if (open) return; if (disabled) return; openMenu(event); }, [open, disabled, openMenu]); var buttonEventHandlers = (0, _react.useMemo)(function () { var buttonEventHandlers = { onKeyDown: handleButtonKeydown }; /** * Bind event of trigger, * not used in in the expanded state of '<Sidenav>' */ if (openMenuOn !== null && openMenuOn !== void 0 && openMenuOn.includes('click')) { buttonEventHandlers.onClick = handleButtonClick; } if (openMenuOn !== null && openMenuOn !== void 0 && openMenuOn.includes('contextmenu')) { buttonEventHandlers.onContextMenu = handleButtonContextMenu; } return buttonEventHandlers; }, [openMenuOn, handleButtonKeydown, handleButtonClick, handleButtonContextMenu]); var buttonId = (0, _hooks.useUniqueId)('menubutton-'); var menuId = (0, _hooks.useUniqueId)('menu-'); var buttonAriaAttributes = (0, _react.useMemo)(function () { // Ref: https://www.w3.org/TR/wai-aria-practices-1.2/#wai-aria-roles-states-and-properties-14 return { role: 'button', 'aria-haspopup': 'menu', 'aria-expanded': open || undefined, // it's recommend to remove aria-expanded when menu is hidden 'aria-controls': menuId }; }, [open, menuId]); var buttonProps = (0, _react.useMemo)(function () { return (0, _extends2.default)({ id: buttonId }, buttonAriaAttributes, buttonEventHandlers, { // render props open: open }); }, [buttonId, buttonAriaAttributes, buttonEventHandlers, open]); var customMenuButton = (0, _react.useMemo)(function () { return renderMenuButton === null || renderMenuButton === void 0 ? void 0 : renderMenuButton(buttonProps, buttonElementRef); }, [renderMenuButton, buttonProps, buttonElementRef]); var buttonElement = customMenuButton !== null && customMenuButton !== void 0 ? customMenuButton : /*#__PURE__*/_react.default.createElement("button", (0, _extends2.default)({ ref: buttonElementRef }, buttonProps), menuButtonText); /** * Keyboard interaction on menu * @see https://www.w3.org/TR/wai-aria-practices-1.2/#keyboard-interaction-12 */ var handleMenuKeydown = (0, _react.useCallback)(function (e) { switch (e.key) { // Move focus to previous item case _constants.KEY_VALUES.UP: e.preventDefault(); e.stopPropagation(); dispatch({ type: _MenuContext.MenuActionTypes.MoveFocus, to: _MenuContext.MoveFocusTo.Prev }); break; // Move focus to next item case _constants.KEY_VALUES.DOWN: e.preventDefault(); e.stopPropagation(); dispatch({ type: _MenuContext.MenuActionTypes.MoveFocus, to: _MenuContext.MoveFocusTo.Next }); break; // When focus is in a menu and on a menuitem that has a submenu, opens the submenu and places focus on its first item. case _constants.KEY_VALUES.RIGHT: e.preventDefault(); e.stopPropagation(); if (!rtl) { if ((activeItem === null || activeItem === void 0 ? void 0 : activeItem.getAttribute('aria-haspopup')) === 'menu') { activeItem.click(); } } else if (isSubmenu) { dispatch({ type: _MenuContext.MenuActionTypes.CloseMenu }); } break; // When focus is in a submenu of an item in a menu, closes the submenu and returns focus to the parent menuitem. case _constants.KEY_VALUES.LEFT: e.preventDefault(); e.stopPropagation(); if (!rtl) { if (isSubmenu) { closeMenu(e); } } else if ((activeItem === null || activeItem === void 0 ? void 0 : activeItem.getAttribute('aria-haspopup')) === 'menu') { activeItem.click(); } break; // Move focus to the first item case _constants.KEY_VALUES.HOME: e.preventDefault(); e.stopPropagation(); dispatch({ type: _MenuContext.MenuActionTypes.MoveFocus, to: _MenuContext.MoveFocusTo.First }); break; // Move focus to the last item case _constants.KEY_VALUES.END: e.preventDefault(); e.stopPropagation(); dispatch({ type: _MenuContext.MenuActionTypes.MoveFocus, to: _MenuContext.MoveFocusTo.Last }); break; // - When focus is on a menuitem that has a submenu, opens the submenu and places focus on its first item. // - Otherwise, activates the item and closes the menu. case _constants.KEY_VALUES.ENTER: case _constants.KEY_VALUES.SPACE: if (activeItem) { e.preventDefault(); e.stopPropagation(); activeItem.click(); if (!activeItem.getAttribute('aria-haspopup')) { closeMenu(e); } } break; // Close the menu that contains focus and return focus to the element or context, // e.g., menu button or parent menuitem, from which the menu was opened. case _constants.KEY_VALUES.ESC: closeMenu(e); break; } }, [dispatch, activeItem, isSubmenu, rtl, closeMenu]); // Only used for clicks bubbling from child `menuitem`s. var handleMenuClick = (0, _react.useCallback)(function (event) { var target = getMenuItemTarget(event); if (!target) return; // Only handle clicks on `menuitem`s if (target.getAttribute('role') !== 'menuitem') return; // Ignore clicks on `menuitem`s that controls a submenu if (target.getAttribute('aria-haspopup') === 'menu') return; // Ignore disabled `menuitem`s if (target.getAttribute('aria-disabled') === 'true') return; closeMenu(event, !isSubmenu); }, [closeMenu, isSubmenu]); // Ref: https://www.w3.org/TR/wai-aria-practices-1.2/#wai-aria-roles-states-and-properties-13 var menuAriaAttributes = { role: 'menu', 'aria-labelledby': buttonId, 'aria-activedescendant': activeItem === null || activeItem === void 0 ? void 0 : activeItem.id }; var menuEventHandlers = { onClick: handleMenuClick, onKeyDown: handleMenuKeydown }; var menuProps = (0, _extends2.default)({ id: menuId }, menuAriaAttributes, menuEventHandlers, { tabIndex: 0 }); var customMenuPopup = renderMenuPopup === null || renderMenuPopup === void 0 ? void 0 : renderMenuPopup((0, _extends2.default)({}, menuProps, { open: open }), menuElementRef); // fixme Wrong children here var menuElement = customMenuPopup !== null && customMenuPopup !== void 0 ? customMenuPopup : /*#__PURE__*/_react.default.createElement("ul", (0, _extends2.default)({ ref: menuElementRef }, menuProps, { hidden: !open }), children); var handleMouseEnter = (0, _react.useCallback)(function (e) { if (!disabled) { openMenu(e); } }, [disabled, openMenu]); var handleMouseLeave = (0, _react.useCallback)(function (e) { if (!disabled) { closeMenu(e); } }, [disabled, closeMenu]); var rootElementRef = (0, _react.useRef)(null); var handleContainerBlur = (0, _react.useCallback)(function (event) { /* istanbul ignore else */ if ((0, _utils.isFocusLeaving)(event)) { closeMenu(event, false); } }, [closeMenu]); var rootEventHandlers = { onBlur: handleContainerBlur }; if (openMenuOn !== null && openMenuOn !== void 0 && openMenuOn.includes('mouseover')) { rootEventHandlers.onMouseEnter = handleMouseEnter; rootEventHandlers.onMouseLeave = handleMouseLeave; } var rootProps = (0, _extends2.default)({}, rootEventHandlers, { children: /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, buttonElement, /*#__PURE__*/_react.default.createElement(_MenuContext.default.Provider, { value: menu }, menuElement)), // render props open: open }); if (isSubmenu) { rootProps.role = 'none presentation'; } return children(rootProps, rootElementRef); } Menu.displayName = 'Menu'; Menu.propTypes = { children: _propTypes.default.func.isRequired }; var _default = exports.default = Menu;