( el: Element, scopePrefix: string, animationIdsByHfId: Map<string, string[]>, )
| 84 | |
| 85 | // fallow-ignore-next-line complexity |
| 86 | function buildElement( |
| 87 | el: Element, |
| 88 | scopePrefix: string, |
| 89 | animationIdsByHfId: Map<string, string[]>, |
| 90 | ): HyperFramesElement | null { |
| 91 | const tag = el.tagName.toLowerCase(); |
| 92 | if (EXCLUDED_TAGS.has(tag)) return null; |
| 93 | |
| 94 | const id = el.getAttribute("data-hf-id") ?? ""; |
| 95 | if (!id) return null; // should never happen after ensureHfIds, but guard defensively |
| 96 | |
| 97 | // scopedId: if we're inside a sub-comp scope, prefix with "scopePrefix/". |
| 98 | // The host element itself is in the PARENT scope (no prefix change for its own id). |
| 99 | const scopedId = scopePrefix ? `${scopePrefix}/${id}` : id; |
| 100 | |
| 101 | // Children inherit the scope prefix from their parent. |
| 102 | // If this element is a new host boundary (starts a new sub-comp scope), its |
| 103 | // children use THIS element's scopedId as their prefix. |
| 104 | // Otherwise, children inherit the same prefix that this element used. |
| 105 | const childPrefix = isNewHostBoundary(el) ? scopedId : scopePrefix; |
| 106 | |
| 107 | const inlineStyles = getElementStyles(el); |
| 108 | |
| 109 | const classAttr = el.getAttribute("class") ?? ""; |
| 110 | const classNames = classAttr |
| 111 | .split(/\s+/) |
| 112 | .map((c) => c.trim()) |
| 113 | .filter(Boolean); |
| 114 | |
| 115 | const attributes: Record<string, string> = {}; |
| 116 | for (const attr of Array.from(el.attributes)) { |
| 117 | if (attr.name === "style" || attr.name === "class" || attr.name.startsWith("data-hf-")) { |
| 118 | continue; |
| 119 | } |
| 120 | attributes[attr.name] = attr.value; |
| 121 | } |
| 122 | |
| 123 | const startAttr = el.getAttribute("data-start"); |
| 124 | const endAttr = el.getAttribute("data-end"); |
| 125 | const trackAttr = el.getAttribute("data-track-index"); |
| 126 | |
| 127 | const start = startAttr !== null ? parseFloat(startAttr) : null; |
| 128 | const duration = |
| 129 | start !== null && endAttr !== null ? Math.max(0, parseFloat(endAttr) - start) : null; |
| 130 | const trackIndex = trackAttr !== null ? parseInt(trackAttr, 10) : null; |
| 131 | |
| 132 | const children: HyperFramesElement[] = []; |
| 133 | for (const child of Array.from(el.children)) { |
| 134 | const built = buildElement(child, childPrefix, animationIdsByHfId); |
| 135 | if (built) children.push(built); |
| 136 | } |
| 137 | |
| 138 | return { |
| 139 | id, |
| 140 | scopedId, |
| 141 | tag, |
| 142 | children, |
| 143 | inlineStyles, |
no test coverage detected