/** * Node-side resolver for relative `data-start` timing references, shared by * every parser that reads media timing out of compiled HTML — video frames * (`parseVideoElements`), images (`parseImageElements`), and audio * (`parseAudioElements`). Keeping the resolution in ONE place is load-bearing: * if audio and video disagree on what `data-start="intro"` means, a relative * reference that renders a video at the right time silently drops the audio * track (they used to — audio parsed `parseFloat("intro") = NaN`). * * Mirrors the browser runtime's startResolver so `snapshot`/`render` agree. * DOM access is via a minimal structural shape so it works against linkedom * (Node) without pulling in lib.dom types. */ import { parseNumeric, parseStartExpression } from "@hyperframes/core"; /** Minimal structural DOM shape the reference resolver needs. */ export interface RefResolverEl { getAttribute(name: string): string | null; } interface RefResolverDoc { getElementById(id: string): RefResolverEl | null; querySelector(selector: string): RefResolverEl | null; } /** * Find the element a relative `data-start` reference points at — by `id` * first, then by `data-composition-id` (a sub-composition can be referenced). * The reference-id grammar (see parseStartExpression) is restricted to * `[A-Za-z0-9_.:-]`, none of which need escaping inside a quoted attribute * selector, so no CSS.escape (absent in linkedom) is required. */ function findReferenceTargetEl(doc: RefResolverDoc, refId: string): RefResolverEl | null { return doc.getElementById(refId) ?? doc.querySelector(`[data-composition-id="${refId}"]`); } /** * Resolve an element's absolute start time (seconds) the same way the browser * runtime's startResolver does, so `