mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-01 19:42:03 +00:00
Merge pull request #2500 from heygen-com/task2-iframe-load-resync
fix(sdk): attachSync re-syncs the override snapshot on iframe load
This commit is contained in:
@@ -121,7 +121,7 @@ interface PreviewAdapter {
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="attachSync" type="(comp: Composition) => () => void">
|
||||
Mirrors a composition's edits onto the adapter's own live document: an immediate full sync of the composition's current overrides, then a subscription that replays every future `patch` event — including undo/redo, since both fire through the same event with forward or inverse patches. Calling `attachSync` again while already attached detaches the previous subscription first. Returns an unsubscribe function.
|
||||
Mirrors a composition's edits onto the adapter's own live document: an immediate full sync of the composition's current overrides, then a subscription that replays every future `patch` event — including undo/redo, since both fire through the same event with forward or inverse patches. Calling `attachSync` again while already attached detaches the previous subscription first. The full-override sync also re-runs on every iframe `load`, so a `srcdoc` navigation that races the attach (or drops patches committed during the load window) converges once the new document arrives. Returns an unsubscribe function.
|
||||
|
||||
Script-tag patches (`/script/gsap` and any future `/script/*` path) are never mirrored — rewriting a live `<script>` tag's content doesn't re-execute it, and re-running GSAP setup from scratch would conflict with running timeline state. Every other patch kind (style, text, attribute, timing, hold, element add/remove, stylesheet, variable value, variable declaration) mirrors as-is.
|
||||
|
||||
@@ -335,4 +335,3 @@ preview.commitPreview();
|
||||
Building a visual editor canvas with the iframe preview adapter and hit-testing.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
|
||||
@@ -252,3 +252,51 @@ window.__timelines = { t: tl };</script>
|
||||
expect(liveTitle.getAttribute("data-end")).toBe("3");
|
||||
});
|
||||
});
|
||||
|
||||
describe("IframePreviewAdapter.attachSync — iframe load re-sync", () => {
|
||||
it("re-applies the override snapshot when the iframe fires `load`", async () => {
|
||||
const iframe = mountIframe(BASE_HTML);
|
||||
const comp = await openComposition(BASE_HTML);
|
||||
const adapter = createIframePreviewAdapter(iframe);
|
||||
adapter.attachSync(comp);
|
||||
comp.setStyle("hf-title", { color: "#f00" });
|
||||
|
||||
// Simulate a navigation: replace the document with a fresh base (the
|
||||
// mirrored edit is gone), then fire the load event a real srcdoc
|
||||
// assignment would produce.
|
||||
const midDoc = iframe.contentDocument;
|
||||
if (!midDoc) throw new Error("iframe has no document");
|
||||
midDoc.open();
|
||||
midDoc.write(BASE_HTML);
|
||||
midDoc.close();
|
||||
iframe.dispatchEvent(new Event("load"));
|
||||
|
||||
const liveTitle = iframe.contentDocument?.querySelector(
|
||||
'[data-hf-id="hf-title"]',
|
||||
) as HTMLElement;
|
||||
expect(liveTitle.style.getPropertyValue("color")).toBe("#f00");
|
||||
});
|
||||
|
||||
it("stops re-syncing on load after detach", async () => {
|
||||
const iframe = mountIframe(BASE_HTML);
|
||||
const comp = await openComposition(BASE_HTML);
|
||||
const adapter = createIframePreviewAdapter(iframe);
|
||||
const detach = adapter.attachSync(comp);
|
||||
comp.setStyle("hf-title", { color: "#f00" });
|
||||
detach();
|
||||
|
||||
const midDoc = iframe.contentDocument;
|
||||
if (!midDoc) throw new Error("iframe has no document");
|
||||
midDoc.open();
|
||||
midDoc.write(BASE_HTML);
|
||||
midDoc.close();
|
||||
iframe.dispatchEvent(new Event("load"));
|
||||
|
||||
const liveTitle = iframe.contentDocument?.querySelector(
|
||||
'[data-hf-id="hf-title"]',
|
||||
) as HTMLElement;
|
||||
// BASE_HTML's authored inline color remains because detach must not
|
||||
// re-apply comp's #f00 override on the load event.
|
||||
expect(liveTitle.style.getPropertyValue("color")).toBe("#fff");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -752,17 +752,27 @@ class IframePreviewAdapter implements PreviewAdapter {
|
||||
attachSync(comp: Composition): () => void {
|
||||
this._syncDetach?.();
|
||||
|
||||
const doc = this.iframe.contentDocument;
|
||||
if (doc) {
|
||||
const syncOverrides = (): void => {
|
||||
const doc = this.iframe.contentDocument;
|
||||
if (!doc) return;
|
||||
try {
|
||||
applyOverrideSet({ document: doc, wrapped: false, stamped: "" }, comp.getOverrides());
|
||||
} catch (err) {
|
||||
// Don't let a bad initial snapshot prevent the ongoing subscription
|
||||
// below from attaching — future patches should still mirror even if
|
||||
// this composition's current overrides couldn't be applied.
|
||||
console.warn("[hyperframes] attachSync: initial override sync failed:", err);
|
||||
// Don't let a bad snapshot prevent the ongoing subscription below
|
||||
// from attaching — future patches should still mirror even if this
|
||||
// composition's current overrides couldn't be applied.
|
||||
console.warn("[hyperframes] attachSync: override sync failed:", err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Immediate snapshot for the current document…
|
||||
syncOverrides();
|
||||
// …and again on every iframe `load`: assigning srcdoc/src is an ASYNC
|
||||
// navigation, so an attach in the same tick snapshots the OUTGOING
|
||||
// document, and patches committed during the load window mirror into it
|
||||
// and die with it. Re-syncing on load converges the new document with
|
||||
// the composition state regardless of attach/navigation ordering.
|
||||
this.iframe.addEventListener("load", syncOverrides);
|
||||
|
||||
const rawUnsubscribe = comp.on("patch", ({ patches }) => {
|
||||
const liveDoc = this.iframe.contentDocument;
|
||||
@@ -778,6 +788,7 @@ class IframePreviewAdapter implements PreviewAdapter {
|
||||
});
|
||||
|
||||
const detach = (): void => {
|
||||
this.iframe.removeEventListener("load", syncOverrides);
|
||||
rawUnsubscribe();
|
||||
if (this._syncDetach === detach) this._syncDetach = null;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user