react-hold
Version:
Hold the empty presentational components in react.js
349 lines (292 loc) • 12.6 kB
JavaScript
;
exports.__esModule = true;
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; }; }();
exports.default = function (targetComponent, condition) {
var defaultHolder = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : _Fill2.default;
var holderDefaultProps = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
if (!(0, _utils.isFunction)(targetComponent) && typeof targetComponent !== 'string') {
throw new TypeError('Expected the target component to be a string or class/function.');
}
if (!(0, _utils.isFunction)(condition)) {
throw new TypeError('Expected the hold condition to be a function.');
}
if ((0, _utils.isObject)(defaultHolder)) {
holderDefaultProps = defaultHolder;
defaultHolder = _Fill2.default;
}
var targetComponentName = (0, _utils.getDisplayName)(targetComponent);
var refiter = (0, _createRefiter2.default)(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 });
}
(0, _utils.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 (!(0, _utils.isNull)(this.originNodeStyle)) {
this.setFakeNodeStyle(this.originNodeStyle);
this.updateHolderSizeIfNecessary();
this.originNodeStyle = null;
}
}
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
(0, _utils.removeHandler)(window, 'resize', this.resizeHandler);
}
}, {
key: 'setFakeNodeStyle',
value: function setFakeNodeStyle(style) {
if (!(0, _utils.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 = (0, _reactDom.findDOMNode)(this);
// store original display property
var computedStyle = (0, _utils.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 = (0, _utils.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 = (0, _utils.isNull)(holderDefaultProps.width) ? holderProps.width : holderDefaultProps.width;
var customHeight = (0, _utils.isNull)(holderDefaultProps.height) ? holderProps.height : holderDefaultProps.height;
if (!(0, _utils.isNull)(customWidth) && !(0, _utils.isNull)(customHeight)) return;
var size = (0, _utils.getNodeSize)(env);
var width = (0, _utils.isNull)(customWidth) ? size.width : null;
var height = (0, _utils.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 _react2.default.createElement(targetComponent, propsForElement);
}
var propsForHolder = _extends({
color: '#eee',
width: null,
height: null
}, holderDefaultProps, props, holderProps, {
cancelHold: this.cancelHold,
targetProps: propsForElement
});
(0, _utils.isNull)(propsForHolder.width) && (propsForHolder.width = holderAutoSize.width);
(0, _utils.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 _react2.default.createElement(
'div',
{ ref: 'fake' },
_react2.default.createElement(
'div',
{ ref: 'env', style: envDefaultStyle },
_react2.default.createElement(holder, propsForHolder)
)
);
}
}]);
return Hold;
}(_react.Component);
(0, _hoistNonReactStatics2.default)(Hold, targetComponent);
Hold.displayName = 'Hold(' + targetComponentName + ')';
Hold.propTypes = {
holder: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.func]),
holderProps: _propTypes2.default.object,
props: _propTypes2.default.object, // The alias of 'holderProps'
innerRef: _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.func])
};
Hold.defaultProps = {
holder: defaultHolder,
holderProps: null,
props: null,
innerRef: null
};
return Hold;
};
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _reactDom = require('react-dom');
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _hoistNonReactStatics = require('hoist-non-react-statics');
var _hoistNonReactStatics2 = _interopRequireDefault(_hoistNonReactStatics);
var _utils = require('./utils');
var _Fill = require('./holders/Fill');
var _Fill2 = _interopRequireDefault(_Fill);
var _createRefiter = require('./createRefiter');
var _createRefiter2 = _interopRequireDefault(_createRefiter);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
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; }
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}
*/
};