UNPKG

@react-hook/size

Version:

A React hook for measuring the size of HTML elements including when they change

824 lines (667 loc) 25.6 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('react')) : typeof define === 'function' && define.amd ? define(['react'], factory) : (global = global || self, global.useSize = factory(global.React)); }(this, (function (React) { 'use strict'; var resizeObservers = []; function _ref(ro) { return ro.activeTargets.length > 0; } var hasActiveObservations = function hasActiveObservations() { return resizeObservers.some(_ref); }; function _ref$1(ro) { return ro.skippedTargets.length > 0; } var hasSkippedObservations = function hasSkippedObservations() { return resizeObservers.some(_ref$1); }; var msg = 'ResizeObserver loop completed with undelivered notifications.'; var deliverResizeLoopError = function deliverResizeLoopError() { var event; if (typeof ErrorEvent === 'function') { event = new ErrorEvent('error', { message: msg }); } else { event = document.createEvent('Event'); event.initEvent('error', false, false); event.message = msg; } window.dispatchEvent(event); }; var ResizeObserverBoxOptions; (function (ResizeObserverBoxOptions) { ResizeObserverBoxOptions["BORDER_BOX"] = "border-box"; ResizeObserverBoxOptions["CONTENT_BOX"] = "content-box"; ResizeObserverBoxOptions["DEVICE_PIXEL_CONTENT_BOX"] = "device-pixel-content-box"; })(ResizeObserverBoxOptions || (ResizeObserverBoxOptions = {})); var freeze = function freeze(obj) { return Object.freeze(obj); }; function _ResizeObserverSize(inlineSize, blockSize) { this.inlineSize = inlineSize; this.blockSize = blockSize; freeze(this); } var ResizeObserverSize = /*#__PURE__*/function () { return _ResizeObserverSize; }(); function _DOMRectReadOnly(x, y, width, height) { this.x = x; this.y = y; this.width = width; this.height = height; this.top = this.y; this.left = this.x; this.bottom = this.top + this.height; this.right = this.left + this.width; return freeze(this); } function _ref$2() { var _a = this, x = _a.x, y = _a.y, top = _a.top, right = _a.right, bottom = _a.bottom, left = _a.left, width = _a.width, height = _a.height; return { x: x, y: y, top: top, right: right, bottom: bottom, left: left, width: width, height: height }; } function _ref2(rectangle) { return new _DOMRectReadOnly(rectangle.x, rectangle.y, rectangle.width, rectangle.height); } var DOMRectReadOnly = /*#__PURE__*/function () { _DOMRectReadOnly.prototype.toJSON = _ref$2; _DOMRectReadOnly.fromRect = _ref2; return _DOMRectReadOnly; }(); var isSVG = function isSVG(target) { return target instanceof SVGElement && 'getBBox' in target; }; var isHidden = function isHidden(target) { if (isSVG(target)) { var _a = target.getBBox(), width = _a.width, height = _a.height; return !width && !height; } var _b = target, offsetWidth = _b.offsetWidth, offsetHeight = _b.offsetHeight; return !(offsetWidth || offsetHeight || target.getClientRects().length); }; var isElement = function isElement(obj) { var _a, _b; if (obj instanceof Element) { return true; } var scope = (_b = (_a = obj) === null || _a === void 0 ? void 0 : _a.ownerDocument) === null || _b === void 0 ? void 0 : _b.defaultView; return !!(scope && obj instanceof scope.Element); }; var isReplacedElement = function isReplacedElement(target) { switch (target.tagName) { case 'INPUT': if (target.type !== 'image') { break; } case 'VIDEO': case 'AUDIO': case 'EMBED': case 'OBJECT': case 'CANVAS': case 'IFRAME': case 'IMG': return true; } return false; }; var global = typeof window !== 'undefined' ? window : {}; var cache = /*#__PURE__*/new WeakMap(); var scrollRegexp = /auto|scroll/; var verticalRegexp = /^tb|vertical/; var IE = /*#__PURE__*/ /msie|trident/i.test(global.navigator && global.navigator.userAgent); var parseDimension = function parseDimension(pixel) { return parseFloat(pixel || '0'); }; var size = function size(inlineSize, blockSize, switchSizes) { if (inlineSize === void 0) { inlineSize = 0; } if (blockSize === void 0) { blockSize = 0; } if (switchSizes === void 0) { switchSizes = false; } return new ResizeObserverSize((switchSizes ? blockSize : inlineSize) || 0, (switchSizes ? inlineSize : blockSize) || 0); }; var zeroBoxes = /*#__PURE__*/freeze({ devicePixelContentBoxSize: /*#__PURE__*/size(), borderBoxSize: /*#__PURE__*/size(), contentBoxSize: /*#__PURE__*/size(), contentRect: /*#__PURE__*/new DOMRectReadOnly(0, 0, 0, 0) }); var calculateBoxSizes = function calculateBoxSizes(target, forceRecalculation) { if (forceRecalculation === void 0) { forceRecalculation = false; } if (cache.has(target) && !forceRecalculation) { return cache.get(target); } if (isHidden(target)) { cache.set(target, zeroBoxes); return zeroBoxes; } var cs = getComputedStyle(target); var svg = isSVG(target) && target.ownerSVGElement && target.getBBox(); var removePadding = !IE && cs.boxSizing === 'border-box'; var switchSizes = verticalRegexp.test(cs.writingMode || ''); var canScrollVertically = !svg && scrollRegexp.test(cs.overflowY || ''); var canScrollHorizontally = !svg && scrollRegexp.test(cs.overflowX || ''); var paddingTop = svg ? 0 : parseDimension(cs.paddingTop); var paddingRight = svg ? 0 : parseDimension(cs.paddingRight); var paddingBottom = svg ? 0 : parseDimension(cs.paddingBottom); var paddingLeft = svg ? 0 : parseDimension(cs.paddingLeft); var borderTop = svg ? 0 : parseDimension(cs.borderTopWidth); var borderRight = svg ? 0 : parseDimension(cs.borderRightWidth); var borderBottom = svg ? 0 : parseDimension(cs.borderBottomWidth); var borderLeft = svg ? 0 : parseDimension(cs.borderLeftWidth); var horizontalPadding = paddingLeft + paddingRight; var verticalPadding = paddingTop + paddingBottom; var horizontalBorderArea = borderLeft + borderRight; var verticalBorderArea = borderTop + borderBottom; var horizontalScrollbarThickness = !canScrollHorizontally ? 0 : target.offsetHeight - verticalBorderArea - target.clientHeight; var verticalScrollbarThickness = !canScrollVertically ? 0 : target.offsetWidth - horizontalBorderArea - target.clientWidth; var widthReduction = removePadding ? horizontalPadding + horizontalBorderArea : 0; var heightReduction = removePadding ? verticalPadding + verticalBorderArea : 0; var contentWidth = svg ? svg.width : parseDimension(cs.width) - widthReduction - verticalScrollbarThickness; var contentHeight = svg ? svg.height : parseDimension(cs.height) - heightReduction - horizontalScrollbarThickness; var borderBoxWidth = contentWidth + horizontalPadding + verticalScrollbarThickness + horizontalBorderArea; var borderBoxHeight = contentHeight + verticalPadding + horizontalScrollbarThickness + verticalBorderArea; var boxes = freeze({ devicePixelContentBoxSize: size(Math.round(contentWidth * devicePixelRatio), Math.round(contentHeight * devicePixelRatio), switchSizes), borderBoxSize: size(borderBoxWidth, borderBoxHeight, switchSizes), contentBoxSize: size(contentWidth, contentHeight, switchSizes), contentRect: new DOMRectReadOnly(paddingLeft, paddingTop, contentWidth, contentHeight) }); cache.set(target, boxes); return boxes; }; var calculateBoxSize = function calculateBoxSize(target, observedBox, forceRecalculation) { var _a = calculateBoxSizes(target, forceRecalculation), borderBoxSize = _a.borderBoxSize, contentBoxSize = _a.contentBoxSize, devicePixelContentBoxSize = _a.devicePixelContentBoxSize; switch (observedBox) { case ResizeObserverBoxOptions.DEVICE_PIXEL_CONTENT_BOX: return devicePixelContentBoxSize; case ResizeObserverBoxOptions.BORDER_BOX: return borderBoxSize; default: return contentBoxSize; } }; function _ResizeObserverEntry(target) { var boxes = calculateBoxSizes(target); this.target = target; this.contentRect = boxes.contentRect; this.borderBoxSize = freeze([boxes.borderBoxSize]); this.contentBoxSize = freeze([boxes.contentBoxSize]); this.devicePixelContentBoxSize = freeze([boxes.devicePixelContentBoxSize]); } var ResizeObserverEntry = /*#__PURE__*/function () { return _ResizeObserverEntry; }(); var calculateDepthForNode = function calculateDepthForNode(node) { if (isHidden(node)) { return Infinity; } var depth = 0; var parent = node.parentNode; while (parent) { depth += 1; parent = parent.parentNode; } return depth; }; var broadcastActiveObservations = function broadcastActiveObservations() { var shallowestDepth = Infinity; var callbacks = []; resizeObservers.forEach(function processObserver(ro) { if (ro.activeTargets.length === 0) { return; } var entries = []; ro.activeTargets.forEach(function processTarget(ot) { var entry = new ResizeObserverEntry(ot.target); var targetDepth = calculateDepthForNode(ot.target); entries.push(entry); ot.lastReportedSize = calculateBoxSize(ot.target, ot.observedBox); if (targetDepth < shallowestDepth) { shallowestDepth = targetDepth; } }); callbacks.push(function resizeObserverCallback() { ro.callback.call(ro.observer, entries, ro.observer); }); ro.activeTargets.splice(0, ro.activeTargets.length); }); for (var _i = 0, callbacks_1 = callbacks; _i < callbacks_1.length; _i++) { var callback = callbacks_1[_i]; callback(); } return shallowestDepth; }; var gatherActiveObservationsAtDepth = function gatherActiveObservationsAtDepth(depth) { resizeObservers.forEach(function processObserver(ro) { ro.activeTargets.splice(0, ro.activeTargets.length); ro.skippedTargets.splice(0, ro.skippedTargets.length); ro.observationTargets.forEach(function processTarget(ot) { if (ot.isActive()) { if (calculateDepthForNode(ot.target) > depth) { ro.activeTargets.push(ot); } else { ro.skippedTargets.push(ot); } } }); }); }; var process = function process() { var depth = 0; gatherActiveObservationsAtDepth(depth); while (hasActiveObservations()) { depth = broadcastActiveObservations(); gatherActiveObservationsAtDepth(depth); } if (hasSkippedObservations()) { deliverResizeLoopError(); } return depth > 0; }; var trigger; var callbacks = []; function _ref$3(cb) { return cb(); } var notify = function notify() { return callbacks.splice(0).forEach(_ref$3); }; function _ref2$1() { return notify(); } var queueMicroTask = function queueMicroTask(callback) { function _trigger() { el_1.textContent = "" + (toggle_1 ? toggle_1-- : toggle_1++); } if (!trigger) { var toggle_1 = 0; var el_1 = document.createTextNode(''); var config = { characterData: true }; new MutationObserver(_ref2$1).observe(el_1, config); trigger = _trigger; } callbacks.push(callback); trigger(); }; var queueResizeObserver = function queueResizeObserver(cb) { queueMicroTask(function ResizeObserver() { requestAnimationFrame(cb); }); }; var watching = 0; var isWatching = function isWatching() { return !!watching; }; var CATCH_PERIOD = 250; var observerConfig = { attributes: true, characterData: true, childList: true, subtree: true }; var events = ['resize', 'load', 'transitionend', 'animationend', 'animationstart', 'animationiteration', 'keyup', 'keydown', 'mouseup', 'mousedown', 'mouseover', 'mouseout', 'blur', 'focus']; var time = function time(timeout) { if (timeout === void 0) { timeout = 0; } return Date.now() + timeout; }; var scheduled = false; function _Scheduler() { var _this = this; this.stopped = true; this.listener = function () { return _this.schedule(); }; } function _ref$4(timeout) { var _this = this; if (timeout === void 0) { timeout = CATCH_PERIOD; } if (scheduled) { return; } scheduled = true; var until = time(timeout); queueResizeObserver(function () { var elementsHaveResized = false; try { elementsHaveResized = process(); } finally { scheduled = false; timeout = until - time(); if (!isWatching()) { return; } if (elementsHaveResized) { _this.run(1000); } else if (timeout > 0) { _this.run(timeout); } else { _this.start(); } } }); } function _ref2$2() { this.stop(); this.run(); } function _ref3() { var _this = this; var cb = function cb() { return _this.observer && _this.observer.observe(document.body, observerConfig); }; document.body ? cb() : global.addEventListener('DOMContentLoaded', cb); } function _ref5() { var _this = this; function _ref4(name) { return global.addEventListener(name, _this.listener, true); } if (this.stopped) { this.stopped = false; this.observer = new MutationObserver(this.listener); this.observe(); events.forEach(_ref4); } } function _ref7() { var _this = this; function _ref6(name) { return global.removeEventListener(name, _this.listener, true); } if (!this.stopped) { this.observer && this.observer.disconnect(); events.forEach(_ref6); this.stopped = true; } } var Scheduler = /*#__PURE__*/function () { _Scheduler.prototype.run = _ref$4; _Scheduler.prototype.schedule = _ref2$2; _Scheduler.prototype.observe = _ref3; _Scheduler.prototype.start = _ref5; _Scheduler.prototype.stop = _ref7; return _Scheduler; }(); var scheduler = /*#__PURE__*/new Scheduler(); var updateCount = function updateCount(n) { !watching && n > 0 && scheduler.start(); watching += n; !watching && scheduler.stop(); }; var skipNotifyOnElement = function skipNotifyOnElement(target) { return !isSVG(target) && !isReplacedElement(target) && getComputedStyle(target).display === 'inline'; }; function _ResizeObservation(target, observedBox) { this.target = target; this.observedBox = observedBox || ResizeObserverBoxOptions.CONTENT_BOX; this.lastReportedSize = { inlineSize: 0, blockSize: 0 }; } function _ref$5() { var size = calculateBoxSize(this.target, this.observedBox, true); if (skipNotifyOnElement(this.target)) { this.lastReportedSize = size; } if (this.lastReportedSize.inlineSize !== size.inlineSize || this.lastReportedSize.blockSize !== size.blockSize) { return true; } return false; } var ResizeObservation = /*#__PURE__*/function () { _ResizeObservation.prototype.isActive = _ref$5; return _ResizeObservation; }(); function _ResizeObserverDetail(resizeObserver, callback) { this.activeTargets = []; this.skippedTargets = []; this.observationTargets = []; this.observer = resizeObserver; this.callback = callback; } var ResizeObserverDetail = /*#__PURE__*/function () { return _ResizeObserverDetail; }(); var observerMap = /*#__PURE__*/new WeakMap(); var getObservationIndex = function getObservationIndex(observationTargets, target) { for (var i = 0; i < observationTargets.length; i += 1) { if (observationTargets[i].target === target) { return i; } } return -1; }; function _ResizeObserverContro() {} function _ref$6(resizeObserver, callback) { var detail = new ResizeObserverDetail(resizeObserver, callback); observerMap.set(resizeObserver, detail); } function _ref2$3(resizeObserver, target, options) { var detail = observerMap.get(resizeObserver); var firstObservation = detail.observationTargets.length === 0; if (getObservationIndex(detail.observationTargets, target) < 0) { firstObservation && resizeObservers.push(detail); detail.observationTargets.push(new ResizeObservation(target, options && options.box)); updateCount(1); scheduler.schedule(); } } function _ref3$1(resizeObserver, target) { var detail = observerMap.get(resizeObserver); var index = getObservationIndex(detail.observationTargets, target); var lastObservation = detail.observationTargets.length === 1; if (index >= 0) { lastObservation && resizeObservers.splice(resizeObservers.indexOf(detail), 1); detail.observationTargets.splice(index, 1); updateCount(-1); } } function _ref4(resizeObserver) { var _this = this; var detail = observerMap.get(resizeObserver); detail.observationTargets.slice().forEach(function (ot) { return _this.unobserve(resizeObserver, ot.target); }); detail.activeTargets.splice(0, detail.activeTargets.length); } var ResizeObserverController = /*#__PURE__*/function () { _ResizeObserverContro.connect = _ref$6; _ResizeObserverContro.observe = _ref2$3; _ResizeObserverContro.unobserve = _ref3$1; _ResizeObserverContro.disconnect = _ref4; return _ResizeObserverContro; }(); function _ResizeObserver(callback) { if (arguments.length === 0) { throw new TypeError("Failed to construct 'ResizeObserver': 1 argument required, but only 0 present."); } if (typeof callback !== 'function') { throw new TypeError("Failed to construct 'ResizeObserver': The callback provided as parameter 1 is not a function."); } ResizeObserverController.connect(this, callback); } function _ref$7(target, options) { if (arguments.length === 0) { throw new TypeError("Failed to execute 'observe' on 'ResizeObserver': 1 argument required, but only 0 present."); } if (!isElement(target)) { throw new TypeError("Failed to execute 'observe' on 'ResizeObserver': parameter 1 is not of type 'Element"); } ResizeObserverController.observe(this, target, options); } function _ref2$4(target) { if (arguments.length === 0) { throw new TypeError("Failed to execute 'unobserve' on 'ResizeObserver': 1 argument required, but only 0 present."); } if (!isElement(target)) { throw new TypeError("Failed to execute 'unobserve' on 'ResizeObserver': parameter 1 is not of type 'Element"); } ResizeObserverController.unobserve(this, target); } function _ref3$2() { ResizeObserverController.disconnect(this); } function _ref4$1() { return 'function ResizeObserver () { [polyfill code] }'; } var ResizeObserver = /*#__PURE__*/function () { _ResizeObserver.prototype.observe = _ref$7; _ResizeObserver.prototype.unobserve = _ref2$4; _ResizeObserver.prototype.disconnect = _ref3$2; _ResizeObserver.toString = _ref4$1; return _ResizeObserver; }(); var usePassiveLayoutEffect = React[typeof document !== 'undefined' && document.createElement !== void 0 ? 'useLayoutEffect' : 'useEffect']; var useLatest = function useLatest(current) { var storedValue = React.useRef(current); storedValue.current = current; return storedValue; }; var rafSchd = function rafSchd(fn) { var lastArgs = []; var frameId = null; function _ref() { frameId = null; fn.apply(void 0, lastArgs); } var wrapperFn = function wrapperFn() { for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } lastArgs = args; if (frameId) { return; } frameId = requestAnimationFrame(_ref); }; wrapperFn.cancel = function () { if (!frameId) { return; } cancelAnimationFrame(frameId); frameId = null; }; return wrapperFn; }; /** * A React hook that fires a callback whenever ResizeObserver detects a change to its size * * @param target A React ref created by `useRef()` or an HTML element * @param callback Invoked with a single `ResizeObserverEntry` any time * the `target` resizes */ function useResizeObserver(target, callback) { var resizeObserver = getResizeObserver(); var storedCallback = useLatest(callback); usePassiveLayoutEffect(function () { var didUnsubscribe = false; var targetEl = target && 'current' in target ? target.current : target; if (!targetEl) return; resizeObserver.subscribe(targetEl, function (entry, observer) { if (didUnsubscribe) return; storedCallback.current(entry, observer); }); return function () { didUnsubscribe = true; resizeObserver.unsubscribe(targetEl); }; }, [target, resizeObserver, storedCallback]); return resizeObserver.observer; } function createResizeObserver() { var callbacks = new Map(); var observer = new ResizeObserver(rafSchd(function (entries, observer) { if (entries.length === 1) { var _callbacks$get; (_callbacks$get = callbacks.get(entries[0].target)) === null || _callbacks$get === void 0 ? void 0 : _callbacks$get(entries[0], observer); } else { for (var i = 0; i < entries.length; i++) { var _callbacks$get2; (_callbacks$get2 = callbacks.get(entries[i].target)) === null || _callbacks$get2 === void 0 ? void 0 : _callbacks$get2(entries[i], observer); } } })); return { observer: observer, subscribe: function subscribe(target, callback) { observer.observe(target); callbacks.set(target, callback); }, unsubscribe: function unsubscribe(target) { observer.unobserve(target); callbacks.delete(target); } }; } var _resizeObserver; var getResizeObserver = function getResizeObserver() { return !_resizeObserver ? _resizeObserver = createResizeObserver() : _resizeObserver; }; /** * A React hook for measuring the size of HTML elements including when they change * * @param target A React ref created by `useRef()` or an HTML element * @param options Configures the initial width and initial height of the hook's state */ var useSize = function useSize(target, options) { var _React$useState = React.useState(function () { var _options$initialWidth, _options$initialHeigh; var targetEl = target && 'current' in target ? target.current : target; return targetEl ? [targetEl.offsetWidth, targetEl.offsetHeight] : [(_options$initialWidth = options === null || options === void 0 ? void 0 : options.initialWidth) !== null && _options$initialWidth !== void 0 ? _options$initialWidth : 0, (_options$initialHeigh = options === null || options === void 0 ? void 0 : options.initialHeight) !== null && _options$initialHeigh !== void 0 ? _options$initialHeigh : 0]; }), size = _React$useState[0], setSize = _React$useState[1]; usePassiveLayoutEffect(function () { var targetEl = target && 'current' in target ? target.current : target; if (!targetEl) return; setSize([targetEl.offsetWidth, targetEl.offsetHeight]); }, [target]); // Where the magic happens useResizeObserver(target, function (entry) { var target = entry.target; setSize([target.offsetWidth, target.offsetHeight]); }); return size; }; return useSize; }))); //# sourceMappingURL=use-size.dev.js.map