292 lines
12 KiB
JavaScript
292 lines
12 KiB
JavaScript
|
"use strict";
|
||
|
'use client';
|
||
|
|
||
|
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(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (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 && Object.prototype.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; }
|
||
|
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/WAI/ARIA/apg/patterns/menu-button/.
|
||
|
* It's exposed to help customization of the [`Menu`](/material-ui/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, {
|
||
|
direction
|
||
|
}) => {
|
||
|
// 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[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)) {
|
||
|
if (activeItemIndex === index) {
|
||
|
activeItemIndex += 1;
|
||
|
if (activeItemIndex >= children.length) {
|
||
|
// there are no focusable items within the list.
|
||
|
activeItemIndex = -1;
|
||
|
}
|
||
|
}
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
if (activeItemIndex === index && (child.props.disabled || child.props.muiSkipListHighlight || child.type.muiSkipListHighlight)) {
|
||
|
activeItemIndex += 1;
|
||
|
if (activeItemIndex >= children.length) {
|
||
|
// there are no focusable items within the list.
|
||
|
activeItemIndex = -1;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
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 `pnpm 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 = exports.default = MenuList;
|