| (el, suppliedPath) => { |
| const norm = s => (s || '').replace(/\s+/g, ' ').trim().slice(0, 512); |
| const tag = el.tagName.toLowerCase(); |
| const type = (el.getAttribute('type') || 'text').toLowerCase(); |
| const auto = (el.getAttribute('autocomplete') || '').toLowerCase(); |
| const sensitive = type === 'password' || /password|cc-|one-time-code/.test(auto); |
| const roles = {button:'button', a:'link', textarea:'textbox', select:'combobox', |
| summary:'button', h1:'heading', h2:'heading', h3:'heading', p:'text', output:'status'}; |
| const inputRoles = {checkbox:'checkbox', radio:'radio', button:'button', submit:'button', range:'slider'}; |
| const role = el.getAttribute('role') || (tag === 'input' ? (inputRoles[type] || 'textbox') : roles[tag]) || 'generic'; |
| const root = el.getRootNode(); |
| const labelled = (el.getAttribute('aria-labelledby') || '').split(/\s+/) |
| .map(id => root.getElementById?.(id)?.textContent || '').join(' ').trim(); |
| const label = [...(el.labels || [])].map(x => x.textContent).join(' '); |
| const name = sensitive ? '[REDACTED]' : norm(labelled || el.getAttribute('aria-label') || label || |
| (['input','textarea','select'].includes(tag) ? (el.getAttribute('placeholder') || el.getAttribute('title')) : el.textContent)); |
| const rect = el.getBoundingClientRect(); |
| const css = getComputedStyle(el); |
| const visible = !!(el.isConnected && rect.width && rect.height && css.visibility !== 'hidden' && css.display !== 'none'); |
| const enabled = !el.matches(':disabled') && el.getAttribute('aria-disabled') !== 'true' && !el.closest('[inert]'); |
| let path = [], node = el; |
| while (suppliedPath == null && node?.nodeType === 1) { |
| const parent = node.parentNode; |
| path.push([...parent.children].indexOf(node)); |
| node = parent.host || parent; |
| } |
| return {role, name, path:suppliedPath ?? path.reverse().join('/'), visible, enabled, sensitive}; |
| } |
|
|