All files / boundless/packages/boundless-portal index.js

100% Statements 11/11
100% Branches 6/6
100% Functions 5/5
100% Lines 11/11
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                                                                                                                                                                      8x 8x   8x       13x     13x   13x 13x     5x     8x 8x       13x              
import React, {PropTypes} from 'react';
import ReactDOM from 'react-dom';
 
import omit from 'boundless-utils-omit-keys';
import uuid from 'boundless-utils-uuid';
 
/**
__A higher-order component for the rendering of components outside the normal React tree.__
 
`Portal` is used in other components such as `Popover` to render content to places like the HTML `<body>` tag, avoiding style leakage and parent layout contexts. Only accepts a single top-level child; naked text, etc will be wrapped in a `<div>`.
 */
export default class Portal extends React.Component {
    static propTypes = {
        /**
         * any [React-supported attribute](https://facebook.github.io/react/docs/tags-and-attributes.html#html-attributes)
         */
        '*': PropTypes.any,
 
        // single child only - arrays not allowed
 
        /**
         * any normal React child, but must be singular; multiple sibling children must have a common wrapper, such as a "layout" `<div>`
 
         * ✅ OK:
 
         * ```jsx
         * <Portal>
         *   foo
         * </Portal>
 
         * <Portal>
         *   <div>foo</div>
         * </Portal>
 
         * <Portal>
         *   <div>
         *       <div>foo</div>
         *       <div>bar</div>
         *   </div>
         * </Portal>
         * ```
 
         * ⛔️ Not OK:
 
         * ```jsx
         * <Portal>
         *   <div>foo</div>
         *   <div>bar</div>
         * </Portal>
         * ```
         */
        children: React.PropTypes.node,
 
        /**
         * the location to append the generated portal and child elements
         */
        destination: PropTypes.instanceOf(HTMLElement),
 
        /**
         * the ID used to link the portal origin to the destination; added to generated `<div>` appended to the destination HTML node
         */
        portalId: PropTypes.string,
    }
 
    static defaultProps = {
        children: null,
        destination: document.body,
        portalId: null,
    }
 
    static internalKeys = Object.keys(Portal.defaultProps)
 
    static PORTAL_DATA_ATTRIBUTE = 'data-portal-id'
 
    id = uuid()
 
    // the <div> that the children are rendered into
    $portal = null
 
    // the top-level child rendered into the $portal
    $passenger = null;
 
    componentWillMount() {
        this.$portal = document.createElement('div');
        this.props.destination.appendChild(this.$portal);
 
        this.renderPortalledContent();
    }
 
    renderPortalledContent() {
        const child = React.isValidElement(this.props.children) ? this.props.children : (<div>{this.props.children}</div>);
 
        // update the portal ID link if needed
        this.$portal.id = this.props.portalId || this.id;
 
        ReactDOM.unstable_renderSubtreeIntoContainer(this, child, this.$portal);
        this.$passenger = this.$portal.children[0];
    }
 
    componentDidUpdate() { this.renderPortalledContent(); }
 
    componentWillUnmount() {
        ReactDOM.unmountComponentAtNode(this.$portal);
        this.props.destination.removeChild(this.$portal);
    }
 
    render() {
        return (
            <span
                {...omit(this.props, Portal.internalKeys)}
                {...{[Portal.PORTAL_DATA_ATTRIBUTE]: this.props.portalId || this.id}} />
        );
    }
}