import m from 'mithril'; import classnames from 'classnames'; import { Classes, IAttrs, safeCall, normalizeStyle } from '../../_shared'; export interface IPortalAttrs extends IAttrs { /** Callback invoked when the component is mounted */ onContentMount?: (rootElement: HTMLElement) => void; /** Optional HTML element to mount to */ container?: HTMLElement; } export class Portal implements m.Component { private rootElement: HTMLElement; private content: m.Component; public oncreate({ attrs, children }: m.Vnode) { const rootElement = document.createElement('div'); const container = attrs.container || document.body; container.appendChild(rootElement); this.rootElement = rootElement; this.setStyles(attrs); this.content = { view: () => children }; m.mount(this.rootElement, this.content); safeCall(attrs.onContentMount, rootElement); } public onupdate({ attrs }: m.Vnode) { this.setStyles(attrs); } public onbeforeupdate({ children }: m.Vnode) { if (!this.content) return false; this.content.view = () => children; } public onremove({ attrs }: m.Vnode) { const container = attrs.container || document.body; if (container.contains(this.rootElement)) { m.mount(this.rootElement, null); container.removeChild(this.rootElement); } } public view() { return m.fragment({}, ''); } private setStyles(attrs: IPortalAttrs) { this.rootElement.className = classnames(Classes.PORTAL, attrs.class); this.rootElement.style.cssText = ''; if (attrs.style) { Object.assign(this.rootElement.style, normalizeStyle(attrs.style)); } } }