MCPcopy Create free account
hub / github.com/heygen-com/hyperframes / IframePreviewAdapter

Class IframePreviewAdapter

packages/sdk/src/adapters/iframe.ts:485–639  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

483type SelectionHandler = (ids: string[]) => void;
484
485class IframePreviewAdapter implements PreviewAdapter {
486 private readonly iframe: HTMLIFrameElement;
487 private readonly _dispatch: ((op: EditOp) => void) | undefined;
488
489 private _selection: string[] = [];
490 private _handlers: SelectionHandler[] = [];
491
492 /** Tracked id and element for the in-progress drag. */
493 private _draftId: string | null = null;
494 private _draftEl: HTMLElement | null = null;
495
496 constructor(iframe: HTMLIFrameElement, dispatch?: (op: EditOp) => void) {
497 this.iframe = iframe;
498 this._dispatch = dispatch;
499 }
500
501 /**
502 * Synchronous hit-test. Returns the nearest `[data-hf-id]` element under
503 * (x, y) in the iframe's coordinate space, or null for a transparent hit
504 * (root, opacity-0, nothing at all, or a transparent image pixel).
505 *
506 * WS-G phase 1: uses elementsFromPoint (z-stack) so a transparent-image hit
507 * falls through to the layer behind. For <img> elements, the alpha at the
508 * mapped natural pixel is sampled from an offscreen canvas. Cross-origin
509 * images that taint the canvas are treated as opaque (safe fallback).
510 *
511 * atTime: reflects the GSAP state at the playhead when this is called.
512 * Seeking to a different time to check visibility is WS-G follow-on.
513 */
514 elementAtPoint(x: number, y: number, _opts?: { atTime?: number }): ElementAtPointResult | null {
515 const doc = this.iframe.contentDocument;
516 if (!doc) return null;
517 const win = this.iframe.contentWindow as (Window & typeof globalThis) | null;
518 if (!win) return null;
519
520 const stack = hitStack(doc, x, y);
521
522 for (const candidate of stack) {
523 // One opacity walk per candidate (candidate → root). An opacity:0 element
524 // is skipped, so the click falls through to the layer painted behind it.
525 if (!isOpacityVisible(candidate, win)) continue;
526
527 // Image-alpha check: if this is an <img>, verify the pixel is opaque.
528 if (win.HTMLImageElement && candidate instanceof win.HTMLImageElement) {
529 if (!imageAlphaOpaqueAt(candidate, x, y, win)) {
530 // Transparent pixel — fall through to the next element in the stack.
531 continue;
532 }
533 }
534
535 // The candidate's whole ancestor chain is already known visible (the walk
536 // above covers it, and the hf node is on that chain), so the resolver
537 // needs no second visibility walk.
538 const result = resolveNearestHfElement(candidate, () => true);
539 if (result !== null) return result;
540 }
541
542 return null;

Callers

nothing calls this directly

Calls

no outgoing calls

Tested by

no test coverage detected