From 50a0942dbaa862ab225c74833deb4a885beffd4e Mon Sep 17 00:00:00 2001 From: Chris McVittie Date: Mon, 2 Nov 2015 20:35:40 +0000 Subject: [PATCH] Render to Layer component (+ squashed fixes) --- package.json | 4 ++ src/render-to-layer.js | 123 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 src/render-to-layer.js diff --git a/package.json b/package.json index 7047efd49a2a69..b248478ed5004e 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,10 @@ "url": "https://github.com/callemall/material-ui/issues" }, "homepage": "http://material-ui.com/", + "dependencies": { + "lodash.throttle": "~3.0.4", + "lodash.debounce": "~3.1.1" + }, "peerDependencies": { "inline-style-prefixer": "^0.3.3", "react": "^0.14.0", diff --git a/src/render-to-layer.js b/src/render-to-layer.js new file mode 100644 index 00000000000000..7771e4b7eed095 --- /dev/null +++ b/src/render-to-layer.js @@ -0,0 +1,123 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import Events from './utils/events'; +import Dom from './utils/dom'; +import debounce from 'lodash.debounce'; + +// heavily inspired by https://github.com/Khan/react-components/blob/master/js/layered-component-mixin.jsx +const RenderToLayer = React.createClass({ + + componentDidMount() { + this._renderLayer(); + }, + + componentDidUpdate() { + this._renderLayer(); + }, + + componentWillUnmount() { + this._unbindClickAway(); + if (this._layer) { + this._unrenderLayer(); + } + }, + + _checkClickAway(e) { + if (!this.canClickAway) { + return; + } + const el = this._layer; + if (e.target !== el && + !Dom.isDescendant(el, e.target) && + document.documentElement.contains(e.target)) { + if (this.props.componentClickAway) { + this.props.componentClickAway(e); + } + } + }, + + _preventClickAway(e) { + if (e.detail === this) { + return; + } + this.canClickAway = false; + }, + + _allowClickAway() { + this.canClickAway = true; + }, + + getLayer() { + return this._layer; + }, + + render() { + return null; + }, + + _renderLayer() { + if (this.props.open) { + if (!this._layer) { + this._layer = document.createElement('div'); + document.body.appendChild(this._layer); + } + this._bindClickAway(); + if (this.reactUnmount) { + this.reactUnmount.cancel(); + } + } else if (this._layer) { + this._unbindClickAway(); + this._unrenderLayer(); + } else { + return; + } + + // By calling this method in componentDidMount() and + // componentDidUpdate(), you're effectively creating a "wormhole" that + // funnels React's hierarchical updates through to a DOM node on an + // entirely different part of the page. + + const layerElement = this.props.render(); + // Renders can return null, but React.render() doesn't like being asked + // to render null. If we get null back from renderLayer(), just render + // a noscript element, like React does when an element's render returns + // null. + if (layerElement === null) { + this.layerElement = ReactDOM.unstable_renderSubtreeIntoContainer (this,