(document: Document, id: string)
| 69 | * element inside a sub-composition even when bare ids collide. |
| 70 | */ |
| 71 | export function resolveScoped(document: Document, id: string): Element | null { |
| 72 | const parts = id.split("/"); |
| 73 | |
| 74 | // Bare id: prefer the canonical (top-level) match when one exists, so |
| 75 | // resolution agrees with getElement (scopedId === id wins over document order). |
| 76 | if (parts.length === 1) { |
| 77 | const escaped = escapeHfId(id); |
| 78 | const matches = Array.from(document.querySelectorAll(`[data-hf-id="${escaped}"]`)); |
| 79 | if (matches.length > 0) { |
| 80 | return matches.find((el) => isCanonicalScope(el)) ?? matches[0] ?? null; |
| 81 | } |
| 82 | // Fall back to a sub-composition ROOT addressed by its composition id. A |
| 83 | // host element carries data-hf-id (its own leaf id) AND data-composition-id |
| 84 | // (the id studio passes when targeting the sub-comp root). data-hf-id takes |
| 85 | // precedence above; only when no hf-id matches do we treat the bare id as a |
| 86 | // composition id, making comp-ids first-class resolvable addresses. |
| 87 | return document.querySelector(`[data-composition-id="${escaped}"]`); |
| 88 | } |
| 89 | |
| 90 | let context: Element | Document = document; |
| 91 | for (const part of parts) { |
| 92 | const escaped = escapeHfId(part); |
| 93 | const found: Element | null = |
| 94 | context === document |
| 95 | ? (context as Document).querySelector(`[data-hf-id="${escaped}"]`) |
| 96 | : (context as Element).querySelector(`[data-hf-id="${escaped}"]`); |
| 97 | if (!found) return null; |
| 98 | context = found; |
| 99 | } |
| 100 | return context as Element; |
| 101 | } |
| 102 | |
| 103 | /** |
| 104 | * Returns true when this element starts a new sub-composition scope — i.e. it |
no test coverage detected