UNPKG

@shopify/polaris

Version:

Shopify’s admin product component library

129 lines (124 loc) 5.89 kB
'use strict'; 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;