New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

use-resize-observer

Package Overview
Dependencies
Maintainers
1
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

use-resize-observer - npm Package Compare versions

Comparing version 7.0.1 to 7.1.0

16

CHANGELOG.md

@@ -0,8 +1,18 @@

# [7.1.0](https://github.com/ZeeCoder/use-resize-observer/compare/v7.0.1...v7.1.0) (2021-08-28)
### Bug Fixes
- The `onResize` callback is no longer incorrectly called with the same values. ([29938a1](https://github.com/ZeeCoder/use-resize-observer/commit/29938a12e6393bd8f5dc98d7cccea3d291db6cf1))
### Features
- Added the `box` option ([f873597](https://github.com/ZeeCoder/use-resize-observer/commit/f873597744140698d941ac626a318ac82adcb736)), closes [#31](https://github.com/ZeeCoder/use-resize-observer/issues/31) [#57](https://github.com/ZeeCoder/use-resize-observer/issues/57)
- Added the `round` option. ([1224bc8](https://github.com/ZeeCoder/use-resize-observer/commit/1224bc8f67bd72dd985b15ac359b7e9139cc7468)), closes [#55](https://github.com/ZeeCoder/use-resize-observer/issues/55) [#46](https://github.com/ZeeCoder/use-resize-observer/issues/46) [#61](https://github.com/ZeeCoder/use-resize-observer/issues/61)
## [7.0.1](https://github.com/ZeeCoder/use-resize-observer/compare/v7.0.0...v7.0.1) (2021-07-27)
### Bug Fixes
* Removed unnecessary entries.length check ([3211d33](https://github.com/ZeeCoder/use-resize-observer/commit/3211d338117b0d2a97ccb229683eb8458de81d01))
* Undefined HTMLElement is no longer an issue in certain SSR edge cases. ([599cace](https://github.com/ZeeCoder/use-resize-observer/commit/599cace5c33ecd4276a0fe2848e0ed920f81e2fe)), closes [#74](https://github.com/ZeeCoder/use-resize-observer/issues/74) [#62](https://github.com/ZeeCoder/use-resize-observer/issues/62)
- Removed unnecessary entries.length check ([3211d33](https://github.com/ZeeCoder/use-resize-observer/commit/3211d338117b0d2a97ccb229683eb8458de81d01))
- Undefined HTMLElement is no longer an issue in certain SSR edge cases. ([599cace](https://github.com/ZeeCoder/use-resize-observer/commit/599cace5c33ecd4276a0fe2848e0ed920f81e2fe)), closes [#74](https://github.com/ZeeCoder/use-resize-observer/issues/74) [#62](https://github.com/ZeeCoder/use-resize-observer/issues/62)

@@ -9,0 +19,0 @@ # [7.0.0](https://github.com/ZeeCoder/use-resize-observer/compare/v6.1.0...v7.0.0) (2020-11-11)

15

CONTRIBUTING.md
# CONTRIBUTING
When contributing to this project, please keep in mind the following goals:
When contributing to this project, please keep in mind the following:
- The hook must remain as simple as possible. It's only a "proxy" to a
ResizeObserver instance, and shouldn't add features that the resize observer
doesn't do.
- The hook must remain as simple as possible. It's only a low-level "proxy" to a
ResizeObserver instance aiming for correctness, which should not add or polyfill
features on top. All that can be done by composing hooks.
- All features must be covered with test(s).

@@ -31,8 +31,11 @@

To do that:
To do so:
- Run `yarn src:watch` in a terminal tab
- Run `KARMA_BROWSERS=Chrome yarn karma:watch` in another.
- Run `yarn karma:watch` in another.
Don't forget to run `yarn test` at the end once you're done with everything, to
make sure the new code is tested for regressions.
If you have a Browserstack account, then you can also run the tests in real browsers using the `test:bs:*` commands.
Just make sure you have the following env variables set: `BS_USERNAME`, `BS_ACCESS_KEY`.

@@ -11,10 +11,5 @@ 'use strict';

var callbackRefElement = react.useRef(null);
var refCallback = react.useCallback(function (element) {
callbackRefElement.current = element;
callSubscriber();
}, []);
var lastReportedElementRef = react.useRef(null);
var lastReportRef = react.useRef(null);
var cleanupRef = react.useRef();
var callSubscriber = function callSubscriber() {
var callSubscriber = react.useCallback(function () {
var element = null;

@@ -32,3 +27,3 @@

if (lastReportedElementRef.current === element) {
if (lastReportRef.current && lastReportRef.current.element === element && lastReportRef.current.reporter === callSubscriber) {
return;

@@ -43,3 +38,6 @@ }

lastReportedElementRef.current = element; // Only calling the subscriber, if there's an actual element to report.
lastReportRef.current = {
reporter: callSubscriber,
element: element
}; // Only calling the subscriber, if there's an actual element to report.

@@ -49,17 +47,64 @@ if (element) {

}
}; // On each render, we check whether a ref changed, or if we got a new raw
}, [refOrElement, subscriber]); // On each render, we check whether a ref changed, or if we got a new raw
// element.
react.useEffect(function () {
// Note that this does not mean that "element" will necessarily be whatever
// the ref currently holds. It'll simply "update" `element` each render to
// the current ref value, but there's no guarantee that the ref value will
// not change later without a render.
// This may or may not be a problem depending on the specific use case.
// With this we're *technically* supporting cases where ref objects' current value changes, but only if there's a
// render accompanying that change as well.
// To guarantee we always have the right element, one must use the ref callback provided instead, but we support
// RefObjects to make the hook API more convenient in certain cases.
callSubscriber();
}, [refOrElement]);
return refCallback;
}, [callSubscriber]);
return react.useCallback(function (element) {
callbackRefElement.current = element;
callSubscriber();
}, [callSubscriber]);
}
// We're only using the first element of the size sequences, until future versions of the spec solidify on how
// exactly it'll be used for fragments in multi-column scenarios:
// From the spec:
// > The box size properties are exposed as FrozenArray in order to support elements that have multiple fragments,
// > which occur in multi-column scenarios. However the current definitions of content rect and border box do not
// > mention how those boxes are affected by multi-column layout. In this spec, there will only be a single
// > ResizeObserverSize returned in the FrozenArray, which will correspond to the dimensions of the first column.
// > A future version of this spec will extend the returned FrozenArray to contain the per-fragment size information.
// (https://drafts.csswg.org/resize-observer/#resize-observer-entry-interface)
//
// Also, testing these new box options revealed that in both Chrome and FF everything is returned in the callback,
// regardless of the "box" option.
// The spec states the following on this:
// > This does not have any impact on which box dimensions are returned to the defined callback when the event
// > is fired, it solely defines which box the author wishes to observe layout changes on.
// (https://drafts.csswg.org/resize-observer/#resize-observer-interface)
// I'm not exactly clear on what this means, especially when you consider a later section stating the following:
// > This section is non-normative. An author may desire to observe more than one CSS box.
// > In this case, author will need to use multiple ResizeObservers.
// (https://drafts.csswg.org/resize-observer/#resize-observer-interface)
// Which is clearly not how current browser implementations behave, and seems to contradict the previous quote.
// For this reason I decided to only return the requested size,
// even though it seems we have access to results for all box types.
// This also means that we get to keep the current api, being able to return a simple { width, height } pair,
// regardless of box option.
var extractSize = function extractSize(entry, boxProp, sizeType) {
if (!entry[boxProp]) {
if (boxProp === "contentBoxSize") {
// The dimensions in `contentBoxSize` and `contentRect` are equivalent according to the spec.
// See the 6th step in the description for the RO algorithm:
// https://drafts.csswg.org/resize-observer/#create-and-populate-resizeobserverentry-h
// > Set this.contentRect to logical this.contentBoxSize given target and observedBox of "content-box".
// In real browser implementations of course these objects differ, but the width/height values should be equivalent.
return entry.contentRect[sizeType === "inlineSize" ? "width" : "height"];
}
return undefined;
} // A couple bytes smaller than calling Array.isArray() and just as effective here.
return entry[boxProp][0] ? entry[boxProp][0][sizeType] : // TS complains about this, because the RO entry type follows the spec and does not reflect Firefox's current
// behaviour of returning objects instead of arrays for `borderBoxSize` and `contentBoxSize`.
// @ts-ignore
entry[boxProp][sizeType];
};
function useResizeObserver(opts) {

@@ -72,6 +117,7 @@ if (opts === void 0) {

// effect dep array, and just passing in an anonymous function without memoising
// will not reinstantiate the hook's ResizeObserver
// will not reinstantiate the hook's ResizeObserver.
var onResize = opts.onResize;
var onResizeRef = react.useRef(undefined);
onResizeRef.current = onResize; // Using a single instance throughout the hook's lifetime
onResizeRef.current = onResize;
var round = opts.round || Math.round; // Using a single instance throughout the hook's lifetime

@@ -94,3 +140,3 @@ var resizeObserverRef = react.useRef();

};
}, []); // Using a ref to track the previous width / height to avoid unnecessary renders
}, []); // Using a ref to track the previous width / height to avoid unnecessary renders.

@@ -104,43 +150,46 @@ var previous = react.useRef({

var refCallback = useResolvedElement(function (element) {
// Initialising the RO instance
if (!resizeObserverRef.current) {
// Saving a single instance, used by the hook from this point on.
resizeObserverRef.current = new ResizeObserver(function (entries) {
if (!Array.isArray(entries)) {
return;
}
var refCallback = useResolvedElement(react.useCallback(function (element) {
// We only use a single Resize Observer instance, and we're instantiating it on demand, only once there's something to observe.
// This instance is also recreated when the `box` option changes, so that a new observation is fired if there was a previously observed element with a different box option.
if (!resizeObserverRef.current || resizeObserverRef.current.box !== opts.box || resizeObserverRef.current.round !== round) {
resizeObserverRef.current = {
box: opts.box,
round: round,
instance: new ResizeObserver(function (entries) {
var entry = entries[0];
var boxProp = opts.box === "border-box" ? "borderBoxSize" : opts.box === "device-pixel-content-box" ? "devicePixelContentBoxSize" : "contentBoxSize";
var reportedWidth = extractSize(entry, boxProp, "inlineSize");
var reportedHeight = extractSize(entry, boxProp, "blockSize");
var newWidth = reportedWidth ? round(reportedWidth) : undefined;
var newHeight = reportedHeight ? round(reportedHeight) : undefined;
var entry = entries[0]; // `Math.round` is in line with how CSS resolves sub-pixel values
var newWidth = Math.round(entry.contentRect.width);
var newHeight = Math.round(entry.contentRect.height);
if (previous.current.width !== newWidth || previous.current.height !== newHeight) {
var newSize = {
width: newWidth,
height: newHeight
};
if (onResizeRef.current) {
onResizeRef.current(newSize);
} else {
if (previous.current.width !== newWidth || previous.current.height !== newHeight) {
var newSize = {
width: newWidth,
height: newHeight
};
previous.current.width = newWidth;
previous.current.height = newHeight;
if (!didUnmount.current) {
setSize(newSize);
if (onResizeRef.current) {
onResizeRef.current(newSize);
} else {
if (!didUnmount.current) {
setSize(newSize);
}
}
}
}
});
})
};
}
resizeObserverRef.current.observe(element);
resizeObserverRef.current.instance.observe(element, {
box: opts.box
});
return function () {
if (resizeObserverRef.current) {
resizeObserverRef.current.unobserve(element);
resizeObserverRef.current.instance.unobserve(element);
}
};
}, opts.ref);
}, [opts.box, round]), opts.ref);
return react.useMemo(function () {

@@ -147,0 +196,0 @@ return {

@@ -1,2 +0,2 @@

import { useRef, useState, useEffect, useMemo, useCallback } from 'react';
import { useRef, useState, useEffect, useCallback, useMemo } from 'react';

@@ -9,10 +9,5 @@ // This of course could've been more streamlined with internal state instead of

var callbackRefElement = useRef(null);
var refCallback = useCallback(function (element) {
callbackRefElement.current = element;
callSubscriber();
}, []);
var lastReportedElementRef = useRef(null);
var lastReportRef = useRef(null);
var cleanupRef = useRef();
var callSubscriber = function callSubscriber() {
var callSubscriber = useCallback(function () {
var element = null;

@@ -30,3 +25,3 @@

if (lastReportedElementRef.current === element) {
if (lastReportRef.current && lastReportRef.current.element === element && lastReportRef.current.reporter === callSubscriber) {
return;

@@ -41,3 +36,6 @@ }

lastReportedElementRef.current = element; // Only calling the subscriber, if there's an actual element to report.
lastReportRef.current = {
reporter: callSubscriber,
element: element
}; // Only calling the subscriber, if there's an actual element to report.

@@ -47,17 +45,64 @@ if (element) {

}
}; // On each render, we check whether a ref changed, or if we got a new raw
}, [refOrElement, subscriber]); // On each render, we check whether a ref changed, or if we got a new raw
// element.
useEffect(function () {
// Note that this does not mean that "element" will necessarily be whatever
// the ref currently holds. It'll simply "update" `element` each render to
// the current ref value, but there's no guarantee that the ref value will
// not change later without a render.
// This may or may not be a problem depending on the specific use case.
// With this we're *technically* supporting cases where ref objects' current value changes, but only if there's a
// render accompanying that change as well.
// To guarantee we always have the right element, one must use the ref callback provided instead, but we support
// RefObjects to make the hook API more convenient in certain cases.
callSubscriber();
}, [refOrElement]);
return refCallback;
}, [callSubscriber]);
return useCallback(function (element) {
callbackRefElement.current = element;
callSubscriber();
}, [callSubscriber]);
}
// We're only using the first element of the size sequences, until future versions of the spec solidify on how
// exactly it'll be used for fragments in multi-column scenarios:
// From the spec:
// > The box size properties are exposed as FrozenArray in order to support elements that have multiple fragments,
// > which occur in multi-column scenarios. However the current definitions of content rect and border box do not
// > mention how those boxes are affected by multi-column layout. In this spec, there will only be a single
// > ResizeObserverSize returned in the FrozenArray, which will correspond to the dimensions of the first column.
// > A future version of this spec will extend the returned FrozenArray to contain the per-fragment size information.
// (https://drafts.csswg.org/resize-observer/#resize-observer-entry-interface)
//
// Also, testing these new box options revealed that in both Chrome and FF everything is returned in the callback,
// regardless of the "box" option.
// The spec states the following on this:
// > This does not have any impact on which box dimensions are returned to the defined callback when the event
// > is fired, it solely defines which box the author wishes to observe layout changes on.
// (https://drafts.csswg.org/resize-observer/#resize-observer-interface)
// I'm not exactly clear on what this means, especially when you consider a later section stating the following:
// > This section is non-normative. An author may desire to observe more than one CSS box.
// > In this case, author will need to use multiple ResizeObservers.
// (https://drafts.csswg.org/resize-observer/#resize-observer-interface)
// Which is clearly not how current browser implementations behave, and seems to contradict the previous quote.
// For this reason I decided to only return the requested size,
// even though it seems we have access to results for all box types.
// This also means that we get to keep the current api, being able to return a simple { width, height } pair,
// regardless of box option.
var extractSize = function extractSize(entry, boxProp, sizeType) {
if (!entry[boxProp]) {
if (boxProp === "contentBoxSize") {
// The dimensions in `contentBoxSize` and `contentRect` are equivalent according to the spec.
// See the 6th step in the description for the RO algorithm:
// https://drafts.csswg.org/resize-observer/#create-and-populate-resizeobserverentry-h
// > Set this.contentRect to logical this.contentBoxSize given target and observedBox of "content-box".
// In real browser implementations of course these objects differ, but the width/height values should be equivalent.
return entry.contentRect[sizeType === "inlineSize" ? "width" : "height"];
}
return undefined;
} // A couple bytes smaller than calling Array.isArray() and just as effective here.
return entry[boxProp][0] ? entry[boxProp][0][sizeType] : // TS complains about this, because the RO entry type follows the spec and does not reflect Firefox's current
// behaviour of returning objects instead of arrays for `borderBoxSize` and `contentBoxSize`.
// @ts-ignore
entry[boxProp][sizeType];
};
function useResizeObserver(opts) {

@@ -70,6 +115,7 @@ if (opts === void 0) {

// effect dep array, and just passing in an anonymous function without memoising
// will not reinstantiate the hook's ResizeObserver
// will not reinstantiate the hook's ResizeObserver.
var onResize = opts.onResize;
var onResizeRef = useRef(undefined);
onResizeRef.current = onResize; // Using a single instance throughout the hook's lifetime
onResizeRef.current = onResize;
var round = opts.round || Math.round; // Using a single instance throughout the hook's lifetime

@@ -92,3 +138,3 @@ var resizeObserverRef = useRef();

};
}, []); // Using a ref to track the previous width / height to avoid unnecessary renders
}, []); // Using a ref to track the previous width / height to avoid unnecessary renders.

@@ -102,43 +148,46 @@ var previous = useRef({

var refCallback = useResolvedElement(function (element) {
// Initialising the RO instance
if (!resizeObserverRef.current) {
// Saving a single instance, used by the hook from this point on.
resizeObserverRef.current = new ResizeObserver(function (entries) {
if (!Array.isArray(entries)) {
return;
}
var refCallback = useResolvedElement(useCallback(function (element) {
// We only use a single Resize Observer instance, and we're instantiating it on demand, only once there's something to observe.
// This instance is also recreated when the `box` option changes, so that a new observation is fired if there was a previously observed element with a different box option.
if (!resizeObserverRef.current || resizeObserverRef.current.box !== opts.box || resizeObserverRef.current.round !== round) {
resizeObserverRef.current = {
box: opts.box,
round: round,
instance: new ResizeObserver(function (entries) {
var entry = entries[0];
var boxProp = opts.box === "border-box" ? "borderBoxSize" : opts.box === "device-pixel-content-box" ? "devicePixelContentBoxSize" : "contentBoxSize";
var reportedWidth = extractSize(entry, boxProp, "inlineSize");
var reportedHeight = extractSize(entry, boxProp, "blockSize");
var newWidth = reportedWidth ? round(reportedWidth) : undefined;
var newHeight = reportedHeight ? round(reportedHeight) : undefined;
var entry = entries[0]; // `Math.round` is in line with how CSS resolves sub-pixel values
var newWidth = Math.round(entry.contentRect.width);
var newHeight = Math.round(entry.contentRect.height);
if (previous.current.width !== newWidth || previous.current.height !== newHeight) {
var newSize = {
width: newWidth,
height: newHeight
};
if (onResizeRef.current) {
onResizeRef.current(newSize);
} else {
if (previous.current.width !== newWidth || previous.current.height !== newHeight) {
var newSize = {
width: newWidth,
height: newHeight
};
previous.current.width = newWidth;
previous.current.height = newHeight;
if (!didUnmount.current) {
setSize(newSize);
if (onResizeRef.current) {
onResizeRef.current(newSize);
} else {
if (!didUnmount.current) {
setSize(newSize);
}
}
}
}
});
})
};
}
resizeObserverRef.current.observe(element);
resizeObserverRef.current.instance.observe(element, {
box: opts.box
});
return function () {
if (resizeObserverRef.current) {
resizeObserverRef.current.unobserve(element);
resizeObserverRef.current.instance.unobserve(element);
}
};
}, opts.ref);
}, [opts.box, round]), opts.ref);
return useMemo(function () {

@@ -153,2 +202,2 @@ return {

export default useResizeObserver;
export { useResizeObserver as default };

@@ -10,6 +10,15 @@ import { RefObject, RefCallback } from "react";

} & ObservedSize;
declare type ResizeObserverBoxOptions = "border-box" | "content-box" | "device-pixel-content-box";
declare global {
interface ResizeObserverEntry {
readonly devicePixelContentBoxSize: ReadonlyArray<ResizeObserverSize>;
}
}
declare type RoundingFunction = (n: number) => number;
declare function useResizeObserver<T extends HTMLElement>(opts?: {
ref?: RefObject<T> | T | null | undefined;
onResize?: ResizeHandler;
box?: ResizeObserverBoxOptions;
round?: RoundingFunction;
}): HookResponse<T>;
export default useResizeObserver;
{
"name": "use-resize-observer",
"version": "7.0.1",
"version": "7.1.0",
"main": "dist/bundle.cjs.js",

@@ -35,3 +35,3 @@ "module": "dist/bundle.esm.js",

"check:types": "tsc -p tests",
"test": "run-s 'build' 'check:size' 'check:types' 'test:create:ssr' 'test:bs:*'",
"test": "run-s 'build' 'check:size' 'check:types' 'test:create:ssr' 'test:headless:chrome'",
"test:create:ssr": "node ./tests/ssr/create-ssr-test.js",

@@ -43,12 +43,18 @@ "test:chrome": "KARMA_BROWSERS=Chrome yarn karma:run",

"karma:run": "karma start --singleRun",
"karma:watch": "karma start",
"karma:watch": "KARMA_BROWSERS=Chrome karma start",
"prepublish": "yarn build",
"test:bs:modern": "yarn karma:run --useBrowserStack",
"test:bs:ie": "yarn karma:run --useBrowserStack --runIeTests"
"test:bs:all": "run-s 'test:bs:modern' 'test:bs:legacy'",
"test:bs:modern": "KARMA_BROWSERS=modern yarn karma:run",
"test:bs:legacy": "KARMA_BROWSERS=legacy yarn karma:run",
"test:bs:chrome": "KARMA_BROWSERS=bs_chrome_latest yarn karma:run",
"test:bs:firefox": "KARMA_BROWSERS=bs_firefox_latest yarn karma:run",
"test:bs:safari": "KARMA_BROWSERS=bs_safari_13 yarn karma:run",
"test:bs:edge": "KARMA_BROWSERS=bs_edge_latest yarn karma:run",
"test:bs:opera": "KARMA_BROWSERS=bs_opera_latest yarn karma:run",
"test:bs:ie": "KARMA_BROWSERS=bs_ie_11 yarn karma:run",
"test:bs:ios_11": "KARMA_BROWSERS=bs_ios_11 yarn karma:run",
"test:bs:ios_14": "KARMA_BROWSERS=bs_ios_14 yarn karma:run",
"test:bs:samsung": "KARMA_BROWSERS=bs_samsung yarn karma:run",
"prepare": "husky install"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {

@@ -86,33 +92,34 @@ "*.{js,ts,md}": [

"@semantic-release/release-notes-generator": "^9.0.1",
"@size-limit/preset-small-lib": "^4.4.5",
"@testing-library/react": "^11.0.4",
"@types/karma": "^5.0.0",
"@types/karma-jasmine": "^3.1.0",
"@types/react": "^16.9.34",
"@types/react-dom": "^16.9.6",
"@size-limit/preset-small-lib": "^5.0.1",
"@testing-library/react": "^12.0.0",
"@types/karma": "^6.3.1",
"@types/karma-jasmine": "^4.0.1",
"@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9",
"babel-loader": "^8.1.0",
"delay": "^4.1.0",
"husky": "^4.2.5",
"karma": "^5.0.1",
"delay": "^5.0.0",
"husky": "^7.0.0",
"karma": "^6.3.4",
"karma-browserstack-launcher": "^1.6.0",
"karma-chrome-launcher": "^3.0.0",
"karma-firefox-launcher": "^1.3.0",
"karma-firefox-launcher": "^2.1.1",
"karma-jasmine": "^4.0.1",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "^0.0.32",
"karma-webpack": "^4.0.2",
"lint-staged": "^10.1.3",
"karma-webpack": "^5.0.0",
"lint-staged": "^11.1.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.0.4",
"react": "^16.9.0",
"react": "^17.0.2",
"react-app-polyfill": "^2.0.0",
"react-dom": "^16.9.0",
"react-dom": "^17.0.2",
"rollup": "^2.6.1",
"semantic-release": "^17.2.2",
"size-limit": "^4.4.5",
"typescript": "^4.0.3"
"size-limit": "^5.0.1",
"typescript": "^4.3.5",
"webpack": "~4"
},
"dependencies": {
"resize-observer-polyfill": "^1.5.1"
"@juggle/resize-observer": "^3.3.1"
}
}

@@ -10,6 +10,15 @@ import { RefObject, RefCallback } from "react";

} & ObservedSize;
declare type ResizeObserverBoxOptions = "border-box" | "content-box" | "device-pixel-content-box";
declare global {
interface ResizeObserverEntry {
readonly devicePixelContentBoxSize: ReadonlyArray<ResizeObserverSize>;
}
}
declare type RoundingFunction = (n: number) => number;
declare function useResizeObserver<T extends HTMLElement>(opts?: {
ref?: RefObject<T> | T | null | undefined;
onResize?: ResizeHandler;
box?: ResizeObserverBoxOptions;
round?: RoundingFunction;
}): HookResponse<T>;
export default useResizeObserver;
'use strict';
var ResizeObserver = require('resize-observer-polyfill');
var resizeObserver = require('@juggle/resize-observer');
var react = require('react');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var ResizeObserver__default = /*#__PURE__*/_interopDefaultLegacy(ResizeObserver);
// This of course could've been more streamlined with internal state instead of

@@ -16,10 +12,5 @@ // refs, but then host hooks / components could not opt out of renders.

var callbackRefElement = react.useRef(null);
var refCallback = react.useCallback(function (element) {
callbackRefElement.current = element;
callSubscriber();
}, []);
var lastReportedElementRef = react.useRef(null);
var lastReportRef = react.useRef(null);
var cleanupRef = react.useRef();
var callSubscriber = function callSubscriber() {
var callSubscriber = react.useCallback(function () {
var element = null;

@@ -37,3 +28,3 @@

if (lastReportedElementRef.current === element) {
if (lastReportRef.current && lastReportRef.current.element === element && lastReportRef.current.reporter === callSubscriber) {
return;

@@ -48,3 +39,6 @@ }

lastReportedElementRef.current = element; // Only calling the subscriber, if there's an actual element to report.
lastReportRef.current = {
reporter: callSubscriber,
element: element
}; // Only calling the subscriber, if there's an actual element to report.

@@ -54,17 +48,64 @@ if (element) {

}
}; // On each render, we check whether a ref changed, or if we got a new raw
}, [refOrElement, subscriber]); // On each render, we check whether a ref changed, or if we got a new raw
// element.
react.useEffect(function () {
// Note that this does not mean that "element" will necessarily be whatever
// the ref currently holds. It'll simply "update" `element` each render to
// the current ref value, but there's no guarantee that the ref value will
// not change later without a render.
// This may or may not be a problem depending on the specific use case.
// With this we're *technically* supporting cases where ref objects' current value changes, but only if there's a
// render accompanying that change as well.
// To guarantee we always have the right element, one must use the ref callback provided instead, but we support
// RefObjects to make the hook API more convenient in certain cases.
callSubscriber();
}, [refOrElement]);
return refCallback;
}, [callSubscriber]);
return react.useCallback(function (element) {
callbackRefElement.current = element;
callSubscriber();
}, [callSubscriber]);
}
// We're only using the first element of the size sequences, until future versions of the spec solidify on how
// exactly it'll be used for fragments in multi-column scenarios:
// From the spec:
// > The box size properties are exposed as FrozenArray in order to support elements that have multiple fragments,
// > which occur in multi-column scenarios. However the current definitions of content rect and border box do not
// > mention how those boxes are affected by multi-column layout. In this spec, there will only be a single
// > ResizeObserverSize returned in the FrozenArray, which will correspond to the dimensions of the first column.
// > A future version of this spec will extend the returned FrozenArray to contain the per-fragment size information.
// (https://drafts.csswg.org/resize-observer/#resize-observer-entry-interface)
//
// Also, testing these new box options revealed that in both Chrome and FF everything is returned in the callback,
// regardless of the "box" option.
// The spec states the following on this:
// > This does not have any impact on which box dimensions are returned to the defined callback when the event
// > is fired, it solely defines which box the author wishes to observe layout changes on.
// (https://drafts.csswg.org/resize-observer/#resize-observer-interface)
// I'm not exactly clear on what this means, especially when you consider a later section stating the following:
// > This section is non-normative. An author may desire to observe more than one CSS box.
// > In this case, author will need to use multiple ResizeObservers.
// (https://drafts.csswg.org/resize-observer/#resize-observer-interface)
// Which is clearly not how current browser implementations behave, and seems to contradict the previous quote.
// For this reason I decided to only return the requested size,
// even though it seems we have access to results for all box types.
// This also means that we get to keep the current api, being able to return a simple { width, height } pair,
// regardless of box option.
var extractSize = function extractSize(entry, boxProp, sizeType) {
if (!entry[boxProp]) {
if (boxProp === "contentBoxSize") {
// The dimensions in `contentBoxSize` and `contentRect` are equivalent according to the spec.
// See the 6th step in the description for the RO algorithm:
// https://drafts.csswg.org/resize-observer/#create-and-populate-resizeobserverentry-h
// > Set this.contentRect to logical this.contentBoxSize given target and observedBox of "content-box".
// In real browser implementations of course these objects differ, but the width/height values should be equivalent.
return entry.contentRect[sizeType === "inlineSize" ? "width" : "height"];
}
return undefined;
} // A couple bytes smaller than calling Array.isArray() and just as effective here.
return entry[boxProp][0] ? entry[boxProp][0][sizeType] : // TS complains about this, because the RO entry type follows the spec and does not reflect Firefox's current
// behaviour of returning objects instead of arrays for `borderBoxSize` and `contentBoxSize`.
// @ts-ignore
entry[boxProp][sizeType];
};
function useResizeObserver(opts) {

@@ -77,6 +118,7 @@ if (opts === void 0) {

// effect dep array, and just passing in an anonymous function without memoising
// will not reinstantiate the hook's ResizeObserver
// will not reinstantiate the hook's ResizeObserver.
var onResize = opts.onResize;
var onResizeRef = react.useRef(undefined);
onResizeRef.current = onResize; // Using a single instance throughout the hook's lifetime
onResizeRef.current = onResize;
var round = opts.round || Math.round; // Using a single instance throughout the hook's lifetime

@@ -99,3 +141,3 @@ var resizeObserverRef = react.useRef();

};
}, []); // Using a ref to track the previous width / height to avoid unnecessary renders
}, []); // Using a ref to track the previous width / height to avoid unnecessary renders.

@@ -109,43 +151,46 @@ var previous = react.useRef({

var refCallback = useResolvedElement(function (element) {
// Initialising the RO instance
if (!resizeObserverRef.current) {
// Saving a single instance, used by the hook from this point on.
resizeObserverRef.current = new ResizeObserver__default['default'](function (entries) {
if (!Array.isArray(entries)) {
return;
}
var refCallback = useResolvedElement(react.useCallback(function (element) {
// We only use a single Resize Observer instance, and we're instantiating it on demand, only once there's something to observe.
// This instance is also recreated when the `box` option changes, so that a new observation is fired if there was a previously observed element with a different box option.
if (!resizeObserverRef.current || resizeObserverRef.current.box !== opts.box || resizeObserverRef.current.round !== round) {
resizeObserverRef.current = {
box: opts.box,
round: round,
instance: new resizeObserver.ResizeObserver(function (entries) {
var entry = entries[0];
var boxProp = opts.box === "border-box" ? "borderBoxSize" : opts.box === "device-pixel-content-box" ? "devicePixelContentBoxSize" : "contentBoxSize";
var reportedWidth = extractSize(entry, boxProp, "inlineSize");
var reportedHeight = extractSize(entry, boxProp, "blockSize");
var newWidth = reportedWidth ? round(reportedWidth) : undefined;
var newHeight = reportedHeight ? round(reportedHeight) : undefined;
var entry = entries[0]; // `Math.round` is in line with how CSS resolves sub-pixel values
var newWidth = Math.round(entry.contentRect.width);
var newHeight = Math.round(entry.contentRect.height);
if (previous.current.width !== newWidth || previous.current.height !== newHeight) {
var newSize = {
width: newWidth,
height: newHeight
};
if (onResizeRef.current) {
onResizeRef.current(newSize);
} else {
if (previous.current.width !== newWidth || previous.current.height !== newHeight) {
var newSize = {
width: newWidth,
height: newHeight
};
previous.current.width = newWidth;
previous.current.height = newHeight;
if (!didUnmount.current) {
setSize(newSize);
if (onResizeRef.current) {
onResizeRef.current(newSize);
} else {
if (!didUnmount.current) {
setSize(newSize);
}
}
}
}
});
})
};
}
resizeObserverRef.current.observe(element);
resizeObserverRef.current.instance.observe(element, {
box: opts.box
});
return function () {
if (resizeObserverRef.current) {
resizeObserverRef.current.unobserve(element);
resizeObserverRef.current.instance.unobserve(element);
}
};
}, opts.ref);
}, [opts.box, round]), opts.ref);
return react.useMemo(function () {

@@ -152,0 +197,0 @@ return {

@@ -13,4 +13,4 @@ # use-resize-observer

[![npm version](https://badge.fury.io/js/use-resize-observer.svg)](https://npmjs.com/package/use-resize-observer)
![build](https://github.com/ZeeCoder/use-resize-observer/workflows/Testing/badge.svg)
[![BrowserStack Status](https://automate.browserstack.com/badge.svg?badge_key=bTAyOUVpa3hENUgwMkJBTVhXcytCQjREangwcTJqT0czUGhRSEZta3ZwYz0tLVRSZ1NhVkdPZ01FMithOEh5ZGxoWHc9PQ==--49d9d8ad43d557894fb270c80fd1c24107a82f51)](https://automate.browserstack.com/public-build/bTAyOUVpa3hENUgwMkJBTVhXcytCQjREangwcTJqT0czUGhRSEZta3ZwYz0tLVRSZ1NhVkdPZ01FMithOEh5ZGxoWHc9PQ==--49d9d8ad43d557894fb270c80fd1c24107a82f51)
[![build](https://github.com/ZeeCoder/use-resize-observer/workflows/Testing/badge.svg)](https://github.com/ZeeCoder/use-resize-observer/actions/workflows/testing.yml)
[![BrowserStack Status](https://automate.browserstack.com/badge.svg?badge_key=ZDJvbEJRdDJ0Vy96M0g4citOQkNCWGxuby81T3VhaWVBODEwZ1pYWjVTVT0tLWE2RmVFN0lRenBJUVRLcW14dUpWN3c9PQ==--da71247daf12790835c0447759700ab462b915ee%)](https://automate.browserstack.com/public-build/ZDJvbEJRdDJ0Vy96M0g4citOQkNCWGxuby81T3VhaWVBODEwZ1pYWjVTVT0tLWE2RmVFN0lRenBJUVRLcW14dUpWN3c9PQ==--da71247daf12790835c0447759700ab462b915ee%)

@@ -20,4 +20,5 @@ ## Highlights

- Written in **TypeScript**.
- **Tiny**: [500B](.size-limit.json) (minified, gzipped) Monitored by [size-limit](https://github.com/ai/size-limit).
- **Tiny**: [645B](.size-limit.json) (minified, gzipped) Monitored by [size-limit](https://github.com/ai/size-limit).
- Exposes an **onResize callback** if you need more control.
- `box` [option](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/observe#syntax).
- Works with **SSR**.

@@ -30,4 +31,4 @@ - Works with **CSS-in-JS**.

(See this documentation and the test cases.)
- [Throttle / Debounce](#throttle--debounce)
- **Tested in real browsers** (Currently latest Chrome, Safari, Firefox and IE 11, sponsored by BrowserStack)
- Easy to compose ([Throttle / Debounce](#throttle--debounce), [Breakpoints](#breakpoints))
- **Tested in real browsers** (Currently latest Chrome, Firefox, Edge, Safari, Opera, IE 11, iOS and Android, sponsored by BrowserStack)

@@ -46,2 +47,19 @@ ## In Action

## Options
| Option | Type | Description | Default |
| -------- | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | -------------- |
| ref | undefined &#124; RefObject &#124; HTMLElement | A ref or element to observe. | undefined |
| box | undefined &#124; "border-box" &#124; "content-box" &#124; "device-pixel-content-box" | The [box model](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver/observe#syntax) to use for observation. | "content-box" |
| onResize | undefined &#124; ({ width?: number, height?: number }) => void | A callback receiving the element size. If given, then the hook will not return the size, and instead will call this callback. | undefined |
| round | undefined &#124; (n: number) => number | A function to use for rounding values instead of the default. | `Math.round()` |
## Response
| Name | Type | Description |
| ------ | ----------------------- | ---------------------------------------------- |
| ref | RefCallback | A callback to be passed to React's "ref" prop. |
| width | undefined &#124; number | The width (or "blockSize") of the element. |
| height | undefined &#124; number | The height (or "inlineSize") of the element. |
## Basic Usage

@@ -67,36 +85,96 @@

Note that "ref" here is a `RefCallback`, not a `RefObject`, meaning you won't be
able to access "ref.current" if you need the element itself.
To get the raw element, either you use your own RefObject (see later in this doc)
or you hook in the returned ref callback, like so:
To observe a different box size other than content box, pass in the `box` option, like so:
### Getting the raw element from the default `RefCallback`
```tsx
const { ref, width, height } = useResizeObserver<HTMLDivElement>({
box: "border-box",
});
```
Note that if the browser does not support the given box type, then the hook won't report any sizes either.
### Box Options
Note that box options are experimental, and as such are not supported by all browsers that implemented ResizeObservers. (See [here](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry).)
`content-box` (default)
Safe to use by all browsers that implemented ResizeObservers. The hook internally will fall back to `contentRect` from
the old spec in case `contentBoxSize` is not available.
`border-box`
Supported well for the most part by evergreen browsers. If you need to support older versions of these browsers however,
then you may want to feature-detect for support, and optionally include a polyfill instead of the native implementation.
`device-pixel-content-box`
Surma has a [very good article](https://web.dev/device-pixel-content-box/) on how this allows us to do pixel perfect
rendering. At the time of writing, however this has very limited support.
The advices on feature detection for `border-box` apply here too.
### Custom Rounding
By default this hook passes the measured values through `Math.round()`, to avoid re-rendering on every subpixel changes.
If this is not what you want, then you can provide your own function:
**Rounding Down Reported Values**
```tsx
import React, { useCallback, useEffect, useRef } from "react";
const { ref, width, height } = useResizeObserver<HTMLDivElement>({
round: Math.floor,
});
```
**Skipping Rounding**
```tsx
import React from "react";
import useResizeObserver from "use-resize-observer";
const useMergedCallbackRef = (...callbacks: Function[]) => {
// Storing callbacks in a ref, so that we don't need to memoise them in
// renders when using this hook.
const callbacksRegistry = useRef<Function[]>(callbacks);
// Outside the hook to ensure this instance does not change unnecessarily.
const noop = (n) => n;
useEffect(() => {
callbacksRegistry.current = callbacks;
}, [...callbacks]);
const App = () => {
const {
ref,
width = 1,
height = 1,
} = useResizeObserver<HTMLDivElement>({ round: noop });
return useCallback((element) => {
callbacksRegistry.current.forEach((callback) => callback(element));
}, []);
return (
<div ref={ref}>
Size: {width}x{height}
</div>
);
};
```
Note that the round option is sensitive to the function reference, so make sure you either use `useCallback`
or declare your rounding function outside of the hook's function scope, if it does not rely on any hook state.
(As shown above.)
### Getting the Raw Element from the Default `RefCallback`
Note that "ref" in the above examples is a `RefCallback`, not a `RefObject`, meaning you won't be
able to access "ref.current" if you need the element itself.
To get the raw element, either you use your own RefObject (see later in this doc),
or you can merge the returned ref with one of your own:
```tsx
import React, { useCallback, useEffect, useRef } from "react";
import useResizeObserver from "use-resize-observer";
import mergeRefs from "react-merge-refs";
const App = () => {
const { ref, width = 1, height = 1 } = useResizeObserver<HTMLDivElement>();
const mergedCallbackRef = useMergedCallbackRef(
const mergedCallbackRef = mergeRefs([
ref,
(element: HTMLDivElement) => {
// Do whatever you want with the `element`.
}
);
},
]);

@@ -136,3 +214,3 @@ return (

## Using a single hook to measure multiple refs
## Using a Single Hook to Measure Multiple Refs

@@ -143,3 +221,3 @@ The hook reacts to ref changes, as it resolves it to an element to observe.

## Opting Out of (or Delaying) ResizeObserver instantiation
## Opting Out of (or Delaying) ResizeObserver Instantiation

@@ -166,3 +244,3 @@ In certain cases you might want to delay creating a ResizeObserver instance.

## The "onResize" callback
## The "onResize" Callback

@@ -199,11 +277,18 @@ By the default the hook will trigger a re-render on all changes to the target

## Throttle / Debounce
## Hook Composition
As this hook intends to remain low-level, it is encouraged to build on top of it via hook composition, if additional features are required.
### Throttle / Debounce
You might want to receive values less frequently than changes actually occur.
While this hook does not come with its own implementation of throttling / debouncing,
you can use the `onResize` callback to implement your own version:
[CodeSandbox Demo](https://codesandbox.io/s/use-resize-observer-throttle-and-debounce-8uvsg)
### Breakpoints
Another popular concept are breakpoints. Here is an example for a simple hook accomplishing that.
[CodeSandbox Demo](https://codesandbox.io/s/use-resize-observer-breakpoints-3hiv8)
## Defaults (SSR)

@@ -253,9 +338,28 @@

That said, there's a [polyfilled](https://github.com/que-etc/resize-observer-polyfill)
CJS module that can be used for convenience (Not affecting globals):
That said, there's a [polyfilled](https://github.com/juggle/resize-observer)
CJS module that can be used for convenience:
```js
```ts
import useResizeObserver from "use-resize-observer/polyfilled";
```
Note that using the above will use the polyfill, [even if the native ResizeObserver is available](https://github.com/juggle/resize-observer#basic-usage).
To use the polyfill as a fallback only when the native RO is unavailable, you can polyfill yourself instead,
either in your app's entry file, or you could create a local `useResizeObserver` module, like so:
```ts
// useResizeObserver.ts
import { ResizeObserver } from "@juggle/resize-observer";
import useResizeObserver from "use-resize-observer";
if (!window.ResizeObserver) {
window.ResizeObserver = ResizeObserver;
}
export default useResizeObserver;
```
The same technique can also be used to provide any of your preferred ResizeObserver polyfills out there.
## Related

@@ -262,0 +366,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc