remeasure
Version:
Get position and size of the DOM element for any React Component
455 lines (393 loc) • 16.1 kB
JavaScript
function _extends() { _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; }; return _extends.apply(this, arguments); }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
// external dependencies
import debounceMethod from 'debounce';
import { deepEqual } from 'fast-equals';
import memoize from 'micro-memoize';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { findDOMNode } from 'react-dom';
import raf from 'raf';
import ResizeObserver from 'resize-observer-polyfill'; // constants
import { COMPONENT_WILL_MOUNT, COMPONENT_WILL_RECEIVE_PROPS, IS_PRODUCTION, KEY_NAMES, KEYS, SOURCES } from './constants'; // utils
import { getNaturalDimensionValue, getStateKeys, isElementVoidTag } from './utils';
/**
* @private
*
* @function getInitialState
*
* @description
* get the initial state of the component instance
*
* @returns {Object} the initial state
*/
export var getInitialState = function getInitialState() {
return KEY_NAMES.reduce(function (state, key) {
state[key] = null;
return state;
}, {});
};
export var createComponentWillMount = function createComponentWillMount(instance) {
return (
/**
* @private
*
* @function componentWillMount
*
* @description
* prior to mount, set the keys to watch for and the render method
*/
function () {
instance.keys = getStateKeys(instance.props);
instance.setRenderMethod(instance.props);
}
);
};
export var createComponentDidMount = function createComponentDidMount(instance) {
return (
/**
* @private
*
* @function componentDidMount
*
* @description
* on mount, get the element and set it's resize observer
*/
function () {
instance._isMounted = true;
instance.element = findDOMNode(instance);
instance.setResizeObserver();
}
);
};
export var createComponentWillReceiveProps = function createComponentWillReceiveProps(instance) {
return (
/**
* @private
*
* @function componentWillReceiveProps
*
* @description
* when the component receives new props, set the render method for future renders
*
* @param {Object} nextProps the next render's props
*/
function (nextProps) {
return instance.setRenderMethod(nextProps);
}
);
};
export var createSetValues = function createSetValues(instance, isDebounce) {
var debounce = instance.props.debounce;
/**
* @private
*
* @function delayedMethod
*
* @description
* on a delay (either requestAnimationFrame or debounce), determine the calculated measurements and assign
* them to state if changed
*/
var delayedMethod = function delayedMethod(event) {
var clientRect = instance.element ? instance.element.getBoundingClientRect() : {};
var isResize = event && event.type === 'resize';
var newValues = KEYS.reduce(function (values, key) {
values[key.key] = ~instance.keys.indexOf(key) ? instance.element ? getNaturalDimensionValue(key.source === SOURCES.CLIENT_RECT ? clientRect : instance.element, key.key) : 0 : null;
return values;
}, {});
if (!instance._isMounted) {
return;
}
if (isResize || !deepEqual(instance.state, newValues)) {
instance.setState(function () {
return newValues;
});
}
};
return isDebounce && typeof debounce === 'number' ? debounceMethod(delayedMethod, debounce) : function (event) {
return raf(function () {
return delayedMethod(event);
});
};
};
export var createComponentDidUpdate = function createComponentDidUpdate(instance) {
return (
/**
* @private
*
* @function componentDidUpdate
*
* @description
* on update, assign the new properties if they have changed
* * element
* * debounce (assign new debounced render method)
* * keys
*
* @param {number} [previousDebounce] the previous props' debounce value
*/
function (_ref) {
var previousDebounce = _ref.debounce;
var debounce = instance.props.debounce;
var element = findDOMNode(instance);
var hasElementChanged = element !== instance.element;
var hasDebounceChanged = debounce !== previousDebounce;
var shouldSetResizeObserver = hasElementChanged || hasDebounceChanged;
if (hasElementChanged) {
instance.element = element;
}
if (hasDebounceChanged) {
instance.setValuesViaDebounce = createSetValues(instance, true);
}
if (shouldSetResizeObserver) {
instance.setResizeObserver();
}
var newKeys = getStateKeys(instance.props);
if (shouldSetResizeObserver || !deepEqual(instance.keys, newKeys)) {
instance.keys = newKeys;
instance.resizeMethod();
}
}
);
};
export var createComponentWillUnmount = function createComponentWillUnmount(instance) {
return (
/**
* @private
*
* @function componentWillUnmount
*
* @description
* prior to unmount, disconnect the resize observer and reset the instance properties
*/
function () {
instance._isMounted = false;
instance.disconnectObserver();
instance.element = null;
instance.keys = [];
instance.resizeMethod = null;
}
);
};
export var createConnectObserver = function createConnectObserver(instance) {
return (
/**
* @private
*
* @function connectObserver
*
* @description
* if render on resize is requested, assign a resize observer to the element with the correct resize method
*/
function () {
var _instance$props = instance.props,
renderOnResize = _instance$props.renderOnResize,
renderOnWindowResize = _instance$props.renderOnWindowResize;
if (renderOnWindowResize) {
window.addEventListener('resize', instance.resizeMethod);
}
if (renderOnResize) {
if (!IS_PRODUCTION && isElementVoidTag(instance.element)) {
/* eslint-disable no-console */
console.warn('WARNING: You are attempting to listen to resizes on a void element, which is not supported. You should wrap this element in an element that supports children, such as a <div>, to ensure correct behavior.');
/* eslint-enable */
}
instance.resizeObserver = new ResizeObserver(instance.resizeMethod);
instance.resizeObserver.observe(instance.element);
}
}
);
};
export var createDisconnectObserver = function createDisconnectObserver(instance) {
return (
/**
* @private
*
* @function disconnectObserver
*
* @description
* if a resize observer exists, disconnect it from the element
*/
function () {
if (instance.resizeObserver) {
instance.resizeObserver.disconnect(instance.element);
instance.resizeObserver = null;
}
window.removeEventListener('resize', instance.resizeMethod);
}
);
};
export var createGetPassedValues = function createGetPassedValues(instance) {
return (
/**
* @private
*
* @function getPassedValues
*
* @description
* get the passed values as an object, namespaced if requested
*
* @param {Object} state the current state values
* @param {string} [namespace] the possible namespace to assign the values to
* @returns {Object} the values to pass
*/
memoize(function (state, namespace) {
var _ref3;
var populatedState = instance.keys.reduce(function (values, _ref2) {
var key = _ref2.key;
values[key] = state[key] || 0;
return values;
}, {});
return namespace ? (_ref3 = {}, _ref3[namespace] = populatedState, _ref3) : populatedState;
})
);
};
export var createSetRef = function createSetRef(instance, ref) {
return (
/**
* @private
*
* @function setRef
*
* @description
* set the DOM node to the ref passed
*
* @param {HTMLElement|ReactComponent} element the element to find the DOM node of
*/
function (element) {
instance[ref] = findDOMNode(element);
}
);
};
export var createSetRenderMethod = function createSetRenderMethod(instance) {
return (
/**
* @private
*
* @function setRenderMethod
*
* @description
* set the render method based on the possible props passed
*
* @param {function} [children] the child render function
* @param {function} [component] the component prop function
* @param {function} [render] the render prop function
*/
function (_ref4) {
var children = _ref4.children,
component = _ref4.component,
render = _ref4.render;
var RenderComponent = children || component || render || null;
if (!IS_PRODUCTION && typeof RenderComponent !== 'function') {
/* eslint-disable no-console */
console.error('ERROR: You must provide a render function, or either a "render" or "component" prop that passes a functional component.');
/* eslint-enable */
}
if (RenderComponent !== instance.RenderComponent) {
instance.RenderComponent = RenderComponent;
}
}
);
};
export var createSetResizeObserver = function createSetResizeObserver(instance) {
return (
/**
* @private
*
* @function setResizeObserver
*
* @description
* set the resize observer on the instance, based on the existence of a currrent resizeObserver and the new element
*/
function () {
var debounce = instance.props.debounce;
var resizeMethod = typeof debounce === 'number' ? instance.setValuesViaDebounce : instance.setValuesViaRaf;
if (instance.resizeObserver) {
instance.disconnectObserver();
}
if (resizeMethod !== instance.resizeMethod) {
instance.resizeMethod = resizeMethod;
resizeMethod();
}
if (instance.element) {
instance.connectObserver();
}
}
);
}; // eslint-disable-next-line react/no-deprecated
var Measured =
/*#__PURE__*/
function (_Component) {
_inheritsLoose(Measured, _Component);
function Measured() {
var _this;
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this = _Component.call.apply(_Component, [this].concat(args)) || this;
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "state", getInitialState());
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "componentDidMount", createComponentDidMount(_assertThisInitialized(_assertThisInitialized(_this))));
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "componentDidUpdate", createComponentDidUpdate(_assertThisInitialized(_assertThisInitialized(_this))));
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "componentWillUnmount", createComponentWillUnmount(_assertThisInitialized(_assertThisInitialized(_this))));
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), COMPONENT_WILL_MOUNT, createComponentWillMount(_assertThisInitialized(_assertThisInitialized(_this))));
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), COMPONENT_WILL_RECEIVE_PROPS, createComponentWillReceiveProps(_assertThisInitialized(_assertThisInitialized(_this))));
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "_isMounted", false);
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "element", null);
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "keys", []);
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "RenderComponent", null);
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "resizeMethod", null);
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "resizeObserver", null);
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "connectObserver", createConnectObserver(_assertThisInitialized(_assertThisInitialized(_this))));
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "disconnectObserver", createDisconnectObserver(_assertThisInitialized(_assertThisInitialized(_this))));
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "getPassedValues", createGetPassedValues(_assertThisInitialized(_assertThisInitialized(_this))));
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "setElementRef", createSetRef(_assertThisInitialized(_assertThisInitialized(_this)), 'element'));
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "setRenderMethod", createSetRenderMethod(_assertThisInitialized(_assertThisInitialized(_this))));
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "setResizeObserver", createSetResizeObserver(_assertThisInitialized(_assertThisInitialized(_this))));
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "setValuesViaDebounce", createSetValues(_assertThisInitialized(_assertThisInitialized(_this)), true));
_defineProperty(_assertThisInitialized(_assertThisInitialized(_this)), "setValuesViaRaf", createSetValues(_assertThisInitialized(_assertThisInitialized(_this)), false));
return _this;
}
var _proto = Measured.prototype;
_proto.render = function render() {
if (!this.RenderComponent) {
return null;
}
var _this$props = this.props,
childrenIgnored = _this$props.children,
componentIgnored = _this$props.component,
debounceIgnored = _this$props.debounce,
keysIgnored = _this$props.keys,
namespace = _this$props.namespace,
renderIgnored = _this$props.render,
renderOnResizeIgnored = _this$props.renderOnResize,
renderOnWindowResizeIgnored = _this$props.renderOnWindowResize,
passThroughProps = _objectWithoutPropertiesLoose(_this$props, ["children", "component", "debounce", "keys", "namespace", "render", "renderOnResize", "renderOnWindowResize"]);
var RenderComponent = this.RenderComponent;
return (
/* eslint-disable prettier */
React.createElement(RenderComponent, _extends({}, passThroughProps, this.getPassedValues(this.state, namespace)))
/* eslint-enable */
);
};
return Measured;
}(Component);
_defineProperty(Measured, "displayName", 'Measured');
_defineProperty(Measured, "propTypes", _extends({
children: PropTypes.func,
component: PropTypes.func,
debounce: PropTypes.number,
namespace: PropTypes.string,
render: PropTypes.func,
renderOnResize: PropTypes.bool.isRequired,
renderOnWindowResize: PropTypes.bool.isRequired
}, KEY_NAMES.reduce(function (keyPropTypes, key) {
keyPropTypes[key] = PropTypes.bool;
return keyPropTypes;
}, {})));
_defineProperty(Measured, "defaultProps", {
renderOnResize: true,
renderOnWindowResize: false
});
export default Measured;