UNPKG

@helpscout/hsds-react

Version:

React component library for Help Scout's Design System

121 lines (94 loc) 4.95 kB
"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(); } } }