UNPKG

remeasure

Version:

Get position and size of the DOM element for any React Component

455 lines (393 loc) 16.1 kB
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;