diff --git a/packages/core/src/compiler/compositionScoping.test.ts b/packages/core/src/compiler/compositionScoping.test.ts
index 4c1a0189b..49208d30c 100644
--- a/packages/core/src/compiler/compositionScoping.test.ts
+++ b/packages/core/src/compiler/compositionScoping.test.ts
@@ -158,6 +158,56 @@ window.__timelines.scene = tl;
expect(gsapTargets).toEqual([["Scene"], ["Scene"]]);
});
+ it("scopes getElementById when duplicate IDs exist across composition roots", () => {
+ const { document } = parseHTML(`
+
+
+ `);
+ const fakeWindow = {
+ document,
+ __selectedComp: "",
+ __timelines: {},
+ };
+ const wrapped = wrapScopedCompositionScript(
+ `
+window.__selectedComp =
+ document.getElementById("gl-canvas")
+ ?.closest("[data-composition-id]")
+ ?.getAttribute("data-composition-id") || "null";
+`,
+ "scene-b",
+ );
+
+ new Function("window", wrapped)(fakeWindow);
+
+ expect(fakeWindow.__selectedComp).toBe("scene-b");
+ });
+
+ it("scopes getElementById for IDs that need CSS selector escaping", () => {
+ const { document } = parseHTML(`
+
+
+ `);
+ const fakeWindow = {
+ document,
+ __selectedComp: "",
+ __timelines: {},
+ };
+ const wrapped = wrapScopedCompositionScript(
+ `
+window.__selectedComp =
+ document.getElementById("clip:1")
+ ?.closest("[data-composition-id]")
+ ?.getAttribute("data-composition-id") || "null";
+`,
+ "scene-b",
+ );
+
+ new Function("window", wrapped)(fakeWindow);
+
+ expect(fakeWindow.__selectedComp).toBe("scene-b");
+ });
+
it("reads scoped proxy accessors with the original target receiver", () => {
const root = {
contains(node: unknown) {
diff --git a/packages/core/src/compiler/compositionScoping.ts b/packages/core/src/compiler/compositionScoping.ts
index bc7153011..5fec5b5c2 100644
--- a/packages/core/src/compiler/compositionScoping.ts
+++ b/packages/core/src/compiler/compositionScoping.ts
@@ -141,17 +141,30 @@ export function wrapScopedCompositionScript(
var matches = __hfQueryAll(selector);
return matches[0] || null;
};
+ var __hfGetElementById = function(id) {
+ var found = window.document.getElementById(id);
+ if (found && __hfContains(found)) return found;
+ var root = __hfFindRoot();
+ if (!root) return found || null;
+ var idValue = id + "";
+ if (root.id === idValue) return root;
+ if (typeof root.querySelector !== "function") return null;
+ if (typeof CSS !== "undefined" && CSS && typeof CSS.escape === "function") {
+ try {
+ return root.querySelector("#" + CSS.escape(idValue)) || null;
+ } catch {}
+ }
+ try {
+ return root.querySelector('[id="' + __hfEscapeAttr(idValue) + '"]') || null;
+ } catch {}
+ return null;
+ };
var __hfScopedDocument = typeof Proxy === "function"
? new Proxy(window.document, {
get: function(target, prop, receiver) {
if (prop === "querySelector") return __hfQueryOne;
if (prop === "querySelectorAll") return __hfQueryAll;
- if (prop === "getElementById") {
- return function(id) {
- var found = target.getElementById(id);
- return found && __hfContains(found) ? found : null;
- };
- }
+ if (prop === "getElementById") return __hfGetElementById;
var value = Reflect.get(target, prop, target);
return typeof value === "function" ? value.bind(target) : value;
},