@shopify/polaris
Version:
Shopify’s admin product component library
140 lines (112 loc) • 5.47 kB
JavaScript
import { isElementInViewport } from './is-element-in-viewport.js';
const FOCUSABLE_SELECTOR = 'a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]';
const KEYBOARD_FOCUSABLE_SELECTORS = 'a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]:not([tabindex="-1"])';
const MENUITEM_FOCUSABLE_SELECTORS = 'a[role="menuitem"],frame[role="menuitem"],iframe[role="menuitem"],input[role="menuitem"]:not([type=hidden]):not(:disabled),select[role="menuitem"]:not(:disabled),textarea[role="menuitem"]:not(:disabled),button[role="menuitem"]:not(:disabled),*[tabindex]:not([tabindex="-1"])';
const handleMouseUpByBlurring = ({
currentTarget
}) => currentTarget.blur();
function nextFocusableNode(node, filter) {
const allFocusableElements = [...document.querySelectorAll(FOCUSABLE_SELECTOR)];
const sliceLocation = allFocusableElements.indexOf(node) + 1;
const focusableElementsAfterNode = allFocusableElements.slice(sliceLocation);
for (const focusableElement of focusableElementsAfterNode) {
if (isElementInViewport(focusableElement) && (!filter || filter && filter(focusableElement))) {
return focusableElement;
}
}
return null;
}
function findFirstFocusableNode(element, onlyDescendants = true) {
if (!onlyDescendants && matches(element, FOCUSABLE_SELECTOR)) {
return element;
}
return element.querySelector(FOCUSABLE_SELECTOR);
} // Popover needs to be able to find its activator even if it is disabled, which FOCUSABLE_SELECTOR doesn't support.
function findFirstFocusableNodeIncludingDisabled(element) {
const focusableSelector = `a,button,frame,iframe,input:not([type=hidden]),select,textarea,*[tabindex]`;
if (matches(element, focusableSelector)) {
return element;
}
return element.querySelector(focusableSelector);
}
function focusFirstFocusableNode(element, onlyDescendants = true) {
var _findFirstFocusableNo;
(_findFirstFocusableNo = findFirstFocusableNode(element, onlyDescendants)) === null || _findFirstFocusableNo === void 0 ? void 0 : _findFirstFocusableNo.focus();
}
function focusNextFocusableNode(node, filter) {
const nextFocusable = nextFocusableNode(node, filter);
if (nextFocusable && nextFocusable instanceof HTMLElement) {
nextFocusable.focus();
return true;
}
return false;
}
function findFirstKeyboardFocusableNode(element, onlyDescendants = true) {
if (!onlyDescendants && matches(element, KEYBOARD_FOCUSABLE_SELECTORS)) {
return element;
}
return element.querySelector(KEYBOARD_FOCUSABLE_SELECTORS);
}
function focusFirstKeyboardFocusableNode(element, onlyDescendants = true) {
const firstFocusable = findFirstKeyboardFocusableNode(element, onlyDescendants);
if (firstFocusable) {
firstFocusable.focus();
return true;
}
return false;
}
function findLastKeyboardFocusableNode(element, onlyDescendants = true) {
if (!onlyDescendants && matches(element, KEYBOARD_FOCUSABLE_SELECTORS)) {
return element;
}
const allFocusable = element.querySelectorAll(KEYBOARD_FOCUSABLE_SELECTORS);
return allFocusable[allFocusable.length - 1];
}
function focusLastKeyboardFocusableNode(element, onlyDescendants = true) {
const lastFocusable = findLastKeyboardFocusableNode(element, onlyDescendants);
if (lastFocusable) {
lastFocusable.focus();
return true;
}
return false;
}
function wrapFocusPreviousFocusableMenuItem(parentElement, currentFocusedElement) {
const allFocusableChildren = getMenuFocusableDescendants(parentElement);
const currentItemIdx = getCurrentFocusedElementIndex(allFocusableChildren, currentFocusedElement);
if (currentItemIdx === -1) {
allFocusableChildren[0].focus();
} else {
allFocusableChildren[(currentItemIdx - 1 + allFocusableChildren.length) % allFocusableChildren.length].focus();
}
}
function wrapFocusNextFocusableMenuItem(parentElement, currentFocusedElement) {
const allFocusableChildren = getMenuFocusableDescendants(parentElement);
const currentItemIdx = getCurrentFocusedElementIndex(allFocusableChildren, currentFocusedElement);
if (currentItemIdx === -1) {
allFocusableChildren[0].focus();
} else {
allFocusableChildren[(currentItemIdx + 1) % allFocusableChildren.length].focus();
}
}
function getMenuFocusableDescendants(element) {
return element.querySelectorAll(MENUITEM_FOCUSABLE_SELECTORS);
}
function getCurrentFocusedElementIndex(allFocusableChildren, currentFocusedElement) {
let currentItemIdx = 0;
for (const focusableChild of allFocusableChildren) {
if (focusableChild === currentFocusedElement) {
break;
}
currentItemIdx++;
}
return currentItemIdx === allFocusableChildren.length ? -1 : currentItemIdx;
}
function matches(node, selector) {
if (node.matches) {
return node.matches(selector);
}
const matches = (node.ownerDocument || document).querySelectorAll(selector);
let i = matches.length;
while (--i >= 0 && matches.item(i) !== node) return i > -1;
}
export { findFirstFocusableNode, findFirstFocusableNodeIncludingDisabled, findFirstKeyboardFocusableNode, findLastKeyboardFocusableNode, focusFirstFocusableNode, focusFirstKeyboardFocusableNode, focusLastKeyboardFocusableNode, focusNextFocusableNode, handleMouseUpByBlurring, nextFocusableNode, wrapFocusNextFocusableMenuItem, wrapFocusPreviousFocusableMenuItem };