import React, { Component, ReactNode, RefObject } from "react"; import ReactDOM from "react-dom"; import { FrameContextProvider } from "./context"; import Content from "./content"; interface FrameProps { style?: React.CSSProperties; head?: ReactNode; title: string; className: string; initialContent?: string; mountTarget?: string; contentDidMount?: () => void; contentDidUpdate?: () => void; children?: ReactNode | ReactNode[]; forwardedRef?: | ((instance: HTMLIFrameElement | null) => void) | React.RefObject; } interface FrameState { iframeLoaded: boolean; } export class Frame extends Component { static defaultProps = { style: {}, head: null, children: undefined, mountTarget: undefined, contentDidMount: () => {}, contentDidUpdate: () => {}, initialContent: '
', }; private _isMounted: boolean = false; private nodeRef: RefObject = React.createRef(); constructor(props: FrameProps) { super(props); this.state = { iframeLoaded: false }; } componentDidMount() { this._isMounted = true; const doc = this.getDoc(); if (doc && doc.readyState === "complete") { this.forceUpdate(); } else { this.nodeRef.current?.addEventListener("load", this.handleLoad); } } componentWillUnmount() { this._isMounted = false; this.nodeRef.current?.removeEventListener("load", this.handleLoad); } getDoc() { return this.nodeRef.current ? this.nodeRef.current.contentDocument : null; } getMountTarget() { const doc = this.getDoc(); if (this.props.mountTarget) { return doc?.querySelector(this.props.mountTarget); } return doc?.body.children[0]; } setRef = (node: HTMLIFrameElement) => { //@ts-ignore this.nodeRef.current = node; const { forwardedRef } = this.props; if (typeof forwardedRef === "function") { forwardedRef(node); } else if (forwardedRef) { //@ts-ignore forwardedRef.current = node; } }; handleLoad = () => { this.setState({ iframeLoaded: true }); }; renderFrameContents() { if (!this._isMounted) { return null; } const doc = this.getDoc(); if (!doc) { return null; } const contentDidMount = this.props.contentDidMount; const contentDidUpdate = this.props.contentDidUpdate; const win = doc.defaultView || (doc as any).parentView; const contents = (
{this.props.children}
); const mountTarget = this.getMountTarget(); return [ ReactDOM.createPortal(this.props.head, (this.getDoc() as any).head), ReactDOM.createPortal(contents, mountTarget as Element), ]; } render() { const props = { ...this.props, srcDoc: this.props.initialContent, children: undefined, }; delete props.head; delete props.initialContent; delete props.mountTarget; delete props.contentDidMount; delete props.contentDidUpdate; delete props.forwardedRef; return ( ); } } export default React.forwardRef((props, ref) => ( ));