From 3aa5cf3ab3faa8a02ad6b4307701434fd2127152 Mon Sep 17 00:00:00 2001 From: Phuong Le <39565248+func25@users.noreply.github.com> Date: Thu, 14 May 2026 11:23:53 +0700 Subject: [PATCH] fix(core): update nested timed element visibility on seek (#823) --- packages/core/src/runtime/init.test.ts | 52 ++++++++++++++++++++++++++ packages/core/src/runtime/init.ts | 15 -------- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/packages/core/src/runtime/init.test.ts b/packages/core/src/runtime/init.test.ts index 32e8d0841..fed63cae3 100644 --- a/packages/core/src/runtime/init.test.ts +++ b/packages/core/src/runtime/init.test.ts @@ -263,6 +263,58 @@ describe("initSandboxRuntimeModular", () => { expect(video.currentTime).toBe(9); }); + it("updates visibility for timed elements inside nested compositions", () => { + 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", "nested"); + child.setAttribute("data-start", "10"); + child.setAttribute("data-duration", "10"); + root.appendChild(child); + + const sceneA = document.createElement("section"); + sceneA.id = "scene-a"; + sceneA.setAttribute("data-start", "0"); + sceneA.setAttribute("data-duration", "4"); + child.appendChild(sceneA); + + const sceneB = document.createElement("section"); + sceneB.id = "scene-b"; + sceneB.setAttribute("data-start", "4"); + sceneB.setAttribute("data-duration", "4"); + child.appendChild(sceneB); + + (window as Window & { __timelines?: Record }).__timelines = { + main: createMockTimeline(20), + nested: createMockTimeline(8), + }; + + initSandboxRuntimeModular(); + + const player = ( + window as Window & { + __player?: { seek: (timeSeconds: number) => void }; + } + ).__player; + expect(player).toBeDefined(); + + player?.seek(11); + + expect(sceneA.style.visibility).toBe("visible"); + expect(sceneB.style.visibility).toBe("hidden"); + + player?.seek(15); + + expect(sceneA.style.visibility).toBe("hidden"); + expect(sceneB.style.visibility).toBe("visible"); + }); + it("clamps nested media to the authored host window on seek", () => { const root = document.createElement("div"); root.setAttribute("data-composition-id", "main"); diff --git a/packages/core/src/runtime/init.ts b/packages/core/src/runtime/init.ts index 438568d6a..5d79b76f8 100644 --- a/packages/core/src/runtime/init.ts +++ b/packages/core/src/runtime/init.ts @@ -1316,27 +1316,12 @@ export function initSandboxRuntimeModular(): void { postRuntimeMessage({ source: "hf-preview", type: "media-autoplay-blocked" }); }, }); - const rootCompId = - document.querySelector("[data-composition-id]")?.getAttribute("data-composition-id") ?? null; const visibilityNodes = Array.from(document.querySelectorAll("[data-start]")); for (const rawNode of visibilityNodes) { if (!(rawNode instanceof HTMLElement)) continue; const tag = rawNode.tagName.toLowerCase(); if (tag === "script" || tag === "style" || tag === "link" || tag === "meta") continue; - // Skip elements INSIDE sub-compositions — their visibility is managed by GSAP, - // not the global time-based adapter. Only manage visibility for: - // 1. Composition host elements (have data-composition-id themselves) - // 2. Direct children of root composition (audio, etc.) - // Skip: elements whose nearest composition ancestor is NOT the root - const ownCompId = rawNode.getAttribute("data-composition-id"); - if (!ownCompId) { - // Not a composition host — check if it's inside a sub-composition - const parentComp = rawNode.closest("[data-composition-id]"); - const parentCompId = parentComp?.getAttribute("data-composition-id") ?? null; - if (parentCompId && parentCompId !== rootCompId) continue; - } - const start = resolveStartForElement(rawNode, 0); let duration = resolveDurationForElement(rawNode); const compId = rawNode.getAttribute("data-composition-id");