@workday/canvas-kit-react
Version:
The parent module that contains all Workday Canvas Kit React components
124 lines (123 loc) • 4.61 kB
JavaScript
// refactor for v5
/// <reference types="@types/node" />
Object.defineProperty(exports, "__esModule", { value: true });
exports.tabTrappingKey = void 0;
const candidateSelectors = [
'input',
'select',
'textarea',
'a[href]',
'button',
'iframe',
'[tabindex]',
'audio[controls]',
'video[controls]',
'[contenteditable]:not([contenteditable="false"])',
];
function isHidden(node) {
// offsetParent being null will allow detecting cases where an element is invisible or inside an invisible element,
// as long as the element does not use position: fixed. For them, their visibility has to be checked directly as well.
return node.offsetParent === null || getComputedStyle(node).visibility === 'hidden';
}
function getCheckedRadio(nodes, form) {
for (let i = 0; i < nodes.length; i++) {
if (nodes[i].checked && nodes[i].form === form) {
return nodes[i];
}
}
return undefined;
}
function isNotRadioOrTabbableRadio(node) {
if (node.tagName !== 'INPUT' || node.type !== 'radio' || !node.name) {
return true;
}
const radioScope = node.form || node.ownerDocument;
if (!radioScope) {
return false;
}
const radioSet = radioScope.querySelectorAll('input[type="radio"][name="' + node.name + '"]');
const checked = getCheckedRadio(radioSet, node.form);
return checked === node || (checked === undefined && radioSet[0] === node);
}
function getAllTabbingElements(parentElem) {
var _a;
const currentActiveElement = document.activeElement;
const tabbableNodes = parentElem.querySelectorAll(candidateSelectors.join(','));
const onlyTabbable = [];
for (let i = 0; i < tabbableNodes.length; i++) {
const node = tabbableNodes[i];
if (currentActiveElement === node ||
(!node.disabled &&
getTabIndex(node) > -1 &&
!isHidden(node) &&
isNotRadioOrTabbableRadio(node))) {
if (node instanceof HTMLIFrameElement) {
const iframeDoc = (_a = node.contentWindow) === null || _a === void 0 ? void 0 : _a.document;
if (iframeDoc) {
const tabbableElementsInIframe = getAllTabbingElements(iframeDoc);
const handleKeyEvent = (event) => {
const blocked = tabTrappingKey(event, iframeDoc, onlyTabbable);
if (blocked) {
iframeDoc.removeEventListener('keydown', handleKeyEvent);
}
};
iframeDoc.addEventListener('keydown', handleKeyEvent);
tabbableElementsInIframe.forEach(elem => {
onlyTabbable.push(elem);
});
}
}
else {
onlyTabbable.push(node);
}
}
}
return onlyTabbable;
}
function tabTrappingKey(event, parentElem, onlyTabbable = null) {
// check if current event keyCode is tab
if (!event || event.key !== 'Tab') {
return;
}
if (!parentElem || !parentElem.contains) {
if (process.env.NODE_ENV === 'development') {
console.warn('focus-trap-js: parent element is not defined');
}
return false;
}
if (!parentElem.contains(event.target)) {
return false;
}
const allTabbingElements = onlyTabbable || getAllTabbingElements(parentElem);
const firstFocusableElement = allTabbingElements[0];
const lastFocusableElement = allTabbingElements[allTabbingElements.length - 1];
if (event.shiftKey && event.target === firstFocusableElement) {
lastFocusableElement.focus();
event.preventDefault();
return true;
}
else if (!event.shiftKey && event.target === lastFocusableElement) {
firstFocusableElement.focus();
event.preventDefault();
return true;
}
return false;
}
exports.tabTrappingKey = tabTrappingKey;
function getTabIndex(node) {
const tabIndexAttr = parseInt(node.getAttribute('tabindex') || '', 10);
if (!isNaN(tabIndexAttr)) {
return tabIndexAttr;
}
// Browsers do not return tabIndex correctly for contentEditable nodes;
// so if they don't have a tabindex attribute specifically set, assume it's 0.
if (isContentEditable(node)) {
return 0;
}
return node.tabIndex;
}
function isContentEditable(node) {
return node.getAttribute('contentEditable');
}
tabTrappingKey.isNotRadioOrTabbableRadio = isNotRadioOrTabbableRadio;
;