UNPKG

grommet

Version:

focus on the essential experience

207 lines (203 loc) 8.4 kB
"use strict"; exports.__esModule = true; exports.withinDropPortal = exports.shouldKeepFocus = exports.setFocusWithoutScroll = exports.makeNodeUnfocusable = exports.makeNodeFocusable = exports.isNodeBeforeScroll = exports.isNodeAfterScroll = exports.isFocusable = exports.getNewContainer = exports.getFirstFocusableDescendant = exports.findVisibleParent = exports.findScrollParents = exports.findScrollParent = exports.findButtonParent = exports.containsFocus = void 0; var findScrollParent = exports.findScrollParent = function findScrollParent(element, horizontal) { var result; if (element) { var parent = element.parentNode; while (!result && parent && parent.getBoundingClientRect) { var rect = parent.getBoundingClientRect(); // 10px is to account for borders and scrollbars in a lazy way if (horizontal) { if (rect.width && parent.scrollWidth > rect.width + 10) { result = parent; } } else if (rect.height && parent.scrollHeight > rect.height + 10) { result = parent; } parent = parent.parentNode; } // last scrollable element will be the document // if nothing else is scrollable in the page if (!result) { result = document; } else if (result.tagName.toLowerCase() === 'body') { result = document; } } return result; }; var documentTags = ['html', 'body']; var findScrollParents = exports.findScrollParents = function findScrollParents(element, horizontal) { var result = []; if (element) { var parent = element.parentNode; while (parent && parent.getBoundingClientRect) { var rect = parent.getBoundingClientRect(); // 10px is to account for borders and scrollbars in a lazy way if (horizontal) { if (rect.width && parent.scrollWidth > rect.width + 10) { result.push(parent); } } else if (rect.height && parent.scrollHeight > rect.height + 10) { result.push(parent); } parent = parent.parentNode; } if (result.length && documentTags.includes(result[0].tagName.toLowerCase())) { result.length = 0; } // last scrollable element will be the document result.push(document); } return result; }; var containsFocus = exports.containsFocus = function containsFocus(node) { var root = node.getRootNode(); var element = root.activeElement; while (element) { if (element === node) break; element = element.parentElement; } return !!element; }; var withinDropPortal = exports.withinDropPortal = function withinDropPortal(node, portalContext) { var root = node == null ? void 0 : node.getRootNode(); var element = node; var portalId; while (element && element !== root) { if (element.hasAttribute('data-g-portal-id')) { portalId = element.getAttribute('data-g-portal-id'); element = root; } else { element = element.parentElement; } } // if portalContext doesn't contain the portalId then the // portal is new and node is within a drop that just opened if (portalId === undefined || portalContext.indexOf(parseInt(portalId, 10)) !== -1) return false; return true; }; // Check if the element.tagName is an input, select or textarea var isFocusable = exports.isFocusable = function isFocusable(element) { var tagName = element.tagName.toLowerCase(); return tagName === 'input' || tagName === 'select' || tagName === 'textarea'; }; // Get the first element that can receive focus var getFirstFocusableDescendant = exports.getFirstFocusableDescendant = function getFirstFocusableDescendant(element) { var children = element.getElementsByTagName('*'); for (var i = 0; i < children.length; i += 1) { var child = children[i]; if (isFocusable(child)) { return child; } } return undefined; }; var shouldKeepFocus = exports.shouldKeepFocus = function shouldKeepFocus(root) { var element = root.activeElement; if (isFocusable(element)) return true; return !!getFirstFocusableDescendant(element); }; var getNewContainer = exports.getNewContainer = function getNewContainer(target, targetChildPosition) { if (target === void 0) { target = document.body; } // setup DOM var container = document.createElement('div'); if (targetChildPosition === 'first') { // for SkipLinks target.prepend(container); } else { target.appendChild(container); } return container; }; var setFocusWithoutScroll = exports.setFocusWithoutScroll = function setFocusWithoutScroll(element) { var x = window.scrollX; var y = window.scrollY; element.focus(); window.scrollTo(x, y); }; var TABINDEX = 'tabindex'; var TABINDEX_STATE = 'data-g-tabindex'; var makeNodeFocusable = exports.makeNodeFocusable = function makeNodeFocusable(node) { // do not touch aria live containers so that announcements work if (!node.hasAttribute('aria-live')) { node.removeAttribute('aria-hidden'); // allow children to receive focus again var elements = node.getElementsByTagName('*'); // only reset elements we've changed in makeNodeUnfocusable() Array.prototype.filter.call(elements || [], function (element) { return element.hasAttribute(TABINDEX_STATE); }).forEach(function (element) { var prior = element.getAttribute(TABINDEX_STATE); if (prior >= 0) { element.setAttribute(TABINDEX, element.getAttribute(TABINDEX_STATE)); } else if (prior === 'none') { element.removeAttribute(TABINDEX); } element.removeAttribute(TABINDEX_STATE); }); } }; // Using ^ and $ to match the whole tagName, and not e.g. <meta> and <data>. var autoFocusingTags = /^(a|area|input|select|textarea|button|iframe)$/; var makeNodeUnfocusable = exports.makeNodeUnfocusable = function makeNodeUnfocusable(node) { // do not touch aria live containers so that announcements work if (!node.hasAttribute('aria-live')) { node.setAttribute('aria-hidden', true); // prevent children to receive focus var elements = node.getElementsByTagName('*'); // first, save off the tabIndex of any element with one Array.prototype.filter.call(elements || [], function (element) { return element.getAttribute(TABINDEX) !== null; }).forEach(function (element) { // if TABINDEX_STATE already exists, it's holding the original value if (!element.getAttribute(TABINDEX_STATE)) element.setAttribute(TABINDEX_STATE, element.getAttribute(TABINDEX)); element.setAttribute(TABINDEX, -1); }); // then, if any element is inherently focusable and not handled above, // give it a tabIndex of -1 so it can't receive focus Array.prototype.filter.call(elements || [], function (element) { var currentTag = element.tagName.toLowerCase(); return currentTag.match(autoFocusingTags) && element.focus && element.getAttribute(TABINDEX_STATE) === null; }).forEach(function (element) { element.setAttribute(TABINDEX_STATE, 'none'); element.setAttribute(TABINDEX, -1); }); } }; var _findVisibleParent = exports.findVisibleParent = function findVisibleParent(element) { if (element) { // Get the closest ancestor element that is positioned. return element.offsetParent ? element : _findVisibleParent(element.parentElement) || element; } return undefined; }; var isNodeAfterScroll = exports.isNodeAfterScroll = function isNodeAfterScroll(node, target) { var _node$getBoundingClie = node.getBoundingClientRect(), bottom = _node$getBoundingClie.bottom; // target will be the document from findScrollParent() var _ref = target.getBoundingClientRect ? target.getBoundingClientRect() : { height: 0, top: 0 }, height = _ref.height, top = _ref.top; return bottom >= top + height; }; var isNodeBeforeScroll = exports.isNodeBeforeScroll = function isNodeBeforeScroll(node, target) { var _node$getBoundingClie2 = node.getBoundingClientRect(), top = _node$getBoundingClie2.top; // target will be the document from findScrollParent() var _ref2 = target.getBoundingClientRect ? target.getBoundingClientRect() : { top: 0 }, targetTop = _ref2.top; return top <= targetTop; }; var _findButtonParent = exports.findButtonParent = function findButtonParent(element) { if (element && element.nodeName !== 'BUTTON' && element.nodeName !== 'A') return _findButtonParent(element.parentElement); return element; };