@material-ui/core
Version:
React components that implement Google's Material Design.
195 lines (153 loc) • 6.58 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = _interopRequireDefault(require("react"));
var _reactDom = _interopRequireDefault(require("react-dom"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _warning = _interopRequireDefault(require("warning"));
var _ownerDocument = _interopRequireDefault(require("../utils/ownerDocument"));
var _reactHelpers = require("../utils/reactHelpers");
/* eslint-disable consistent-return, jsx-a11y/no-noninteractive-tabindex */
function TrapFocus(props) {
var children = props.children,
_props$disableAutoFoc = props.disableAutoFocus,
disableAutoFocus = _props$disableAutoFoc === void 0 ? false : _props$disableAutoFoc,
_props$disableEnforce = props.disableEnforceFocus,
disableEnforceFocus = _props$disableEnforce === void 0 ? false : _props$disableEnforce,
_props$disableRestore = props.disableRestoreFocus,
disableRestoreFocus = _props$disableRestore === void 0 ? false : _props$disableRestore,
getDoc = props.getDoc,
isEnabled = props.isEnabled,
open = props.open;
var ignoreNextEnforceFocus = _react.default.useRef();
var sentinelStart = _react.default.useRef(null);
var sentinelEnd = _react.default.useRef(null);
var lastFocus = _react.default.useRef();
var rootRef = _react.default.useRef(null); // can be removed once we drop support for non ref forwarding class components
var handleOwnRef = _react.default.useCallback(function (instance) {
// #StrictMode ready
rootRef.current = _reactDom.default.findDOMNode(instance);
}, []);
var handleRef = (0, _reactHelpers.useForkRef)(children.ref, handleOwnRef); // ⚠️ You may rely on React.useMemo as a performance optimization, not as a semantic guarantee.
// https://reactjs.org/docs/hooks-reference.html#usememo
_react.default.useMemo(function () {
if (!open) {
return;
}
lastFocus.current = getDoc().activeElement;
}, [open]); // eslint-disable-line react-hooks/exhaustive-deps
_react.default.useEffect(function () {
if (!open) {
return;
}
var doc = (0, _ownerDocument.default)(rootRef.current); // We might render an empty child.
if (!disableAutoFocus && rootRef.current && !rootRef.current.contains(doc.activeElement)) {
if (!rootRef.current.hasAttribute('tabIndex')) {
process.env.NODE_ENV !== "production" ? (0, _warning.default)(false, ['Material-UI: the modal content node does not accept focus.', 'For the benefit of assistive technologies, ' + 'the tabIndex of the node is being set to "-1".'].join('\n')) : void 0;
rootRef.current.setAttribute('tabIndex', -1);
}
rootRef.current.focus();
}
var enforceFocus = function enforceFocus() {
if (disableEnforceFocus || !isEnabled() || ignoreNextEnforceFocus.current) {
ignoreNextEnforceFocus.current = false;
return;
}
if (rootRef.current && !rootRef.current.contains(doc.activeElement)) {
rootRef.current.focus();
}
};
var loopFocus = function loopFocus(event) {
// 9 = Tab
if (disableEnforceFocus || !isEnabled() || event.keyCode !== 9) {
return;
} // Make sure the next tab starts from the right place.
if (doc.activeElement === rootRef.current) {
// We need to ignore the next enforceFocus as
// it will try to move the focus back to the rootRef element.
ignoreNextEnforceFocus.current = true;
if (event.shiftKey) {
sentinelEnd.current.focus();
} else {
sentinelStart.current.focus();
}
}
};
doc.addEventListener('focus', enforceFocus, true);
doc.addEventListener('keydown', loopFocus, true);
return function () {
doc.removeEventListener('focus', enforceFocus, true);
doc.removeEventListener('keydown', loopFocus, true); // restoreLastFocus()
if (!disableRestoreFocus) {
// In IE 11 it is possible for document.activeElement to be null resulting
// in lastFocus.current being null.
// Not all elements in IE 11 have a focus method.
// Once IE 11 support is dropped the focus() call can be unconditional.
if (lastFocus.current && lastFocus.current.focus) {
lastFocus.current.focus();
}
lastFocus.current = null;
}
};
}, [disableAutoFocus, disableEnforceFocus, disableRestoreFocus, isEnabled, open]);
return _react.default.createElement(_react.default.Fragment, null, _react.default.createElement("div", {
tabIndex: 0,
ref: sentinelStart,
"data-test": "sentinelStart"
}), _react.default.cloneElement(children, {
ref: handleRef
}), _react.default.createElement("div", {
tabIndex: 0,
ref: sentinelEnd,
"data-test": "sentinelEnd"
}));
}
/**
* @ignore - internal component.
*/
process.env.NODE_ENV !== "production" ? TrapFocus.propTypes = {
/**
* A single child content element.
*/
children: _propTypes.default.element.isRequired,
/**
* If `true`, the modal will not automatically shift focus to itself when it opens, and
* replace it to the last focused element when it closes.
* This also works correctly with any modal children that have the `disableAutoFocus` prop.
*
* Generally this should never be set to `true` as it makes the modal less
* accessible to assistive technologies, like screen readers.
*/
disableAutoFocus: _propTypes.default.bool,
/**
* If `true`, the modal will not prevent focus from leaving the modal while open.
*
* Generally this should never be set to `true` as it makes the modal less
* accessible to assistive technologies, like screen readers.
*/
disableEnforceFocus: _propTypes.default.bool,
/**
* If `true`, the modal will not restore focus to previously focused element once
* modal is hidden.
*/
disableRestoreFocus: _propTypes.default.bool,
/**
* Return the document to consider.
* We use it to implement the restore focus between different browser documents.
*/
getDoc: _propTypes.default.func.isRequired,
/**
* Do we still want to enforce the focus?
* This property helps nesting TrapFocus elements.
*/
isEnabled: _propTypes.default.func.isRequired,
/**
* If `true`, the modal is open.
*/
open: _propTypes.default.bool.isRequired
} : void 0;
var _default = TrapFocus;
exports.default = _default;