/**
 * Copyright (c) Microsoft Corporation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import * as css from '@isomorphic/cssTokenizer';
import { beginDOMCaches, closestCrossShadow, elementSafeTagName, enclosingShadowRootOrDocument, endDOMCaches, getElementComputedStyle, isElementStyleVisibilityVisible, isVisibleTextNode, parentElementOrShadowHost } from './domUtils';
function hasExplicitAccessibleName(e) {
    return e.hasAttribute('aria-label') || e.hasAttribute('aria-labelledby');
}
// https://www.w3.org/TR/wai-aria-practices/examples/landmarks/HTML5.html
const kAncestorPreventingLandmark = 'article:not([role]), aside:not([role]), main:not([role]), nav:not([role]), section:not([role]), [role=article], [role=complementary], [role=main], [role=navigation], [role=region]';
// https://www.w3.org/TR/wai-aria-1.2/#global_states
const kGlobalAriaAttributes = [
    ['aria-atomic', undefined],
    ['aria-busy', undefined],
    ['aria-controls', undefined],
    ['aria-current', undefined],
    ['aria-describedby', undefined],
    ['aria-details', undefined],
    // Global use deprecated in ARIA 1.2
    // ['aria-disabled', undefined],
    ['aria-dropeffect', undefined],
    // Global use deprecated in ARIA 1.2
    // ['aria-errormessage', undefined],
    ['aria-flowto', undefined],
    ['aria-grabbed', undefined],
    // Global use deprecated in ARIA 1.2
    // ['aria-haspopup', undefined],
    ['aria-hidden', undefined],
    // Global use deprecated in ARIA 1.2
    // ['aria-invalid', undefined],
    ['aria-keyshortcuts', undefined],
    ['aria-label', ['caption', 'code', 'deletion', 'emphasis', 'generic', 'insertion', 'paragraph', 'presentation', 'strong', 'subscript', 'superscript']],
    ['aria-labelledby', ['caption', 'code', 'deletion', 'emphasis', 'generic', 'insertion', 'paragraph', 'presentation', 'strong', 'subscript', 'superscript']],
    ['aria-live', undefined],
    ['aria-owns', undefined],
    ['aria-relevant', undefined],
    ['aria-roledescription', ['generic']],
];
function hasGlobalAriaAttribute(element, forRole) {
    return kGlobalAriaAttributes.some(([attr, prohibited]) => {
        return !prohibited?.includes(forRole || '') && element.hasAttribute(attr);
    });
}
function hasTabIndex(element) {
    return !Number.isNaN(Number(String(element.getAttribute('tabindex'))));
}
function isFocusable(element) {
    // TODO:
    // - "inert" attribute makes the whole substree not focusable
    // - when dialog is open on the page - everything but the dialog is not focusable
    return !isNativelyDisabled(element) && (isNativelyFocusable(element) || hasTabIndex(element));
}
function isNativelyFocusable(element) {
    const tagName = elementSafeTagName(element);
    if (['BUTTON', 'DETAILS', 'SELECT', 'TEXTAREA'].includes(tagName))
        return true;
    if (tagName === 'A' || tagName === 'AREA')
        return element.hasAttribute('href');
    if (tagName === 'INPUT')
        return !element.hidden;
    return false;
}
// https://w3c.github.io/html-aam/#html-element-role-mappings
// https://www.w3.org/TR/html-aria/#docconformance
const kImplicitRoleByTagName = {
    'A': (e) => {
        return e.hasAttribute('href') ? 'link' : null;
    },
    'AREA': (e) => {
        return e.hasAttribute('href') ? 'link' : null;
    },
    'ARTICLE': () => 'article',
    'ASIDE': () => 'complementary',
    'BLOCKQUOTE': () => 'blockquote',
    'BUTTON': () => 'button',
    'CAPTION': () => 'caption',
    'CODE': () => 'code',
    'DATALIST': () => 'listbox',
    'DD': () => 'definition',
    'DEL': () => 'deletion',
    'DETAILS': () => 'group',
    'DFN': () => 'term',
    'DIALOG': () => 'dialog',
    'DT': () => 'term',
    'EM': () => 'emphasis',
    'FIELDSET': () => 'group',
    'FIGURE': () => 'figure',
    'FOOTER': (e) => closestCrossShadow(e, kAncestorPreventingLandmark) ? null : 'contentinfo',
    'FORM': (e) => hasExplicitAccessibleName(e) ? 'form' : null,
    'H1': () => 'heading',
    'H2': () => 'heading',
    'H3': () => 'heading',
    'H4': () => 'heading',
    'H5': () => 'heading',
    'H6': () => 'heading',
    'HEADER': (e) => closestCrossShadow(e, kAncestorPreventingLandmark) ? null : 'banner',
    'HR': () => 'separator',
    'HTML': () => 'document',
    'IMG': (e) => (e.getAttribute('alt') === '') && !e.getAttribute('title') && !hasGlobalAriaAttribute(e) && !hasTabIndex(e) ? 'presentation' : 'img',
    'INPUT': (e) => {
        const type = e.type.toLowerCase();
        if (type === 'search')
            return e.hasAttribute('list') ? 'combobox' : 'searchbox';
        if (['email', 'tel', 'text', 'url', ''].includes(type)) {
            // https://html.spec.whatwg.org/multipage/input.html#concept-input-list
            const list = getIdRefs(e, e.getAttribute('list'))[0];
            return (list && elementSafeTagName(list) === 'DATALIST') ? 'combobox' : 'textbox';
        }
        if (type === 'hidden')
            return null;
        // File inputs do not have a role by the spec: https://www.w3.org/TR/html-aam-1.0/#el-input-file.
        // However, there are open issues about fixing it: https://github.com/w3c/aria/issues/1926.
        // All browsers report it as a button, and it is rendered as a button, so we do "button".
        if (type === 'file')
            return 'button';
        return inputTypeToRole[type] || 'textbox';
    },
    'INS': () => 'insertion',
    'LI': () => 'listitem',
    'MAIN': () => 'main',
    'MARK': () => 'mark',
    'MATH': () => 'math',
    'MENU': () => 'list',
    'METER': () => 'meter',
    'NAV': () => 'navigation',
    'OL': () => 'list',
    'OPTGROUP': () => 'group',
    'OPTION': () => 'option',
    'OUTPUT': () => 'status',
    'P': () => 'paragraph',
    'PROGRESS': () => 'progressbar',
    'SEARCH': () => 'search',
    'SECTION': (e) => hasExplicitAccessibleName(e) ? 'region' : null,
    'SELECT': (e) => e.hasAttribute('multiple') || e.size > 1 ? 'listbox' : 'combobox',
    'STRONG': () => 'strong',
    'SUB': () => 'subscript',
    'SUP': () => 'superscript',
    // For <svg> we default to Chrome behavior:
    // - Chrome reports 'img'.
    // - Firefox reports 'diagram' that is not in official ARIA spec yet.
    // - Safari reports 'no role', but still computes accessible name.
    'SVG': () => 'img',
    'TABLE': () => 'table',
    'TBODY': () => 'rowgroup',
    'TD': (e) => {
        const table = closestCrossShadow(e, 'table');
        const role = table ? getExplicitAriaRole(table) : '';
        return (role === 'grid' || role === 'treegrid') ? 'gridcell' : 'cell';
    },
    'TEXTAREA': () => 'textbox',
    'TFOOT': () => 'rowgroup',
    'TH': (e) => {
        const scope = e.getAttribute('scope');
        if (scope === 'col' || scope === 'colgroup')
            return 'columnheader';
        if (scope === 'row' || scope === 'rowgroup')
            return 'rowheader';
        const nextSibling = e.nextElementSibling;
        const prevSibling = e.previousElementSibling;
        const row = !!e.parentElement && elementSafeTagName(e.parentElement) === 'TR' ? e.parentElement : undefined;
        // Chromium/Safari: A TH that is the only cell in a table is not labeling any content, thus it's technically not a header. Do not assign a role.
        // Firefox: Follows the spec and assigns `columnheader`. We prioritize Chrome/Safari semantics.
        if (!nextSibling && !prevSibling) {
            if (row) {
                const table = closestCrossShadow(row, 'table');
                // If there's only one row in the table, this TH has no column to head
                if (table && table.rows.length <= 1)
                    return null;
            }
            return 'columnheader';
        }
        // Tables are built up incrementally by iterating over them in a particular pattern. In order to emulate this,
        // we check only immediate siblings and occasionally the parent row
        // This doesn't seem to directly follow the spec, but matches Chromium behavior
        // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/accessibility/ax_node_object.cc;l=1585-1623
        if (isHeaderCell(nextSibling) && isHeaderCell(prevSibling))
            return 'columnheader';
        if (isNonEmptyDataCell(nextSibling) || isNonEmptyDataCell(prevSibling))
            return 'rowheader';
        // As long as we didn't exclude it above, it's still a TH, so default to columnheader
        return 'columnheader';
    },
    'THEAD': () => 'rowgroup',
    'TIME': () => 'time',
    'TR': () => 'row',
    'UL': () => 'list',
};
function isHeaderCell(element) {
    return !!element && elementSafeTagName(element) === 'TH';
}
function isNonEmptyDataCell(element) {
    if (!element || elementSafeTagName(element) !== 'TD')
        return false;
    return !!(element.textContent?.trim() || element.children.length > 0);
}
const kPresentationInheritanceParents = {
    'DD': ['DL', 'DIV'],
    'DIV': ['DL'],
    'DT': ['DL', 'DIV'],
    'LI': ['OL', 'UL'],
    'TBODY': ['TABLE'],
    'TD': ['TR'],
    'TFOOT': ['TABLE'],
    'TH': ['TR'],
    'THEAD': ['TABLE'],
    'TR': ['THEAD', 'TBODY', 'TFOOT', 'TABLE'],
};
function getImplicitAriaRole(element) {
    const implicitRole = kImplicitRoleByTagName[elementSafeTagName(element)]?.(element) || '';
    if (!implicitRole)
        return null;
    // Inherit presentation role when required.
    // https://www.w3.org/TR/wai-aria-1.2/#conflict_resolution_presentation_none
    let ancestor = element;
    while (ancestor) {
        const parent = parentElementOrShadowHost(ancestor);
        const parents = kPresentationInheritanceParents[elementSafeTagName(ancestor)];
        if (!parents || !parent || !parents.includes(elementSafeTagName(parent)))
            break;
        const parentExplicitRole = getExplicitAriaRole(parent);
        if ((parentExplicitRole === 'none' || parentExplicitRole === 'presentation') && !hasPresentationConflictResolution(parent, parentExplicitRole))
            return parentExplicitRole;
        ancestor = parent;
    }
    return implicitRole;
}
const validRoles = ['alert', 'alertdialog', 'application', 'article', 'banner', 'blockquote', 'button', 'caption', 'cell', 'checkbox', 'code', 'columnheader', 'combobox',
    'complementary', 'contentinfo', 'definition', 'deletion', 'dialog', 'directory', 'document', 'emphasis', 'feed', 'figure', 'form', 'generic', 'grid',
    'gridcell', 'group', 'heading', 'img', 'insertion', 'link', 'list', 'listbox', 'listitem', 'log', 'main', 'mark', 'marquee', 'math', 'meter', 'menu',
    'menubar', 'menuitem', 'menuitemcheckbox', 'menuitemradio', 'navigation', 'none', 'note', 'option', 'paragraph', 'presentation', 'progressbar', 'radio', 'radiogroup',
    'region', 'row', 'rowgroup', 'rowheader', 'scrollbar', 'search', 'searchbox', 'separator', 'slider',
    'spinbutton', 'status', 'strong', 'subscript', 'superscript', 'switch', 'tab', 'table', 'tablist', 'tabpanel', 'term', 'textbox', 'time', 'timer',
    'toolbar', 'tooltip', 'tree', 'treegrid', 'treeitem'];
function getExplicitAriaRole(element) {
    // https://www.w3.org/TR/wai-aria-1.2/#document-handling_author-errors_roles
    const roles = (element.getAttribute('role') || '').split(' ').map(role => role.trim());
    return roles.find(role => validRoles.includes(role)) || null;
}
function hasPresentationConflictResolution(element, role) {
    // https://www.w3.org/TR/wai-aria-1.2/#conflict_resolution_presentation_none
    return hasGlobalAriaAttribute(element, role) || isFocusable(element);
}
export function getAriaRole(element) {
    const explicitRole = getExplicitAriaRole(element);
    if (!explicitRole)
        return getImplicitAriaRole(element);
    if (explicitRole === 'none' || explicitRole === 'presentation') {
        const implicitRole = getImplicitAriaRole(element);
        if (hasPresentationConflictResolution(element, implicitRole))
            return implicitRole;
    }
    return explicitRole;
}
function getAriaBoolean(attr) {
    return attr === null ? undefined : attr.toLowerCase() === 'true';
}
export function isElementIgnoredForAria(element) {
    return ['STYLE', 'SCRIPT', 'NOSCRIPT', 'TEMPLATE'].includes(elementSafeTagName(element));
}
// https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion, but including "none" and "presentation" roles
// Not implemented:
//   `Any descendants of elements that have the characteristic "Children Presentational: True"`
// https://www.w3.org/TR/wai-aria-1.2/#aria-hidden
export function isElementHiddenForAria(element) {
    if (isElementIgnoredForAria(element))
        return true;
    const style = getElementComputedStyle(element);
    const isSlot = element.nodeName === 'SLOT';
    if (style?.display === 'contents' && !isSlot) {
        // display:contents is not rendered itself, but its child nodes are.
        for (let child = element.firstChild; child; child = child.nextSibling) {
            if (child.nodeType === 1 /* Node.ELEMENT_NODE */ && !isElementHiddenForAria(child))
                return false;
            if (child.nodeType === 3 /* Node.TEXT_NODE */ && isVisibleTextNode(child))
                return false;
        }
        return true;
    }
    // Note: <option> inside <select> are not affected by visibility or content-visibility.
    // Same goes for <slot>.
    const isOptionInsideSelect = element.nodeName === 'OPTION' && !!element.closest('select');
    if (!isOptionInsideSelect && !isSlot && !isElementStyleVisibilityVisible(element, style))
        return true;
    return belongsToDisplayNoneOrAriaHiddenOrNonSlotted(element);
}
function belongsToDisplayNoneOrAriaHiddenOrNonSlotted(element) {
    let hidden = cacheIsHidden?.get(element);
    if (hidden === undefined) {
        hidden = false;
        // When parent has a shadow root, all light dom children must be assigned to a slot,
        // otherwise they are not rendered and considered hidden for aria.
        // Note: we can remove this logic once WebKit supports `Element.checkVisibility`.
        if (element.parentElement && element.parentElement.shadowRoot && !element.assignedSlot)
            hidden = true;
        // display:none and aria-hidden=true are considered hidden for aria.
        if (!hidden) {
            const style = getElementComputedStyle(element);
            hidden = !style || style.display === 'none' || getAriaBoolean(element.getAttribute('aria-hidden')) === true;
        }
        // Check recursively.
        if (!hidden) {
            const parent = parentElementOrShadowHost(element);
            if (parent)
                hidden = belongsToDisplayNoneOrAriaHiddenOrNonSlotted(parent);
        }
        cacheIsHidden?.set(element, hidden);
    }
    return hidden;
}
function getIdRefs(element, ref) {
    if (!ref)
        return [];
    const root = enclosingShadowRootOrDocument(element);
    if (!root)
        return [];
    try {
        const ids = ref.split(' ').filter(id => !!id);
        const result = [];
        for (const id of ids) {
            // https://www.w3.org/TR/wai-aria-1.2/#mapping_additional_relations_error_processing
            // "If more than one element has the same ID, the user agent SHOULD use the first element found with the given ID"
            const firstElement = root.querySelector('#' + CSS.escape(id));
            if (firstElement && !result.includes(firstElement))
                result.push(firstElement);
        }
        return result;
    }
    catch (e) {
        return [];
    }
}
function trimFlatString(s) {
    // "Flat string" at https://w3c.github.io/accname/#terminology
    return s.trim();
}
function asFlatString(s) {
    // "Flat string" at https://w3c.github.io/accname/#terminology
    // Note that non-breaking spaces are preserved.
    return s.split('\u00A0').map(chunk => chunk.replace(/\r\n/g, '\n').replace(/[\u200b\u00ad]/g, '').replace(/\s\s*/g, ' ')).join('\u00A0').trim();
}
function queryInAriaOwned(element, selector) {
    const result = [...element.querySelectorAll(selector)];
    for (const owned of getIdRefs(element, element.getAttribute('aria-owns'))) {
        if (owned.matches(selector))
            result.push(owned);
        result.push(...owned.querySelectorAll(selector));
    }
    return result;
}
export function getCSSContent(element, pseudo) {
    // Relevant spec: 2.6.2 from https://w3c.github.io/accname/#computation-steps.
    // Additional considerations: https://github.com/w3c/accname/issues/204.
    const cache = pseudo === '::before' ? cachePseudoContentBefore : (pseudo === '::after' ? cachePseudoContentAfter : cachePseudoContent);
    if (cache?.has(element))
        return cache?.get(element);
    const style = getElementComputedStyle(element, pseudo);
    let content;
    if (style) {
        const contentValue = style.content;
        if (contentValue && contentValue !== 'none' && contentValue !== 'normal') {
            if (style.display !== 'none' && style.visibility !== 'hidden') {
                // Note: all browsers ignore display:none and visibility:hidden pseudos.
                content = parseCSSContentPropertyAsString(element, contentValue, !!pseudo);
            }
        }
    }
    if (pseudo && content !== undefined) {
        // SPEC DIFFERENCE.
        // Spec says "CSS textual content, without a space", but we account for display
        // to pass "name_file-label-inline-block-styles-manual.html"
        const display = style?.display || 'inline';
        if (display !== 'inline')
            content = ' ' + content + ' ';
    }
    if (cache)
        cache.set(element, content);
    return content;
}
function parseCSSContentPropertyAsString(element, content, isPseudo) {
    // Welcome to the mini CSS parser!
    // It aims to support the following syntax and any subset of it:
    // content: "one" attr(...) "two" "three" / "alt" attr(...) "more alt"
    // See https://developer.mozilla.org/en-US/docs/Web/CSS/content for more details.
    if (!content || content === 'none' || content === 'normal') {
        // Common fast path.
        return;
    }
    try {
        let tokens = css.tokenize(content).filter(token => !(token instanceof css.WhitespaceToken));
        const delimIndex = tokens.findIndex(token => token instanceof css.DelimToken && token.value === '/');
        if (delimIndex !== -1) {
            // Use the alternative text part when exists.
            // content: ... / "alternative text"
            tokens = tokens.slice(delimIndex + 1);
        }
        else if (!isPseudo) {
            // For non-pseudo elements, the only valid content is a url() or various gradients.
            // Therefore, we follow Chrome and only consider the alternative text.
            // Firefox, on the other hand, calculates accessible name to be empty.
            return;
        }
        const accumulated = [];
        let index = 0;
        while (index < tokens.length) {
            if (tokens[index] instanceof css.StringToken) {
                // content: "some text"
                accumulated.push(tokens[index].value);
                index++;
            }
            else if (index + 2 < tokens.length && tokens[index] instanceof css.FunctionToken && tokens[index].value === 'attr' && tokens[index + 1] instanceof css.IdentToken && tokens[index + 2] instanceof css.CloseParenToken) {
                // content: attr(...)
                // Firefox does not resolve attribute accessors in content, so we do it manually.
                const attrName = tokens[index + 1].value;
                accumulated.push(element.getAttribute(attrName) || '');
                index += 3;
            }
            else {
                // Failed to parse the content, so ignore it.
                return;
            }
        }
        return accumulated.join('');
    }
    catch {
    }
}
export function getAriaLabelledByElements(element) {
    const ref = element.getAttribute('aria-labelledby');
    if (ref === null)
        return null;
    const refs = getIdRefs(element, ref);
    // step 2b:
    // "if the current node has an aria-labelledby attribute that contains at least one valid IDREF"
    // Therefore, if none of the refs match an element, we consider aria-labelledby to be missing.
    return refs.length ? refs : null;
}
function allowsNameFromContent(role, targetDescendant) {
    // SPEC: https://w3c.github.io/aria/#namefromcontent
    //
    // Note: there is a spec proposal https://github.com/w3c/aria/issues/1821 that
    // is roughly aligned with what Chrome/Firefox do, and we follow that.
    //
    // See chromium implementation here:
    // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/accessibility/ax_object.cc;l=6338;drc=3decef66bc4c08b142a19db9628e9efe68973e64;bpv=0;bpt=1
    const alwaysAllowsNameFromContent = ['button', 'cell', 'checkbox', 'columnheader', 'gridcell', 'heading', 'link', 'menuitem', 'menuitemcheckbox', 'menuitemradio', 'option', 'radio', 'row', 'rowheader', 'switch', 'tab', 'tooltip', 'treeitem'].includes(role);
    const descendantAllowsNameFromContent = targetDescendant && ['', 'caption', 'code', 'contentinfo', 'definition', 'deletion', 'emphasis', 'insertion', 'list', 'listitem', 'mark', 'none', 'paragraph', 'presentation', 'region', 'row', 'rowgroup', 'section', 'strong', 'subscript', 'superscript', 'table', 'term', 'time'].includes(role);
    return alwaysAllowsNameFromContent || descendantAllowsNameFromContent;
}
export function getElementAccessibleName(element, includeHidden) {
    const cache = (includeHidden ? cacheAccessibleNameHidden : cacheAccessibleName);
    let accessibleName = cache?.get(element);
    if (accessibleName === undefined) {
        // https://w3c.github.io/accname/#computation-steps
        accessibleName = '';
        // step 1.
        // https://w3c.github.io/aria/#namefromprohibited
        const elementProhibitsNaming = ['caption', 'code', 'definition', 'deletion', 'emphasis', 'generic', 'insertion', 'mark', 'paragraph', 'presentation', 'strong', 'subscript', 'suggestion', 'superscript', 'term', 'time'].includes(getAriaRole(element) || '');
        if (!elementProhibitsNaming) {
            // step 2.
            accessibleName = asFlatString(getTextAlternativeInternal(element, {
                includeHidden,
                visitedElements: new Set(),
                embeddedInTargetElement: 'self',
            }));
        }
        cache?.set(element, accessibleName);
    }
    return accessibleName;
}
export function getElementAccessibleDescription(element, includeHidden) {
    const cache = (includeHidden ? cacheAccessibleDescriptionHidden : cacheAccessibleDescription);
    let accessibleDescription = cache?.get(element);
    if (accessibleDescription === undefined) {
        // https://w3c.github.io/accname/#mapping_additional_nd_description
        // https://www.w3.org/TR/html-aam-1.0/#accdesc-computation
        accessibleDescription = '';
        if (element.hasAttribute('aria-describedby')) {
            // precedence 1
            const describedBy = getIdRefs(element, element.getAttribute('aria-describedby'));
            accessibleDescription = asFlatString(describedBy.map(ref => getTextAlternativeInternal(ref, {
                includeHidden,
                visitedElements: new Set(),
                embeddedInDescribedBy: { element: ref, hidden: isElementHiddenForAria(ref) },
            })).join(' '));
        }
        else if (element.hasAttribute('aria-description')) {
            // precedence 2
            accessibleDescription = asFlatString(element.getAttribute('aria-description') || '');
        }
        else {
            // TODO: handle precedence 3 - html-aam-specific cases like table>caption.
            // https://www.w3.org/TR/html-aam-1.0/#accdesc-computation
            // precedence 4
            accessibleDescription = asFlatString(element.getAttribute('title') || '');
        }
        cache?.set(element, accessibleDescription);
    }
    return accessibleDescription;
}
function getAriaInvalid(element) {
    // https://www.w3.org/TR/wai-aria-1.2/#aria-invalid
    // This state is being deprecated as a global state in ARIA 1.2.
    // In future versions it will only be allowed on roles where it is specifically supported.
    const ariaInvalid = element.getAttribute('aria-invalid');
    if (!ariaInvalid || ariaInvalid.trim() === '' || ariaInvalid.toLocaleLowerCase() === 'false')
        return 'false';
    if (ariaInvalid === 'true' || ariaInvalid === 'grammar' || ariaInvalid === 'spelling')
        return ariaInvalid;
    return 'true';
}
function getValidityInvalid(element) {
    if ('validity' in element) {
        const validity = element.validity;
        return validity?.valid === false;
    }
    return false;
}
export function getElementAccessibleErrorMessage(element) {
    // SPEC: https://w3c.github.io/aria/#aria-errormessage
    //
    // TODO: support https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/validationMessage
    const cache = cacheAccessibleErrorMessage;
    let accessibleErrorMessage = cacheAccessibleErrorMessage?.get(element);
    if (accessibleErrorMessage === undefined) {
        accessibleErrorMessage = '';
        const isAriaInvalid = getAriaInvalid(element) !== 'false';
        const isValidityInvalid = getValidityInvalid(element);
        if (isAriaInvalid || isValidityInvalid) {
            const errorMessageId = element.getAttribute('aria-errormessage');
            const errorMessages = getIdRefs(element, errorMessageId);
            // Ideally, this should be a separate "embeddedInErrorMessage", but it would follow the exact same rules.
            // Relevant vague spec: https://w3c.github.io/core-aam/#ariaErrorMessage.
            const parts = errorMessages.map(errorMessage => asFlatString(getTextAlternativeInternal(errorMessage, {
                visitedElements: new Set(),
                embeddedInDescribedBy: { element: errorMessage, hidden: isElementHiddenForAria(errorMessage) },
            })));
            accessibleErrorMessage = parts.join(' ').trim();
        }
        cache?.set(element, accessibleErrorMessage);
    }
    return accessibleErrorMessage;
}
function getTextAlternativeInternal(element, options) {
    if (options.visitedElements.has(element))
        return '';
    const childOptions = {
        ...options,
        embeddedInTargetElement: options.embeddedInTargetElement === 'self' ? 'descendant' : options.embeddedInTargetElement,
    };
    // step 2a. Hidden Not Referenced: If the current node is hidden and is:
    // Not part of an aria-labelledby or aria-describedby traversal, where the node directly referenced by that relation was hidden.
    // Nor part of a native host language text alternative element (e.g. label in HTML) or attribute traversal, where the root of that traversal was hidden.
    if (!options.includeHidden) {
        const isEmbeddedInHiddenReferenceTraversal = !!options.embeddedInLabelledBy?.hidden ||
            !!options.embeddedInDescribedBy?.hidden ||
            !!options.embeddedInNativeTextAlternative?.hidden ||
            !!options.embeddedInLabel?.hidden;
        if (isElementIgnoredForAria(element) ||
            (!isEmbeddedInHiddenReferenceTraversal && isElementHiddenForAria(element))) {
            options.visitedElements.add(element);
            return '';
        }
    }
    const labelledBy = getAriaLabelledByElements(element);
    // step 2b. LabelledBy:
    // Otherwise, if the current node has an aria-labelledby attribute that contains
    // at least one valid IDREF, and the current node is not already part of an ongoing
    // aria-labelledby or aria-describedby traversal, process its IDREFs in the order they occur...
    if (!options.embeddedInLabelledBy) {
        const accessibleName = (labelledBy || []).map(ref => getTextAlternativeInternal(ref, {
            ...options,
            embeddedInLabelledBy: { element: ref, hidden: isElementHiddenForAria(ref) },
            embeddedInDescribedBy: undefined,
            embeddedInTargetElement: undefined,
            embeddedInLabel: undefined,
            embeddedInNativeTextAlternative: undefined,
        })).join(' ');
        if (accessibleName)
            return accessibleName;
    }
    const role = getAriaRole(element) || '';
    const tagName = elementSafeTagName(element);
    // step 2c:
    //   if the current node is a control embedded within the label (e.g. any element directly referenced by aria-labelledby) for another widget...
    //
    // also step 2d "skip to rule Embedded Control" section:
    //   If traversal of the current node is due to recursion and the current node is an embedded control...
    // Note this is not strictly by the spec, because spec only applies this logic when "aria-label" is present.
    // However, browsers and and wpt test name_heading-combobox-focusable-alternative-manual.html follow this behavior,
    // and there is an issue filed for this: https://github.com/w3c/accname/issues/64
    if (!!options.embeddedInLabel || !!options.embeddedInLabelledBy || options.embeddedInTargetElement === 'descendant') {
        const isOwnLabel = [...element.labels || []].includes(element);
        const isOwnLabelledBy = (labelledBy || []).includes(element);
        if (!isOwnLabel && !isOwnLabelledBy) {
            if (role === 'textbox') {
                options.visitedElements.add(element);
                if (tagName === 'INPUT' || tagName === 'TEXTAREA')
                    return element.value;
                return element.textContent || '';
            }
            if (['combobox', 'listbox'].includes(role)) {
                options.visitedElements.add(element);
                let selectedOptions;
                if (tagName === 'SELECT') {
                    selectedOptions = [...element.selectedOptions];
                    if (!selectedOptions.length && element.options.length)
                        selectedOptions.push(element.options[0]);
                }
                else {
                    const listbox = role === 'combobox' ? queryInAriaOwned(element, '*').find(e => getAriaRole(e) === 'listbox') : element;
                    selectedOptions = listbox ? queryInAriaOwned(listbox, '[aria-selected="true"]').filter(e => getAriaRole(e) === 'option') : [];
                }
                if (!selectedOptions.length && tagName === 'INPUT') {
                    // SPEC DIFFERENCE:
                    // This fallback is not explicitly mentioned in the spec, but all browsers and
                    // wpt test name_heading-combobox-focusable-alternative-manual.html do this.
                    return element.value;
                }
                return selectedOptions.map(option => getTextAlternativeInternal(option, childOptions)).join(' ');
            }
            if (['progressbar', 'scrollbar', 'slider', 'spinbutton', 'meter'].includes(role)) {
                options.visitedElements.add(element);
                if (element.hasAttribute('aria-valuetext'))
                    return element.getAttribute('aria-valuetext') || '';
                if (element.hasAttribute('aria-valuenow'))
                    return element.getAttribute('aria-valuenow') || '';
                return element.getAttribute('value') || '';
            }
            if (['menu'].includes(role)) {
                // https://github.com/w3c/accname/issues/67#issuecomment-553196887
                options.visitedElements.add(element);
                return '';
            }
        }
    }
    // step 2d.
    const ariaLabel = element.getAttribute('aria-label') || '';
    if (trimFlatString(ariaLabel)) {
        options.visitedElements.add(element);
        return ariaLabel;
    }
    // step 2e.
    if (!['presentation', 'none'].includes(role)) {
        // https://w3c.github.io/html-aam/#input-type-button-input-type-submit-and-input-type-reset-accessible-name-computation
        //
        // SPEC DIFFERENCE.
        // Spec says to ignore this when aria-labelledby is defined.
        // WebKit follows the spec, while Chromium and Firefox do not.
        // We align with Chromium and Firefox here.
        if (tagName === 'INPUT' && ['button', 'submit', 'reset'].includes(element.type)) {
            options.visitedElements.add(element);
            const value = element.value || '';
            if (trimFlatString(value))
                return value;
            if (element.type === 'submit')
                return 'Submit';
            if (element.type === 'reset')
                return 'Reset';
            const title = element.getAttribute('title') || '';
            return title;
        }
        // SPEC DIFFERENCE.
        // There is no spec for this, but Chromium/WebKit do "Choose File" so we follow that.
        // All browsers respect labels, aria-labelledby and aria-label.
        // No browsers respect the title attribute, although w3c accname tests disagree. We follow browsers.
        if (tagName === 'INPUT' && element.type === 'file') {
            options.visitedElements.add(element);
            const labels = element.labels || [];
            if (labels.length && !options.embeddedInLabelledBy)
                return getAccessibleNameFromAssociatedLabels(labels, options);
            return 'Choose File';
        }
        // https://w3c.github.io/html-aam/#input-type-image-accessible-name-computation
        //
        // SPEC DIFFERENCE.
        // Spec says to ignore this when aria-labelledby is defined, but all browsers take it into account.
        if (tagName === 'INPUT' && element.type === 'image') {
            options.visitedElements.add(element);
            const labels = element.labels || [];
            if (labels.length && !options.embeddedInLabelledBy)
                return getAccessibleNameFromAssociatedLabels(labels, options);
            const alt = element.getAttribute('alt') || '';
            if (trimFlatString(alt))
                return alt;
            const title = element.getAttribute('title') || '';
            if (trimFlatString(title))
                return title;
            // SPEC DIFFERENCE.
            // Spec says return localized "Submit Query", but browsers and axe-core insist on "Submit".
            return 'Submit';
        }
        // https://w3c.github.io/html-aam/#button-element-accessible-name-computation
        if (!labelledBy && tagName === 'BUTTON') {
            options.visitedElements.add(element);
            const labels = element.labels || [];
            if (labels.length)
                return getAccessibleNameFromAssociatedLabels(labels, options);
            // From here, fallthrough to step 2f.
        }
        // https://w3c.github.io/html-aam/#output-element-accessible-name-computation
        if (!labelledBy && tagName === 'OUTPUT') {
            options.visitedElements.add(element);
            const labels = element.labels || [];
            if (labels.length)
                return getAccessibleNameFromAssociatedLabels(labels, options);
            return element.getAttribute('title') || '';
        }
        // https://w3c.github.io/html-aam/#input-type-text-input-type-password-input-type-number-input-type-search-input-type-tel-input-type-email-input-type-url-and-textarea-element-accessible-name-computation
        // https://w3c.github.io/html-aam/#other-form-elements-accessible-name-computation
        // For "other form elements", we count select and any other input.
        //
        // Note: WebKit does not follow the spec and uses placeholder when aria-labelledby is present.
        if (!labelledBy && (tagName === 'TEXTAREA' || tagName === 'SELECT' || tagName === 'INPUT')) {
            options.visitedElements.add(element);
            const labels = element.labels || [];
            if (labels.length)
                return getAccessibleNameFromAssociatedLabels(labels, options);
            const usePlaceholder = (tagName === 'INPUT' && ['text', 'password', 'search', 'tel', 'email', 'url'].includes(element.type)) || tagName === 'TEXTAREA';
            const placeholder = element.getAttribute('placeholder') || '';
            const title = element.getAttribute('title') || '';
            if (!usePlaceholder || title)
                return title;
            return placeholder;
        }
        // https://w3c.github.io/html-aam/#fieldset-and-legend-elements
        if (!labelledBy && tagName === 'FIELDSET') {
            options.visitedElements.add(element);
            for (let child = element.firstElementChild; child; child = child.nextElementSibling) {
                if (elementSafeTagName(child) === 'LEGEND') {
                    return getTextAlternativeInternal(child, {
                        ...childOptions,
                        embeddedInNativeTextAlternative: { element: child, hidden: isElementHiddenForAria(child) },
                    });
                }
            }
            const title = element.getAttribute('title') || '';
            return title;
        }
        // https://w3c.github.io/html-aam/#figure-and-figcaption-elements
        if (!labelledBy && tagName === 'FIGURE') {
            options.visitedElements.add(element);
            for (let child = element.firstElementChild; child; child = child.nextElementSibling) {
                if (elementSafeTagName(child) === 'FIGCAPTION') {
                    return getTextAlternativeInternal(child, {
                        ...childOptions,
                        embeddedInNativeTextAlternative: { element: child, hidden: isElementHiddenForAria(child) },
                    });
                }
            }
            const title = element.getAttribute('title') || '';
            return title;
        }
        // https://w3c.github.io/html-aam/#img-element
        //
        // SPEC DIFFERENCE.
        // Spec says to ignore this when aria-labelledby is defined, but all browsers take it into account.
        if (tagName === 'IMG') {
            options.visitedElements.add(element);
            const alt = element.getAttribute('alt') || '';
            if (trimFlatString(alt))
                return alt;
            const title = element.getAttribute('title') || '';
            return title;
        }
        // https://w3c.github.io/html-aam/#table-element
        if (tagName === 'TABLE') {
            options.visitedElements.add(element);
            for (let child = element.firstElementChild; child; child = child.nextElementSibling) {
                if (elementSafeTagName(child) === 'CAPTION') {
                    return getTextAlternativeInternal(child, {
                        ...childOptions,
                        embeddedInNativeTextAlternative: { element: child, hidden: isElementHiddenForAria(child) },
                    });
                }
            }
            // SPEC DIFFERENCE.
            // Spec does not say a word about <table summary="...">, but all browsers actually support it.
            const summary = element.getAttribute('summary') || '';
            if (summary)
                return summary;
            // SPEC DIFFERENCE.
            // Spec says "if the table element has a title attribute, then use that attribute".
            // We ignore title to pass "name_from_content-manual.html".
        }
        // https://w3c.github.io/html-aam/#area-element
        if (tagName === 'AREA') {
            options.visitedElements.add(element);
            const alt = element.getAttribute('alt') || '';
            if (trimFlatString(alt))
                return alt;
            const title = element.getAttribute('title') || '';
            return title;
        }
        // https://www.w3.org/TR/svg-aam-1.0/#mapping_additional_nd
        if (tagName === 'SVG' || element.ownerSVGElement) {
            options.visitedElements.add(element);
            for (let child = element.firstElementChild; child; child = child.nextElementSibling) {
                if (elementSafeTagName(child) === 'TITLE' && child.ownerSVGElement) {
                    return getTextAlternativeInternal(child, {
                        ...childOptions,
                        embeddedInLabelledBy: { element: child, hidden: isElementHiddenForAria(child) },
                    });
                }
            }
        }
        if (element.ownerSVGElement && tagName === 'A') {
            const title = element.getAttribute('xlink:title') || '';
            if (trimFlatString(title)) {
                options.visitedElements.add(element);
                return title;
            }
        }
    }
    // See https://w3c.github.io/html-aam/#summary-element-accessible-name-computation for "summary"-specific check.
    const shouldNameFromContentForSummary = tagName === 'SUMMARY' && !['presentation', 'none'].includes(role);
    // step 2f + step 2h.
    if (allowsNameFromContent(role, options.embeddedInTargetElement === 'descendant') ||
        shouldNameFromContentForSummary ||
        !!options.embeddedInLabelledBy || !!options.embeddedInDescribedBy ||
        !!options.embeddedInLabel || !!options.embeddedInNativeTextAlternative) {
        options.visitedElements.add(element);
        const accessibleName = innerAccumulatedElementText(element, childOptions);
        // Spec says "Return the accumulated text if it is not the empty string". However, that is not really
        // compatible with the real browser behavior and wpt tests, where an element with empty contents will fallback to the title.
        // So we follow the spec everywhere except for the target element itself. This can probably be improved.
        const maybeTrimmedAccessibleName = options.embeddedInTargetElement === 'self' ? trimFlatString(accessibleName) : accessibleName;
        if (maybeTrimmedAccessibleName)
            return accessibleName;
    }
    // step 2i.
    if (!['presentation', 'none'].includes(role) || tagName === 'IFRAME') {
        options.visitedElements.add(element);
        const title = element.getAttribute('title') || '';
        if (trimFlatString(title))
            return title;
    }
    options.visitedElements.add(element);
    return '';
}
function innerAccumulatedElementText(element, options) {
    const tokens = [];
    const visit = (node, skipSlotted) => {
        if (skipSlotted && node.assignedSlot)
            return;
        if (node.nodeType === 1 /* Node.ELEMENT_NODE */) {
            const display = getElementComputedStyle(node)?.display || 'inline';
            let token = getTextAlternativeInternal(node, options);
            // SPEC DIFFERENCE.
            // Spec says "append the result to the accumulated text", assuming "with space".
            // However, multiple tests insist that inline elements do not add a space.
            // Additionally, <br> insists on a space anyway, see "name_file-label-inline-block-elements-manual.html"
            if (display !== 'inline' || node.nodeName === 'BR')
                token = ' ' + token + ' ';
            tokens.push(token);
        }
        else if (node.nodeType === 3 /* Node.TEXT_NODE */) {
            // step 2g.
            tokens.push(node.textContent || '');
        }
    };
    tokens.push(getCSSContent(element, '::before') || '');
    const content = getCSSContent(element);
    if (content !== undefined) {
        // `content` CSS property replaces everything inside the element.
        // I was not able to find any spec or description on how this interacts with accname,
        // so this is a guess based on what browsers do.
        tokens.push(content);
    }
    else {
        // step 2h.
        const assignedNodes = element.nodeName === 'SLOT' ? element.assignedNodes() : [];
        if (assignedNodes.length) {
            for (const child of assignedNodes)
                visit(child, false);
        }
        else {
            for (let child = element.firstChild; child; child = child.nextSibling)
                visit(child, true);
            if (element.shadowRoot) {
                for (let child = element.shadowRoot.firstChild; child; child = child.nextSibling)
                    visit(child, true);
            }
            for (const owned of getIdRefs(element, element.getAttribute('aria-owns')))
                visit(owned, true);
        }
    }
    tokens.push(getCSSContent(element, '::after') || '');
    return tokens.join('');
}
export const kAriaSelectedRoles = ['gridcell', 'option', 'row', 'tab', 'rowheader', 'columnheader', 'treeitem'];
export function getAriaSelected(element) {
    // https://www.w3.org/TR/wai-aria-1.2/#aria-selected
    // https://www.w3.org/TR/html-aam-1.0/#html-attribute-state-and-property-mappings
    if (elementSafeTagName(element) === 'OPTION')
        return element.selected;
    if (kAriaSelectedRoles.includes(getAriaRole(element) || ''))
        return getAriaBoolean(element.getAttribute('aria-selected')) === true;
    return false;
}
export const kAriaCheckedRoles = ['checkbox', 'menuitemcheckbox', 'option', 'radio', 'switch', 'menuitemradio', 'treeitem'];
export function getAriaChecked(element) {
    const result = getChecked(element, true);
    return result === 'error' ? false : result;
}
export function getCheckedAllowMixed(element) {
    return getChecked(element, true);
}
export function getCheckedWithoutMixed(element) {
    const result = getChecked(element, false);
    return result;
}
function getChecked(element, allowMixed) {
    const tagName = elementSafeTagName(element);
    // https://www.w3.org/TR/wai-aria-1.2/#aria-checked
    // https://www.w3.org/TR/html-aam-1.0/#html-attribute-state-and-property-mappings
    if (allowMixed && tagName === 'INPUT' && element.indeterminate)
        return 'mixed';
    if (tagName === 'INPUT' && ['checkbox', 'radio'].includes(element.type))
        return element.checked;
    if (kAriaCheckedRoles.includes(getAriaRole(element) || '')) {
        const checked = element.getAttribute('aria-checked');
        if (checked === 'true')
            return true;
        if (allowMixed && checked === 'mixed')
            return 'mixed';
        return false;
    }
    return 'error';
}
// https://w3c.github.io/aria/#aria-readonly
const kAriaReadonlyRoles = ['checkbox', 'combobox', 'grid', 'gridcell', 'listbox', 'radiogroup', 'slider', 'spinbutton', 'textbox', 'columnheader', 'rowheader', 'searchbox', 'switch', 'treegrid'];
export function getReadonly(element) {
    const tagName = elementSafeTagName(element);
    // https://www.w3.org/TR/wai-aria-1.2/#aria-checked
    // https://www.w3.org/TR/html-aam-1.0/#html-attribute-state-and-property-mappings
    if (['INPUT', 'TEXTAREA', 'SELECT'].includes(tagName))
        return element.hasAttribute('readonly');
    if (kAriaReadonlyRoles.includes(getAriaRole(element) || ''))
        return element.getAttribute('aria-readonly') === 'true';
    if (element.isContentEditable)
        return false;
    return 'error';
}
export const kAriaPressedRoles = ['button'];
export function getAriaPressed(element) {
    // https://www.w3.org/TR/wai-aria-1.2/#aria-pressed
    if (kAriaPressedRoles.includes(getAriaRole(element) || '')) {
        const pressed = element.getAttribute('aria-pressed');
        if (pressed === 'true')
            return true;
        if (pressed === 'mixed')
            return 'mixed';
    }
    return false;
}
export const kAriaExpandedRoles = ['application', 'button', 'checkbox', 'combobox', 'gridcell', 'link', 'listbox', 'menuitem', 'row', 'rowheader', 'tab', 'treeitem', 'columnheader', 'menuitemcheckbox', 'menuitemradio', 'rowheader', 'switch'];
export function getAriaExpanded(element) {
    // https://www.w3.org/TR/wai-aria-1.2/#aria-expanded
    // https://www.w3.org/TR/html-aam-1.0/#html-attribute-state-and-property-mappings
    if (elementSafeTagName(element) === 'DETAILS')
        return element.open;
    if (kAriaExpandedRoles.includes(getAriaRole(element) || '')) {
        const expanded = element.getAttribute('aria-expanded');
        if (expanded === null)
            return undefined;
        if (expanded === 'true')
            return true;
        return false;
    }
    return undefined;
}
export const kAriaLevelRoles = ['heading', 'listitem', 'row', 'treeitem'];
export function getAriaLevel(element) {
    // https://www.w3.org/TR/wai-aria-1.2/#aria-level
    // https://www.w3.org/TR/html-aam-1.0/#html-attribute-state-and-property-mappings
    const native = { 'H1': 1, 'H2': 2, 'H3': 3, 'H4': 4, 'H5': 5, 'H6': 6 }[elementSafeTagName(element)];
    if (native)
        return native;
    if (kAriaLevelRoles.includes(getAriaRole(element) || '')) {
        const attr = element.getAttribute('aria-level');
        const value = attr === null ? Number.NaN : Number(attr);
        if (Number.isInteger(value) && value >= 1)
            return value;
    }
    return 0;
}
export const kAriaDisabledRoles = ['application', 'button', 'composite', 'gridcell', 'group', 'input', 'link', 'menuitem', 'scrollbar', 'separator', 'tab', 'checkbox', 'columnheader', 'combobox', 'grid', 'listbox', 'menu', 'menubar', 'menuitemcheckbox', 'menuitemradio', 'option', 'radio', 'radiogroup', 'row', 'rowheader', 'searchbox', 'select', 'slider', 'spinbutton', 'switch', 'tablist', 'textbox', 'toolbar', 'tree', 'treegrid', 'treeitem'];
export function getAriaDisabled(element) {
    // https://www.w3.org/TR/wai-aria-1.2/#aria-disabled
    // Note that aria-disabled applies to all descendants, so we look up the hierarchy.
    return isNativelyDisabled(element) || hasExplicitAriaDisabled(element);
}
function isNativelyDisabled(element) {
    // https://www.w3.org/TR/html-aam-1.0/#html-attribute-state-and-property-mappings
    const isNativeFormControl = ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA', 'OPTION', 'OPTGROUP'].includes(elementSafeTagName(element));
    return isNativeFormControl && (element.hasAttribute('disabled') || belongsToDisabledOptGroup(element) || belongsToDisabledFieldSet(element));
}
function belongsToDisabledOptGroup(element) {
    return elementSafeTagName(element) === 'OPTION' && !!element.closest('OPTGROUP[DISABLED]');
}
function belongsToDisabledFieldSet(element) {
    const fieldSetElement = element?.closest('FIELDSET[DISABLED]');
    if (!fieldSetElement)
        return false;
    const legendElement = fieldSetElement.querySelector(':scope > LEGEND');
    return !legendElement || !legendElement.contains(element);
}
function hasExplicitAriaDisabled(element, isAncestor = false) {
    if (!element)
        return false;
    if (isAncestor || kAriaDisabledRoles.includes(getAriaRole(element) || '')) {
        const attribute = (element.getAttribute('aria-disabled') || '').toLowerCase();
        if (attribute === 'true')
            return true;
        if (attribute === 'false')
            return false;
        // aria-disabled works across shadow boundaries.
        return hasExplicitAriaDisabled(parentElementOrShadowHost(element), true);
    }
    return false;
}
function getAccessibleNameFromAssociatedLabels(labels, options) {
    return [...labels].map(label => getTextAlternativeInternal(label, {
        ...options,
        embeddedInLabel: { element: label, hidden: isElementHiddenForAria(label) },
        embeddedInNativeTextAlternative: undefined,
        embeddedInLabelledBy: undefined,
        embeddedInDescribedBy: undefined,
        embeddedInTargetElement: undefined,
    })).filter(accessibleName => !!accessibleName).join(' ');
}
export function receivesPointerEvents(element) {
    const cache = cachePointerEvents;
    let e = element;
    let result;
    const parents = [];
    for (; e; e = parentElementOrShadowHost(e)) {
        const cached = cache.get(e);
        if (cached !== undefined) {
            result = cached;
            break;
        }
        parents.push(e);
        const style = getElementComputedStyle(e);
        if (!style) {
            result = true;
            break;
        }
        const value = style.pointerEvents;
        if (value) {
            result = value !== 'none';
            break;
        }
    }
    if (result === undefined)
        result = true;
    for (const parent of parents)
        cache.set(parent, result);
    return result;
}
let cacheAccessibleName;
let cacheAccessibleNameHidden;
let cacheAccessibleDescription;
let cacheAccessibleDescriptionHidden;
let cacheAccessibleErrorMessage;
let cacheIsHidden;
let cachePseudoContent;
let cachePseudoContentBefore;
let cachePseudoContentAfter;
let cachePointerEvents;
let cachesCounter = 0;
export function beginAriaCaches() {
    beginDOMCaches();
    ++cachesCounter;
    cacheAccessibleName ??= new Map();
    cacheAccessibleNameHidden ??= new Map();
    cacheAccessibleDescription ??= new Map();
    cacheAccessibleDescriptionHidden ??= new Map();
    cacheAccessibleErrorMessage ??= new Map();
    cacheIsHidden ??= new Map();
    cachePseudoContent ??= new Map();
    cachePseudoContentBefore ??= new Map();
    cachePseudoContentAfter ??= new Map();
    cachePointerEvents ??= new Map();
}
export function endAriaCaches() {
    if (!--cachesCounter) {
        cacheAccessibleName = undefined;
        cacheAccessibleNameHidden = undefined;
        cacheAccessibleDescription = undefined;
        cacheAccessibleDescriptionHidden = undefined;
        cacheAccessibleErrorMessage = undefined;
        cacheIsHidden = undefined;
        cachePseudoContent = undefined;
        cachePseudoContentBefore = undefined;
        cachePseudoContentAfter = undefined;
        cachePointerEvents = undefined;
    }
    endDOMCaches();
}
const inputTypeToRole = {
    'button': 'button',
    'checkbox': 'checkbox',
    'image': 'button',
    'number': 'spinbutton',
    'radio': 'radio',
    'range': 'slider',
    'reset': 'button',
    'submit': 'button',
};
//# sourceMappingURL=roleUtils.js.map