import { focusVisibleElement } from '@utils/helpers'; /** * This query string selects elements that * are eligible to receive focus. We select * interactive elements that meet the following * criteria: * 1. Element does not have a negative tabindex * 2. Element does not have `hidden` * 3. Element does not have `disabled` for non-Ionic components. * 4. Element does not have `disabled` or `disabled="true"` for Ionic components. * Note: We need this distinction because `disabled="false"` is * valid usage for the disabled property on ion-button. */ export const focusableQueryString = '[tabindex]:not([tabindex^="-"]):not([hidden]):not([disabled]), input:not([type=hidden]):not([tabindex^="-"]):not([hidden]):not([disabled]), textarea:not([tabindex^="-"]):not([hidden]):not([disabled]), button:not([tabindex^="-"]):not([hidden]):not([disabled]), select:not([tabindex^="-"]):not([hidden]):not([disabled]), ion-checkbox:not([tabindex^="-"]):not([hidden]):not([disabled]), ion-radio:not([tabindex^="-"]):not([hidden]):not([disabled]), .ion-focusable:not([tabindex^="-"]):not([hidden]):not([disabled]), .ion-focusable[disabled="false"]:not([tabindex^="-"]):not([hidden])'; /** * Focuses the first descendant in a context * that can receive focus. If none exists, * a fallback element will be focused. * This fallback is typically an ancestor * container such as a menu or overlay so focus does not * leave the container we are trying to trap focus in. * * If no fallback is specified then we focus the container itself. */ export const focusFirstDescendant = (ref: R, fallbackElement?: T) => { const firstInput = ref.querySelector(focusableQueryString); focusElementInContext(firstInput, fallbackElement ?? ref); }; /** * Focuses the last descendant in a context * that can receive focus. If none exists, * a fallback element will be focused. * This fallback is typically an ancestor * container such as a menu or overlay so focus does not * leave the container we are trying to trap focus in. * * If no fallback is specified then we focus the container itself. */ export const focusLastDescendant = (ref: R, fallbackElement?: T) => { const inputs = Array.from(ref.querySelectorAll(focusableQueryString)); const lastInput = inputs.length > 0 ? inputs[inputs.length - 1] : null; focusElementInContext(lastInput, fallbackElement ?? ref); }; /** * Focuses a particular element in a context. If the element * doesn't have anything focusable associated with it then * a fallback element will be focused. * * This fallback is typically an ancestor * container such as a menu or overlay so focus does not * leave the container we are trying to trap focus in. * This should be used instead of the focus() method * on most elements because the focusable element * may not be the host element. * * For example, if an ion-button should be focused * then we should actually focus the native