UNPKG

react-hold

Version:

Hold the empty presentational components in react.js

325 lines (283 loc) 11.8 kB
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } import React, { Component } from 'react'; import { findDOMNode } from 'react-dom'; import PropTypes from 'prop-types'; import hoistNonReactStatic from 'hoist-non-react-statics'; import { isNull, isObject, isFunction, getNodeSize, getComputedStyle, getDisplayName, addHandler, removeHandler } from './utils'; import Fill from './holders/Fill'; import createRefiter from './createRefiter'; var $nbsp = '\xA0'; var blankLength = 10; var envDefaultStyle = { position: 'relative', padding: '0px', margin: '0px', width: '100%', height: '100%', border: 'none', overflow: 'visible' /** * Create a higher-order component to manage * original component and placeholder component. * * @param {Component|String} targetComponent * @param {Function} condition * @param {Component} defaultHolder * @param {Object} holderDefaultProps * @returns {Component} */ };export default function (targetComponent, condition) { var defaultHolder = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : Fill; var holderDefaultProps = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; if (!isFunction(targetComponent) && typeof targetComponent !== 'string') { throw new TypeError('Expected the target component to be a string or class/function.'); } if (!isFunction(condition)) { throw new TypeError('Expected the hold condition to be a function.'); } if (isObject(defaultHolder)) { holderDefaultProps = defaultHolder; defaultHolder = Fill; } var targetComponentName = getDisplayName(targetComponent); var refiter = createRefiter(targetComponent); var Hold = function (_Component) { _inherits(Hold, _Component); function Hold() { var _ref; _classCallCheck(this, Hold); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } var _this = _possibleConstructorReturn(this, (_ref = Hold.__proto__ || Object.getPrototypeOf(Hold)).call.apply(_ref, [this].concat(args))); _this.state = { hold: true, copy: true, holderAutoSize: { width: null, height: null } // The style of original node };_this.originNodeStyle = null; // window resize handler _this.resizeHandler = function () { if (_this.state.hold) { _this.updateHolderSizeIfNecessary(); } }; _this.cancelHold = _this.cancelHold.bind(_this); return _this; } _createClass(Hold, [{ key: 'componentWillMount', value: function componentWillMount() { if (condition.call(null, this.props, {})) { refiter.refit(); } else { this.cancelHold(); } } }, { key: 'componentDidMount', value: function componentDidMount() { if (this.state.hold) { this.originNodeStyle = this.computeOriginNodeStyle(); this.setState({ copy: false }); } addHandler(window, 'resize', this.resizeHandler); } }, { key: 'componentWillReceiveProps', value: function componentWillReceiveProps(nextProps) { if (condition.call(null, nextProps, this.props)) { this.setState({ hold: true, copy: true }); } else { this.cancelHold(); } } }, { key: 'componentDidUpdate', value: function componentDidUpdate() { if (this.state.hold) { if (this.state.copy) { refiter.refit(); this.originNodeStyle = this.computeOriginNodeStyle(); this.setState({ copy: false }); } else if (!isNull(this.originNodeStyle)) { this.setFakeNodeStyle(this.originNodeStyle); this.updateHolderSizeIfNecessary(); this.originNodeStyle = null; } } } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { removeHandler(window, 'resize', this.resizeHandler); } }, { key: 'setFakeNodeStyle', value: function setFakeNodeStyle(style) { if (!isObject(style)) return; var _refs = this.refs, fake = _refs.fake, env = _refs.env; var isInline = style.display && style.display.indexOf('inline') > -1; // hidden element fake.style.display = 'none'; // set style Object.keys(style).forEach(function (name) { if (name !== 'display') { fake.style[name] = style[name]; } }); // fix fake style fake.style.opacity = 1; fake.style.background = 'transparent'; fake.style.borderColor = 'transparent'; // fix env style if (isInline) { env.style.overflow = 'hidden'; } else { env.style.overflow = 'visible'; } // display fake fake.style.display = isInline ? 'inline-block' : style.display; } }, { key: 'computeOriginNodeStyle', value: function computeOriginNodeStyle() { var result = null; var originNode = findDOMNode(this); // store original display property var computedStyle = getComputedStyle(originNode, null); var originDisplay = computedStyle.getPropertyValue('display'); // set display to 'none' before recompute is very **important**, // don't remove or move this step! originNode.style.display = 'none'; // compute node style computedStyle = getComputedStyle(originNode, null); // copy style Object.keys(computedStyle).forEach(function (key) { if (/[0-9]+/.test(key)) { var name = computedStyle[key]; result = result || {}; if (name === 'display') { result[name] = originDisplay; } else { result[name] = computedStyle.getPropertyValue(name); } } }); return result; } }, { key: 'cancelHold', value: function cancelHold() { var _refs2 = this.refs, fake = _refs2.fake, env = _refs2.env; // manual restore fake and env node style, // because their style had been modified by method 'setFakeNodeStyle' if (fake) fake.removeAttribute('style'); if (env) env.style.overflow = 'visible'; // restore component lifecycle methods refiter.undo(); // clear origin node style this.originNodeStyle = null; // exit hold state this.setState({ hold: false, copy: false }); } }, { key: 'updateHolderSizeIfNecessary', value: function updateHolderSizeIfNecessary() { var env = this.refs.env; if (!env) return; var holderAutoSize = this.state.holderAutoSize; var holderProps = this.props.holderProps || this.props.props || {}; var customWidth = isNull(holderDefaultProps.width) ? holderProps.width : holderDefaultProps.width; var customHeight = isNull(holderDefaultProps.height) ? holderProps.height : holderDefaultProps.height; if (!isNull(customWidth) && !isNull(customHeight)) return; var size = getNodeSize(env); var width = isNull(customWidth) ? size.width : null; var height = isNull(customHeight) ? size.height : null; if (holderAutoSize.width !== width || holderAutoSize.height !== height) { this.setState({ holderAutoSize: { width: width, height: height } }); } } }, { key: 'render', value: function render() { var _state = this.state, hold = _state.hold, copy = _state.copy, holderAutoSize = _state.holderAutoSize; var _props = this.props, innerRef = _props.innerRef, holder = _props.holder, holderProps = _props.holderProps, props = _props.props, propsForElement = _objectWithoutProperties(_props, ['innerRef', 'holder', 'holderProps', 'props']); if (!hold || copy) { if (innerRef && !hold) propsForElement.ref = innerRef; return React.createElement(targetComponent, propsForElement); } var propsForHolder = _extends({ color: '#eee', width: null, height: null }, holderDefaultProps, props, holderProps, { cancelHold: this.cancelHold, targetProps: propsForElement }); isNull(propsForHolder.width) && (propsForHolder.width = holderAutoSize.width); isNull(propsForHolder.height) && (propsForHolder.height = holderAutoSize.height); if (typeof propsForHolder.children === 'string') { propsForHolder.children = propsForHolder.children.replace(/ /g, $nbsp); } propsForHolder.children = propsForHolder.children || $nbsp.repeat(blankLength); return React.createElement( 'div', { ref: 'fake' }, React.createElement( 'div', { ref: 'env', style: envDefaultStyle }, React.createElement(holder, propsForHolder) ) ); } }]); return Hold; }(Component); hoistNonReactStatic(Hold, targetComponent); Hold.displayName = 'Hold(' + targetComponentName + ')'; Hold.propTypes = { holder: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), holderProps: PropTypes.object, props: PropTypes.object, // The alias of 'holderProps' innerRef: PropTypes.oneOfType([PropTypes.string, PropTypes.func]) }; Hold.defaultProps = { holder: defaultHolder, holderProps: null, props: null, innerRef: null }; return Hold; }