type TimelineLike = { getChildren?: (nested: boolean) => Array<{ targets?: () => Element[] }> }; let _gsapCachedTimelines: Record | undefined; let _gsapTargetIds: Set | undefined; let _gsapTargetNodes: WeakSet | undefined; function addTargetsFromTimeline(tl: TimelineLike, ids: Set, nodes: WeakSet): void { const children = tl.getChildren?.(true); if (!children) return; for (const child of children) { const targets = child.targets?.(); if (!targets) continue; for (const t of targets) { nodes.add(t); if (t.id) ids.add(t.id); } } } function collectGsapTargets(timelines: Record): { ids: Set; nodes: WeakSet; } { const ids = new Set(); const nodes = new WeakSet(); for (const tl of Object.values(timelines)) { if (!tl) continue; try { addTargetsFromTimeline(tl, ids, nodes); } catch { /* teardown race */ } } return { ids, nodes }; } function readTimelines(iframe: HTMLIFrameElement | null): Record | undefined { if (!iframe?.contentWindow) return undefined; try { return (iframe.contentWindow as Window & { __timelines?: Record }) .__timelines; } catch { return undefined; } } export function isElementGsapTargeted( iframe: HTMLIFrameElement | null, element: HTMLElement, ): boolean { const timelines = readTimelines(iframe); if (!timelines) return false; if (timelines !== _gsapCachedTimelines) { const cache = collectGsapTargets(timelines); _gsapTargetIds = cache.ids; _gsapTargetNodes = cache.nodes; _gsapCachedTimelines = timelines; } return _gsapTargetNodes!.has(element) || !!(element.id && _gsapTargetIds!.has(element.id)); }