UNPKG

@material-ui/core

Version:

React components that implement Google's Material Design.

289 lines (256 loc) 9.39 kB
import _extends from "@babel/runtime/helpers/esm/extends"; import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose"; import * as React from 'react'; import { isFragment } from 'react-is'; import PropTypes from 'prop-types'; import clsx from 'clsx'; import { HTMLElementType } from '@material-ui/utils'; import withStyles from '../styles/withStyles'; import Popover from '../Popover'; import MenuList from '../MenuList'; import * as ReactDOM from 'react-dom'; import setRef from '../utils/setRef'; import useTheme from '../styles/useTheme'; import deprecatedPropType from '../utils/deprecatedPropType'; const RTL_ORIGIN = { vertical: 'top', horizontal: 'right' }; const LTR_ORIGIN = { vertical: 'top', horizontal: 'left' }; export const styles = { /* Styles applied to the `Paper` component. */ paper: { // specZ: The maximum height of a simple menu should be one or more rows less than the view // height. This ensures a tapable area outside of the simple menu with which to dismiss // the menu. maxHeight: 'calc(100% - 96px)', // Add iOS momentum scrolling. WebkitOverflowScrolling: 'touch' }, /* Styles applied to the `List` component via `MenuList`. */ list: { // We disable the focus ring for mouse, touch and keyboard users. outline: 0 } }; const Menu = /*#__PURE__*/React.forwardRef(function Menu(props, ref) { const { autoFocus = true, children, classes, disableAutoFocusItem = false, MenuListProps = {}, onClose, onEntering: onEnteringProp, open, PaperProps = {}, PopoverClasses, transitionDuration = 'auto', TransitionProps: { onEntering } = {}, variant = 'selectedMenu' } = props, TransitionProps = _objectWithoutPropertiesLoose(props.TransitionProps, ["onEntering"]), other = _objectWithoutPropertiesLoose(props, ["autoFocus", "children", "classes", "disableAutoFocusItem", "MenuListProps", "onClose", "onEntering", "open", "PaperProps", "PopoverClasses", "transitionDuration", "TransitionProps", "variant"]); const theme = useTheme(); const autoFocusItem = autoFocus && !disableAutoFocusItem && open; const menuListActionsRef = React.useRef(null); const contentAnchorRef = React.useRef(null); const getContentAnchorEl = () => contentAnchorRef.current; const handleEntering = (element, isAppearing) => { if (menuListActionsRef.current) { menuListActionsRef.current.adjustStyleForScrollbar(element, theme); } if (onEnteringProp) { onEnteringProp(element, isAppearing); } if (onEntering) { onEntering(element, isAppearing); } }; const handleListKeyDown = event => { if (event.key === 'Tab') { event.preventDefault(); if (onClose) { onClose(event, 'tabKeyDown'); } } }; /** * 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.map(children, (child, index) => { if (! /*#__PURE__*/React.isValidElement(child)) { return; } if (process.env.NODE_ENV !== 'production') { if (isFragment(child)) { console.error(["Material-UI: The Menu component doesn't accept a Fragment as a child.", 'Consider providing an array instead.'].join('\n')); } } if (!child.props.disabled) { if (variant !== "menu" && child.props.selected) { activeItemIndex = index; } else if (activeItemIndex === -1) { activeItemIndex = index; } } }); const items = React.Children.map(children, (child, index) => { if (index === activeItemIndex) { return /*#__PURE__*/React.cloneElement(child, { ref: instance => { // #StrictMode ready contentAnchorRef.current = ReactDOM.findDOMNode(instance); setRef(child.ref, instance); } }); } return child; }); return /*#__PURE__*/React.createElement(Popover, _extends({ getContentAnchorEl: getContentAnchorEl, classes: PopoverClasses, onClose: onClose, TransitionProps: _extends({ onEntering: handleEntering }, TransitionProps), anchorOrigin: theme.direction === 'rtl' ? RTL_ORIGIN : LTR_ORIGIN, transformOrigin: theme.direction === 'rtl' ? RTL_ORIGIN : LTR_ORIGIN, PaperProps: _extends({}, PaperProps, { classes: _extends({}, PaperProps.classes, { root: classes.paper }) }), open: open, ref: ref, transitionDuration: transitionDuration }, other), /*#__PURE__*/React.createElement(MenuList, _extends({ onKeyDown: handleListKeyDown, actions: menuListActionsRef, autoFocus: autoFocus && (activeItemIndex === -1 || disableAutoFocusItem), autoFocusItem: autoFocusItem, variant: variant }, MenuListProps, { className: clsx(classes.list, MenuListProps.className) }), items)); }); process.env.NODE_ENV !== "production" ? Menu.propTypes = { // ----------------------------- Warning -------------------------------- // | These PropTypes are generated from the TypeScript type definitions | // | To update them edit the d.ts file and run "yarn proptypes" | // ---------------------------------------------------------------------- /** * A HTML element, or a function that returns it. * It's used to set the position of the menu. */ anchorEl: PropTypes /* @typescript-to-proptypes-ignore */ .oneOfType([HTMLElementType, PropTypes.func]), /** * If `true` (Default) will focus the `[role="menu"]` if no focusable child is found. Disabled * children are not focusable. If you set this prop to `false` focus will be placed * on the parent modal container. This has severe accessibility implications * and should only be considered if you manage focus otherwise. */ autoFocus: PropTypes.bool, /** * Menu contents, normally `MenuItem`s. */ children: PropTypes.node, /** * Override or extend the styles applied to the component. * See [CSS API](#css) below for more details. */ classes: PropTypes.object, /** * When opening the menu will not focus the active item but the `[role="menu"]` * unless `autoFocus` is also set to `false`. Not using the default means not * following WAI-ARIA authoring practices. Please be considerate about possible * accessibility implications. */ disableAutoFocusItem: PropTypes.bool, /** * Props applied to the [`MenuList`](/api/menu-list/) element. */ MenuListProps: PropTypes.object, /** * Callback fired when the component requests to be closed. * * @param {object} event The event source of the callback. * @param {string} reason Can be: `"escapeKeyDown"`, `"backdropClick"`, `"tabKeyDown"`. */ onClose: PropTypes.func, /** * Callback fired before the Menu enters. * @deprecated Use the `TransitionProps` prop instead. */ onEnter: deprecatedPropType(PropTypes.func, 'Use the `TransitionProps` prop instead.'), /** * Callback fired when the Menu has entered. * @deprecated Use the `TransitionProps` prop instead. */ onEntered: deprecatedPropType(PropTypes.func, 'Use the `TransitionProps` prop instead.'), /** * Callback fired when the Menu is entering. * @deprecated Use the `TransitionProps` prop instead. */ onEntering: deprecatedPropType(PropTypes.func, 'Use the `TransitionProps` prop instead.'), /** * Callback fired before the Menu exits. * @deprecated Use the `TransitionProps` prop instead. */ onExit: deprecatedPropType(PropTypes.func, 'Use the `TransitionProps` prop instead.'), /** * Callback fired when the Menu has exited. * @deprecated Use the `TransitionProps` prop instead. */ onExited: deprecatedPropType(PropTypes.func, 'Use the `TransitionProps` prop instead.'), /** * Callback fired when the Menu is exiting. * @deprecated Use the `TransitionProps` prop instead. */ onExiting: deprecatedPropType(PropTypes.func, 'Use the `TransitionProps` prop instead.'), /** * If `true`, the menu is visible. */ open: PropTypes.bool.isRequired, /** * @ignore */ PaperProps: PropTypes.object, /** * `classes` prop applied to the [`Popover`](/api/popover/) element. */ PopoverClasses: PropTypes.object, /** * The length of the transition in `ms`, or 'auto' */ transitionDuration: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number, PropTypes.shape({ appear: PropTypes.number, enter: PropTypes.number, exit: PropTypes.number })]), /** * Props applied to the transition element. * By default, the element is based on this [`Transition`](http://reactcommunity.org/react-transition-group/transition) component. */ TransitionProps: PropTypes.object, /** * The variant to use. Use `menu` to prevent selected items from impacting the initial focus * and the vertical alignment relative to the anchor element. */ variant: PropTypes.oneOf(['menu', 'selectedMenu']) } : void 0; export default withStyles(styles, { name: 'MuiMenu' })(Menu);