(target: HTMLElement)
| 101 | * Identifies an element and returns a human-readable name + path |
| 102 | */ |
| 103 | export function identifyElement(target: HTMLElement): { name: string; path: string } { |
| 104 | const path = getElementPath(target); |
| 105 | |
| 106 | if (target.dataset.element) { |
| 107 | return { name: target.dataset.element, path }; |
| 108 | } |
| 109 | |
| 110 | const tag = target.tagName.toLowerCase(); |
| 111 | |
| 112 | // SVG elements |
| 113 | if (["path", "circle", "rect", "line", "g"].includes(tag)) { |
| 114 | // Try to find parent SVG context (crossing shadow boundaries) |
| 115 | const svg = closestCrossingShadow(target, "svg"); |
| 116 | if (svg) { |
| 117 | const parent = getParentElement(svg); |
| 118 | if (parent instanceof HTMLElement) { |
| 119 | const parentName = identifyElement(parent).name; |
| 120 | return { name: `graphic in ${parentName}`, path }; |
| 121 | } |
| 122 | } |
| 123 | return { name: "graphic element", path }; |
| 124 | } |
| 125 | if (tag === "svg") { |
| 126 | const parent = getParentElement(target); |
| 127 | if (parent?.tagName.toLowerCase() === "button") { |
| 128 | const btnText = parent.textContent?.trim(); |
| 129 | return { name: btnText ? `icon in "${btnText}" button` : "button icon", path }; |
| 130 | } |
| 131 | return { name: "icon", path }; |
| 132 | } |
| 133 | |
| 134 | // Interactive elements |
| 135 | if (tag === "button") { |
| 136 | const text = target.textContent?.trim(); |
| 137 | const ariaLabel = target.getAttribute("aria-label"); |
| 138 | if (ariaLabel) return { name: `button [${ariaLabel}]`, path }; |
| 139 | return { name: text ? `button "${text.slice(0, 25)}"` : "button", path }; |
| 140 | } |
| 141 | if (tag === "a") { |
| 142 | const text = target.textContent?.trim(); |
| 143 | const href = target.getAttribute("href"); |
| 144 | if (text) return { name: `link "${text.slice(0, 25)}"`, path }; |
| 145 | if (href) return { name: `link to ${href.slice(0, 30)}`, path }; |
| 146 | return { name: "link", path }; |
| 147 | } |
| 148 | if (tag === "input") { |
| 149 | const type = target.getAttribute("type") || "text"; |
| 150 | const placeholder = target.getAttribute("placeholder"); |
| 151 | const name = target.getAttribute("name"); |
| 152 | if (placeholder) return { name: `input "${placeholder}"`, path }; |
| 153 | if (name) return { name: `input [${name}]`, path }; |
| 154 | return { name: `${type} input`, path }; |
| 155 | } |
| 156 | |
| 157 | // Headings |
| 158 | if (["h1", "h2", "h3", "h4", "h5", "h6"].includes(tag)) { |
| 159 | const text = target.textContent?.trim(); |
| 160 | return { name: text ? `${tag} "${text.slice(0, 35)}"` : tag, path }; |
no test coverage detected
searching dependent graphs…