grommet
Version:
focus on the essential experience
216 lines (163 loc) • 6.36 kB
JavaScript
;
exports.__esModule = true;
exports.isNodeBeforeScroll = exports.isNodeAfterScroll = exports.findVisibleParent = exports.makeNodeUnfocusable = exports.makeNodeFocusable = exports.copyAttribute = exports.setTabIndex = exports.setFocusWithoutScroll = exports.getNewContainer = exports.getBodyChildElements = exports.getFirstFocusableDescendant = exports.findScrollParents = exports.filterByFocusable = void 0;
var filterByFocusable = function filterByFocusable(elements) {
return Array.prototype.filter.call(elements || [], function (element) {
var currentTag = element.tagName.toLowerCase();
var validTags = /(svg|a|area|input|select|textarea|button|iframe|div)$/;
var isValidTag = currentTag.match(validTags) && element.focus;
if (currentTag === 'a') {
return isValidTag && element.childNodes.length > 0 && element.getAttribute('href');
}
if (currentTag === 'svg' || currentTag === 'div') {
return isValidTag && element.hasAttribute('tabindex') && element.getAttribute('tabindex') !== '-1';
}
return isValidTag;
});
};
exports.filterByFocusable = filterByFocusable;
var 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;
} // last scrollable element will be the document
// if nothing else is scrollable in the page
if (result.length === 0) {
result.push(document);
} else if (result[0].tagName.toLowerCase() === 'body') {
result.length = 0;
result.push(document);
}
}
return result;
};
exports.findScrollParents = findScrollParents;
var getFirstFocusableDescendant = function getFirstFocusableDescendant(element) {
var children = element.getElementsByTagName('*');
for (var i = 0; i < children.length; i += 1) {
var child = children[i];
var tagName = child.tagName.toLowerCase();
if (tagName === 'input' || tagName === 'select') {
return child;
}
}
return undefined;
};
exports.getFirstFocusableDescendant = getFirstFocusableDescendant;
var getBodyChildElements = function getBodyChildElements() {
var excludeMatch = /^(script|link)$/i;
var children = [];
[].forEach.call(document.body.children, function (node) {
if (!excludeMatch.test(node.tagName)) {
children.push(node);
}
});
return children;
};
exports.getBodyChildElements = getBodyChildElements;
var getNewContainer = function getNewContainer() {
// setup DOM
var container = document.createElement('div');
document.body.appendChild(container);
return container;
};
exports.getNewContainer = getNewContainer;
var setFocusWithoutScroll = function setFocusWithoutScroll(element) {
var x = window.scrollX;
var y = window.scrollY;
element.focus();
window.scrollTo(x, y);
};
exports.setFocusWithoutScroll = setFocusWithoutScroll;
var setTabIndex = function setTabIndex(tabIndex) {
return function (element) {
element.setAttribute('tabindex', tabIndex);
};
};
exports.setTabIndex = setTabIndex;
var copyAttribute = function copyAttribute(source) {
return function (target) {
return function (element) {
element.setAttribute(target, element.getAttribute(source));
};
};
};
exports.copyAttribute = copyAttribute;
var deleteAttribute = function deleteAttribute(attribute) {
return function (element) {
return element.removeAttribute(attribute);
};
};
var unsetTabIndex = setTabIndex(-1);
var saveTabIndex = copyAttribute('tabindex')('data-g-tabindex');
var restoreTabIndex = copyAttribute('data-g-tabindex')('tabindex');
var deleteTabIndex = deleteAttribute('tabindex');
var deleteTabIndexCopy = deleteAttribute('data-g-tabindex');
var makeNodeFocusable = function makeNodeFocusable(node) {
// do not touch aria live containers so that announcements work
if (!node.hasAttribute('aria-live')) {
node.setAttribute('aria-hidden', false); // allow children to receive focus again
filterByFocusable(node.getElementsByTagName('*')).forEach(function (child) {
if (child.hasAttribute('data-g-tabindex')) {
restoreTabIndex(child);
} else {
deleteTabIndex(child);
}
deleteTabIndexCopy(child);
});
}
};
exports.makeNodeFocusable = makeNodeFocusable;
var 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
filterByFocusable(node.getElementsByTagName('*')).forEach(function (child) {
if (child.hasAttribute('tabindex')) {
saveTabIndex(child);
}
unsetTabIndex(child);
});
}
};
exports.makeNodeUnfocusable = makeNodeUnfocusable;
var findVisibleParent = function findVisibleParent(element) {
if (element) {
return element.offsetParent ? element : findVisibleParent(element.parentElement) || element;
}
return undefined;
};
exports.findVisibleParent = findVisibleParent;
var isNodeAfterScroll = function isNodeAfterScroll(node, target) {
if (target === void 0) {
target = window;
}
var _node$getBoundingClie = node.getBoundingClientRect(),
bottom = _node$getBoundingClie.bottom;
var _target$getBoundingCl = target.getBoundingClientRect(),
height = _target$getBoundingCl.height,
top = _target$getBoundingCl.top;
return bottom >= top + height;
};
exports.isNodeAfterScroll = isNodeAfterScroll;
var isNodeBeforeScroll = function isNodeBeforeScroll(node, target) {
if (target === void 0) {
target = window;
}
var _node$getBoundingClie2 = node.getBoundingClientRect(),
top = _node$getBoundingClie2.top;
var _target$getBoundingCl2 = target.getBoundingClientRect(),
targetTop = _target$getBoundingCl2.top;
return top <= targetTop;
};
exports.isNodeBeforeScroll = isNodeBeforeScroll;