@helpscout/hsds-react
Version:
React component library for Help Scout's Design System
121 lines (94 loc) • 4.95 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.getClosestFocusableParent = getClosestFocusableParent;
exports.manageTrappedFocus = manageTrappedFocus;
exports.focusPreviousFocusableNode = exports.focusNextFocusableNode = exports.findPreviousFocusableNode = exports.findNextFocusableNode = exports.findCurrentFocusedNodeIndex = exports.findLastFocusableNode = exports.findFirstFocusableNode = exports.findFocusableNodes = exports.FOCUSABLE_SELECTOR = void 0;
var _lodash = _interopRequireDefault(require("lodash.isnil"));
var _node = require("./node");
var FOCUSABLE_SELECTOR = 'a[href],frame,iframe,input:not([type=hidden]):not([disabled]),select,textarea,button:not([disabled]):not([tabindex="-1"]),*[tabindex]:not([tabindex="-1"])';
exports.FOCUSABLE_SELECTOR = FOCUSABLE_SELECTOR;
var findFocusableNodes = function findFocusableNodes(nodeScope) {
var scope = (0, _node.getNodeScope)(nodeScope);
return scope.querySelectorAll(FOCUSABLE_SELECTOR);
};
exports.findFocusableNodes = findFocusableNodes;
var findFirstFocusableNode = function findFirstFocusableNode(nodeScope) {
var focusableNodes = findFocusableNodes(nodeScope);
return focusableNodes[0];
};
exports.findFirstFocusableNode = findFirstFocusableNode;
var findLastFocusableNode = function findLastFocusableNode(nodeScope) {
var focusableNodes = findFocusableNodes(nodeScope);
return focusableNodes[focusableNodes.length - 1];
};
exports.findLastFocusableNode = findLastFocusableNode;
var findCurrentFocusedNodeIndex = function findCurrentFocusedNodeIndex(currentNode, nodeScope) {
if (!(0, _node.isNodeElement)(currentNode)) return;
var focusableNodes = findFocusableNodes(nodeScope);
var currentNodeIndex = Array.prototype.indexOf.call(focusableNodes, currentNode);
return currentNodeIndex !== -1 ? currentNodeIndex : false;
};
exports.findCurrentFocusedNodeIndex = findCurrentFocusedNodeIndex;
var findNextFocusableNode = function findNextFocusableNode(currentNode, nodeScope) {
if (!(0, _node.isNodeElement)(currentNode)) return;
var scope = (0, _node.getNodeScope)(nodeScope);
var focusableNodes = findFocusableNodes(scope);
var currentNodeIndex = findCurrentFocusedNodeIndex(currentNode, scope);
var nextNode = currentNodeIndex + 1 <= focusableNodes.length - 1 ? focusableNodes[currentNodeIndex + 1] : scope;
return nextNode;
};
exports.findNextFocusableNode = findNextFocusableNode;
var findPreviousFocusableNode = function findPreviousFocusableNode(currentNode, nodeScope) {
if (!(0, _node.isNodeElement)(currentNode)) return;
var scope = (0, _node.getNodeScope)(nodeScope);
var focusableNodes = findFocusableNodes(scope);
var currentNodeIndex = findCurrentFocusedNodeIndex(currentNode, scope);
var prevNode = currentNodeIndex - 1 >= 0 ? focusableNodes[currentNodeIndex - 1] : scope;
return prevNode;
};
exports.findPreviousFocusableNode = findPreviousFocusableNode;
var focusNextFocusableNode = function focusNextFocusableNode(currentNode, nodeScope) {
var scope = (0, _node.getNodeScope)(nodeScope);
var node = findNextFocusableNode(currentNode, scope);
node && node !== document ? node.focus() : findFirstFocusableNode(nodeScope).focus();
return node;
};
exports.focusNextFocusableNode = focusNextFocusableNode;
var focusPreviousFocusableNode = function focusPreviousFocusableNode(currentNode, nodeScope) {
var scope = (0, _node.getNodeScope)(nodeScope);
var node = findPreviousFocusableNode(currentNode, scope);
node && node !== document ? node.focus() : findLastFocusableNode(nodeScope).focus();
return node;
};
/**
* Method that will find the closest parent that can is focusable
* @param {HTMLElement} element The element that we are traversing up to find a focusable parent
* @returns HTMLElement
*/
exports.focusPreviousFocusableNode = focusPreviousFocusableNode;
function getClosestFocusableParent(element) {
return element.closest(FOCUSABLE_SELECTOR) || document.body;
}
/**
* Method that will ensure tabbing will cycle between focusable nodes inside a given container
* @param {HTMLElement} container The Element where the focus should be trapped in
* @param {Event} e The tab keyboard event
*/
function manageTrappedFocus(container, e) {
var focusedNode = e.target;
var isContainerFocused = focusedNode === container;
var focusableNodes = findFocusableNodes(container);
if (!(0, _lodash.default)(focusableNodes)) {
var focusedNodeIndex = Array.prototype.indexOf.call(focusableNodes, focusedNode);
var isFirstNode = focusedNodeIndex === 0;
var isLastNode = focusedNodeIndex === focusableNodes.length - 1;
if (!e.shiftKey && isLastNode) {
e.preventDefault();
focusableNodes[0].focus();
} else if (e.shiftKey && (isFirstNode || isContainerFocused)) {
e.preventDefault();
focusableNodes[focusableNodes.length - 1].focus();
}
}
}