grommet
Version:
focus on the essential experience
178 lines (175 loc) • 8.11 kB
JavaScript
;
exports.__esModule = true;
exports.FocusedContainer = void 0;
var _react = _interopRequireWildcard(require("react"));
var _utils = require("../utils");
var _RootsContext = require("../contexts/RootsContext");
var _excluded = ["hidden", "restrictScroll", "children", "trapFocus"];
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
var isFocusable = function isFocusable(element) {
if ((element == null ? void 0 : element.tabIndex) < 0 || element != null && element.disabled) {
return false;
}
switch (element == null ? void 0 : element.nodeName) {
case 'A':
return !!element.href && element.rel !== 'ignore';
case 'INPUT':
return element.type !== 'hidden';
case 'BUTTON':
case 'SELECT':
case 'TEXTAREA':
return true;
case 'DIV':
return element.tabIndex >= 0;
default:
return false;
}
};
var FocusedContainer = exports.FocusedContainer = function FocusedContainer(_ref) {
var _ref$hidden = _ref.hidden,
hidden = _ref$hidden === void 0 ? false : _ref$hidden,
_ref$restrictScroll = _ref.restrictScroll,
restrictScroll = _ref$restrictScroll === void 0 ? false : _ref$restrictScroll,
children = _ref.children,
trapFocus = _ref.trapFocus,
rest = _objectWithoutPropertiesLoose(_ref, _excluded);
var _useState = (0, _react.useState)(''),
bodyOverflowStyle = _useState[0],
setBodyOverflowStyle = _useState[1];
var ref = (0, _react.useRef)(null);
var preNodeRef = (0, _react.useRef)(null);
var postNodeRef = (0, _react.useRef)(null);
var _useRoots = (0, _RootsContext.useRoots)(),
contextValue = _useRoots.contextValue,
hasRoots = _useRoots.hasRoots;
var contextRoots = contextValue.roots;
// When attemptFocus moves focus around to find the focusable element,
// set this to true so handleTrapFocus doesn't focus the element.
var ignoreUtilFocusRef = (0, _react.useRef)(false);
var lastFocusRef = (0, _react.useRef)(null);
(0, _react.useEffect)(function () {
var container = ref.current;
var roots = contextRoots.current;
var attemptFocus = function attemptFocus(element) {
// Check if the element is focusable; if not, return false
if (!isFocusable(element)) {
return false;
}
ignoreUtilFocusRef.current = true;
try {
element.focus();
} catch (_unused) {
// continue regardless of error
}
ignoreUtilFocusRef.current = false;
// Return true if the element is currently the active element or has focus
return document.activeElement === element;
};
var _focusFirstDescendant = function focusFirstDescendant(element) {
// Iterate through all child nodes of the provided element
for (var i = 0; i < element.childNodes.length; i += 1) {
var child = element.childNodes[i];
if (attemptFocus(child) || _focusFirstDescendant(child)) return true;
}
// If no focusable child or descendant was found, return false
return false;
};
var _focusLastDescendant = function focusLastDescendant(element) {
for (var i = element.childNodes.length - 1; i >= 0; i -= 1) {
var child = element.childNodes[i];
if (attemptFocus(child) || _focusLastDescendant(child)) return true;
}
return false;
};
var handleTrapFocus = function handleTrapFocus(e) {
if (!hidden && trapFocus && ignoreUtilFocusRef.current === false && container &&
// only perform focus if this is the most recently opened drop
roots[roots.length - 1] === container) {
if (container.contains(e.target)) {
lastFocusRef.current = e.target;
} else {
_focusFirstDescendant(container);
if (lastFocusRef.current === document.activeElement) {
_focusLastDescendant(container);
}
lastFocusRef.current = document.activeElement;
}
}
};
// add container to the global roots
if (container) roots.push(container);
// Create and insert focusable nodes to help track when focus
// has left this container but without letting focus be noticeably
// placed on anything outside the container
if (!hidden && trapFocus) {
var preDiv = document.createElement('div');
var postDiv = document.createElement('div');
var commonStyles = {
position: 'absolute',
height: '1px',
left: '0',
right: '0'
};
Object.assign(preDiv.style, _extends({}, commonStyles, {
top: '0'
}));
Object.assign(postDiv.style, _extends({}, commonStyles, {
bottom: '0'
}));
preNodeRef.current = container.parentNode.insertBefore(preDiv, container);
postNodeRef.current = container.parentNode.insertBefore(postDiv, container.nextSibling);
preNodeRef.current.tabIndex = 0;
postNodeRef.current.tabIndex = 0;
}
document.addEventListener('focus', handleTrapFocus, true);
return function () {
var _preNodeRef$current, _postNodeRef$current;
// remove from global roots array
if (roots.includes(container)) roots.splice(roots.indexOf(container), 1);
document.removeEventListener('focus', handleTrapFocus, true);
if (roots != null && roots[roots.length - 1]) (0, _utils.makeNodeFocusable)(roots[roots.length - 1]);
preNodeRef == null || (_preNodeRef$current = preNodeRef.current) == null || _preNodeRef$current.remove();
postNodeRef == null || (_postNodeRef$current = postNodeRef.current) == null || _postNodeRef$current.remove();
};
}, [hidden, contextRoots, trapFocus]);
(0, _react.useEffect)(function () {
if (bodyOverflowStyle !== 'hidden' && !hidden && restrictScroll && trapFocus) {
setBodyOverflowStyle(document.body.style.overflow);
document.body.style.overflow = 'hidden';
}
return function () {
if (bodyOverflowStyle !== 'hidden' && !hidden && restrictScroll && trapFocus) {
document.body.style.overflow = bodyOverflowStyle;
}
};
}, [bodyOverflowStyle, hidden, trapFocus, restrictScroll]);
(0, _react.useEffect)(function () {
var roots = contextRoots.current;
var timer = setTimeout(function () {
if (!hidden && trapFocus) {
// make every root before this one unfocusable
roots == null || roots.forEach(function (root, index) {
if (index < roots.length - 1) (0, _utils.makeNodeUnfocusable)(root);
});
}
}, 0);
return function () {
return clearTimeout(timer);
};
}, [hidden, contextRoots, trapFocus]);
var focusedContainer = /*#__PURE__*/_react["default"].createElement("div", _extends({
ref: ref,
"aria-hidden": hidden
}, rest), children);
if (hasRoots) return focusedContainer;
return (
/*#__PURE__*/
// for cases outside of Grommet React tree, manage trapFocus when
// Drop/Layer opens another Drop/Layer
_react["default"].createElement(_RootsContext.RootsContext.Provider, {
value: contextValue
}, focusedContainer)
);
};