UNPKG

@mui/material

Version:

Quickly build beautiful React apps. MUI is a simple and customizable component library to build faster, beautiful, and more accessible React applications. Follow your own design system, or start with Material Design.

336 lines (272 loc) 11.4 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose")); var React = _interopRequireWildcard(require("react")); var _reactIs = require("react-is"); var _propTypes = _interopRequireDefault(require("prop-types")); var _ownerDocument = _interopRequireDefault(require("../utils/ownerDocument")); var _List = _interopRequireDefault(require("../List")); var _getScrollbarSize = _interopRequireDefault(require("../utils/getScrollbarSize")); var _useForkRef = _interopRequireDefault(require("../utils/useForkRef")); var _useEnhancedEffect = _interopRequireDefault(require("../utils/useEnhancedEffect")); var _jsxRuntime = require("react/jsx-runtime"); const _excluded = ["actions", "autoFocus", "autoFocusItem", "children", "className", "disabledItemsFocusable", "disableListWrap", "onKeyDown", "variant"]; function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } function nextItem(list, item, disableListWrap) { if (list === item) { return list.firstChild; } if (item && item.nextElementSibling) { return item.nextElementSibling; } return disableListWrap ? null : list.firstChild; } function previousItem(list, item, disableListWrap) { if (list === item) { return disableListWrap ? list.firstChild : list.lastChild; } if (item && item.previousElementSibling) { return item.previousElementSibling; } return disableListWrap ? null : list.lastChild; } function textCriteriaMatches(nextFocus, textCriteria) { if (textCriteria === undefined) { return true; } let text = nextFocus.innerText; if (text === undefined) { // jsdom doesn't support innerText text = nextFocus.textContent; } text = text.trim().toLowerCase(); if (text.length === 0) { return false; } if (textCriteria.repeating) { return text[0] === textCriteria.keys[0]; } return text.indexOf(textCriteria.keys.join('')) === 0; } function moveFocus(list, currentFocus, disableListWrap, disabledItemsFocusable, traversalFunction, textCriteria) { let wrappedOnce = false; let nextFocus = traversalFunction(list, currentFocus, currentFocus ? disableListWrap : false); while (nextFocus) { // Prevent infinite loop. if (nextFocus === list.firstChild) { if (wrappedOnce) { return false; } wrappedOnce = true; } // Same logic as useAutocomplete.js const nextFocusDisabled = disabledItemsFocusable ? false : nextFocus.disabled || nextFocus.getAttribute('aria-disabled') === 'true'; if (!nextFocus.hasAttribute('tabindex') || !textCriteriaMatches(nextFocus, textCriteria) || nextFocusDisabled) { // Move to the next element. nextFocus = traversalFunction(list, nextFocus, disableListWrap); } else { nextFocus.focus(); return true; } } return false; } /** * A permanently displayed menu following https://www.w3.org/TR/wai-aria-practices/#menubutton. * It's exposed to help customization of the [`Menu`](/api/menu/) component if you * use it separately you need to move focus into the component manually. Once * the focus is placed inside the component it is fully keyboard accessible. */ const MenuList = /*#__PURE__*/React.forwardRef(function MenuList(props, ref) { const { // private // eslint-disable-next-line react/prop-types actions, autoFocus = false, autoFocusItem = false, children, className, disabledItemsFocusable = false, disableListWrap = false, onKeyDown, variant = 'selectedMenu' } = props, other = (0, _objectWithoutPropertiesLoose2.default)(props, _excluded); const listRef = React.useRef(null); const textCriteriaRef = React.useRef({ keys: [], repeating: true, previousKeyMatched: true, lastTime: null }); (0, _useEnhancedEffect.default)(() => { if (autoFocus) { listRef.current.focus(); } }, [autoFocus]); React.useImperativeHandle(actions, () => ({ adjustStyleForScrollbar: (containerElement, theme) => { // Let's ignore that piece of logic if users are already overriding the width // of the menu. const noExplicitWidth = !listRef.current.style.width; if (containerElement.clientHeight < listRef.current.clientHeight && noExplicitWidth) { const scrollbarSize = `${(0, _getScrollbarSize.default)((0, _ownerDocument.default)(containerElement))}px`; listRef.current.style[theme.direction === 'rtl' ? 'paddingLeft' : 'paddingRight'] = scrollbarSize; listRef.current.style.width = `calc(100% + ${scrollbarSize})`; } return listRef.current; } }), []); const handleKeyDown = event => { const list = listRef.current; const key = event.key; /** * @type {Element} - will always be defined since we are in a keydown handler * attached to an element. A keydown event is either dispatched to the activeElement * or document.body or document.documentElement. Only the first case will * trigger this specific handler. */ const currentFocus = (0, _ownerDocument.default)(list).activeElement; if (key === 'ArrowDown') { // Prevent scroll of the page event.preventDefault(); moveFocus(list, currentFocus, disableListWrap, disabledItemsFocusable, nextItem); } else if (key === 'ArrowUp') { event.preventDefault(); moveFocus(list, currentFocus, disableListWrap, disabledItemsFocusable, previousItem); } else if (key === 'Home') { event.preventDefault(); moveFocus(list, null, disableListWrap, disabledItemsFocusable, nextItem); } else if (key === 'End') { event.preventDefault(); moveFocus(list, null, disableListWrap, disabledItemsFocusable, previousItem); } else if (key.length === 1) { const criteria = textCriteriaRef.current; const lowerKey = key.toLowerCase(); const currTime = performance.now(); if (criteria.keys.length > 0) { // Reset if (currTime - criteria.lastTime > 500) { criteria.keys = []; criteria.repeating = true; criteria.previousKeyMatched = true; } else if (criteria.repeating && lowerKey !== criteria.keys[0]) { criteria.repeating = false; } } criteria.lastTime = currTime; criteria.keys.push(lowerKey); const keepFocusOnCurrent = currentFocus && !criteria.repeating && textCriteriaMatches(currentFocus, criteria); if (criteria.previousKeyMatched && (keepFocusOnCurrent || moveFocus(list, currentFocus, false, disabledItemsFocusable, nextItem, criteria))) { event.preventDefault(); } else { criteria.previousKeyMatched = false; } } if (onKeyDown) { onKeyDown(event); } }; const handleRef = (0, _useForkRef.default)(listRef, ref); /** * the index of the item should receive focus * in a `variant="selectedMenu"` it's the first `selected` item * otherwise it's the very first item. */ let activeItemIndex = -1; // since we inject focus related props into children we have to do a lookahead // to check if there is a `selected` item. We're looking for the last `selected` // item and use the first valid item as a fallback React.Children.forEach(children, (child, index) => { if (! /*#__PURE__*/React.isValidElement(child)) { return; } if (process.env.NODE_ENV !== 'production') { if ((0, _reactIs.isFragment)(child)) { console.error(["MUI: The Menu component doesn't accept a Fragment as a child.", 'Consider providing an array instead.'].join('\n')); } } if (!child.props.disabled) { if (variant === 'selectedMenu' && child.props.selected) { activeItemIndex = index; } else if (activeItemIndex === -1) { activeItemIndex = index; } } }); const items = React.Children.map(children, (child, index) => { if (index === activeItemIndex) { const newChildProps = {}; if (autoFocusItem) { newChildProps.autoFocus = true; } if (child.props.tabIndex === undefined && variant === 'selectedMenu') { newChildProps.tabIndex = 0; } return /*#__PURE__*/React.cloneElement(child, newChildProps); } return child; }); return /*#__PURE__*/(0, _jsxRuntime.jsx)(_List.default, (0, _extends2.default)({ role: "menu", ref: handleRef, className: className, onKeyDown: handleKeyDown, tabIndex: autoFocus ? 0 : -1 }, other, { children: items })); }); process.env.NODE_ENV !== "production" ? MenuList.propTypes /* remove-proptypes */ = { // ----------------------------- Warning -------------------------------- // | These PropTypes are generated from the TypeScript type definitions | // | To update them edit the d.ts file and run "yarn proptypes" | // ---------------------------------------------------------------------- /** * If `true`, will focus the `[role="menu"]` container and move into tab order. * @default false */ autoFocus: _propTypes.default.bool, /** * If `true`, will focus the first menuitem if `variant="menu"` or selected item * if `variant="selectedMenu"`. * @default false */ autoFocusItem: _propTypes.default.bool, /** * MenuList contents, normally `MenuItem`s. */ children: _propTypes.default.node, /** * @ignore */ className: _propTypes.default.string, /** * If `true`, will allow focus on disabled items. * @default false */ disabledItemsFocusable: _propTypes.default.bool, /** * If `true`, the menu items will not wrap focus. * @default false */ disableListWrap: _propTypes.default.bool, /** * @ignore */ onKeyDown: _propTypes.default.func, /** * The variant to use. Use `menu` to prevent selected items from impacting the initial focus * and the vertical alignment relative to the anchor element. * @default 'selectedMenu' */ variant: _propTypes.default.oneOf(['menu', 'selectedMenu']) } : void 0; var _default = MenuList; exports.default = _default;