mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-01 19:42:03 +00:00
Merge pull request #2502 from heygen-com/task4-start-resolver-export
feat(core): export runtime start resolver with documentRef parameter
This commit is contained in:
@@ -113,6 +113,12 @@
|
||||
"import": "./src/runtime/startExpression.ts",
|
||||
"types": "./src/runtime/startExpression.ts"
|
||||
},
|
||||
"./runtime/start-resolver": {
|
||||
"bun": "./src/runtime/startResolver.ts",
|
||||
"node": "./dist/runtime/startResolver.js",
|
||||
"import": "./src/runtime/startResolver.ts",
|
||||
"types": "./src/runtime/startResolver.ts"
|
||||
},
|
||||
"./runtime/protocol": {
|
||||
"bun": "./src/runtime/protocol.ts",
|
||||
"node": "./dist/runtime/protocol.js",
|
||||
@@ -329,6 +335,10 @@
|
||||
"import": "./dist/runtime/startExpression.js",
|
||||
"types": "./dist/runtime/startExpression.d.ts"
|
||||
},
|
||||
"./runtime/start-resolver": {
|
||||
"import": "./dist/runtime/startResolver.js",
|
||||
"types": "./dist/runtime/startResolver.d.ts"
|
||||
},
|
||||
"./runtime/protocol": {
|
||||
"import": "./dist/runtime/protocol.js",
|
||||
"types": "./dist/runtime/protocol.d.ts"
|
||||
|
||||
@@ -258,6 +258,13 @@ export {
|
||||
type ClipTimingDiagnosticCode,
|
||||
type ClipTimingUpdate,
|
||||
} from "./compositionContract.js";
|
||||
// Also exposed via the ./runtime/start-resolver subpath. This root re-export
|
||||
// additionally makes tsc EMIT dist/runtime/startResolver.js: src/runtime is
|
||||
// excluded from the tsconfig include set, so runtime files only reach dist
|
||||
// when an included module imports them — without this line the subpath's
|
||||
// publishConfig entry points at a file the pack doesn't contain
|
||||
// (verify:packed-manifests catches exactly that).
|
||||
export { createRuntimeStartTimeResolver } from "./runtime/startResolver.js";
|
||||
|
||||
// Variable validation (CLI / tooling-side)
|
||||
export {
|
||||
|
||||
@@ -399,3 +399,35 @@ describe("createRuntimeStartTimeResolver", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("documentRef", () => {
|
||||
it("resolves references against a supplied document instead of the global one", () => {
|
||||
const doc = document.implementation.createHTMLDocument("t");
|
||||
const intro = doc.createElement("div");
|
||||
intro.id = "intro";
|
||||
intro.setAttribute("data-start", "0");
|
||||
intro.setAttribute("data-duration", "4");
|
||||
doc.body.appendChild(intro);
|
||||
const outro = doc.createElement("div");
|
||||
outro.setAttribute("data-start", "intro + 2");
|
||||
doc.body.appendChild(outro);
|
||||
|
||||
const resolver = createRuntimeStartTimeResolver({ documentRef: doc });
|
||||
// intro ends at 0 + 4; outro starts 2s after → 6.
|
||||
expect(resolver.resolveStartForElement(outro)).toBe(6);
|
||||
});
|
||||
|
||||
it("defaults to the global document when documentRef is omitted", () => {
|
||||
const el = document.createElement("div");
|
||||
el.id = "ref-a";
|
||||
el.setAttribute("data-start", "0");
|
||||
el.setAttribute("data-duration", "2");
|
||||
document.body.appendChild(el);
|
||||
const dependent = document.createElement("div");
|
||||
dependent.setAttribute("data-start", "ref-a");
|
||||
document.body.appendChild(dependent);
|
||||
|
||||
const resolver = createRuntimeStartTimeResolver({});
|
||||
expect(resolver.resolveStartForElement(dependent)).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,25 +25,40 @@ function parseAuthoredEndAttr(element: Element): number | null {
|
||||
export function createRuntimeStartTimeResolver(params: {
|
||||
timelineRegistry?: Record<string, RuntimeTimelineLike | undefined>;
|
||||
includeAuthoredTimingAttrs?: boolean;
|
||||
/**
|
||||
* The document that reference lookups (`data-start="intro + 2"`) resolve
|
||||
* against. Defaults to the global `document` — the runtime bundle's own
|
||||
* realm. Hosts driving a composition in an IFRAME must pass that iframe's
|
||||
* document, or every reference silently resolves against the host page.
|
||||
*/
|
||||
documentRef?: Document;
|
||||
}): {
|
||||
resolveStartForElement: (element: Element, fallback?: number) => number;
|
||||
resolveDurationForElement: (element: Element) => number | null;
|
||||
} {
|
||||
const timelineRegistry = params.timelineRegistry ?? {};
|
||||
const includeAuthoredTimingAttrs = params.includeAuthoredTimingAttrs ?? false;
|
||||
const doc = params.documentRef ?? document;
|
||||
const startCache = new WeakMap<Element, number | null>();
|
||||
const durationCache = new WeakMap<Element, number | null>();
|
||||
const visiting = new Set<Element>();
|
||||
|
||||
const findReferenceTarget = (refId: string): Element | null => {
|
||||
const byId = document.getElementById(refId);
|
||||
const byId = doc.getElementById(refId);
|
||||
if (byId) return byId;
|
||||
return (
|
||||
(document.querySelector(`[data-composition-id="${CSS.escape(refId)}"]`) as Element | null) ??
|
||||
null
|
||||
(doc.querySelector(`[data-composition-id="${CSS.escape(refId)}"]`) as Element | null) ?? null
|
||||
);
|
||||
};
|
||||
|
||||
// Realm-safe: an iframe document's media elements are instances of THAT
|
||||
// frame's HTMLMediaElement, never this module's global one.
|
||||
const isMediaElement = (el: Element): el is HTMLMediaElement => {
|
||||
const RealmMedia = el.ownerDocument.defaultView?.HTMLMediaElement;
|
||||
if (RealmMedia) return el instanceof RealmMedia;
|
||||
return typeof HTMLMediaElement !== "undefined" && el instanceof HTMLMediaElement;
|
||||
};
|
||||
|
||||
const resolveDurationForElement = (element: Element): number | null => {
|
||||
const cached = durationCache.get(element);
|
||||
if (cached !== undefined) return cached;
|
||||
@@ -66,7 +81,7 @@ export function createRuntimeStartTimeResolver(params: {
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((resolved == null || resolved <= 0) && element instanceof HTMLMediaElement) {
|
||||
if ((resolved == null || resolved <= 0) && isMediaElement(element)) {
|
||||
const playbackStart =
|
||||
parseNumeric(element.getAttribute("data-playback-start")) ??
|
||||
parseNumeric(element.getAttribute("data-media-start")) ??
|
||||
@@ -181,3 +196,5 @@ export function createRuntimeStartTimeResolver(params: {
|
||||
resolveDurationForElement: (element: Element) => resolveDurationForElement(element),
|
||||
};
|
||||
}
|
||||
|
||||
export type { RuntimeTimelineLike } from "./types";
|
||||
|
||||
Reference in New Issue
Block a user