Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
72e7957
fix(producer): preserve inner root element during sub-composition inl…
miguel-heygen Jun 11, 2026
a2eb030
test(producer): add regression test for sub-composition inner root cl…
miguel-heygen Jun 11, 2026
5bda16f
test(producer): fix regression test — move scene out of gitignored co…
miguel-heygen Jun 11, 2026
d36bd7d
fix(producer): address review — drop compoundAuthoredRoot, use real p…
miguel-heygen Jun 11, 2026
97a6586
test(core): add regression tests for compoundAuthoredRoot + id != com…
miguel-heygen Jun 11, 2026
9814541
fix(producer): regenerate baselines for missing-host-comp-id and over…
miguel-heygen Jun 11, 2026
0b40430
fix(core): move prepareFlattenedInnerRoot to inlineSubCompositions to…
miguel-heygen Jun 11, 2026
367b05f
fix(core): propagate inferred data-composition-id to host when using …
miguel-heygen Jun 11, 2026
37f99aa
fix(core): skip flattenInnerRoot when host lacks data-composition-id
miguel-heygen Jun 11, 2026
d131f25
fix(producer): regenerate style-7-prod baseline for flattenInnerRoot …
miguel-heygen Jun 11, 2026
15d821d
fix(producer): use light flattenInnerRoot that preserves layout
miguel-heygen Jun 11, 2026
837b6f8
fix(producer): revert flattenInnerRoot in producer — breaks flex layout
miguel-heygen Jun 11, 2026
10195c4
fix(core): preserve inner root classes via CSS scoping instead of DOM…
miguel-heygen Jun 11, 2026
a93d2ac
fix(producer): regenerate sub-comp-t0 stale baseline
miguel-heygen Jun 11, 2026
c9580eb
fix(producer): restore compoundAuthoredRoot for correct #id selector …
miguel-heygen Jun 11, 2026
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
14 changes: 14 additions & 0 deletions packages/core/src/compiler/compositionScoping.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -660,4 +660,18 @@ window.__timelines['intro'] = tl;
// data-hf-authored-id="intro" element, finding the .title child.
expect(gsapTargets).toEqual([["HELLO"]]);
});

it("innerRootClasses emits both compound and descendant selectors for root class", () => {
const css = `.scene_1-root { background: #1a1a2e; }
.scene_1-root .title { color: red; }
.unrelated { margin: 0; }`;
const result = scopeCssToComposition(css, "scene_1", undefined, null, {
innerRootClasses: ["scene_1-root"],
});
expect(result).toContain('[data-composition-id="scene_1"].scene_1-root');
expect(result).toContain('[data-composition-id="scene_1"] .scene_1-root');
expect(result).toContain('[data-composition-id="scene_1"].scene_1-root .title');
expect(result).toContain('[data-composition-id="scene_1"] .scene_1-root .title');
expect(result).toContain('[data-composition-id="scene_1"] .unrelated');
});
});
40 changes: 37 additions & 3 deletions packages/core/src/compiler/compositionScoping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,39 @@ function scopeSelector(
return `${leading}${scope} ${trimmed}${trailing}`;
}

function scopeSelectorWithRootClasses(
selector: string,
scope: string,
compositionId: string,
authoredRootId?: string | null,
compoundAuthoredRoot?: boolean,
innerRootClasses?: string[],
): string[] {
const scoped = scopeSelector(
selector,
scope,
compositionId,
authoredRootId,
compoundAuthoredRoot,
);
if (!innerRootClasses?.length) return [scoped];
const trimmed = selector.trim();
for (const cls of innerRootClasses) {
const dotCls = `.${cls}`;
if (
trimmed === dotCls ||
trimmed.startsWith(`${dotCls} `) ||
trimmed.startsWith(`${dotCls}.`) ||
trimmed.startsWith(`${dotCls}:`)
) {
const rest = trimmed.slice(dotCls.length);
const compound = `${scope}${dotCls}${rest}`;
if (compound !== scoped) return [compound, scoped];
}
}
return [scoped];
}

function normalizeCompositionRootSelector(
selector: string,
scope: string,
Expand Down Expand Up @@ -168,7 +201,7 @@ export function scopeCssToComposition(
compositionId: string,
scopeSelectorOverride?: string,
authoredRootId?: string | null,
options?: { compoundAuthoredRoot?: boolean },
options?: { compoundAuthoredRoot?: boolean; innerRootClasses?: string[] },
): string {
const trimmedCompositionId = compositionId.trim();
if (!css || !trimmedCompositionId) return css;
Expand All @@ -179,13 +212,14 @@ export function scopeCssToComposition(

root.walkRules((rule) => {
if (isInsideGlobalAtRule(rule)) return;
rule.selectors = rule.selectors.map((selector) =>
scopeSelector(
rule.selectors = rule.selectors.flatMap((selector) =>
scopeSelectorWithRootClasses(
selector,
scope,
trimmedCompositionId,
authoredRootId,
options?.compoundAuthoredRoot,
options?.innerRootClasses,
),
);
});
Expand Down
38 changes: 5 additions & 33 deletions packages/core/src/compiler/htmlBundler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,39 +379,11 @@ function parseHostVariableValues(host: Element): Record<string, unknown> {
return parsed as Record<string, unknown>;
}

export const FLATTENED_INNER_ROOT_STRIP_ATTRS = [
"data-composition-id",
"data-composition-file",
"data-start",
"data-duration",
"data-end",
"data-track-index",
"data-track",
"data-composition-src",
"data-hf-authored-duration",
"data-hf-authored-end",
];

export function prepareFlattenedInnerRoot(innerRoot: Element): Element {
const prepared = innerRoot.cloneNode(true) as Element;
const authoredRootId = prepared.getAttribute("id")?.trim();
for (const attrName of FLATTENED_INNER_ROOT_STRIP_ATTRS) {
prepared.removeAttribute(attrName);
}
if (authoredRootId) {
prepared.removeAttribute("id");
prepared.setAttribute("data-hf-authored-id", authoredRootId);
}
prepared.setAttribute("data-hf-inner-root", "true");
const w = prepared.getAttribute("data-width");
const h = prepared.getAttribute("data-height");
const widthVal = w ? `${w}px` : "100%";
const heightVal = h ? `${h}px` : "100%";
const existingStyle = (prepared.getAttribute("style") || "").trim();
const fill = `width:${widthVal};height:${heightVal}`;
prepared.setAttribute("style", existingStyle ? `${existingStyle};${fill}` : fill);
return prepared;
}
export {
FLATTENED_INNER_ROOT_STRIP_ATTRS,
prepareFlattenedInnerRoot,
} from "./inlineSubCompositions";
import { prepareFlattenedInnerRoot } from "./inlineSubCompositions";

function enforceCompositionPixelSizing(document: Document): void {
const compositionEls = [
Expand Down
Loading
Loading