All files index.tsx

95.91% Statements 47/49
85.71% Branches 30/35
91.66% Functions 11/12
97.77% Lines 44/45

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115                                                      1x   24x 24x 24x 29x 14x       24x   24x 24x 24x 8x 8x 2x   6x   24x 24x   8x         8x 8x   8x 8x           24x 10x         24x 15x 8x   15x 14x 8x         24x 8x 8x 8x   8x 8x 8x         24x 24x 3x 3x 3x   21x   24x               1x      
import { useCallback, useEffect, useState, forwardRef, useImperativeHandle, useMemo, useRef } from 'react';
import { createPortal } from 'react-dom';
import { FrameContext } from './Context';
 
export { FrameContext, useFrame } from './Context';
 
export interface IFrameProps
  extends React.DetailedHTMLProps<React.IframeHTMLAttributes<HTMLIFrameElement>, HTMLIFrameElement> {
  /**
   * The `head` prop is a dom node that gets inserted before the children of the frame.
   */
  head?: React.ReactNode;
  /**
   * The `initialContent` props is the initial html injected into frame.
   * It is only injected once,
   * but allows you to insert any html into the frame (e.g. a [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head) tag, [`<script>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script) tags, etc).
   * Note that it does not update if you change the prop.
   *
   * Defaults to `<!DOCTYPE html><html><head></head><body></body></html>`
   */
  initialContent?: string;
  /**
   * The `mountTarget` attribute is a css selector (`#target`/`.target`) that specifies the child within the initial content of the iframe to be mounted.
   */
  mountTarget?: string;
}
 
const IFrame = forwardRef<HTMLIFrameElement, IFrameProps>(
  ({ children, head, initialContent, src, mountTarget, ...other }, ref) => {
    const [iframeLoaded, setIframeLoaded] = useState(false);
    const [mountNode, setMountNode] = useState<HTMLIFrameElement>();
    const refContent = (node: HTMLIFrameElement) => {
      if (node) {
        setMountNode(node);
      }
    };
 
    useImperativeHandle(ref, () => mountNode!, [mountNode]);
 
    const html = initialContent || `<!DOCTYPE html><html><head></head><body></body></html>`;
    const getDoc = () => (mountNode ? mountNode.contentDocument : null);
    const getMountTarget = () => {
      const doc = getDoc();
      if (mountTarget) {
        return doc?.querySelector(mountTarget);
      }
      return doc?.body;
    };
    const evnRef = useRef<React.SyntheticEvent<HTMLIFrameElement, Event>>();
    const handleLoad = useCallback<(evn: React.SyntheticEvent<HTMLIFrameElement, Event> | Event) => void>(
      (evn) => {
        evnRef.current = evn as React.SyntheticEvent<HTMLIFrameElement, Event>;
        /**
         * In certain situations on a cold cache DOMContentLoaded never gets called
         * fallback to an interval to check if that's the case
         */
        const loadCheck = () => setInterval(() => handleLoad(evn), 500);
        clearInterval(loadCheck());
        // Bail update as some browsers will trigger on both DOMContentLoaded & onLoad ala firefox
        Eif (!iframeLoaded) {
          setIframeLoaded(true);
        }
      },
      [iframeLoaded],
    );
 
    useMemo(() => {
      Iif (!src && other.onLoad && iframeLoaded) {
        other.onLoad(evnRef.current!);
      }
    }, [iframeLoaded]);
 
    useEffect(() => {
      if (mountNode && !src) {
        mountNode.contentWindow?.addEventListener('DOMContentLoaded', handleLoad);
      }
      return () => {
        if (mountNode && !src) {
          mountNode.contentWindow?.removeEventListener('DOMContentLoaded', handleLoad);
        }
      };
    }, [mountNode, handleLoad]);
 
    const renderFrameContents = () => {
      const doc = getDoc();
      const header = getDoc()?.head;
      const mountTarget = getMountTarget();
      // @ts-ignore
      const win = doc?.defaultView || doc?.parentView;
      const contents = <FrameContext.Provider value={{ document: doc, window: win }}>{children}</FrameContext.Provider>;
      return [
        header && head && createPortal(head, header),
        mountNode && mountTarget && createPortal(contents, mountTarget),
      ];
    };
    const reProps: IFrameProps = {};
    if (src) {
      delete reProps.srcDoc;
      reProps.src = src;
      reProps.onLoad = other.onLoad;
    } else {
      reProps.srcDoc = html;
    }
    return (
      <iframe title={other.title} ref={refContent} {...other} onLoad={handleLoad} {...reProps}>
        {iframeLoaded && renderFrameContents()}
      </iframe>
    );
  },
);
 
IFrame.displayName = 'IFrame';
 
export default IFrame;