mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(sdk): include template GSAP scripts in resolver parity
This commit is contained in:
@@ -14,6 +14,7 @@ import { parseGsapScriptAcornForWrite } from "@hyperframes/core/gsap-parser-acor
|
||||
import {
|
||||
findRoot,
|
||||
getElementStyles,
|
||||
getGsapScripts,
|
||||
getOwnText,
|
||||
isNewHostBoundary,
|
||||
querySelectorAllDeep,
|
||||
@@ -67,22 +68,22 @@ function parseLocatedCached(script: string): Array<{ id: string; selector: strin
|
||||
*/
|
||||
function buildAnimationIdMap(document: Document): Map<string, string[]> {
|
||||
const map = new Map<string, string[]>();
|
||||
const script = extractGsapScript(document);
|
||||
if (!script) return map;
|
||||
for (const { id, selector } of parseLocatedCached(script)) {
|
||||
if (!selector) continue;
|
||||
let matches: Element[] = [];
|
||||
try {
|
||||
matches = querySelectorAllDeep(document, selector);
|
||||
} catch {
|
||||
continue; // selector not valid for querySelectorAll — skip
|
||||
}
|
||||
for (const el of matches) {
|
||||
const hfId = el.getAttribute("data-hf-id");
|
||||
if (!hfId) continue;
|
||||
const list = map.get(hfId);
|
||||
if (list) list.push(id);
|
||||
else map.set(hfId, [id]);
|
||||
for (const script of getGsapScripts(document)) {
|
||||
for (const { id, selector } of parseLocatedCached(script)) {
|
||||
if (!selector) continue;
|
||||
let matches: Element[] = [];
|
||||
try {
|
||||
matches = querySelectorAllDeep(document, selector);
|
||||
} catch {
|
||||
continue; // selector not valid for querySelectorAll — skip
|
||||
}
|
||||
for (const el of matches) {
|
||||
const hfId = el.getAttribute("data-hf-id");
|
||||
if (!hfId) continue;
|
||||
const list = map.get(hfId);
|
||||
if (list) list.push(id);
|
||||
else map.set(hfId, [id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
@@ -198,16 +199,7 @@ function buildElement(
|
||||
|
||||
// fallow-ignore-next-line complexity
|
||||
function extractGsapScript(doc: Document): string | null {
|
||||
// GSAP script is the first <script> tag whose text references gsap. Marker
|
||||
// set must match studio sdkShadow.ts isGsapScriptBody so both pick the same
|
||||
// script from a given composition.
|
||||
for (const script of Array.from(doc.querySelectorAll("script"))) {
|
||||
const text = script.textContent ?? "";
|
||||
if (text.includes("gsap") || text.includes("__timelines") || text.includes("ScrollTrigger")) {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return getGsapScripts(doc)[0] ?? null;
|
||||
}
|
||||
|
||||
function extractStyles(doc: Document): string | null {
|
||||
|
||||
@@ -372,11 +372,39 @@ export function setStyleSheet(document: Document, css: string): void {
|
||||
|
||||
// ─── GSAP script helpers ──────────────────────────────────────────────────────
|
||||
|
||||
function findScriptElementsDeep(document: Document): Element[] {
|
||||
const scripts: Element[] = [];
|
||||
const walk = (parent: Element): void => {
|
||||
for (const child of Array.from(parent.children)) {
|
||||
const tag = child.tagName.toLowerCase();
|
||||
if (tag === "script") {
|
||||
scripts.push(child);
|
||||
continue;
|
||||
}
|
||||
if (tag === "template") {
|
||||
if (isCompositionTemplate(child)) walk(child);
|
||||
continue;
|
||||
}
|
||||
walk(child);
|
||||
}
|
||||
};
|
||||
if (document.documentElement) walk(document.documentElement);
|
||||
return scripts;
|
||||
}
|
||||
|
||||
export function getGsapScripts(document: Document): string[] {
|
||||
return findScriptElementsDeep(document)
|
||||
.map((script) => script.textContent ?? "")
|
||||
.filter(
|
||||
(text) =>
|
||||
text.includes("gsap") || text.includes("__timelines") || text.includes("ScrollTrigger"),
|
||||
);
|
||||
}
|
||||
|
||||
function findGsapScriptElement(document: Document): Element | null {
|
||||
const scripts = document.querySelectorAll("script");
|
||||
for (const script of Array.from(scripts)) {
|
||||
for (const script of findScriptElementsDeep(document)) {
|
||||
const text = script.textContent ?? "";
|
||||
if (text.includes("gsap") || text.includes("ScrollTrigger"))
|
||||
if (text.includes("gsap") || text.includes("__timelines") || text.includes("ScrollTrigger"))
|
||||
return script as unknown as Element;
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -78,6 +78,22 @@ describe("template-based sub-comp compositions", () => {
|
||||
);
|
||||
expect(comp.getElement("hf-dup")?.text).toBe("tpl");
|
||||
});
|
||||
|
||||
it("models GSAP animations declared inside a composition template", async () => {
|
||||
const comp = await openComposition(`
|
||||
<template data-composition-id="document-card">
|
||||
<div data-hf-id="hf-line" class="line">line</div>
|
||||
<script>
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
tl.to("[data-hf-id=\\"hf-line\\"]", { x: 100, duration: 1 }, 0);
|
||||
</script>
|
||||
</template>
|
||||
`);
|
||||
|
||||
const animationIds = comp.getElement("hf-line")?.animationIds ?? [];
|
||||
expect(animationIds).toHaveLength(1);
|
||||
expect(comp.getAllAnimationIds()).toEqual(new Set(animationIds));
|
||||
});
|
||||
});
|
||||
|
||||
// The authored sub-comp form `hyperframes add` scaffolds: the composition id is
|
||||
|
||||
@@ -35,7 +35,12 @@ import type { PersistAdapter, PreviewAdapter } from "./adapters/types.js";
|
||||
import { parseMutable } from "./engine/model.js";
|
||||
import type { ParsedDocument } from "./engine/model.js";
|
||||
import { applyOp, validateOp, type MutationResult } from "./engine/mutate.js";
|
||||
import { getGsapScript, resolveScoped, declarationElement } from "./engine/model.js";
|
||||
import {
|
||||
getGsapScript,
|
||||
getGsapScripts,
|
||||
resolveScoped,
|
||||
declarationElement,
|
||||
} from "./engine/model.js";
|
||||
import { extractGsapLabels } from "@hyperframes/core/gsap-parser-acorn";
|
||||
import { stripEmbeddedRuntimeScripts } from "@hyperframes/core/compiler/html-document";
|
||||
import { parseStartExpression } from "@hyperframes/core/runtime/start-expression";
|
||||
@@ -555,8 +560,11 @@ class CompositionImpl implements Composition {
|
||||
}
|
||||
|
||||
getAllAnimationIds(): Set<string> {
|
||||
const script = getGsapScript(this.parsed.document);
|
||||
return script ? parsedAnimationIds(script) : new Set();
|
||||
const ids = new Set<string>();
|
||||
for (const script of getGsapScripts(this.parsed.document)) {
|
||||
for (const id of parsedAnimationIds(script)) ids.add(id);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
// ── Selection API ────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user