export function isFocusable()

in src/utils/iterate-focusable-elements.ts [76:109]


export function isFocusable(elem: HTMLElement, strict = false): boolean {
  // Certain conditions cause an element to never be focusable, even if they have tabindex="0"
  const disabledAttrInert =
    ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA', 'OPTGROUP', 'OPTION', 'FIELDSET'].includes(elem.tagName) &&
    (elem as HTMLElement & {disabled: boolean}).disabled
  const hiddenInert = elem.hidden
  const hiddenInputInert = elem instanceof HTMLInputElement && elem.type === 'hidden'
  if (disabledAttrInert || hiddenInert || hiddenInputInert) {
    return false
  }

  // Each of the conditions checked below require a reflow, thus are gated by the `strict`
  // argument. If any are true, the element is not focusable, even if tabindex is set.
  if (strict) {
    const sizeInert = elem.offsetWidth === 0 || elem.offsetHeight === 0
    const visibilityInert = ['hidden', 'collapse'].includes(getComputedStyle(elem).visibility)
    const clientRectsInert = elem.getClientRects().length === 0
    if (sizeInert || visibilityInert || clientRectsInert) {
      return false
    }
  }

  // Any element with `tabindex` explicitly set can be focusable, even if it's set to "-1"
  if (elem.getAttribute('tabindex') != null) {
    return true
  }

  // One last way `elem.tabIndex` can be wrong.
  if (elem instanceof HTMLAnchorElement && elem.getAttribute('href') == null) {
    return false
  }

  return elem.tabIndex !== -1
}