UNPKG

9.39 kBJavaScriptView Raw
1import _extends from "@babel/runtime/helpers/esm/extends";
2import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
3import * as React from 'react';
4import { isFragment } from 'react-is';
5import PropTypes from 'prop-types';
6import clsx from 'clsx';
7import { HTMLElementType } from '@material-ui/utils';
8import withStyles from '../styles/withStyles';
9import Popover from '../Popover';
10import MenuList from '../MenuList';
11import * as ReactDOM from 'react-dom';
12import setRef from '../utils/setRef';
13import useTheme from '../styles/useTheme';
14import deprecatedPropType from '../utils/deprecatedPropType';
15const RTL_ORIGIN = {
16 vertical: 'top',
17 horizontal: 'right'
18};
19const LTR_ORIGIN = {
20 vertical: 'top',
21 horizontal: 'left'
22};
23export const styles = {
24 /* Styles applied to the `Paper` component. */
25 paper: {
26 // specZ: The maximum height of a simple menu should be one or more rows less than the view
27 // height. This ensures a tapable area outside of the simple menu with which to dismiss
28 // the menu.
29 maxHeight: 'calc(100% - 96px)',
30 // Add iOS momentum scrolling.
31 WebkitOverflowScrolling: 'touch'
32 },
33
34 /* Styles applied to the `List` component via `MenuList`. */
35 list: {
36 // We disable the focus ring for mouse, touch and keyboard users.
37 outline: 0
38 }
39};
40const Menu = /*#__PURE__*/React.forwardRef(function Menu(props, ref) {
41 const {
42 autoFocus = true,
43 children,
44 classes,
45 disableAutoFocusItem = false,
46 MenuListProps = {},
47 onClose,
48 onEntering: onEnteringProp,
49 open,
50 PaperProps = {},
51 PopoverClasses,
52 transitionDuration = 'auto',
53 TransitionProps: {
54 onEntering
55 } = {},
56 variant = 'selectedMenu'
57 } = props,
58 TransitionProps = _objectWithoutPropertiesLoose(props.TransitionProps, ["onEntering"]),
59 other = _objectWithoutPropertiesLoose(props, ["autoFocus", "children", "classes", "disableAutoFocusItem", "MenuListProps", "onClose", "onEntering", "open", "PaperProps", "PopoverClasses", "transitionDuration", "TransitionProps", "variant"]);
60
61 const theme = useTheme();
62 const autoFocusItem = autoFocus && !disableAutoFocusItem && open;
63 const menuListActionsRef = React.useRef(null);
64 const contentAnchorRef = React.useRef(null);
65
66 const getContentAnchorEl = () => contentAnchorRef.current;
67
68 const handleEntering = (element, isAppearing) => {
69 if (menuListActionsRef.current) {
70 menuListActionsRef.current.adjustStyleForScrollbar(element, theme);
71 }
72
73 if (onEnteringProp) {
74 onEnteringProp(element, isAppearing);
75 }
76
77 if (onEntering) {
78 onEntering(element, isAppearing);
79 }
80 };
81
82 const handleListKeyDown = event => {
83 if (event.key === 'Tab') {
84 event.preventDefault();
85
86 if (onClose) {
87 onClose(event, 'tabKeyDown');
88 }
89 }
90 };
91 /**
92 * the index of the item should receive focus
93 * in a `variant="selectedMenu"` it's the first `selected` item
94 * otherwise it's the very first item.
95 */
96
97
98 let activeItemIndex = -1; // since we inject focus related props into children we have to do a lookahead
99 // to check if there is a `selected` item. We're looking for the last `selected`
100 // item and use the first valid item as a fallback
101
102 React.Children.map(children, (child, index) => {
103 if (! /*#__PURE__*/React.isValidElement(child)) {
104 return;
105 }
106
107 if (process.env.NODE_ENV !== 'production') {
108 if (isFragment(child)) {
109 console.error(["Material-UI: The Menu component doesn't accept a Fragment as a child.", 'Consider providing an array instead.'].join('\n'));
110 }
111 }
112
113 if (!child.props.disabled) {
114 if (variant !== "menu" && child.props.selected) {
115 activeItemIndex = index;
116 } else if (activeItemIndex === -1) {
117 activeItemIndex = index;
118 }
119 }
120 });
121 const items = React.Children.map(children, (child, index) => {
122 if (index === activeItemIndex) {
123 return /*#__PURE__*/React.cloneElement(child, {
124 ref: instance => {
125 // #StrictMode ready
126 contentAnchorRef.current = ReactDOM.findDOMNode(instance);
127 setRef(child.ref, instance);
128 }
129 });
130 }
131
132 return child;
133 });
134 return /*#__PURE__*/React.createElement(Popover, _extends({
135 getContentAnchorEl: getContentAnchorEl,
136 classes: PopoverClasses,
137 onClose: onClose,
138 TransitionProps: _extends({
139 onEntering: handleEntering
140 }, TransitionProps),
141 anchorOrigin: theme.direction === 'rtl' ? RTL_ORIGIN : LTR_ORIGIN,
142 transformOrigin: theme.direction === 'rtl' ? RTL_ORIGIN : LTR_ORIGIN,
143 PaperProps: _extends({}, PaperProps, {
144 classes: _extends({}, PaperProps.classes, {
145 root: classes.paper
146 })
147 }),
148 open: open,
149 ref: ref,
150 transitionDuration: transitionDuration
151 }, other), /*#__PURE__*/React.createElement(MenuList, _extends({
152 onKeyDown: handleListKeyDown,
153 actions: menuListActionsRef,
154 autoFocus: autoFocus && (activeItemIndex === -1 || disableAutoFocusItem),
155 autoFocusItem: autoFocusItem,
156 variant: variant
157 }, MenuListProps, {
158 className: clsx(classes.list, MenuListProps.className)
159 }), items));
160});
161process.env.NODE_ENV !== "production" ? Menu.propTypes = {
162 // ----------------------------- Warning --------------------------------
163 // | These PropTypes are generated from the TypeScript type definitions |
164 // | To update them edit the d.ts file and run "yarn proptypes" |
165 // ----------------------------------------------------------------------
166
167 /**
168 * A HTML element, or a function that returns it.
169 * It's used to set the position of the menu.
170 */
171 anchorEl: PropTypes
172 /* @typescript-to-proptypes-ignore */
173 .oneOfType([HTMLElementType, PropTypes.func]),
174
175 /**
176 * If `true` (Default) will focus the `[role="menu"]` if no focusable child is found. Disabled
177 * children are not focusable. If you set this prop to `false` focus will be placed
178 * on the parent modal container. This has severe accessibility implications
179 * and should only be considered if you manage focus otherwise.
180 */
181 autoFocus: PropTypes.bool,
182
183 /**
184 * Menu contents, normally `MenuItem`s.
185 */
186 children: PropTypes.node,
187
188 /**
189 * Override or extend the styles applied to the component.
190 * See [CSS API](#css) below for more details.
191 */
192 classes: PropTypes.object,
193
194 /**
195 * When opening the menu will not focus the active item but the `[role="menu"]`
196 * unless `autoFocus` is also set to `false`. Not using the default means not
197 * following WAI-ARIA authoring practices. Please be considerate about possible
198 * accessibility implications.
199 */
200 disableAutoFocusItem: PropTypes.bool,
201
202 /**
203 * Props applied to the [`MenuList`](/api/menu-list/) element.
204 */
205 MenuListProps: PropTypes.object,
206
207 /**
208 * Callback fired when the component requests to be closed.
209 *
210 * @param {object} event The event source of the callback.
211 * @param {string} reason Can be: `"escapeKeyDown"`, `"backdropClick"`, `"tabKeyDown"`.
212 */
213 onClose: PropTypes.func,
214
215 /**
216 * Callback fired before the Menu enters.
217 * @deprecated Use the `TransitionProps` prop instead.
218 */
219 onEnter: deprecatedPropType(PropTypes.func, 'Use the `TransitionProps` prop instead.'),
220
221 /**
222 * Callback fired when the Menu has entered.
223 * @deprecated Use the `TransitionProps` prop instead.
224 */
225 onEntered: deprecatedPropType(PropTypes.func, 'Use the `TransitionProps` prop instead.'),
226
227 /**
228 * Callback fired when the Menu is entering.
229 * @deprecated Use the `TransitionProps` prop instead.
230 */
231 onEntering: deprecatedPropType(PropTypes.func, 'Use the `TransitionProps` prop instead.'),
232
233 /**
234 * Callback fired before the Menu exits.
235 * @deprecated Use the `TransitionProps` prop instead.
236 */
237 onExit: deprecatedPropType(PropTypes.func, 'Use the `TransitionProps` prop instead.'),
238
239 /**
240 * Callback fired when the Menu has exited.
241 * @deprecated Use the `TransitionProps` prop instead.
242 */
243 onExited: deprecatedPropType(PropTypes.func, 'Use the `TransitionProps` prop instead.'),
244
245 /**
246 * Callback fired when the Menu is exiting.
247 * @deprecated Use the `TransitionProps` prop instead.
248 */
249 onExiting: deprecatedPropType(PropTypes.func, 'Use the `TransitionProps` prop instead.'),
250
251 /**
252 * If `true`, the menu is visible.
253 */
254 open: PropTypes.bool.isRequired,
255
256 /**
257 * @ignore
258 */
259 PaperProps: PropTypes.object,
260
261 /**
262 * `classes` prop applied to the [`Popover`](/api/popover/) element.
263 */
264 PopoverClasses: PropTypes.object,
265
266 /**
267 * The length of the transition in `ms`, or 'auto'
268 */
269 transitionDuration: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number, PropTypes.shape({
270 appear: PropTypes.number,
271 enter: PropTypes.number,
272 exit: PropTypes.number
273 })]),
274
275 /**
276 * Props applied to the transition element.
277 * By default, the element is based on this [`Transition`](http://reactcommunity.org/react-transition-group/transition) component.
278 */
279 TransitionProps: PropTypes.object,
280
281 /**
282 * The variant to use. Use `menu` to prevent selected items from impacting the initial focus
283 * and the vertical alignment relative to the anchor element.
284 */
285 variant: PropTypes.oneOf(['menu', 'selectedMenu'])
286} : void 0;
287export default withStyles(styles, {
288 name: 'MuiMenu'
289})(Menu);
\No newline at end of file