fix: hold external sub-compositions through host duration (#917)

This commit is contained in:
Puneet Dixit
2026-05-17 17:09:43 +02:00
committed by GitHub
parent b30fd29695
commit 551607efd6
2 changed files with 61 additions and 6 deletions
+46
View File
@@ -165,6 +165,52 @@ describe("initSandboxRuntimeModular", () => {
expect(child.style.visibility).toBe("hidden"); expect(child.style.visibility).toBe("hidden");
}); });
it("keeps external composition hosts visible through their authored duration", async () => {
const root = document.createElement("div");
root.setAttribute("data-composition-id", "main");
root.setAttribute("data-root", "true");
root.setAttribute("data-start", "0");
root.setAttribute("data-width", "1920");
root.setAttribute("data-height", "1080");
document.body.appendChild(root);
const child = document.createElement("div");
child.setAttribute("data-composition-id", "sub");
child.setAttribute("data-composition-src", "compositions/sub.html");
child.setAttribute("data-start", "0");
child.setAttribute("data-duration", "3");
root.appendChild(child);
const template = document.createElement("template");
template.id = "sub-template";
template.innerHTML = `
<div data-composition-id="sub" data-width="1920" data-height="1080">
<div id="hold-marker">HOLD ME</div>
</div>
`;
document.body.appendChild(template);
(window as Window & { __timelines?: Record<string, RuntimeTimelineLike> }).__timelines = {
main: createMockTimeline(3),
sub: createMockTimeline(1),
};
initSandboxRuntimeModular();
await new Promise<void>((resolve) => window.setTimeout(resolve, 0));
const player = (
window as Window & {
__player?: { renderSeek: (timeSeconds: number) => void };
}
).__player;
expect(player).toBeDefined();
expect(child.querySelector("#hold-marker")?.textContent).toBe("HOLD ME");
player?.renderSeek(2);
expect(child.style.visibility).toBe("visible");
});
it("pads the root timeline to the authored composition schedule before seeking visibility", () => { it("pads the root timeline to the authored composition schedule before seeking visibility", () => {
const root = document.createElement("div"); const root = document.createElement("div");
root.setAttribute("data-composition-id", "main"); root.setAttribute("data-composition-id", "main");
+15 -6
View File
@@ -255,9 +255,9 @@ export function initSandboxRuntimeModular(): void {
if (authoredEnd != null && !node.hasAttribute(AUTHORED_END_ATTR)) { if (authoredEnd != null && !node.hasAttribute(AUTHORED_END_ATTR)) {
node.setAttribute(AUTHORED_END_ATTR, authoredEnd); node.setAttribute(AUTHORED_END_ATTR, authoredEnd);
} }
// Non-root compositions derive visible duration from timeline. // Strip public timing attrs on non-root compositions after preserving
// Strip both data-duration AND data-end so the visibility system // authored values privately. Runtime timing can still distinguish
// falls back to the GSAP timeline duration (parity with preview). // authored host windows from live child timeline durations.
node.removeAttribute("data-duration"); node.removeAttribute("data-duration");
node.removeAttribute("data-end"); node.removeAttribute("data-end");
} }
@@ -1335,9 +1335,18 @@ export function initSandboxRuntimeModular(): void {
} }
} }
// Composition hosts must respect both the authored parent clip window const usesExternalCompositionSlot = rawNode.hasAttribute("data-composition-src");
// and the child composition's own live timeline duration.
if (duration != null && duration > 0 && liveDuration != null) { // Generic child compositions retain legacy behavior and respect both
// the authored parent clip window and the live child timeline duration.
// External composition hosts render into an authored slot, so a shorter
// child timeline should hold its final state through that slot.
if (
duration != null &&
duration > 0 &&
liveDuration != null &&
!usesExternalCompositionSlot
) {
duration = Math.min(duration, liveDuration); duration = Math.min(duration, liveDuration);
} else if ((duration == null || duration <= 0) && liveDuration != null) { } else if ((duration == null || duration <= 0) && liveDuration != null) {
duration = liveDuration; duration = liveDuration;