/*!
 * Custom elements for Nanopore-Digital Web applications
 */
import { Build } from '@stencil/core/internal/client';

// Determines if the specified element is tabbable using heuristics inspired by https://github.com/focus-trap/tabbable
function isTabbable(el, skipVisible = false) {
    const tag = el.tagName.toLowerCase();
    if (![
        'button',
        'input',
        'select',
        'textarea',
        'a',
        'audio',
        'video',
        'summary',
    ].includes(tag)) {
        return false;
    }
    // Elements with a -1 tab index are not tabbable
    if (el.getAttribute('tabindex') === '-1') {
        return false;
    }
    // Elements with a disabled attribute are not tabbable
    if (el.hasAttribute('disabled')) {
        return false;
    }
    // Elements with aria-disabled are not tabbable
    if (el.hasAttribute('aria-disabled') &&
        el.getAttribute('aria-disabled') !== 'false') {
        return false;
    }
    // Elements with a tabindex other than -1 are tabbable
    if (el.hasAttribute('tabindex')) {
        return true;
    }
    // Elements with a contenteditable attribute are tabbable
    if (el.hasAttribute('contenteditable') &&
        el.getAttribute('contenteditable') !== 'false') {
        return true;
    }
    // Audio and video elements with the controls attribute are tabbable
    if ((tag === 'audio' || tag === 'video') && el.hasAttribute('controls')) {
        return true;
    }
    // Radios without a checked attribute are not tabbable
    if (tag === 'input' &&
        el.getAttribute('type') === 'radio' &&
        !el.hasAttribute('checked')) {
        return false;
    }
    // Anchors without href are not tabbable
    if (tag === 'a' && !el.hasAttribute('href')) {
        return false;
    }
    if (!skipVisible) {
        // Elements that are hidden have no offsetParent and are not tabbable
        if (!(el.offsetWidth || el.offsetHeight || el.getClientRects().length)) {
            return false;
        }
        if (typeof el.checkVisibility === 'function' &&
            !el.checkVisibility({ opacityProperty: true, visibilityProperty: true })) {
            return false;
        }
    }
    return true;
}
/**
 * Recursively locate all tabbable elements within an element.
 * If the target element is tabbable, it will be included in the
 * resulting array. This function will also look in open shadow roots.
 *
 * @param root the root to search from
 * @param first stop drilling after finding the first tabbable elements
 * @param skipVisible if true, will skip visibility checks
 * @returns an array of tabbable elements
 */
function getTabbableElements(root, first = false, skipVisible = false, skipShadow = false) {
    if (Build.isServer)
        return [];
    const tabbableElements = [];
    if (root instanceof HTMLElement) {
        // Is the root element tabbable?
        if (isTabbable(root, skipVisible)) {
            tabbableElements.push(root);
            if (first)
                return tabbableElements;
        }
        // Look for tabbable elements in the shadow root
        if (!skipShadow && root.shadowRoot && root.shadowRoot.mode === 'open') {
            getTabbableElements(root.shadowRoot, first, skipVisible).map((el) => {
                if (!tabbableElements.includes(el))
                    tabbableElements.push(el);
            });
        }
        // Look at slotted elements
        if (HTMLSlotElement && root instanceof HTMLSlotElement) {
            root.assignedElements().map((slottedEl) => {
                getTabbableElements(slottedEl, first, skipVisible).map((el) => {
                    if (!tabbableElements.includes(el))
                        tabbableElements.push(el);
                });
            });
        }
    }
    // Look for tabbable elements in children
    Array.from(root.children).map((el) => {
        getTabbableElements(el, first, skipVisible).map((el) => {
            if (!tabbableElements.includes(el))
                tabbableElements.push(el);
        });
    });
    return tabbableElements;
}
function getNearestTabbableElement(el) {
    const tabbableElements = getTabbableElements(el, true, true);
    return tabbableElements.length ? tabbableElements[0] : null;
}

export { getNearestTabbableElement as a, getTabbableElements as g };
