@shopify/polaris
Version:
Shopify’s admin product component library
129 lines (124 loc) • 5.89 kB
JavaScript
var isElementInViewport = require('./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([aria-disabled="true"]):not([tabindex="-1"]):not(:disabled),*[tabindex]';
const KEYBOARD_FOCUSABLE_SELECTORS = 'a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not([aria-disabled="true"]):not([tabindex="-1"]):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.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) {
findFirstFocusableNode(element, onlyDescendants)?.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;
}
exports.findFirstFocusableNode = findFirstFocusableNode;
exports.findFirstFocusableNodeIncludingDisabled = findFirstFocusableNodeIncludingDisabled;
exports.findFirstKeyboardFocusableNode = findFirstKeyboardFocusableNode;
exports.findLastKeyboardFocusableNode = findLastKeyboardFocusableNode;
exports.focusFirstFocusableNode = focusFirstFocusableNode;
exports.focusFirstKeyboardFocusableNode = focusFirstKeyboardFocusableNode;
exports.focusLastKeyboardFocusableNode = focusLastKeyboardFocusableNode;
exports.focusNextFocusableNode = focusNextFocusableNode;
exports.handleMouseUpByBlurring = handleMouseUpByBlurring;
exports.nextFocusableNode = nextFocusableNode;
exports.wrapFocusNextFocusableMenuItem = wrapFocusNextFocusableMenuItem;
exports.wrapFocusPreviousFocusableMenuItem = wrapFocusPreviousFocusableMenuItem;
;