@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
JavaScript
(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