UNPKG

@shopify/polaris

Version:

Shopify’s admin product component library

140 lines (112 loc) 5.47 kB
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 };