Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions packages/lint/src/rules/gsap.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1698,4 +1698,25 @@ describe("GSAP rules", () => {
const finding = result.findings.find((f) => f.code === "gsap_non_transform_motion");
expect(finding).toBeUndefined();
});

it("scene_layer_missing_visibility_kill: does NOT mistake fromTo entrance opacity for an exit", async () => {
const html = `
<html><body>
<div data-composition-id="c1" data-width="1920" data-height="1080">
<div id="scene1"></div>
<div id="scene2"></div>
</div>
<script>
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
tl.to("#scene1", { opacity: 0, duration: 0.5 }, 2.0);
tl.set("#scene1", { visibility: "hidden" }, 2.5);
tl.fromTo("#scene2", { opacity: 0 }, { opacity: 1, duration: 0.5 }, 2.0);
window.__timelines["c1"] = tl;
</script>
</body></html>`;
const result = await lintHyperframeHtml(html);
const finding = result.findings.find((f) => f.code === "scene_layer_missing_visibility_kill");
expect(finding).toBeUndefined();
});
});
13 changes: 10 additions & 3 deletions packages/lint/src/rules/gsap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1020,9 +1020,16 @@ export const gsapRules: LintRule<LintContext>[] = [
// For each scene, check if there's a visibility:hidden set after exit tweens
for (const tag of sceneElements) {
const id = readAttr(tag.raw, "id") || "";
// Check if this scene has exit tweens (opacity: 0)
const exitPattern = new RegExp(`["']#${id}["'][^)]*opacity\\s*:\\s*0`);
const hasExit = exitPattern.test(content);
// Check for an actual exit tween. A fromTo entrance often puts
// opacity:0 in its FIRST vars object; that is not an exit and the final
// scene must remain visible through the last frame.
const toExitPattern = new RegExp(
`\\.to\\(\\s*["']#${id}["']\\s*,\\s*\\{[^}]*opacity\\s*:\\s*0`,
);
const fromToExitPattern = new RegExp(
`\\.fromTo\\(\\s*["']#${id}["']\\s*,\\s*\\{[^}]*\\}\\s*,\\s*\\{[^}]*opacity\\s*:\\s*0`,
);
const hasExit = toExitPattern.test(content) || fromToExitPattern.test(content);
if (!hasExit) continue;

// Check if there's a hard visibility kill
Expand Down
Loading