diff --git a/packages/core/src/compiler/htmlDocument.test.ts b/packages/core/src/compiler/htmlDocument.test.ts index e9ae8a4730..615fd6c66c 100644 --- a/packages/core/src/compiler/htmlDocument.test.ts +++ b/packages/core/src/compiler/htmlDocument.test.ts @@ -196,6 +196,14 @@ describe("htmlDocument helpers", () => { ); }); + it("puts markup before the outer , past a nested one and a commented one", () => { + const sub = + "\n"; + expect(insertBeforeCloseTag(sub, "template", "X")).toBe( + "\n", + ); + }); + it("ends a comment at --!>, but not on its own opening dashes", () => { expect(insertBeforeCloseTag("", "head", "X")).toBe( "X", diff --git a/packages/core/src/compiler/htmlDocument.ts b/packages/core/src/compiler/htmlDocument.ts index f18c994dd8..cf98279623 100644 --- a/packages/core/src/compiler/htmlDocument.ts +++ b/packages/core/src/compiler/htmlDocument.ts @@ -207,7 +207,7 @@ function inlineScriptTags(scripts: readonly string[]): string { const RAW_TEXT_TAGS = ["script", "style", "title", "textarea"] as const; -type DocumentTag = "/g; function* markupStarts(lowered: string): Generator { @@ -290,15 +290,25 @@ function findRawTextClose(lowered: string, name: string, from: number): number { return -1; } +function findOuterTemplateClose(html: string): number { + const lowered = lowerAscii(html); + let depth = 0; + for (const open of markupStarts(lowered)) { + if (isTagAt(lowered, open, "` or ``; null when it has none. */ +/** Insert markup before the document's own ``, `` or ``; null if it has none. */ export function insertBeforeCloseTag( html: string, - name: "head" | "body", + name: "head" | "body" | "template", markup: string, ): string | null { return insertBeforeDocumentTag(html, ` { + const projectDir = createProjectDir(); + const app = new Hono(); + registerFileRoutes(app, createAdapter(projectDir)); + mkdirSync(join(projectDir, "compositions"), { recursive: true }); + writeHtml(projectDir, "compositions/sub.html", sub); + const res = await app.request( + "http://localhost/projects/demo/gsap-mutations/compositions/sub.html", + { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + type: "add", + targetSelector: "#card", + method: "to", + position: 0, + properties: { opacity: 0 }, + }), + }, + ); + expect(res.status).toBe(200); + return readFileSync(join(projectDir, "compositions/sub.html"), "utf-8"); + } + + it("a first animation in a sub-composition file lands inside its template", async () => { + const html = await addFirstAnimation( + '\n', + ); + const close = html.indexOf(""); + expect(html.indexOf('window.__timelines["sub"]')).toBeGreaterThan(-1); + expect(html.lastIndexOf(""); + expect(html).not.toContain(" file lands before , outside its template", async () => { + const html = await addFirstAnimation( + '\n', + ); + expect(html.indexOf("")); + expect(html.lastIndexOf("")).toBeLessThan(html.indexOf("")); + }); + it("consolidate-position-writes leaves exactly one position write per selector", async () => { const projectDir = createProjectDir(); const CORRUPTED = `", ].join("\n"); - html = insertBeforeCloseTag(html, "body", `${bootstrap}\n`) ?? `${html}\n${bootstrap}`; + html = + insertBeforeCloseTag(html, "body", `${bootstrap}\n`) ?? + insertBeforeCloseTag(html, "template", `${bootstrap}\n`) ?? + `${html}\n${bootstrap}`; block = extractGsapScriptBlock(html); } if (