mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(studio): support web-component refs in useTimelinePlayer (#245)
* fix(studio): support web-component refs in useTimelinePlayer The studio's `useTimelinePlayer` hook returns an `iframeRef` that consumers attach to an `<iframe>` element. When consumers wrap the iframe in a custom element (e.g. `<hyperframes-player>`) that puts the iframe inside its shadow DOM, every `iframeRef.current.contentWindow` access returned `null` and `getAdapter()` silently failed — meaning timeline seek, play, pause, and `refreshPlayer` all became no-ops. Changes: - Add `resolveIframe(el)` helper that returns the underlying iframe whether the host is the iframe itself, a custom element with a shadow-DOM iframe, or a wrapper with a descendant iframe. - Export `resolveIframe` from the studio so consumers can pre-resolve the iframe before assigning it to `iframeRef`. - Internal `useTimelinePlayer` keeps the strict `HTMLIFrameElement` ref type, so existing consumers attaching directly to an `<iframe>` are unaffected. Also adds: - JSDoc on the player's `iframeElement` getter. - "Advanced: iframe access" docs section in `packages/player/README.md` and `docs/packages/player.mdx`. - Type-safety lint rules in `.oxlintrc.json` and a "Type-safety conventions" section in `CONTRIBUTING.md`. Backward compatible — App.tsx and NLELayout.tsx continue to work unchanged. * chore(lint): defer no-explicit-any rule; it broke existing codebase The new rules added 37 errors across 32 existing files — mostly legitimate `window as any` casts at browser-global and test-mock boundaries. Enabling them without fixing all violations breaks CI. Revert the `.oxlintrc.json` additions and soften the CONTRIBUTING.md wording to describe the convention without claiming lint enforcement (that enforcement will come in a follow-up PR that fixes all sites).
This commit is contained in:
@@ -76,8 +76,63 @@ player.ready; // boolean (read-only)
|
||||
player.playbackRate; // number (read/write)
|
||||
player.muted; // boolean (read/write)
|
||||
player.loop; // boolean (read/write)
|
||||
|
||||
// Inner iframe access (for advanced consumers — see "Advanced: iframe access" below)
|
||||
player.iframeElement; // HTMLIFrameElement (read-only)
|
||||
```
|
||||
|
||||
## Advanced: iframe access
|
||||
|
||||
The composition runs inside a sandboxed `<iframe>` in the player's Shadow DOM. For most use cases you don't need direct access — the JavaScript API above is enough. But if you're building an editor, recorder, or custom timeline that needs to inspect the composition's DOM or read its `__player` / `__timelines` runtime objects, use the `iframeElement` getter:
|
||||
|
||||
```js
|
||||
const player = document.querySelector("hyperframes-player");
|
||||
const iframe = player.iframeElement;
|
||||
|
||||
// Now you can reach into the composition's DOM and runtime
|
||||
iframe.contentDocument.querySelectorAll("[data-composition-id]");
|
||||
iframe.contentWindow.__timelines;
|
||||
```
|
||||
|
||||
This is the canonical way to bridge the player into tools like [`@hyperframes/studio`](../studio). The studio exports a `resolveIframe` helper that works with both iframe refs and web-component refs:
|
||||
|
||||
```ts
|
||||
import { useTimelinePlayer, resolveIframe } from "@hyperframes/studio";
|
||||
|
||||
const { iframeRef } = useTimelinePlayer();
|
||||
const player = document.createElement("hyperframes-player");
|
||||
player.setAttribute("src", src);
|
||||
container.appendChild(player);
|
||||
|
||||
// Forward the inner iframe so useTimelinePlayer can drive play/pause/seek.
|
||||
iframeRef.current = resolveIframe(player);
|
||||
```
|
||||
|
||||
### React: declarative ref pattern
|
||||
|
||||
If you prefer JSX over imperative element creation, attach a ref directly to the web component and resolve the iframe inside an effect:
|
||||
|
||||
```tsx
|
||||
import "@hyperframes/player";
|
||||
import type { HyperframesPlayer } from "@hyperframes/player";
|
||||
import { useTimelinePlayer, resolveIframe } from "@hyperframes/studio";
|
||||
|
||||
function StudioPreview({ src }: { src: string }) {
|
||||
const { iframeRef, onIframeLoad } = useTimelinePlayer();
|
||||
const playerRef = useRef<HyperframesPlayer>(null);
|
||||
|
||||
useEffect(() => {
|
||||
iframeRef.current = resolveIframe(playerRef.current);
|
||||
});
|
||||
|
||||
return <hyperframes-player ref={playerRef} src={src} onLoad={onIframeLoad} />;
|
||||
}
|
||||
```
|
||||
|
||||
> **Heads up — common gotcha**
|
||||
>
|
||||
> If you pass the `<hyperframes-player>` element itself (not `iframeElement`) into a hook that expects an `<iframe>`, every `.contentWindow` / `.contentDocument` access returns `null` because the iframe lives inside the player's Shadow DOM. Always extract `iframeElement` first, or use `resolveIframe` from `@hyperframes/studio` which handles both iframe and web-component hosts transparently.
|
||||
|
||||
## Events
|
||||
|
||||
| Event | Detail | Fired when |
|
||||
|
||||
@@ -120,7 +120,27 @@ class HyperframesPlayer extends HTMLElement {
|
||||
|
||||
// ── Public API ──
|
||||
|
||||
/** Access the inner iframe element (for advanced consumers like the studio). */
|
||||
/**
|
||||
* Access the inner `<iframe>` element rendering the composition.
|
||||
*
|
||||
* Use this when integrating the player with editors, recorders, or
|
||||
* timeline tools (e.g. `@hyperframes/studio`) that need to inspect
|
||||
* the composition's DOM or read its `__player` / `__timelines`
|
||||
* runtime objects.
|
||||
*
|
||||
* **Common pitfall:** the iframe lives inside the player's Shadow DOM.
|
||||
* Passing the `<hyperframes-player>` element itself to code that expects
|
||||
* an `<iframe>` will silently break — `.contentWindow` returns `null`.
|
||||
* Always extract `iframeElement` first:
|
||||
*
|
||||
* ```ts
|
||||
* // ❌ Wrong — element ref doesn't expose contentWindow
|
||||
* iframeRef.current = playerRef.current;
|
||||
*
|
||||
* // ✓ Right — bridge the actual iframe
|
||||
* iframeRef.current = playerRef.current.iframeElement;
|
||||
* ```
|
||||
*/
|
||||
get iframeElement(): HTMLIFrameElement {
|
||||
return this.iframe;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user