UNPKG

ivya

Version:

Fork of Playwright's locator resolution

1,352 lines (1,343 loc) 46.5 kB
import { A as kAriaPressedRoles, C as getElementAccessibleName, D as kAriaDisabledRoles, E as kAriaCheckedRoles, F as normalizeWhiteSpace, I as _defineProperty, M as getElementComputedStyle, O as kAriaExpandedRoles, T as isElementHiddenForAria, _ as getAriaLevel, b as getAriaSelected, d as endAriaCaches, f as getAriaChecked, j as kAriaSelectedRoles, k as kAriaLevelRoles, m as getAriaExpanded, p as getAriaDisabled, u as beginAriaCaches, v as getAriaPressed, w as getPseudoContent, y as getAriaRole } from "../src-c2dVUPx0.js"; //#region \0rolldown/runtime.js var __defProp = Object.defineProperty; var __exportAll = (all, no_symbols) => { let target = {}; for (var name in all) { __defProp(target, name, { get: all[name], enumerable: true }); } if (!no_symbols) { __defProp(target, Symbol.toStringTag, { value: "Module" }); } return target; }; //#endregion //#region src/aria/folk/isomorphic/ariaSnapshot.ts function parseAriaSnapshotUnsafe(yaml, text, options = {}) { const result = parseAriaSnapshot(yaml, text, options); if (result.errors.length) throw new Error(result.errors[0].message); return result.fragment; } function parseAriaSnapshot(yaml, text, options = {}) { var _fragment$children; const lineCounter = new yaml.LineCounter(); const parseOptions = { keepSourceTokens: true, lineCounter, ...options }; const yamlDoc = yaml.parseDocument(text, parseOptions); const errors = []; const convertRange = (range) => { return [lineCounter.linePos(range[0]), lineCounter.linePos(range[1])]; }; const addError = (error) => { errors.push({ message: error.message, range: [lineCounter.linePos(error.pos[0]), lineCounter.linePos(error.pos[1])] }); }; const convertSeq = (container, seq) => { for (const item of seq.items) { if (item instanceof yaml.Scalar && typeof item.value === "string") { const childNode = KeyParser.parse(item, parseOptions, errors); if (childNode) { container.children = container.children || []; container.children.push(childNode); } continue; } if (item instanceof yaml.YAMLMap) { convertMap(container, item); continue; } errors.push({ message: "Sequence items should be strings or maps", range: convertRange(item.range || seq.range) }); } }; const convertMap = (container, map) => { for (const entry of map.items) { container.children = container.children || []; if (!(entry.key instanceof yaml.Scalar && typeof entry.key.value === "string")) { errors.push({ message: "Only string keys are supported", range: convertRange(entry.key.range || map.range) }); continue; } const key = entry.key; const value = entry.value; if (key.value === "text") { if (!(value instanceof yaml.Scalar && typeof value.value === "string")) { errors.push({ message: "Text value should be a string", range: convertRange(entry.value.range || map.range) }); continue; } container.children.push({ kind: "text", text: textValue(value.value) }); continue; } if (key.value === "/children") { if (!(value instanceof yaml.Scalar && typeof value.value === "string") || value.value !== "contain" && value.value !== "equal" && value.value !== "deep-equal") { errors.push({ message: "Strict value should be \"contain\", \"equal\" or \"deep-equal\"", range: convertRange(entry.value.range || map.range) }); continue; } container.containerMode = value.value; continue; } if (key.value.startsWith("/")) { var _container$props; if (!(value instanceof yaml.Scalar && typeof value.value === "string")) { errors.push({ message: "Property value should be a string", range: convertRange(entry.value.range || map.range) }); continue; } container.props = (_container$props = container.props) !== null && _container$props !== void 0 ? _container$props : {}; container.props[key.value.slice(1)] = textValue(value.value); continue; } const childNode = KeyParser.parse(key, parseOptions, errors); if (!childNode) continue; if (value instanceof yaml.Scalar) { const type = typeof value.value; if (type !== "string" && type !== "number" && type !== "boolean") { errors.push({ message: "Node value should be a string or a sequence", range: convertRange(entry.value.range || map.range) }); continue; } container.children.push({ ...childNode, children: [{ kind: "text", text: textValue(String(value.value)) }] }); continue; } if (value instanceof yaml.YAMLSeq) { container.children.push(childNode); convertSeq(childNode, value); continue; } errors.push({ message: "Map values should be strings or sequences", range: convertRange(entry.value.range || map.range) }); } }; const fragment = { kind: "role", role: "fragment" }; yamlDoc.errors.forEach(addError); if (errors.length) return { errors, fragment }; if (!yamlDoc.contents) return { fragment, errors: [] }; if (!(yamlDoc.contents instanceof yaml.YAMLSeq)) errors.push({ message: "Aria snapshot must be a YAML sequence, elements starting with \" -\"", range: convertRange(yamlDoc.contents.range) }); if (errors.length) return { errors, fragment }; convertSeq(fragment, yamlDoc.contents); if (errors.length) return { errors, fragment: emptyFragment }; if (((_fragment$children = fragment.children) === null || _fragment$children === void 0 ? void 0 : _fragment$children.length) === 1 && (!fragment.containerMode || fragment.containerMode === "contain")) return { fragment: fragment.children[0], errors: [] }; return { fragment, errors: [] }; } const emptyFragment = { kind: "role", role: "fragment" }; function normalizeWhitespace(text) { return text.replace(/[\u200b\u00ad]/g, "").replace(/[\r\n\s\t]+/g, " ").trim(); } function textValue(value) { return { raw: value, normalized: normalizeWhitespace(value) }; } var KeyParser = class KeyParser { static parse(text, options, errors) { try { return new KeyParser(text.value)._parse(); } catch (e) { if (e instanceof ParserError) { const message = options.prettyErrors === false ? e.message : `${e.message}:\n\n${text.value}\n${" ".repeat(e.pos)}^\n`; errors.push({ message, range: [options.lineCounter.linePos(text.range[0]), options.lineCounter.linePos(text.range[0] + e.pos)] }); return null; } throw e; } } constructor(input) { _defineProperty(this, "_input", void 0); _defineProperty(this, "_pos", void 0); _defineProperty(this, "_length", void 0); this._input = input; this._pos = 0; this._length = input.length; } _peek() { return this._input[this._pos] || ""; } _next() { if (this._pos < this._length) return this._input[this._pos++]; return null; } _eof() { return this._pos >= this._length; } _isWhitespace() { return !this._eof() && /\s/.test(this._peek()); } _skipWhitespace() { while (this._isWhitespace()) this._pos++; } _readIdentifier(type) { if (this._eof()) this._throwError(`Unexpected end of input when expecting ${type}`); const start = this._pos; while (!this._eof() && /[a-zA-Z]/.test(this._peek())) this._pos++; return this._input.slice(start, this._pos); } _readString() { let result = ""; let escaped = false; while (!this._eof()) { const ch = this._next(); if (escaped) { result += ch; escaped = false; } else if (ch === "\\") escaped = true; else if (ch === "\"") return result; else result += ch; } this._throwError("Unterminated string"); } _throwError(message, offset = 0) { throw new ParserError(message, offset || this._pos); } _readRegex() { let result = ""; let escaped = false; let insideClass = false; while (!this._eof()) { const ch = this._next(); if (escaped) { result += ch; escaped = false; } else if (ch === "\\") { escaped = true; result += ch; } else if (ch === "/" && !insideClass) return { pattern: result }; else if (ch === "[") { insideClass = true; result += ch; } else if (ch === "]" && insideClass) { result += ch; insideClass = false; } else result += ch; } this._throwError("Unterminated regex"); } _readStringOrRegex() { const ch = this._peek(); if (ch === "\"") { this._next(); return normalizeWhitespace(this._readString()); } if (ch === "/") { this._next(); return this._readRegex(); } return null; } _readAttributes(result) { let errorPos = this._pos; while (true) { this._skipWhitespace(); if (this._peek() === "[") { this._next(); this._skipWhitespace(); errorPos = this._pos; const flagName = this._readIdentifier("attribute"); this._skipWhitespace(); let flagValue = ""; if (this._peek() === "=") { this._next(); this._skipWhitespace(); errorPos = this._pos; while (this._peek() !== "]" && !this._isWhitespace() && !this._eof()) flagValue += this._next(); } this._skipWhitespace(); if (this._peek() !== "]") this._throwError("Expected ]"); this._next(); this._applyAttribute(result, flagName, flagValue || "true", errorPos); } else break; } } _parse() { this._skipWhitespace(); const role = this._readIdentifier("role"); if (role === "fragment") this._throwError("Invalid role \"fragment\""); this._skipWhitespace(); const result = { kind: "role", role, name: this._readStringOrRegex() || void 0 }; this._readAttributes(result); this._skipWhitespace(); if (!this._eof()) this._throwError("Unexpected input"); return result; } _applyAttribute(node, key, value, errorPos) { if (key === "checked") { this._assert(value === "true" || value === "false" || value === "mixed", "Value of \"checked\" attribute must be a boolean or \"mixed\"", errorPos); node.checked = value === "true" ? true : value === "false" ? false : "mixed"; return; } if (key === "disabled") { this._assert(value === "true" || value === "false", "Value of \"disabled\" attribute must be a boolean", errorPos); node.disabled = value === "true"; return; } if (key === "expanded") { this._assert(value === "true" || value === "false", "Value of \"expanded\" attribute must be a boolean", errorPos); node.expanded = value === "true"; return; } if (key === "active") { this._assert(value === "true" || value === "false", "Value of \"active\" attribute must be a boolean", errorPos); node.active = value === "true"; return; } if (key === "level") { this._assert(!Number.isNaN(Number(value)), "Value of \"level\" attribute must be a number", errorPos); node.level = Number(value); return; } if (key === "pressed") { this._assert(value === "true" || value === "false" || value === "mixed", "Value of \"pressed\" attribute must be a boolean or \"mixed\"", errorPos); node.pressed = value === "true" ? true : value === "false" ? false : "mixed"; return; } if (key === "selected") { this._assert(value === "true" || value === "false", "Value of \"selected\" attribute must be a boolean", errorPos); node.selected = value === "true"; return; } this._assert(false, `Unsupported attribute [${key}]`, errorPos); } _assert(value, message, valuePos) { if (!value) this._throwError(message || "Assertion error", valuePos); } }; var ParserError = class extends Error { constructor(message, pos) { super(message); _defineProperty(this, "pos", void 0); this.pos = pos; } }; 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 === null || template === void 0 ? void 0 : template.normalized)) return true; if (!text) return false; if (text === template.normalized) return true; 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] !== void 0) 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 (_unused) { regex = null; } template[cachedRegexSymbol] = regex; return regex; } /** * Full-depth recursive match: checks the node, its attributes, and all * descendants against the template. The `isDeepEqual` parameter does NOT * control recursion depth — matching is always full-depth. It only * controls the *child list mode* when `template.containerMode` is unset: * - `false` → default contain semantics (template children are a subsequence) * - `true` → inherited deep-equal (positional 1:1, propagated to descendants) * When `template.containerMode` is explicitly set (e.g. via `/children: equal`), * it takes precedence over `isDeepEqual`. */ 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 !== void 0 && template.checked !== node.checked) return false; if (template.disabled !== void 0 && template.disabled !== node.disabled) return false; if (template.expanded !== void 0 && template.expanded !== node.expanded) return false; if (template.level !== void 0 && template.level !== node.level) return false; if (template.pressed !== void 0 && template.pressed !== node.pressed) return false; if (template.selected !== void 0 && template.selected !== node.selected) return false; if (!matchesStringOrRegex(node.name, template.name)) return false; if (template.props) { for (const [key, value] of Object.entries(template.props)) if (!matchesTextValue(node.props[key] || "", value)) return false; } 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; } //#endregion //#region src/aria/yaml.ts var yaml_exports = /* @__PURE__ */ __exportAll({ LineCounter: () => LineCounter, Scalar: () => Scalar, YAMLError: () => YAMLError, YAMLMap: () => YAMLMap, YAMLSeq: () => YAMLSeq, parseDocument: () => parseDocument }); var Scalar = class { constructor(value, range = [ 0, 0, 0 ]) { _defineProperty(this, "value", void 0); _defineProperty(this, "range", void 0); this.value = value; this.range = range; } }; var YAMLMap = class { constructor(range = [ 0, 0, 0 ]) { _defineProperty(this, "items", void 0); _defineProperty(this, "range", void 0); this.items = []; this.range = range; } }; var YAMLSeq = class { constructor(range = [ 0, 0, 0 ]) { _defineProperty(this, "items", void 0); _defineProperty(this, "range", void 0); this.items = []; this.range = range; } }; var LineCounter = class { constructor() { _defineProperty(this, "lineStarts", [0]); } addNewLine(offset) { if (offset > this.lineStarts[this.lineStarts.length - 1]) this.lineStarts.push(offset); } linePos(offset) { let low = 0; let high = this.lineStarts.length - 1; while (low < high) { const mid = low + high + 1 >> 1; if (this.lineStarts[mid] <= offset) low = mid; else high = mid - 1; } return { line: low + 1, col: offset - this.lineStarts[low] + 1 }; } }; var YAMLError = class extends Error { constructor(message, pos) { super(message); _defineProperty(this, "pos", void 0); this.pos = pos; } }; function parseDocument(text, options = {}) { const lineCounter = options.lineCounter; const errors = []; if (lineCounter) { for (let i = 0; i < text.length; i++) if (text[i] === "\n") lineCounter.addNewLine(i + 1); } try { return { contents: new Parser(text, errors).parseRoot(), errors }; } catch (e) { if (e instanceof YAMLError) { errors.push(e); return { contents: null, errors }; } throw e; } } /** * Single-pass line-based recursive descent parser. * * Call graph (recursion points are parseNode calls): * * parseRoot * → parseNode(minIndent) * → parseSequence(baseIndent) * → parseNode(baseIndent + 1) "- \n" then indented child * → parseInlineMap(contentIndent) "- key: value" * → parseScalarValue inline value * → parseMapValue(contentIndent) "- key:\n" then child * → parseNode(contentIndent + 1) indented child * → parseNode(contentIndent) block seq at same indent * → parseScalarValue "- scalar" * → parseMap(baseIndent) * → parseScalarValue inline value * → parseMapValue "key:\n" then child * → parseScalarValue * → parseQuotedScalar starts with " */ var Parser = class { constructor(text, errors) { _defineProperty(this, "lines", void 0); _defineProperty(this, "pos", void 0); _defineProperty(this, "text", void 0); _defineProperty(this, "errors", void 0); this.text = text; this.errors = errors; this.lines = []; this.pos = 0; let offset = 0; const rawLines = text.split("\n"); for (const raw of rawLines) { const stripped = raw.replace(/\r$/, ""); const trimmed = stripped.replace(/^\s+/, ""); const indent = stripped.length - trimmed.length; this.lines.push({ indent, content: trimmed, offset: offset + indent, lineOffset: offset, raw: stripped }); offset += raw.length + 1; } while (this.lines.length > 0 && this.lines[this.lines.length - 1].content === "") this.lines.pop(); } parseRoot() { if (this.lines.length === 0) return null; const result = this.parseNode(0); this.skipEmpty(); if (this.pos < this.lines.length) { const line = this.currentLine(); this.addError("Unexpected scalar at node end", line.offset); } return result; } currentLine() { return this.lines[this.pos]; } parseNode(minIndent) { this.skipEmpty(); const line = this.currentLine(); if (!line || line.indent < minIndent) return null; if (line.content.startsWith("- ") || line.content === "-") return this.parseSequence(line.indent); if (this.isMapEntry(line.content)) return this.parseMap(line.indent); this.pos++; return this.parseScalarValue(line.content, line.offset, line); } parseSequence(baseIndent) { const startLine = this.currentLine(); const seq = new YAMLSeq([ startLine.offset - startLine.indent, 0, 0 ]); let lastOffset = startLine.offset; while (this.pos < this.lines.length) { this.skipEmpty(); const line = this.currentLine(); if (!line || line.indent < baseIndent) break; if (line.indent > baseIndent) { this.addError("Bad indentation of a sequence entry", line.offset); break; } if (!line.content.startsWith("- ") && line.content !== "-") break; const dashLen = line.content === "-" ? 1 : 2; const itemContent = line.content.slice(dashLen); const itemOffset = line.offset + dashLen; this.pos++; if (itemContent === "" || itemContent.trim() === "") { const child = this.parseNode(baseIndent + 1); if (child) { seq.items.push(child); lastOffset = this.peekLastOffset(); } } else if (this.isMapEntry(itemContent)) { const map = this.parseInlineMap(itemContent, itemOffset, baseIndent + dashLen, line); seq.items.push(map); lastOffset = map.range[2]; } else { const scalar = this.parseScalarValue(itemContent, itemOffset, line); seq.items.push(scalar); lastOffset = scalar.range[2]; } } seq.range[1] = lastOffset; seq.range[2] = lastOffset; return seq; } parseMap(baseIndent) { const startLine = this.currentLine(); const map = new YAMLMap([ startLine.offset - startLine.indent, 0, 0 ]); let lastOffset = startLine.offset; while (this.pos < this.lines.length) { this.skipEmpty(); const line = this.currentLine(); if (!line || line.indent < baseIndent) break; if (line.indent > baseIndent) { this.addError("Bad indentation of a mapping entry", line.offset); break; } if (!this.isMapEntry(line.content)) break; const { key, valueStr, colonOffset, valueOffset } = this.splitMapEntry(line.content, line.offset); const keyScalar = new Scalar(key, [ line.offset, line.offset + key.length, colonOffset ]); this.pos++; let value; if (valueStr === "") value = this.parseMapValue(baseIndent, colonOffset); else value = this.parseScalarValue(valueStr.trim(), valueOffset, line); map.items.push({ key: keyScalar, value }); lastOffset = value ? this.getNodeEnd(value) : colonOffset + 1; } map.range[1] = lastOffset; map.range[2] = lastOffset; return map; } /** * Parse an inline map that starts after "- " in a sequence. * E.g. "- key: value" or "- key:\n - child" * May continue with more entries at the same indent level. */ parseInlineMap(firstEntry, entryOffset, contentIndent, _sourceLine) { const map = new YAMLMap([ entryOffset, 0, 0 ]); let lastOffset = entryOffset; const { key, valueStr, colonOffset, valueOffset } = this.splitMapEntry(firstEntry, entryOffset); const keyScalar = new Scalar(key, [ entryOffset, entryOffset + key.length, colonOffset ]); let value; if (valueStr === "") value = this.parseMapValue(contentIndent, colonOffset); else value = this.parseScalarValue(valueStr.trim(), valueOffset, _sourceLine); map.items.push({ key: keyScalar, value }); lastOffset = value ? this.getNodeEnd(value) : colonOffset + 1; while (this.pos < this.lines.length) { this.skipEmpty(); const line = this.currentLine(); if (!line || line.indent < contentIndent) break; if (line.indent > contentIndent) break; if (!this.isMapEntry(line.content)) break; if (line.content.startsWith("- ")) break; const entry = this.splitMapEntry(line.content, line.offset); const ks = new Scalar(entry.key, [ line.offset, line.offset + entry.key.length, entry.colonOffset ]); this.pos++; let v; if (entry.valueStr === "") v = this.parseMapValue(contentIndent, entry.colonOffset); else v = this.parseScalarValue(entry.valueStr.trim(), entry.valueOffset, line); map.items.push({ key: ks, value: v }); lastOffset = v ? this.getNodeEnd(v) : entry.colonOffset + 1; } map.range[1] = lastOffset; map.range[2] = lastOffset; return map; } /** * Parse the value of a map entry when the value is on subsequent lines. * In YAML, a block sequence can start at the same indent as the key, * but other block collections must be indented further. */ parseMapValue(contentIndent, colonOffset) { this.skipEmpty(); const nextLine = this.currentLine(); if (!nextLine || nextLine.indent < contentIndent) return new Scalar(null, [ colonOffset + 1, colonOffset + 1, colonOffset + 1 ]); if (nextLine.indent === contentIndent && (nextLine.content.startsWith("- ") || nextLine.content === "-")) return this.parseNode(contentIndent); if (nextLine.indent > contentIndent) return this.parseNode(contentIndent + 1); return new Scalar(null, [ colonOffset + 1, colonOffset + 1, colonOffset + 1 ]); } getNodeEnd(node) { return node.range[2]; } peekLastOffset() { if (this.pos > 0 && this.pos <= this.lines.length) { const prev = this.lines[this.pos - 1]; return prev.lineOffset + prev.raw.length; } return this.text.length; } parseScalarValue(raw, offset, line) { const trimmed = raw.trim(); const adjOffset = offset + raw.indexOf(trimmed); const end = adjOffset + trimmed.length; const lineEnd = line.lineOffset + line.raw.length; if (trimmed.startsWith("\"")) return this.parseQuotedScalar(trimmed, adjOffset, lineEnd); if (trimmed === "true" || trimmed === "false") return new Scalar(trimmed === "true", [ adjOffset, end, lineEnd ]); if (trimmed === "null" || trimmed === "~") return new Scalar(null, [ adjOffset, end, lineEnd ]); if (trimmed !== "" && isNumeric(trimmed)) return new Scalar(Number(trimmed), [ adjOffset, end, lineEnd ]); return new Scalar(trimmed, [ adjOffset, end, lineEnd ]); } parseQuotedScalar(raw, offset, lineEnd) { let result = ""; let i = 1; while (i < raw.length) { const ch = raw[i]; if (ch === "\\") { i++; if (i >= raw.length) { this.addError("Unterminated double-quoted string", offset + i); break; } const esc = raw[i]; switch (esc) { case "n": result += "\n"; break; case "t": result += " "; break; case "r": result += "\r"; break; case "\"": result += "\""; break; case "\\": result += "\\"; break; case "/": result += "/"; break; default: result += esc; } } else if (ch === "\"") { const end = offset + i + 1; return new Scalar(result, [ offset, end, lineEnd ]); } else result += ch; i++; } this.addError("Unterminated double-quoted string", offset); return new Scalar(result, [ offset, offset + raw.length, lineEnd ]); } skipEmpty() { while (this.pos < this.lines.length && this.lines[this.pos].content === "") this.pos++; } /** * Check if content looks like a map entry: `key: value` or `key:`. * Must not start with "- ". * The colon must be followed by a space or end of string. */ isMapEntry(content) { return this.findMapColon(content) >= 0; } /** * Find the colon index for a map entry. * Skip colons inside double-quoted strings. */ findMapColon(content) { let inQuote = false; let escaped = false; for (let i = 0; i < content.length; i++) { const ch = content[i]; if (escaped) { escaped = false; continue; } if (ch === "\\" && inQuote) { escaped = true; continue; } if (ch === "\"") { inQuote = !inQuote; continue; } if (!inQuote && ch === ":") { if (i + 1 >= content.length || content[i + 1] === " ") return i; } } return -1; } splitMapEntry(content, baseOffset) { const colonIdx = this.findMapColon(content); const key = content.slice(0, colonIdx).trim(); const colonOffset = baseOffset + colonIdx; const afterColon = content.slice(colonIdx + 1); const valueStr = afterColon.trimStart(); const valueOffset = colonOffset + 1 + (afterColon.length - afterColon.trimStart().length); if (key.startsWith("\"") && key.endsWith("\"")) return { key: key.slice(1, -1), valueStr, colonOffset, valueOffset }; return { key, valueStr, colonOffset, valueOffset }; } addError(message, offset) { this.errors.push(new YAMLError(message, [offset, offset + 1])); } }; function isNumeric(str) { if (str === "" || str === "-" || str === "+") return false; return /^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(str); } //#endregion //#region src/aria/folk/isomorphic/yaml.ts function yamlEscapeValueIfNeeded(str) { if (!yamlStringNeedsQuotes(str)) return str; return "\"" + str.replace(/[\\"\x00-\x1f\x7f-\x9f]/g, (c) => { switch (c) { case "\\": return "\\\\"; case "\"": return "\\\""; case "\b": return "\\b"; case "\f": return "\\f"; case "\n": return "\\n"; case "\r": return "\\r"; case " ": return "\\t"; default: return "\\x" + c.charCodeAt(0).toString(16).padStart(2, "0"); } }) + "\""; } function yamlStringNeedsQuotes(str) { if (str.length === 0) return true; if (/^\s|\s$/.test(str)) return true; if (/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]/.test(str)) return true; if (/^-/.test(str)) return true; if (/[\n:](\s|$)/.test(str)) return true; if (/\s#/.test(str)) return true; if (/[\n\r]/.test(str)) return true; if (/^[&*\],?!>|@"'#%]/.test(str)) return true; if (/[{}`]/.test(str)) return true; if (/^\[/.test(str)) return true; if (!isNaN(Number(str)) || [ "y", "n", "yes", "no", "true", "false", "on", "off", "null" ].includes(str.toLowerCase())) return true; return false; } //#endregion //#region src/aria/folk/injected/ariaSnapshot.ts const defaultBox = { visible: true, inline: false }; function generateAriaTree(rootElement) { const visited = /* @__PURE__ */ new Set(); const root = { role: "fragment", name: "", children: [], props: {}, box: defaultBox, receivesPointerEvents: true }; 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; if (ariaNode.role !== "textbox" && text) ariaNode.children.push(node.nodeValue || ""); return; } if (node.nodeType !== Node.ELEMENT_NODE) return; const element = node; const visible = !isElementHiddenForAria(element); if (!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 = toAriaNode(element); if (childAriaNode) ariaNode.children.push(childAriaNode); processElement(childAriaNode || ariaNode, element, ariaChildren, visible); }; function processElement(ariaNode, element, ariaChildren, parentElementVisible) { var _getElementComputedSt; const treatAsBlock = (((_getElementComputedSt = getElementComputedStyle(element)) === null || _getElementComputedSt === void 0 ? void 0 : _getElementComputedSt.display) || "inline") !== "inline" || element.nodeName === "BR" ? " " : ""; if (treatAsBlock) ariaNode.children.push(treatAsBlock); ariaNode.children.push(getPseudoContent(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(getPseudoContent(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; } } beginAriaCaches(); try { visit(root, rootElement, true); } finally { endAriaCaches(); } normalizeStringChildren(root); return root; } function toAriaNode(element) { var _roleUtils$getAriaRol; const role = (_roleUtils$getAriaRol = getAriaRole(element)) !== null && _roleUtils$getAriaRol !== void 0 ? _roleUtils$getAriaRol : null; if (!role || role === "presentation" || role === "none") return null; const result = { role, name: normalizeWhiteSpace(getElementAccessibleName(element, false) || ""), children: [], props: {}, box: defaultBox, receivesPointerEvents: true }; if (kAriaCheckedRoles.includes(role)) result.checked = getAriaChecked(element); if (kAriaDisabledRoles.includes(role)) result.disabled = getAriaDisabled(element) || void 0; if (kAriaExpandedRoles.includes(role)) { const expanded = getAriaExpanded(element); result.expanded = expanded === "none" ? void 0 : expanded; } if (kAriaLevelRoles.includes(role)) result.level = getAriaLevel(element); if (kAriaPressedRoles.includes(role)) result.pressed = getAriaPressed(element); if (kAriaSelectedRoles.includes(role)) result.selected = 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 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 renderAriaProps(props, options) { let s = ""; if (props.level) s += ` [level=${props.level}]`; if (props.checked === true) s += " [checked]"; if (props.checked === "mixed") s += " [checked=mixed]"; if (props.disabled) s += " [disabled]"; if (props.expanded === true) s += " [expanded]"; if (props.expanded === false && (options === null || options === void 0 ? void 0 : options.renderExpandedFalse)) s += " [expanded=false]"; if (props.pressed === true) s += " [pressed]"; if (props.pressed === "mixed") s += " [pressed=mixed]"; if (props.selected) s += " [selected]"; return s; } function createAriaKey(ariaNode) { let key = ariaNode.role; if (ariaNode.name && ariaNode.name.length <= 900) key += ` ${JSON.stringify(ariaNode.name)}`; key += renderAriaProps(ariaNode); return key; } function renderNodeLines(node, indent, lines) { const key = createAriaKey(node); const hasProps = Object.keys(node.props).length > 0; const singleTextChild = node.children.length === 1 && typeof node.children[0] === "string" && !hasProps ? node.children[0] : void 0; if (!node.children.length && !hasProps) lines.push(`${indent}- ${key}`); else if (singleTextChild !== void 0) lines.push(`${indent}- ${key}: ${yamlEscapeValueIfNeeded(singleTextChild)}`); else { lines.push(`${indent}- ${key}:`); for (const [name, value] of Object.entries(node.props)) lines.push(`${indent} - /${name}: ${yamlEscapeValueIfNeeded(value)}`); for (const child of node.children) if (typeof child === "string") { if (child) lines.push(`${indent} - text: ${yamlEscapeValueIfNeeded(child)}`); } else renderNodeLines(child, `${indent} `, lines); } } function renderAriaTree(root) { const lines = []; const nodesToRender = root.role === "fragment" ? root.children : [root]; for (const nodeToRender of nodesToRender) if (typeof nodeToRender === "string") { if (nodeToRender) lines.push(`- text: ${yamlEscapeValueIfNeeded(nodeToRender)}`); } else renderNodeLines(nodeToRender, "", lines); return lines.join("\n"); } //#endregion //#region src/aria/template.ts function formatTextValue(tv) { if (cachedRegex(tv)) return `/${tv.raw.slice(1, -1)}/`; return yamlEscapeValueIfNeeded(tv.normalized); } function formatNameValue(name) { if (typeof name === "string") return JSON.stringify(name); return `/${name.pattern}/`; } function renderAriaTemplate(template) { const lines = []; if (template.kind === "text") lines.push(`- text: ${formatTextValue(template.text)}`); else if (template.role === "fragment") { if (template.containerMode && template.containerMode !== "contain") lines.push(`- /children: ${template.containerMode}`); for (const child of template.children || []) renderTemplateLines(child, "", lines); } else renderTemplateLines(template, "", lines); return lines.join("\n"); } function renderTemplateKey(tmpl) { let key = tmpl.role; if (tmpl.name !== void 0) key += ` ${formatNameValue(tmpl.name)}`; key += renderAriaProps(tmpl); return key; } function renderTemplateLines(node, indent, lines) { if (node.kind === "text") { lines.push(`${indent}- text: ${formatTextValue(node.text)}`); return; } const key = renderTemplateKey(node); const children = node.children || []; const pseudoLines = []; if (node.containerMode && node.containerMode !== "contain") pseudoLines.push(`${indent} - /children: ${node.containerMode}`); if (node.props) for (const [name, tv] of Object.entries(node.props)) pseudoLines.push(`${indent} - /${name}: ${formatTextValue(tv)}`); if (children.length === 0 && pseudoLines.length === 0) { lines.push(`${indent}- ${key}`); return; } if (children.length === 1 && children[0].kind === "text" && pseudoLines.length === 0) { lines.push(`${indent}- ${key}: ${formatTextValue(children[0].text)}`); return; } lines.push(`${indent}- ${key}:`); lines.push(...pseudoLines); for (const child of children) renderTemplateLines(child, `${indent} `, lines); } //#endregion //#region src/aria/match.ts function matchAriaTree(root, template) { var _template$children; const normalizedRoot = root.role === "fragment" ? root.children : [root]; const normalizedTemplate = isAriaTemplateFragement(template) ? (_template$children = template.children) !== null && _template$children !== void 0 ? _template$children : [] : [template]; const containerMode = isAriaTemplateFragement(template) ? template.containerMode : void 0; const result = mergeChildLists(normalizedRoot, normalizedTemplate, "", containerMode); if (result.pass && containerMode && containerMode !== "contain") result.resolved.unshift(`- /children: ${containerMode}`); return { pass: result.pass, resolved: result.resolved.join("\n") }; } function isAriaTemplateFragement(template) { return template.kind === "role" && template.role === "fragment"; } function isRegexName(name) { return typeof name === "object" && name !== null && "pattern" in name; } /** Build the resolved key through the template's lens: * only include name/attributes that the template mentions. */ function renderResolvedKey(node, template) { let key = node.role; if (template.name === void 0) {} else if (isRegexName(template.name) && matchesStringOrRegex(node.name, template.name)) key += ` ${formatNameValue(template.name)}`; else if (node.name) key += ` ${JSON.stringify(node.name)}`; if (template.level !== void 0) key += ` [level=${node.level}]`; if (template.checked !== void 0) { if (node.checked === true) key += " [checked]"; else if (node.checked === "mixed") key += " [checked=mixed]"; } if (template.disabled !== void 0 && node.disabled) key += " [disabled]"; if (template.expanded !== void 0) { if (node.expanded === true) key += " [expanded]"; else if (node.expanded === false) key += " [expanded=false]"; } if (template.pressed !== void 0) { if (node.pressed === true) key += " [pressed]"; else if (node.pressed === "mixed") key += " [pressed=mixed]"; } if (template.selected !== void 0 && node.selected) key += " [selected]"; return key; } function pairChildren(children, templates) { const pairs = /* @__PURE__ */ new Map(); let ti = 0; for (let ci = 0; ci < children.length && ti < templates.length; ci++) if (matchesNode(children[ci], templates[ti], false)) { pairs.set(ci, ti); ti++; } return pairs; } /** Pass 2: O(C × T) unordered exact match to recover pairs that pass 1's * greedy left-to-right scan missed (e.g. due to ordering differences). * Only affects merge output quality, not pass/fail. */ function pairChildrenFull(children, templates) { const pairs = /* @__PURE__ */ new Map(); const pairedChildren = /* @__PURE__ */ new Set(); for (let ti = 0; ti < templates.length; ti++) for (let ci = 0; ci < children.length; ci++) { if (pairedChildren.has(ci)) continue; if (matchesNode(children[ci], templates[ti], false)) { pairs.set(ci, ti); pairedChildren.add(ci); break; } } return pairs; } function renderChildLines(child, indent) { const lines = []; if (typeof child === "string") lines.push(`${indent}- text: ${child}`); else renderNodeLines(child, indent, lines); return lines; } function mergeChildLists(children, templates, indent, containerMode) { if (children.some((c) => typeof c !== "string" && c.role === "fragment") || templates.some((t) => isAriaTemplateFragement(t))) throw new Error("Internal error: fragment wrappers must be unwrapped at matchAriaTree root"); if (containerMode === "equal" || containerMode === "deep-equal") return mergeChildListsEqual(children, templates, indent, containerMode === "deep-equal"); const resolved = []; const pairs = pairChildren(children, templates); if (!(templates.length === pairs.size)) { const recoveredPairs = pairChildrenFull(children, templates); for (let ci = 0; ci < children.length; ci++) { const ti = recoveredPairs.get(ci); if (ti !== void 0) resolved.push(...mergeNode(children[ci], templates[ti], indent)); else resolved.push(...renderChildLines(children[ci], indent)); } return { resolved, pass: false }; } for (let ci = 0; ci < children.length; ci++) { const ti = pairs.get(ci); if (ti !== void 0) resolved.push(...mergeNode(children[ci], templates[ti], indent)); } return { resolved, pass: true }; } /** Equal/deep-equal mode: positional 1:1 matching. Pass iff same count * and every positional pair matches (full-depth). When isDeepEqual is * true, descendant nodes without explicit containerMode inherit equal * semantics. */ function mergeChildListsEqual(children, templates, indent, isDeepEqual) { const resolved = []; const allPositionalMatched = children.length === templates.length && children.every((c, i) => matchesNode(c, templates[i], isDeepEqual)); for (let ci = 0; ci < children.length; ci++) if (ci < templates.length) resolved.push(...mergeNode(children[ci], templates[ci], indent, isDeepEqual)); else resolved.push(...renderChildLines(children[ci], indent)); return { resolved, pass: allPositionalMatched }; } /** Render a single actual node through the template's lens. * * Intentionally returns only resolved lines, not pass/fail. This is * the unusual part of the design: pass/fail is decided *before* this * function is called, by mergeChildLists (which uses matchesNode to * pair children and determine pass). mergeNode is called only after * pairing is settled — it doesn't need to re-decide, and couldn't * change the outcome if it tried, because list-level decisions (which * children to pair, which to skip) have already been made. */ function mergeNode(node, template, indent, isDeepEqual = false) { var _template$containerMo; if (typeof node === "string" && template.kind === "text") return [`${indent}- text: ${matchesTextValue(node, template.text) && cachedRegex(template.text) ? formatTextValue(template.text) : node}`]; if (typeof node === "string" || template.kind === "text") return renderChildLines(node, indent); const resolvedKey = renderResolvedKey(node, template); const childResult = mergeChildLists(node.children, template.children || [], `${indent} `, (_template$containerMo = template.containerMode) !== null && _template$containerMo !== void 0 ? _template$containerMo : isDeepEqual ? "equal" : "contain"); const resolvedPseudo = []; if (template.containerMode && template.containerMode !== "contain") resolvedPseudo.push(`${indent} - /children: ${template.containerMode}`); const allPropKeys = new Set([...Object.keys(node.props), ...Object.keys(template.props || {})]); for (const prop of allPropKeys) { var _template$props; const nodeVal = node.props[prop]; const tmplVal = (_template$props = template.props) === null || _template$props === void 0 ? void 0 : _template$props[prop]; if (nodeVal !== void 0) { const display = (tmplVal === void 0 || matchesTextValue(nodeVal, tmplVal)) && tmplVal && cachedRegex(tmplVal) ? formatTextValue(tmplVal) : nodeVal; resolvedPseudo.push(`${indent} - /${prop}: ${display}`); } } const resolved = []; if (!childResult.resolved.length && !resolvedPseudo.length) resolved.push(`${indent}- ${resolvedKey}`); else if (childResult.resolved.length === 1 && childResult.resolved[0].trimStart().startsWith("- text: ") && !resolvedPseudo.length) { const text = childResult.resolved[0].trimStart().slice(8); resolved.push(`${indent}- ${resolvedKey}: ${text}`); } else { resolved.push(`${indent}- ${resolvedKey}:`); resolved.push(...resolvedPseudo); resolved.push(...childResult.resolved); } return resolved; } //#endregion //#region src/aria/index.ts /** * ARIA snapshot: capture, render, parse, match. * * Re-exports the Playwright-forked folk/ layer and adds vitest-specific * three-way merge matching (matchAriaTree) on top. * */ function parseAriaTemplate(text) { return parseAriaSnapshotUnsafe(yaml_exports, text); } //#endregion export { generateAriaTree, matchAriaTree, parseAriaTemplate, renderAriaTemplate, renderAriaTree };