/**
 * 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 aria from '@isomorphic/ariaSnapshot';
import { escapeRegExp, longestCommonSubstring, normalizeWhiteSpace } from '@isomorphic/stringUtils';
import { yamlEscapeKeyIfNeeded, yamlEscapeValueIfNeeded } from '@isomorphic/yaml';
import { computeBox, getElementComputedStyle, isElementVisible } from './domUtils';
import * as roleUtils from './roleUtils';
let lastRef = 0;
function toInternalOptions(options) {
    const renderBoxes = options.boxes;
    if (options.mode === 'ai') {
        // For AI consumption.
        return {
            visibility: 'ariaOrVisible',
            refs: 'interactable',
            refPrefix: options.refPrefix,
            includeGenericRole: true,
            renderActive: !options.doNotRenderActive,
            renderCursorPointer: true,
            renderBoxes,
        };
    }
    if (options.mode === 'autoexpect') {
        // To auto-generate assertions on visible elements.
        return { visibility: 'ariaAndVisible', refs: 'none', renderBoxes };
    }
    if (options.mode === 'codegen') {
        // To generate aria assertion with regex heurisitcs.
        return { visibility: 'aria', refs: 'none', renderStringsAsRegex: true, renderBoxes };
    }
    // To match aria snapshot.
    return { visibility: 'aria', refs: 'none', renderBoxes };
}
export function generateAriaTree(rootElement, publicOptions) {
    const options = toInternalOptions(publicOptions);
    const visited = new Set();
    const snapshot = {
        root: { role: 'fragment', name: '', children: [], props: {}, box: computeBox(rootElement), receivesPointerEvents: true },
        elements: new Map(),
        refs: new Map(),
        iframeRefs: [],
    };
    setAriaNodeElement(snapshot.root, rootElement);
    const visit = (ariaNode, node, parentElementVisible) => {
        if (visited.has(node))
            return;
        visited.add(node);
        if (node.nodeType === Node.TEXT_NODE && node.nodeValue) {
            if (!parentElementVisible)
                return;
            const text = node.nodeValue;
            // <textarea>AAA</textarea> should not report AAA as a child of the textarea.
            if (ariaNode.role !== 'textbox' && text)
                ariaNode.children.push(node.nodeValue || '');
            return;
        }
        if (node.nodeType !== Node.ELEMENT_NODE)
            return;
        const element = node;
        const isElementVisibleForAria = !roleUtils.isElementHiddenForAria(element);
        let visible = isElementVisibleForAria;
        if (options.visibility === 'ariaOrVisible')
            visible = isElementVisibleForAria || isElementVisible(element);
        if (options.visibility === 'ariaAndVisible')
            visible = isElementVisibleForAria && isElementVisible(element);
        // Optimization: if we only consider aria visibility, we can skip child elements because
        // they will not be visible for aria as well.
        if (options.visibility === 'aria' && !visible)
            return;
        const ariaChildren = [];
        if (element.hasAttribute('aria-owns')) {
            const ids = element.getAttribute('aria-owns').split(/\s+/);
            for (const id of ids) {
                const ownedElement = rootElement.ownerDocument.getElementById(id);
                if (ownedElement)
                    ariaChildren.push(ownedElement);
            }
        }
        const childAriaNode = visible ? toAriaNode(element, options) : null;
        if (childAriaNode) {
            if (childAriaNode.ref) {
                snapshot.elements.set(childAriaNode.ref, element);
                snapshot.refs.set(element, childAriaNode.ref);
                if (childAriaNode.role === 'iframe')
                    snapshot.iframeRefs.push(childAriaNode.ref);
            }
            ariaNode.children.push(childAriaNode);
        }
        processElement(childAriaNode || ariaNode, element, ariaChildren, visible);
    };
    function processElement(ariaNode, element, ariaChildren, parentElementVisible) {
        // Surround every element with spaces for the sake of concatenated text nodes.
        const display = getElementComputedStyle(element)?.display || 'inline';
        const treatAsBlock = (display !== 'inline' || element.nodeName === 'BR') ? ' ' : '';
        if (treatAsBlock)
            ariaNode.children.push(treatAsBlock);
        ariaNode.children.push(roleUtils.getCSSContent(element, '::before') || '');
        const assignedNodes = element.nodeName === 'SLOT' ? element.assignedNodes() : [];
        if (assignedNodes.length) {
            for (const child of assignedNodes)
                visit(ariaNode, child, parentElementVisible);
        }
        else {
            for (let child = element.firstChild; child; child = child.nextSibling) {
                if (!child.assignedSlot)
                    visit(ariaNode, child, parentElementVisible);
            }
            if (element.shadowRoot) {
                for (let child = element.shadowRoot.firstChild; child; child = child.nextSibling)
                    visit(ariaNode, child, parentElementVisible);
            }
        }
        for (const child of ariaChildren)
            visit(ariaNode, child, parentElementVisible);
        ariaNode.children.push(roleUtils.getCSSContent(element, '::after') || '');
        if (treatAsBlock)
            ariaNode.children.push(treatAsBlock);
        if (ariaNode.children.length === 1 && ariaNode.name === ariaNode.children[0])
            ariaNode.children = [];
        if (ariaNode.role === 'link' && element.hasAttribute('href')) {
            const href = element.getAttribute('href');
            ariaNode.props['url'] = href;
        }
        if (ariaNode.role === 'textbox' && element.hasAttribute('placeholder') && element.getAttribute('placeholder') !== ariaNode.name) {
            const placeholder = element.getAttribute('placeholder');
            ariaNode.props['placeholder'] = placeholder;
        }
    }
    roleUtils.beginAriaCaches();
    try {
        visit(snapshot.root, rootElement, true);
    }
    finally {
        roleUtils.endAriaCaches();
    }
    normalizeStringChildren(snapshot.root);
    normalizeGenericRoles(snapshot.root);
    return snapshot;
}
function computeAriaRef(ariaNode, options) {
    if (options.refs === 'none')
        return;
    if (options.refs === 'interactable' && (!ariaNode.box.visible || !ariaNode.receivesPointerEvents))
        return;
    const element = ariaNodeElement(ariaNode);
    let ariaRef = element._ariaRef;
    if (!ariaRef || ariaRef.role !== ariaNode.role || ariaRef.name !== ariaNode.name) {
        ariaRef = { role: ariaNode.role, name: ariaNode.name, ref: (options.refPrefix ?? '') + 'e' + (++lastRef) };
        element._ariaRef = ariaRef;
    }
    ariaNode.ref = ariaRef.ref;
}
function toAriaNode(element, options) {
    const active = element.ownerDocument.activeElement === element;
    if (element.nodeName === 'IFRAME') {
        const ariaNode = {
            role: 'iframe',
            name: '',
            children: [],
            props: {},
            box: computeBox(element),
            receivesPointerEvents: true,
            active
        };
        setAriaNodeElement(ariaNode, element);
        computeAriaRef(ariaNode, options);
        return ariaNode;
    }
    const defaultRole = options.includeGenericRole ? 'generic' : null;
    const role = roleUtils.getAriaRole(element) ?? defaultRole;
    if (!role || role === 'presentation' || role === 'none')
        return null;
    const name = normalizeWhiteSpace(roleUtils.getElementAccessibleName(element, false) || '');
    const receivesPointerEvents = roleUtils.receivesPointerEvents(element);
    const box = computeBox(element);
    if (role === 'generic' && box.inline && element.childNodes.length === 1 && element.childNodes[0].nodeType === Node.TEXT_NODE)
        return null;
    const result = {
        role,
        name,
        children: [],
        props: {},
        box,
        receivesPointerEvents,
        active
    };
    setAriaNodeElement(result, element);
    computeAriaRef(result, options);
    if (roleUtils.kAriaCheckedRoles.includes(role))
        result.checked = roleUtils.getAriaChecked(element);
    if (roleUtils.kAriaDisabledRoles.includes(role))
        result.disabled = roleUtils.getAriaDisabled(element);
    if (roleUtils.kAriaExpandedRoles.includes(role))
        result.expanded = roleUtils.getAriaExpanded(element);
    if (roleUtils.kAriaLevelRoles.includes(role))
        result.level = roleUtils.getAriaLevel(element);
    if (roleUtils.kAriaPressedRoles.includes(role))
        result.pressed = roleUtils.getAriaPressed(element);
    if (roleUtils.kAriaSelectedRoles.includes(role))
        result.selected = roleUtils.getAriaSelected(element);
    if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
        if (element.type !== 'checkbox' && element.type !== 'radio' && element.type !== 'file')
            result.children = [element.value];
    }
    return result;
}
function normalizeGenericRoles(node) {
    const normalizeChildren = (node) => {
        const result = [];
        for (const child of node.children || []) {
            if (typeof child === 'string') {
                result.push(child);
                continue;
            }
            const normalized = normalizeChildren(child);
            result.push(...normalized);
        }
        // Only remove generic that encloses one element, logical grouping still makes sense, even if it is not ref-able.
        const removeSelf = node.role === 'generic' && !node.name && result.length <= 1 && result.every(c => typeof c !== 'string' && !!c.ref);
        if (removeSelf)
            return result;
        node.children = result;
        return [node];
    };
    normalizeChildren(node);
}
function normalizeStringChildren(rootA11yNode) {
    const flushChildren = (buffer, normalizedChildren) => {
        if (!buffer.length)
            return;
        const text = normalizeWhiteSpace(buffer.join(''));
        if (text)
            normalizedChildren.push(text);
        buffer.length = 0;
    };
    const visit = (ariaNode) => {
        const normalizedChildren = [];
        const buffer = [];
        for (const child of ariaNode.children || []) {
            if (typeof child === 'string') {
                buffer.push(child);
            }
            else {
                flushChildren(buffer, normalizedChildren);
                visit(child);
                normalizedChildren.push(child);
            }
        }
        flushChildren(buffer, normalizedChildren);
        ariaNode.children = normalizedChildren.length ? normalizedChildren : [];
        if (ariaNode.children.length === 1 && ariaNode.children[0] === ariaNode.name)
            ariaNode.children = [];
    };
    visit(rootA11yNode);
}
function matchesStringOrRegex(text, template) {
    if (!template)
        return true;
    if (!text)
        return false;
    if (typeof template === 'string')
        return text === template;
    return !!text.match(new RegExp(template.pattern));
}
function matchesTextValue(text, template) {
    if (!template?.normalized)
        return true;
    if (!text)
        return false;
    if (text === template.normalized)
        return true;
    // Accept pattern as value.
    if (text === template.raw)
        return true;
    const regex = cachedRegex(template);
    if (regex)
        return !!text.match(regex);
    return false;
}
const cachedRegexSymbol = Symbol('cachedRegex');
function cachedRegex(template) {
    if (template[cachedRegexSymbol] !== undefined)
        return template[cachedRegexSymbol];
    const { raw } = template;
    const canBeRegex = raw.startsWith('/') && raw.endsWith('/') && raw.length > 1;
    let regex;
    try {
        regex = canBeRegex ? new RegExp(raw.slice(1, -1)) : null;
    }
    catch (e) {
        regex = null;
    }
    template[cachedRegexSymbol] = regex;
    return regex;
}
export function matchesExpectAriaTemplate(rootElement, template) {
    const snapshot = generateAriaTree(rootElement, { mode: 'default' });
    const matches = matchesNodeDeep(snapshot.root, template, false, false);
    return {
        matches,
        received: {
            raw: renderAriaTree(snapshot, { mode: 'default' }).text,
            regex: renderAriaTree(snapshot, { mode: 'codegen' }).text,
        }
    };
}
export function getAllElementsMatchingExpectAriaTemplate(rootElement, template) {
    const root = generateAriaTree(rootElement, { mode: 'default' }).root;
    const matches = matchesNodeDeep(root, template, true, false);
    return matches.map(n => ariaNodeElement(n));
}
function matchesNode(node, template, isDeepEqual) {
    if (typeof node === 'string' && template.kind === 'text')
        return matchesTextValue(node, template.text);
    if (node === null || typeof node !== 'object' || template.kind !== 'role')
        return false;
    if (template.role !== 'fragment' && template.role !== node.role)
        return false;
    if (template.checked !== undefined && template.checked !== node.checked)
        return false;
    if (template.disabled !== undefined && template.disabled !== node.disabled)
        return false;
    if (template.expanded !== undefined && template.expanded !== node.expanded)
        return false;
    if (template.level !== undefined && template.level !== node.level)
        return false;
    if (template.pressed !== undefined && template.pressed !== node.pressed)
        return false;
    if (template.selected !== undefined && template.selected !== node.selected)
        return false;
    if (!matchesStringOrRegex(node.name, template.name))
        return false;
    if (!matchesTextValue(node.props.url, template.props?.url))
        return false;
    // Proceed based on the container mode.
    if (template.containerMode === 'contain')
        return containsList(node.children || [], template.children || []);
    if (template.containerMode === 'equal')
        return listEqual(node.children || [], template.children || [], false);
    if (template.containerMode === 'deep-equal' || isDeepEqual)
        return listEqual(node.children || [], template.children || [], true);
    return containsList(node.children || [], template.children || []);
}
function listEqual(children, template, isDeepEqual) {
    if (template.length !== children.length)
        return false;
    for (let i = 0; i < template.length; ++i) {
        if (!matchesNode(children[i], template[i], isDeepEqual))
            return false;
    }
    return true;
}
function containsList(children, template) {
    if (template.length > children.length)
        return false;
    const cc = children.slice();
    const tt = template.slice();
    for (const t of tt) {
        let c = cc.shift();
        while (c) {
            if (matchesNode(c, t, false))
                break;
            c = cc.shift();
        }
        if (!c)
            return false;
    }
    return true;
}
function matchesNodeDeep(root, template, collectAll, isDeepEqual) {
    const results = [];
    const visit = (node, parent) => {
        if (matchesNode(node, template, isDeepEqual)) {
            const result = typeof node === 'string' ? parent : node;
            if (result)
                results.push(result);
            return !collectAll;
        }
        if (typeof node === 'string')
            return false;
        for (const child of node.children || []) {
            if (visit(child, node))
                return true;
        }
        return false;
    };
    visit(root, null);
    return results;
}
function buildByRefMap(root, map = new Map()) {
    if (root?.ref)
        map.set(root.ref, root);
    for (const child of root?.children || []) {
        if (typeof child !== 'string')
            buildByRefMap(child, map);
    }
    return map;
}
function compareSnapshots(ariaSnapshot, previousSnapshot) {
    const previousByRef = buildByRefMap(previousSnapshot?.root);
    const result = new Map();
    // Returns whether ariaNode is the same as previousNode.
    const visit = (ariaNode, previousNode) => {
        let same = ariaNode.children.length === previousNode?.children.length && aria.ariaNodesEqual(ariaNode, previousNode);
        let canBeSkipped = same;
        for (let childIndex = 0; childIndex < ariaNode.children.length; childIndex++) {
            const child = ariaNode.children[childIndex];
            const previousChild = previousNode?.children[childIndex];
            if (typeof child === 'string') {
                same &&= child === previousChild;
                canBeSkipped &&= child === previousChild;
            }
            else {
                let previous = typeof previousChild !== 'string' ? previousChild : undefined;
                if (child.ref)
                    previous = previousByRef.get(child.ref);
                const sameChild = visit(child, previous);
                // New child, different order of children, or changed child with no ref -
                // we have to include this node to list children in the right order.
                if (!previous || (!sameChild && !child.ref) || (previous !== previousChild))
                    canBeSkipped = false;
                same &&= (sameChild && previous === previousChild);
            }
        }
        result.set(ariaNode, same ? 'same' : (canBeSkipped ? 'skip' : 'changed'));
        return same;
    };
    visit(ariaSnapshot.root, previousByRef.get(previousSnapshot?.root?.ref));
    return result;
}
// Chooses only the changed parts of the snapshot and returns them as new roots.
function filterSnapshotDiff(nodes, statusMap) {
    const result = [];
    const visit = (ariaNode) => {
        const status = statusMap.get(ariaNode);
        if (status === 'same') {
            // No need to render unchanged root at all.
        }
        else if (status === 'skip') {
            // Only render changed children.
            for (const child of ariaNode.children) {
                if (typeof child !== 'string')
                    visit(child);
            }
        }
        else {
            // Render this node's subtree.
            result.push(ariaNode);
        }
    };
    for (const node of nodes) {
        if (typeof node === 'string')
            result.push(node);
        else
            visit(node);
    }
    return result;
}
function indent(depth) {
    return '  '.repeat(depth);
}
export function renderAriaTree(ariaSnapshot, publicOptions, previousSnapshot) {
    const options = toInternalOptions(publicOptions);
    const lines = [];
    const iframeDepths = {};
    const includeText = options.renderStringsAsRegex ? textContributesInfo : () => true;
    const renderString = options.renderStringsAsRegex ? convertToBestGuessRegex : (str) => str;
    // Do not render the root fragment, just its children.
    let nodesToRender = ariaSnapshot.root.role === 'fragment' ? ariaSnapshot.root.children : [ariaSnapshot.root];
    const statusMap = compareSnapshots(ariaSnapshot, previousSnapshot);
    if (previousSnapshot)
        nodesToRender = filterSnapshotDiff(nodesToRender, statusMap);
    const visitText = (text, depth) => {
        if (publicOptions.depth && depth > publicOptions.depth)
            return;
        const escaped = yamlEscapeValueIfNeeded(renderString(text));
        if (escaped)
            lines.push(indent(depth) + '- text: ' + escaped);
    };
    const createKey = (ariaNode, renderCursorPointer) => {
        let key = ariaNode.role;
        // Yaml has a limit of 1024 characters per key, and we leave some space for role and attributes.
        if (ariaNode.name && ariaNode.name.length <= 900) {
            const name = renderString(ariaNode.name);
            if (name) {
                const stringifiedName = name.startsWith('/') && name.endsWith('/') ? name : JSON.stringify(name);
                key += ' ' + stringifiedName;
            }
        }
        if (ariaNode.checked === 'mixed')
            key += ` [checked=mixed]`;
        if (ariaNode.checked === true)
            key += ` [checked]`;
        if (ariaNode.disabled)
            key += ` [disabled]`;
        if (ariaNode.expanded)
            key += ` [expanded]`;
        if (ariaNode.active && options.renderActive)
            key += ` [active]`;
        if (ariaNode.level)
            key += ` [level=${ariaNode.level}]`;
        if (ariaNode.pressed === 'mixed')
            key += ` [pressed=mixed]`;
        if (ariaNode.pressed === true)
            key += ` [pressed]`;
        if (ariaNode.selected === true)
            key += ` [selected]`;
        if (ariaNode.ref) {
            key += ` [ref=${ariaNode.ref}]`;
            if (renderCursorPointer && aria.hasPointerCursor(ariaNode))
                key += ' [cursor=pointer]';
        }
        if (options.renderBoxes) {
            const element = ariaNodeElement(ariaNode);
            if (element) {
                const r = element.getBoundingClientRect();
                key += ` [box=${Math.round(r.x)},${Math.round(r.y)},${Math.round(r.width)},${Math.round(r.height)}]`;
            }
        }
        return key;
    };
    const getSingleInlinedTextChild = (ariaNode) => {
        return ariaNode?.children.length === 1 && typeof ariaNode.children[0] === 'string' && !Object.keys(ariaNode.props).length ? ariaNode.children[0] : undefined;
    };
    const visit = (ariaNode, depth, renderCursorPointer) => {
        if (publicOptions.depth && depth > publicOptions.depth)
            return;
        if (ariaNode.role === 'iframe' && ariaNode.ref)
            iframeDepths[ariaNode.ref] = depth;
        // Replace the whole subtree with a single reference when possible.
        if (statusMap.get(ariaNode) === 'same' && ariaNode.ref) {
            lines.push(indent(depth) + `- ref=${ariaNode.ref} [unchanged]`);
            return;
        }
        // When producing a diff, add <changed> marker to all diff roots.
        const isDiffRoot = !!previousSnapshot && !depth;
        const escapedKey = indent(depth) + '- ' + (isDiffRoot ? '<changed> ' : '') + yamlEscapeKeyIfNeeded(createKey(ariaNode, renderCursorPointer));
        const singleInlinedTextChild = getSingleInlinedTextChild(ariaNode);
        const isAtDepthLimit = !!publicOptions.depth && depth === publicOptions.depth;
        const hasNoChildren = !singleInlinedTextChild && (!ariaNode.children.length || isAtDepthLimit);
        if (hasNoChildren && !Object.keys(ariaNode.props).length) {
            // Leaf node without children.
            lines.push(escapedKey);
        }
        else if (singleInlinedTextChild !== undefined) {
            // Leaf node with just some text inside.
            const shouldInclude = includeText(ariaNode, singleInlinedTextChild);
            if (shouldInclude)
                lines.push(escapedKey + ': ' + yamlEscapeValueIfNeeded(renderString(singleInlinedTextChild)));
            else
                lines.push(escapedKey);
        }
        else {
            // Node with (optional) props and some children.
            lines.push(escapedKey + ':');
            for (const [name, value] of Object.entries(ariaNode.props))
                lines.push(indent(depth + 1) + '- /' + name + ': ' + yamlEscapeValueIfNeeded(value));
            const inCursorPointer = !!ariaNode.ref && renderCursorPointer && aria.hasPointerCursor(ariaNode);
            for (const child of ariaNode.children) {
                if (typeof child === 'string')
                    visitText(includeText(ariaNode, child) ? child : '', depth + 1);
                else
                    visit(child, depth + 1, renderCursorPointer && !inCursorPointer);
            }
        }
    };
    for (const nodeToRender of nodesToRender) {
        if (typeof nodeToRender === 'string')
            visitText(nodeToRender, 0);
        else
            visit(nodeToRender, 0, !!options.renderCursorPointer);
    }
    return { text: lines.join('\n'), iframeDepths };
}
function convertToBestGuessRegex(text) {
    const dynamicContent = [
        // 550e8400-e29b-41d4-a716-446655440000
        { regex: /\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b/, replacement: '[0-9a-fA-F-]+' },
        // 2mb
        { regex: /\b[\d,.]+[bkmBKM]+\b/, replacement: '[\\d,.]+[bkmBKM]+' },
        // 2ms, 20s
        { regex: /\b\d+[hmsp]+\b/, replacement: '\\d+[hmsp]+' },
        { regex: /\b[\d,.]+[hmsp]+\b/, replacement: '[\\d,.]+[hmsp]+' },
        // Do not replace single digits with regex by default.
        // 2+ digits: [Issue 22, 22.3, 2.33, 2,333]
        { regex: /\b\d+,\d+\b/, replacement: '\\d+,\\d+' },
        { regex: /\b\d+\.\d{2,}\b/, replacement: '\\d+\\.\\d+' },
        { regex: /\b\d{2,}\.\d+\b/, replacement: '\\d+\\.\\d+' },
        { regex: /\b\d{2,}\b/, replacement: '\\d+' },
    ];
    let pattern = '';
    let lastIndex = 0;
    const combinedRegex = new RegExp(dynamicContent.map(r => '(' + r.regex.source + ')').join('|'), 'g');
    text.replace(combinedRegex, (match, ...args) => {
        const offset = args[args.length - 2];
        const groups = args.slice(0, -2);
        pattern += escapeRegExp(text.slice(lastIndex, offset));
        for (let i = 0; i < groups.length; i++) {
            if (groups[i]) {
                const { replacement } = dynamicContent[i];
                pattern += replacement;
                break;
            }
        }
        lastIndex = offset + match.length;
        return match;
    });
    if (!pattern)
        return text;
    pattern += escapeRegExp(text.slice(lastIndex));
    return String(new RegExp(pattern));
}
function textContributesInfo(node, text) {
    if (!text.length)
        return false;
    if (!node.name)
        return true;
    // Figure out if text adds any value. "longestCommonSubstring" is expensive, so limit strings length.
    const substr = (text.length <= 200 && node.name.length <= 200) ? longestCommonSubstring(text, node.name) : '';
    let filtered = text;
    while (substr && filtered.includes(substr))
        filtered = filtered.replace(substr, '');
    return filtered.trim().length / text.length > 0.1;
}
const elementSymbol = Symbol('element');
function ariaNodeElement(ariaNode) {
    return ariaNode[elementSymbol];
}
function setAriaNodeElement(ariaNode, element) {
    ariaNode[elementSymbol] = element;
}
export function findNewElement(from, to) {
    const node = aria.findNewNode(from, to);
    return node ? ariaNodeElement(node) : undefined;
}
//# sourceMappingURL=ariaSnapshot.js.map