@crimson_dev/use-resize-observer 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/dist/core.d.ts +1 -1
- package/dist/core.d.ts.map +1 -1
- package/dist/core.js +18 -11
- package/dist/core.js.map +1 -1
- package/dist/extract-BiQI71T_.js +40 -0
- package/dist/extract-BiQI71T_.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -52
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/shim.d.ts +1 -1
- package/dist/shim.js.map +1 -1
- package/dist/{types-ASPFw2w_.d.ts → types-ypIHKARt.d.ts} +1 -1
- package/dist/{types-ASPFw2w_.d.ts.map → types-ypIHKARt.d.ts.map} +1 -1
- package/dist/worker.d.ts +1 -1
- package/dist/worker.js +1 -1
- package/dist/worker.js.map +1 -1
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,33 @@ All notable changes to `@crimson_dev/use-resize-observer` will be documented in
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.2.0] - 2026-03-05
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- React Compiler compatibility — verified with `babel-plugin-react-compiler@0.0.0-experimental-1371fcb-20260304`
|
|
12
|
+
- Compiler integration test suite (`tests/compiler/`) under React Compiler transformation
|
|
13
|
+
- Shared `extractDimensions` / `extractBoxSize` module (`src/extract.ts`) — eliminates duplication across hook, hook-multi, and core
|
|
14
|
+
- Coverage thresholds raised to 95% lines/functions/statements, 85% branches
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- Full ES2026 modernization audit across all source files
|
|
18
|
+
- `Error.isError()` used consistently for cross-realm error discrimination in pool and worker
|
|
19
|
+
- `readonly` parameter types on `sumPrecise` and internal WeakMap values
|
|
20
|
+
- Redundant worker ready signal removed (dead code at module load)
|
|
21
|
+
- Benchmark modernization: `using` declarations, `structuredClone()`, `Array.from()`, hoisted allocations
|
|
22
|
+
- Test suite hardened: stronger assertions, `findObserverFor` pattern, disposal verification
|
|
23
|
+
- 94 unit tests (up from 72), 3 compiler tests — 97 total
|
|
24
|
+
- Coverage: 97.65% statements, 90.8% branches, 98.07% functions, 97.52% lines
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- Architecture docs: pool API method names corrected in Mermaid diagrams
|
|
28
|
+
- Box model docs: `device-pixel-content-box` fallback description matched actual `extractBoxSize` behavior
|
|
29
|
+
- Performance docs: `FinalizationRegistry` snippet matched actual `WeakRef<Element>` implementation
|
|
30
|
+
- Advanced docs: `createResizeObservable` description accuracy (standalone observer, not shared pool)
|
|
31
|
+
- Troubleshooting docs: device-pixel fallback description corrected
|
|
32
|
+
- Changelog: "Rolldown 2.x" corrected to "Rolldown 1.x"
|
|
33
|
+
- Code block language tags added across worker, bundle-size, and troubleshooting docs
|
|
34
|
+
|
|
8
35
|
## [0.1.1] - 2026-03-06
|
|
9
36
|
|
|
10
37
|
### Fixed
|
|
@@ -48,5 +75,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
48
75
|
- Stable callback identity via ref pattern (React Compiler safe)
|
|
49
76
|
- Worker mode: SharedArrayBuffer + Float16Array + Atomics for off-main-thread
|
|
50
77
|
|
|
78
|
+
[0.2.0]: https://github.com/ABCrimson/use-resize-observer/releases/tag/v0.2.0
|
|
51
79
|
[0.1.1]: https://github.com/ABCrimson/use-resize-observer/releases/tag/v0.1.1
|
|
52
80
|
[0.1.0]: https://github.com/ABCrimson/use-resize-observer/releases/tag/v0.1.0
|
package/dist/core.d.ts
CHANGED
package/dist/core.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.d.ts","names":[],"sources":["../src/core.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"core.d.ts","names":[],"sources":["../src/core.ts"],"mappings":";;;;;UAqBiB,iBAAA;EAAA,SACN,KAAA;EAAA,SACA,MAAA;EAAA,SACA,KAAA,EAAO,mBAAA;AAAA;;cAIL,WAAA,SAAoB,WAAA,CAAY,iBAAA;EAC3C,WAAA,CAAY,MAAA,EAAQ,iBAAA;AAAA;;UAML,6BAAA;EAPjB;EASE,GAAA,GAAM,wBAAA;AAAA;;UAIS,gBAAA,SAAyB,WAAA,EAAa,UAAA;;EAErD,UAAA;AAAA;;;;;;;;AARF;;;cAqBa,sBAAA,GACX,MAAA,EAAQ,OAAA,EACR,OAAA,GAAS,6BAAA,KACR,gBAAA"}
|
package/dist/core.js
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
'use client';
|
|
2
|
+
import { t as extractBoxSize } from "./extract-BiQI71T_.js";
|
|
3
|
+
|
|
2
4
|
//#region src/core.ts
|
|
5
|
+
/**
|
|
6
|
+
* Framework-agnostic core observable for ResizeObserver events.
|
|
7
|
+
*
|
|
8
|
+
* Uses the `EventTarget` API for zero-dependency event dispatching.
|
|
9
|
+
* Can be adapted by any framework (React, Solid, Vue, Svelte, vanilla).
|
|
10
|
+
*
|
|
11
|
+
* Implements `Disposable` for ES2026 `using` declarations.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* using observable = createResizeObservable(element, { box: 'content-box' });
|
|
16
|
+
* observable.addEventListener('resize', (e) => {
|
|
17
|
+
* console.log(e.detail.width, e.detail.height);
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
3
21
|
/** Custom event type for resize observations. */
|
|
4
22
|
var ResizeEvent = class extends CustomEvent {
|
|
5
23
|
constructor(detail) {
|
|
@@ -7,17 +25,6 @@ var ResizeEvent = class extends CustomEvent {
|
|
|
7
25
|
}
|
|
8
26
|
};
|
|
9
27
|
/**
|
|
10
|
-
* Extract the first size entry for the given box model.
|
|
11
|
-
* @internal
|
|
12
|
-
*/
|
|
13
|
-
const extractBoxSize = (entry, box) => {
|
|
14
|
-
switch (box) {
|
|
15
|
-
case "border-box": return entry.borderBoxSize[0];
|
|
16
|
-
case "device-pixel-content-box": return (entry.devicePixelContentBoxSize ?? entry.contentBoxSize)[0];
|
|
17
|
-
default: return entry.contentBoxSize[0];
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
/**
|
|
21
28
|
* Create a framework-agnostic resize observable for an element.
|
|
22
29
|
*
|
|
23
30
|
* Wraps a `ResizeObserver` with `EventTarget` dispatching — consumers
|
package/dist/core.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.js","names":[],"sources":["../src/core.ts"],"sourcesContent":["/**\n * Framework-agnostic core observable for ResizeObserver events.\n *\n * Uses the `EventTarget` API for zero-dependency event dispatching.\n * Can be adapted by any framework (React, Solid, Vue, Svelte, vanilla).\n *\n * Implements `Disposable` for ES2026 `using` declarations.\n *\n * @example\n * ```ts\n * using observable = createResizeObservable(element, { box: 'content-box' });\n * observable.addEventListener('resize', (e) => {\n * console.log(e.detail.width, e.detail.height);\n * });\n * ```\n */\n\nimport type { ResizeObserverBoxOptions } from './types.js';\n\n/** Detail payload for resize events dispatched by the observable. */\nexport interface ResizeEventDetail {\n readonly width: number;\n readonly height: number;\n readonly entry: ResizeObserverEntry;\n}\n\n/** Custom event type for resize observations. */\nexport class ResizeEvent extends CustomEvent<ResizeEventDetail> {\n constructor(detail: ResizeEventDetail) {\n super('resize', { detail });\n }\n}\n\n/** Options for the framework-agnostic observable. */\nexport interface CreateResizeObservableOptions {\n /** Which box model to report. @default 'content-box' */\n box?: ResizeObserverBoxOptions;\n}\n\n/** Framework-agnostic resize observable with EventTarget-based dispatching. */\nexport interface ResizeObservable extends EventTarget, Disposable {\n /** Stop observing and clean up resources. */\n disconnect(): void;\n}\n\n/**\n *
|
|
1
|
+
{"version":3,"file":"core.js","names":[],"sources":["../src/core.ts"],"sourcesContent":["/**\n * Framework-agnostic core observable for ResizeObserver events.\n *\n * Uses the `EventTarget` API for zero-dependency event dispatching.\n * Can be adapted by any framework (React, Solid, Vue, Svelte, vanilla).\n *\n * Implements `Disposable` for ES2026 `using` declarations.\n *\n * @example\n * ```ts\n * using observable = createResizeObservable(element, { box: 'content-box' });\n * observable.addEventListener('resize', (e) => {\n * console.log(e.detail.width, e.detail.height);\n * });\n * ```\n */\n\nimport { extractBoxSize } from './extract.js';\nimport type { ResizeObserverBoxOptions } from './types.js';\n\n/** Detail payload for resize events dispatched by the observable. */\nexport interface ResizeEventDetail {\n readonly width: number;\n readonly height: number;\n readonly entry: ResizeObserverEntry;\n}\n\n/** Custom event type for resize observations. */\nexport class ResizeEvent extends CustomEvent<ResizeEventDetail> {\n constructor(detail: ResizeEventDetail) {\n super('resize', { detail });\n }\n}\n\n/** Options for the framework-agnostic observable. */\nexport interface CreateResizeObservableOptions {\n /** Which box model to report. @default 'content-box' */\n box?: ResizeObserverBoxOptions;\n}\n\n/** Framework-agnostic resize observable with EventTarget-based dispatching. */\nexport interface ResizeObservable extends EventTarget, Disposable {\n /** Stop observing and clean up resources. */\n disconnect(): void;\n}\n\n/**\n * Create a framework-agnostic resize observable for an element.\n *\n * Wraps a `ResizeObserver` with `EventTarget` dispatching — consumers\n * subscribe via `addEventListener('resize', handler)`.\n *\n * @param target - The DOM element to observe.\n * @param options - Configuration options.\n * @returns A `ResizeObservable` with `addEventListener` and `disconnect`.\n */\nexport const createResizeObservable = (\n target: Element,\n options: CreateResizeObservableOptions = {},\n): ResizeObservable => {\n const { box = 'content-box' } = options;\n const eventTarget = new EventTarget();\n\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const sizeEntry = extractBoxSize(entry, box);\n\n const detail: ResizeEventDetail = {\n width: sizeEntry?.inlineSize ?? 0,\n height: sizeEntry?.blockSize ?? 0,\n entry,\n };\n\n eventTarget.dispatchEvent(new ResizeEvent(detail));\n }\n });\n\n observer.observe(target, { box });\n\n const disconnect = (): void => {\n observer.disconnect();\n };\n\n return Object.assign(eventTarget, {\n disconnect,\n [Symbol.dispose](): void {\n disconnect();\n },\n });\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA4BA,IAAa,cAAb,cAAiC,YAA+B;CAC9D,YAAY,QAA2B;AACrC,QAAM,UAAU,EAAE,QAAQ,CAAC;;;;;;;;;;;;;AA0B/B,MAAa,0BACX,QACA,UAAyC,EAAE,KACtB;CACrB,MAAM,EAAE,MAAM,kBAAkB;CAChC,MAAM,cAAc,IAAI,aAAa;CAErC,MAAM,WAAW,IAAI,gBAAgB,YAAY;AAC/C,OAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,YAAY,eAAe,OAAO,IAAI;GAE5C,MAAM,SAA4B;IAChC,OAAO,WAAW,cAAc;IAChC,QAAQ,WAAW,aAAa;IAChC;IACD;AAED,eAAY,cAAc,IAAI,YAAY,OAAO,CAAC;;GAEpD;AAEF,UAAS,QAAQ,QAAQ,EAAE,KAAK,CAAC;CAEjC,MAAM,mBAAyB;AAC7B,WAAS,YAAY;;AAGvB,QAAO,OAAO,OAAO,aAAa;EAChC;EACA,CAAC,OAAO,WAAiB;AACvB,eAAY;;EAEf,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
//#region src/extract.ts
|
|
3
|
+
/**
|
|
4
|
+
* Extract the first size entry for the given box model.
|
|
5
|
+
*
|
|
6
|
+
* Shared by `hook.ts`, `hook-multi.ts`, and `core.ts` to avoid duplication.
|
|
7
|
+
* Falls back to `contentBoxSize` when `devicePixelContentBoxSize` is
|
|
8
|
+
* unavailable (Safari/WebKit).
|
|
9
|
+
*
|
|
10
|
+
* @param entry - The ResizeObserverEntry from the observer callback.
|
|
11
|
+
* @param box - Which box model to extract dimensions from.
|
|
12
|
+
* @returns The first ResizeObserverSize for the requested box model.
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
const extractBoxSize = (entry, box) => {
|
|
16
|
+
switch (box) {
|
|
17
|
+
case "border-box": return entry.borderBoxSize[0];
|
|
18
|
+
case "device-pixel-content-box": return (entry.devicePixelContentBoxSize ?? entry.contentBoxSize)[0];
|
|
19
|
+
default: return entry.contentBoxSize[0];
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Extract width and height from a ResizeObserverEntry for the given box model.
|
|
24
|
+
*
|
|
25
|
+
* @param entry - The ResizeObserverEntry from the observer callback.
|
|
26
|
+
* @param box - Which box model to extract dimensions from.
|
|
27
|
+
* @returns Object with `width` (inlineSize) and `height` (blockSize).
|
|
28
|
+
* @internal
|
|
29
|
+
*/
|
|
30
|
+
const extractDimensions = (entry, box) => {
|
|
31
|
+
const size = extractBoxSize(entry, box);
|
|
32
|
+
return {
|
|
33
|
+
width: size?.inlineSize ?? 0,
|
|
34
|
+
height: size?.blockSize ?? 0
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
export { extractDimensions as n, extractBoxSize as t };
|
|
40
|
+
//# sourceMappingURL=extract-BiQI71T_.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-BiQI71T_.js","names":[],"sources":["../src/extract.ts"],"sourcesContent":["import type { ResizeObserverBoxOptions } from './types.js';\n\n/**\n * Extract the first size entry for the given box model.\n *\n * Shared by `hook.ts`, `hook-multi.ts`, and `core.ts` to avoid duplication.\n * Falls back to `contentBoxSize` when `devicePixelContentBoxSize` is\n * unavailable (Safari/WebKit).\n *\n * @param entry - The ResizeObserverEntry from the observer callback.\n * @param box - Which box model to extract dimensions from.\n * @returns The first ResizeObserverSize for the requested box model.\n * @internal\n */\nexport const extractBoxSize = (\n entry: ResizeObserverEntry,\n box: ResizeObserverBoxOptions,\n): ResizeObserverSize | undefined => {\n switch (box) {\n case 'border-box':\n return entry.borderBoxSize[0];\n case 'device-pixel-content-box':\n return (entry.devicePixelContentBoxSize ?? entry.contentBoxSize)[0];\n default:\n return entry.contentBoxSize[0];\n }\n};\n\n/**\n * Extract width and height from a ResizeObserverEntry for the given box model.\n *\n * @param entry - The ResizeObserverEntry from the observer callback.\n * @param box - Which box model to extract dimensions from.\n * @returns Object with `width` (inlineSize) and `height` (blockSize).\n * @internal\n */\nexport const extractDimensions = (\n entry: ResizeObserverEntry,\n box: ResizeObserverBoxOptions,\n): { readonly width: number; readonly height: number } => {\n const size = extractBoxSize(entry, box);\n return { width: size?.inlineSize ?? 0, height: size?.blockSize ?? 0 };\n};\n"],"mappings":";;;;;;;;;;;;;;AAcA,MAAa,kBACX,OACA,QACmC;AACnC,SAAQ,KAAR;EACE,KAAK,aACH,QAAO,MAAM,cAAc;EAC7B,KAAK,2BACH,SAAQ,MAAM,6BAA6B,MAAM,gBAAgB;EACnE,QACE,QAAO,MAAM,eAAe;;;;;;;;;;;AAYlC,MAAa,qBACX,OACA,QACwD;CACxD,MAAM,OAAO,eAAe,OAAO,IAAI;AACvC,QAAO;EAAE,OAAO,MAAM,cAAc;EAAG,QAAQ,MAAM,aAAa;EAAG"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { a as UseResizeObserverOptions, i as ResizeObserverFactory, n as ResizeCallback, o as UseResizeObserverResult, r as ResizeObserverBoxOptions, t as CreateResizeObserverOptions } from "./types-
|
|
2
|
+
import { a as UseResizeObserverOptions, i as ResizeObserverFactory, n as ResizeCallback, o as UseResizeObserverResult, r as ResizeObserverBoxOptions, t as CreateResizeObserverOptions } from "./types-ypIHKARt.js";
|
|
3
3
|
import React, { RefObject } from "react";
|
|
4
4
|
|
|
5
5
|
//#region src/context.d.ts
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/context.ts","../src/factory.ts","../src/hook.ts","../src/hook-multi.ts"],"mappings":";;;;;;;;AAqBA;;;;;;;;;;;;ACIA;cDJa,qBAAA,EAAuB,KAAA,CAAM,OAAA,QAAe,cAAA;;;;;;AAAzD;;;;;;;;;;;;ACIA;;;cAAa,oBAAA,GACX,OAAA,GAAS,2BAAA,KACR,qBAAA,GAAwB,UAAA;;;;;;ADN3B;;;;;;;;;;;;ACIA;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/context.ts","../src/factory.ts","../src/hook.ts","../src/hook-multi.ts"],"mappings":";;;;;;;;AAqBA;;;;;;;;;;;;ACIA;cDJa,qBAAA,EAAuB,KAAA,CAAM,OAAA,QAAe,cAAA;;;;;;AAAzD;;;;;;;;;;;;ACIA;;;cAAa,oBAAA,GACX,OAAA,GAAS,2BAAA,KACR,qBAAA,GAAwB,UAAA;;;;;;ADN3B;;;;;;;;;;;;ACIA;;;;cCca,iBAAA,aAA+B,OAAA,GAAU,OAAA,EACpD,OAAA,GAAS,wBAAA,CAAyB,CAAA,MACjC,uBAAA,CAAwB,CAAA;;;;UC/BV,WAAA;EAAA,SACN,KAAA;EAAA,SACA,MAAA;EAAA,SACA,KAAA,EAAO,mBAAA;AAAA;;UAID,+BAAA;;EAEf,GAAA,GAAM,wBAAA;EHEiD;EGAvD,IAAA,GAAO,QAAA,GAAW,UAAA;AAAA;;AFIpB;;;;;;;;;;;;;;cEca,wBAAA,GACX,IAAA,EAAM,aAAA,CAAc,SAAA,CAAU,OAAA,WAC9B,OAAA,GAAS,+BAAA,KACR,GAAA,CAAI,OAAA,EAAS,WAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
'use client';
|
|
3
3
|
|
|
4
|
+
import { n as extractDimensions, t as extractBoxSize } from "./extract-BiQI71T_.js";
|
|
4
5
|
import { createContext, startTransition, useEffect, useRef, useState } from "react";
|
|
5
6
|
|
|
6
7
|
//#region src/context.ts
|
|
@@ -167,7 +168,7 @@ const getSharedPool = (root) => {
|
|
|
167
168
|
Promise.try(() => {
|
|
168
169
|
if (typeof globalThis.ResizeObserver === "undefined") throw new Error("[@crimson_dev/use-resize-observer] ResizeObserver is not available. Import the /shim entry or use the /server entry for SSR.");
|
|
169
170
|
}).catch((error) => {
|
|
170
|
-
console.error(error);
|
|
171
|
+
console.error(Error.isError(error) ? error : new Error(String(error)));
|
|
171
172
|
});
|
|
172
173
|
const pool = new ObserverPool();
|
|
173
174
|
poolRegistry.set(root, pool);
|
|
@@ -232,36 +233,6 @@ const createResizeObserver = (options = {}) => {
|
|
|
232
233
|
//#endregion
|
|
233
234
|
//#region src/hook.ts
|
|
234
235
|
/**
|
|
235
|
-
* Extract width/height from a ResizeObserverEntry based on the selected box model.
|
|
236
|
-
* Uses destructuring with fallback for Safari's missing `devicePixelContentBoxSize`.
|
|
237
|
-
* @internal
|
|
238
|
-
*/
|
|
239
|
-
const extractDimensions = (entry, box) => {
|
|
240
|
-
switch (box) {
|
|
241
|
-
case "border-box": {
|
|
242
|
-
const size = entry.borderBoxSize[0];
|
|
243
|
-
return {
|
|
244
|
-
width: size?.inlineSize ?? 0,
|
|
245
|
-
height: size?.blockSize ?? 0
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
case "device-pixel-content-box": {
|
|
249
|
-
const size = (entry.devicePixelContentBoxSize ?? entry.contentBoxSize)[0];
|
|
250
|
-
return {
|
|
251
|
-
width: size?.inlineSize ?? 0,
|
|
252
|
-
height: size?.blockSize ?? 0
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
default: {
|
|
256
|
-
const size = entry.contentBoxSize[0];
|
|
257
|
-
return {
|
|
258
|
-
width: size?.inlineSize ?? 0,
|
|
259
|
-
height: size?.blockSize ?? 0
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
};
|
|
264
|
-
/**
|
|
265
236
|
* Primary React hook for observing element resize events.
|
|
266
237
|
*
|
|
267
238
|
* Features:
|
|
@@ -284,9 +255,7 @@ const useResizeObserver = (options = {}) => {
|
|
|
284
255
|
const { ref: externalRef, box = "content-box", root, onResize } = options;
|
|
285
256
|
const internalRef = useRef(null);
|
|
286
257
|
const targetRef = externalRef ?? internalRef;
|
|
287
|
-
const [
|
|
288
|
-
const [height, setHeight] = useState(void 0);
|
|
289
|
-
const [entry, setEntry] = useState(void 0);
|
|
258
|
+
const [state, setState] = useState(void 0);
|
|
290
259
|
const onResizeRef = useRef(onResize);
|
|
291
260
|
onResizeRef.current = onResize;
|
|
292
261
|
const boxRef = useRef(box);
|
|
@@ -297,9 +266,11 @@ const useResizeObserver = (options = {}) => {
|
|
|
297
266
|
const pool = getSharedPool(root ?? element.ownerDocument);
|
|
298
267
|
const callback = (resizeEntry) => {
|
|
299
268
|
const { width: w, height: h } = extractDimensions(resizeEntry, boxRef.current);
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
269
|
+
setState({
|
|
270
|
+
width: w,
|
|
271
|
+
height: h,
|
|
272
|
+
entry: resizeEntry
|
|
273
|
+
});
|
|
303
274
|
onResizeRef.current?.(resizeEntry);
|
|
304
275
|
};
|
|
305
276
|
pool.observe(element, { box }, callback);
|
|
@@ -313,26 +284,15 @@ const useResizeObserver = (options = {}) => {
|
|
|
313
284
|
]);
|
|
314
285
|
return {
|
|
315
286
|
ref: targetRef,
|
|
316
|
-
width,
|
|
317
|
-
height,
|
|
318
|
-
entry
|
|
287
|
+
width: state?.width,
|
|
288
|
+
height: state?.height,
|
|
289
|
+
entry: state?.entry
|
|
319
290
|
};
|
|
320
291
|
};
|
|
321
292
|
|
|
322
293
|
//#endregion
|
|
323
294
|
//#region src/hook-multi.ts
|
|
324
295
|
/**
|
|
325
|
-
* Extract the first size entry for the given box model.
|
|
326
|
-
* @internal
|
|
327
|
-
*/
|
|
328
|
-
const extractSize = (entry, box) => {
|
|
329
|
-
switch (box) {
|
|
330
|
-
case "border-box": return entry.borderBoxSize[0];
|
|
331
|
-
case "device-pixel-content-box": return (entry.devicePixelContentBoxSize ?? entry.contentBoxSize)[0];
|
|
332
|
-
default: return entry.contentBoxSize[0];
|
|
333
|
-
}
|
|
334
|
-
};
|
|
335
|
-
/**
|
|
336
296
|
* Multi-element variant: observe multiple elements simultaneously through
|
|
337
297
|
* a single pool subscription.
|
|
338
298
|
*
|
|
@@ -360,7 +320,7 @@ const useResizeObserverEntries = (refs, options = {}) => {
|
|
|
360
320
|
const pool = getSharedPool(root ?? element.ownerDocument);
|
|
361
321
|
const currentBox = boxRef.current;
|
|
362
322
|
const callback = (resizeEntry) => {
|
|
363
|
-
const sizeEntry =
|
|
323
|
+
const sizeEntry = extractBoxSize(resizeEntry, currentBox);
|
|
364
324
|
setEntries((prev) => {
|
|
365
325
|
const next = new Map(prev);
|
|
366
326
|
next.set(element, {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["#queue","#requestFlush","#rafId","#flush","#scheduler","#registry","#finalizer","#observer","#size"],"sources":["../src/context.ts","../src/scheduler.ts","../src/pool.ts","../src/factory.ts","../src/hook.ts","../src/hook-multi.ts"],"sourcesContent":["'use client';\n\nimport type React from 'react';\nimport { createContext, useContext } from 'react';\n\n/**\n * Context for injecting a custom `ResizeObserver` constructor.\n *\n * Useful for:\n * - **Testing**: Inject a mock `ResizeObserver` for deterministic tests.\n * - **SSR**: Inject a no-op implementation to avoid `ReferenceError`.\n * - **Polyfills**: Inject a polyfill without modifying `globalThis`.\n *\n * @example\n * ```tsx\n * // In tests:\n * <ResizeObserverContext.Provider value={MockResizeObserver}>\n * <ComponentThatUsesResize />\n * </ResizeObserverContext.Provider>\n * ```\n */\nexport const ResizeObserverContext: React.Context<typeof ResizeObserver | null> = createContext<\n typeof ResizeObserver | null\n>(null);\n\nResizeObserverContext.displayName = 'ResizeObserverContext';\n\n/**\n * Access the injected ResizeObserver constructor, falling back to the global.\n * @internal\n */\nexport const useResizeObserverConstructor = (): typeof ResizeObserver => {\n const contextValue = useContext(ResizeObserverContext);\n return contextValue ?? globalThis.ResizeObserver;\n};\n","'use client';\n\nimport { startTransition } from 'react';\n\nimport type { ResizeCallback } from './types.js';\n\n/**\n * Per-frame flush entry — snapshot of callbacks + latest entry for one element.\n * @internal\n */\ninterface FlushEntry {\n readonly callbacks: ReadonlySet<ResizeCallback>;\n readonly entry: ResizeObserverEntry;\n}\n\n/**\n * Batching scheduler that coalesces all ResizeObserver callbacks into a single\n * `requestAnimationFrame` flush, wrapped in React `startTransition` for\n * non-urgent update scheduling.\n *\n * Uses a `Map<Element, FlushEntry>` with last-write-wins semantics so that\n * 100 simultaneous resize events produce exactly 1 React render cycle.\n *\n * Implements `Disposable` for ES2026 `using` declarations.\n *\n * @internal\n */\nexport class RafScheduler implements Disposable {\n readonly #queue = new Map<Element, FlushEntry>();\n #rafId: number | null = null;\n\n /** Enqueue a resize observation for the next rAF flush. */\n schedule(target: Element, entry: ResizeObserverEntry, cbs: ReadonlySet<ResizeCallback>): void {\n this.#queue.set(target, { callbacks: cbs, entry });\n this.#requestFlush();\n }\n\n #requestFlush(): void {\n if (this.#rafId !== null) return;\n this.#rafId = requestAnimationFrame(() => {\n this.#rafId = null;\n this.#flush();\n });\n }\n\n #flush(): void {\n // Snapshot and clear before dispatching to avoid re-entrant mutations\n const snapshot = new Map(this.#queue);\n this.#queue.clear();\n\n startTransition(() => {\n for (const { callbacks, entry } of snapshot.values()) {\n for (const cb of callbacks) {\n cb(entry);\n }\n }\n });\n }\n\n /** Cancel any pending rAF and clear the queue. */\n cancel(): void {\n if (this.#rafId !== null) {\n cancelAnimationFrame(this.#rafId);\n this.#rafId = null;\n }\n this.#queue.clear();\n }\n\n /** Disposable contract (ES2026 explicit resource management). */\n [Symbol.dispose](): void {\n this.cancel();\n }\n}\n\n/** Create a new scheduler instance. @internal */\nexport const createScheduler = (): RafScheduler => new RafScheduler();\n","import { createScheduler, type RafScheduler } from './scheduler.js';\nimport type { ResizeCallback } from './types.js';\n\n/**\n * Shared observer pool that multiplexes many element observations through a\n * single `ResizeObserver` instance per document root.\n *\n * Uses `WeakMap` + `FinalizationRegistry` for GC-backed cleanup of detached\n * elements, and `RafScheduler` for batched, non-urgent React state updates.\n *\n * Implements `Disposable` for ES2026 `using` declarations.\n *\n * @internal\n */\nexport class ObserverPool implements Disposable {\n readonly #scheduler: RafScheduler;\n readonly #registry = new WeakMap<Element, Set<ResizeCallback>>();\n readonly #finalizer = new FinalizationRegistry<WeakRef<Element>>((ref) => {\n const el = ref.deref();\n if (el) {\n this.#observer.unobserve(el);\n this.#size--;\n }\n });\n readonly #observer: ResizeObserver;\n #size = 0;\n\n constructor(scheduler?: RafScheduler) {\n this.#scheduler = scheduler ?? createScheduler();\n this.#observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const callbacks = this.#registry.get(entry.target);\n if (callbacks?.size) {\n this.#scheduler.schedule(entry.target, entry, callbacks);\n }\n }\n });\n }\n\n /** Begin observing an element with the given options and callback. */\n observe(target: Element, options: ResizeObserverOptions, cb: ResizeCallback): void {\n let callbacks = this.#registry.get(target);\n if (!callbacks) {\n callbacks = new Set();\n this.#registry.set(target, callbacks);\n this.#finalizer.register(target, new WeakRef(target), target);\n this.#observer.observe(target, options);\n this.#size++;\n }\n callbacks.add(cb);\n }\n\n /** Stop a specific callback from observing the target. */\n unobserve(target: Element, cb: ResizeCallback): void {\n const callbacks = this.#registry.get(target);\n if (!callbacks) return;\n callbacks.delete(cb);\n if (callbacks.size === 0) {\n this.#registry.delete(target);\n this.#finalizer.unregister(target);\n this.#observer.unobserve(target);\n this.#size--;\n }\n }\n\n /** Number of currently observed elements. */\n get observedCount(): number {\n return this.#size;\n }\n\n /** Disposable contract (ES2026 explicit resource management). */\n [Symbol.dispose](): void {\n this.#observer.disconnect();\n this.#scheduler.cancel();\n this.#size = 0;\n }\n}\n\n/**\n * Module-level weak registry of pools per document/shadow root.\n * Ensures a single shared pool per root context.\n */\nconst poolRegistry = new WeakMap<Document | ShadowRoot, ObserverPool>();\n\n/**\n * Get or create the shared observer pool for the given root.\n * Uses `Promise.try()` (ES2026) for safe async-context creation\n * with synchronous return path.\n *\n * @param root - Document or ShadowRoot to scope the pool to.\n * @returns The shared `ObserverPool` for the given root.\n * @internal\n */\nexport const getSharedPool = (root: Document | ShadowRoot): ObserverPool => {\n const existing = poolRegistry.get(root);\n if (existing) return existing;\n\n // Promise.try() (ES2026) — safely wraps synchronous pool creation in a\n // microtask-aware context, catching any constructor exceptions into a\n // rejected promise for diagnostics while returning synchronously.\n Promise.try(() => {\n if (typeof globalThis.ResizeObserver === 'undefined') {\n throw new Error(\n '[@crimson_dev/use-resize-observer] ResizeObserver is not available. ' +\n 'Import the /shim entry or use the /server entry for SSR.',\n );\n }\n }).catch((error: unknown) => {\n console.error(error);\n });\n\n const pool = new ObserverPool();\n poolRegistry.set(root, pool);\n return pool;\n};\n","import { getSharedPool } from './pool.js';\nimport type {\n CreateResizeObserverOptions,\n ResizeCallback,\n ResizeObserverFactory,\n} from './types.js';\n\n/**\n * Framework-agnostic factory for creating a ResizeObserver subscription\n * using the shared pool architecture.\n *\n * Uses the same pool and scheduler as the React hook — no duplicate observers.\n * Implements cleanup tracking with `Map` for efficient iteration.\n *\n * @param options - Configuration options.\n * @returns An object with `observe`, `unobserve`, and `disconnect` methods.\n *\n * @example\n * ```ts\n * using observer = createResizeObserver({ box: 'border-box' });\n * observer.observe(element, (entry) => {\n * console.log(entry.contentRect.width);\n * });\n * ```\n */\nexport const createResizeObserver = (\n options: CreateResizeObserverOptions = {},\n): ResizeObserverFactory & Disposable => {\n const { box = 'content-box', root = globalThis.document } = options;\n const pool = getSharedPool(root);\n const tracked = new Map<Element, Set<ResizeCallback>>();\n\n const observe = (target: Element, callback: ResizeCallback): void => {\n pool.observe(target, { box }, callback);\n\n let cbs = tracked.get(target);\n if (!cbs) {\n cbs = new Set();\n tracked.set(target, cbs);\n }\n cbs.add(callback);\n };\n\n const unobserve = (target: Element, callback: ResizeCallback): void => {\n pool.unobserve(target, callback);\n const cbs = tracked.get(target);\n if (cbs) {\n cbs.delete(callback);\n if (cbs.size === 0) tracked.delete(target);\n }\n };\n\n const disconnect = (): void => {\n for (const [target, cbs] of tracked) {\n for (const cb of cbs) {\n pool.unobserve(target, cb);\n }\n }\n tracked.clear();\n };\n\n return {\n observe,\n unobserve,\n disconnect,\n [Symbol.dispose](): void {\n disconnect();\n },\n };\n};\n","'use client';\n\nimport { useEffect, useRef, useState } from 'react';\n\nimport { getSharedPool } from './pool.js';\nimport type {\n ResizeCallback,\n ResizeObserverBoxOptions,\n UseResizeObserverOptions,\n UseResizeObserverResult,\n} from './types.js';\n\n/**\n * Extract width/height from a ResizeObserverEntry based on the selected box model.\n * Uses destructuring with fallback for Safari's missing `devicePixelContentBoxSize`.\n * @internal\n */\nconst extractDimensions = (\n entry: ResizeObserverEntry,\n box: ResizeObserverBoxOptions,\n): { readonly width: number; readonly height: number } => {\n switch (box) {\n case 'border-box': {\n const size = entry.borderBoxSize[0];\n return { width: size?.inlineSize ?? 0, height: size?.blockSize ?? 0 };\n }\n case 'device-pixel-content-box': {\n const size = (entry.devicePixelContentBoxSize ?? entry.contentBoxSize)[0];\n return { width: size?.inlineSize ?? 0, height: size?.blockSize ?? 0 };\n }\n default: {\n const size = entry.contentBoxSize[0];\n return { width: size?.inlineSize ?? 0, height: size?.blockSize ?? 0 };\n }\n }\n};\n\n/**\n * Primary React hook for observing element resize events.\n *\n * Features:\n * - Single shared `ResizeObserver` per document root (pool architecture)\n * - `requestAnimationFrame` batching with `startTransition` wrapping\n * - GC-backed cleanup via `FinalizationRegistry`\n * - React Compiler-safe (stable callback identity via ref pattern)\n * - Sub-300B gzip bundle contribution\n *\n * @param options - Configuration options.\n * @returns Ref, width, height, and raw entry.\n *\n * @example\n * ```tsx\n * const { ref, width, height } = useResizeObserver<HTMLDivElement>();\n * return <div ref={ref}>Size: {width} x {height}</div>;\n * ```\n */\nexport const useResizeObserver = <T extends Element = Element>(\n options: UseResizeObserverOptions<T> = {},\n): UseResizeObserverResult<T> => {\n const { ref: externalRef, box = 'content-box', root, onResize } = options;\n\n const internalRef = useRef<T | null>(null);\n const targetRef = externalRef ?? internalRef;\n\n const [width, setWidth] = useState<number | undefined>(undefined);\n const [height, setHeight] = useState<number | undefined>(undefined);\n const [entry, setEntry] = useState<ResizeObserverEntry | undefined>(undefined);\n\n // Stable callback ref — survives re-renders without triggering re-observation.\n // Follows useEffectEvent semantics: latest closure captured, identity stable.\n const onResizeRef = useRef(onResize);\n onResizeRef.current = onResize;\n\n const boxRef = useRef(box);\n boxRef.current = box;\n\n useEffect(() => {\n const element = targetRef.current;\n if (!element) return;\n\n const observerRoot = root ?? element.ownerDocument;\n const pool = getSharedPool(observerRoot);\n\n const callback: ResizeCallback = (resizeEntry) => {\n const { width: w, height: h } = extractDimensions(resizeEntry, boxRef.current);\n setWidth(w);\n setHeight(h);\n setEntry(resizeEntry);\n onResizeRef.current?.(resizeEntry);\n };\n\n pool.observe(element, { box }, callback);\n\n return () => {\n pool.unobserve(element, callback);\n };\n }, [targetRef, box, root]);\n\n return { ref: targetRef, width, height, entry };\n};\n\nexport type { ResizeObserverBoxOptions, UseResizeObserverOptions, UseResizeObserverResult };\n","'use client';\n\nimport type { RefObject } from 'react';\nimport { useEffect, useRef, useState } from 'react';\n\nimport { getSharedPool } from './pool.js';\nimport type { ResizeCallback, ResizeObserverBoxOptions } from './types.js';\n\n/** Entry data for a single observed element in the multi-element hook. */\nexport interface ResizeEntry {\n readonly width: number;\n readonly height: number;\n readonly entry: ResizeObserverEntry;\n}\n\n/** Options for `useResizeObserverEntries`. */\nexport interface UseResizeObserverEntriesOptions {\n /** Which box model to report. @default 'content-box' */\n box?: ResizeObserverBoxOptions;\n /** Document or ShadowRoot scoping the pool. @default document */\n root?: Document | ShadowRoot;\n}\n\n/**\n * Extract the first size entry for the given box model.\n * @internal\n */\nconst extractSize = (\n entry: ResizeObserverEntry,\n box: ResizeObserverBoxOptions,\n): ResizeObserverSize | undefined => {\n switch (box) {\n case 'border-box':\n return entry.borderBoxSize[0];\n case 'device-pixel-content-box':\n return (entry.devicePixelContentBoxSize ?? entry.contentBoxSize)[0];\n default:\n return entry.contentBoxSize[0];\n }\n};\n\n/**\n * Multi-element variant: observe multiple elements simultaneously through\n * a single pool subscription.\n *\n * @param refs - Array of refs pointing to elements to observe.\n * @param options - Configuration options.\n * @returns A `Map<Element, ResizeEntry>` keyed by observed element.\n *\n * @example\n * ```tsx\n * const ref1 = useRef<HTMLDivElement>(null);\n * const ref2 = useRef<HTMLDivElement>(null);\n * const entries = useResizeObserverEntries([ref1, ref2]);\n * ```\n */\nexport const useResizeObserverEntries = (\n refs: ReadonlyArray<RefObject<Element | null>>,\n options: UseResizeObserverEntriesOptions = {},\n): Map<Element, ResizeEntry> => {\n const { box = 'content-box', root } = options;\n const [entries, setEntries] = useState<Map<Element, ResizeEntry>>(new Map());\n const boxRef = useRef(box);\n boxRef.current = box;\n\n useEffect(() => {\n const cleanups: Array<() => void> = [];\n\n for (const ref of refs) {\n const element = ref.current;\n if (!element) continue;\n\n const observerRoot = root ?? element.ownerDocument;\n const pool = getSharedPool(observerRoot);\n const currentBox = boxRef.current;\n\n const callback: ResizeCallback = (resizeEntry) => {\n const sizeEntry = extractSize(resizeEntry, currentBox);\n\n setEntries((prev) => {\n const next = new Map(prev);\n next.set(element, {\n width: sizeEntry?.inlineSize ?? 0,\n height: sizeEntry?.blockSize ?? 0,\n entry: resizeEntry,\n });\n return next;\n });\n };\n\n pool.observe(element, { box: currentBox }, callback);\n cleanups.push(() => pool.unobserve(element, callback));\n }\n\n return () => {\n for (const cleanup of cleanups) {\n cleanup();\n }\n };\n }, [refs, root]);\n\n return entries;\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["#queue","#requestFlush","#rafId","#flush","#scheduler","#registry","#finalizer","#observer","#size"],"sources":["../src/context.ts","../src/scheduler.ts","../src/pool.ts","../src/factory.ts","../src/hook.ts","../src/hook-multi.ts"],"sourcesContent":["'use client';\n\nimport type React from 'react';\nimport { createContext, useContext } from 'react';\n\n/**\n * Context for injecting a custom `ResizeObserver` constructor.\n *\n * Useful for:\n * - **Testing**: Inject a mock `ResizeObserver` for deterministic tests.\n * - **SSR**: Inject a no-op implementation to avoid `ReferenceError`.\n * - **Polyfills**: Inject a polyfill without modifying `globalThis`.\n *\n * @example\n * ```tsx\n * // In tests:\n * <ResizeObserverContext.Provider value={MockResizeObserver}>\n * <ComponentThatUsesResize />\n * </ResizeObserverContext.Provider>\n * ```\n */\nexport const ResizeObserverContext: React.Context<typeof ResizeObserver | null> = createContext<\n typeof ResizeObserver | null\n>(null);\n\nResizeObserverContext.displayName = 'ResizeObserverContext';\n\n/**\n * Access the injected ResizeObserver constructor, falling back to the global.\n * @internal\n */\nexport const useResizeObserverConstructor = (): typeof ResizeObserver => {\n const contextValue = useContext(ResizeObserverContext);\n return contextValue ?? globalThis.ResizeObserver;\n};\n","'use client';\n\nimport { startTransition } from 'react';\n\nimport type { ResizeCallback } from './types.js';\n\n/**\n * Per-frame flush entry — snapshot of callbacks + latest entry for one element.\n * @internal\n */\ninterface FlushEntry {\n readonly callbacks: ReadonlySet<ResizeCallback>;\n readonly entry: ResizeObserverEntry;\n}\n\n/**\n * Batching scheduler that coalesces all ResizeObserver callbacks into a single\n * `requestAnimationFrame` flush, wrapped in React `startTransition` for\n * non-urgent update scheduling.\n *\n * Uses a `Map<Element, FlushEntry>` with last-write-wins semantics so that\n * 100 simultaneous resize events produce exactly 1 React render cycle.\n *\n * Implements `Disposable` for ES2026 `using` declarations.\n *\n * @internal\n */\nexport class RafScheduler implements Disposable {\n readonly #queue = new Map<Element, FlushEntry>();\n #rafId: number | null = null;\n\n /** Enqueue a resize observation for the next rAF flush. */\n schedule(target: Element, entry: ResizeObserverEntry, cbs: ReadonlySet<ResizeCallback>): void {\n this.#queue.set(target, { callbacks: cbs, entry });\n this.#requestFlush();\n }\n\n #requestFlush(): void {\n if (this.#rafId !== null) return;\n this.#rafId = requestAnimationFrame(() => {\n this.#rafId = null;\n this.#flush();\n });\n }\n\n #flush(): void {\n // Snapshot and clear before dispatching to avoid re-entrant mutations\n const snapshot = new Map(this.#queue);\n this.#queue.clear();\n\n startTransition(() => {\n for (const { callbacks, entry } of snapshot.values()) {\n for (const cb of callbacks) {\n cb(entry);\n }\n }\n });\n }\n\n /** Cancel any pending rAF and clear the queue. */\n cancel(): void {\n if (this.#rafId !== null) {\n cancelAnimationFrame(this.#rafId);\n this.#rafId = null;\n }\n this.#queue.clear();\n }\n\n /** Disposable contract (ES2026 explicit resource management). */\n [Symbol.dispose](): void {\n this.cancel();\n }\n}\n\n/** Create a new scheduler instance. @internal */\nexport const createScheduler = (): RafScheduler => new RafScheduler();\n","import { createScheduler, type RafScheduler } from './scheduler.js';\nimport type { ResizeCallback } from './types.js';\n\n/**\n * Shared observer pool that multiplexes many element observations through a\n * single `ResizeObserver` instance per document root.\n *\n * Uses `WeakMap` + `FinalizationRegistry` for GC-backed cleanup of detached\n * elements, and `RafScheduler` for batched, non-urgent React state updates.\n *\n * Implements `Disposable` for ES2026 `using` declarations.\n *\n * @internal\n */\nexport class ObserverPool implements Disposable {\n readonly #scheduler: RafScheduler;\n readonly #registry = new WeakMap<Element, Set<ResizeCallback>>();\n readonly #finalizer = new FinalizationRegistry<WeakRef<Element>>((ref) => {\n const el = ref.deref();\n if (el) {\n this.#observer.unobserve(el);\n this.#size--;\n }\n });\n readonly #observer: ResizeObserver;\n #size = 0;\n\n constructor(scheduler?: RafScheduler) {\n this.#scheduler = scheduler ?? createScheduler();\n this.#observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const callbacks = this.#registry.get(entry.target);\n if (callbacks?.size) {\n this.#scheduler.schedule(entry.target, entry, callbacks);\n }\n }\n });\n }\n\n /** Begin observing an element with the given options and callback. */\n observe(target: Element, options: ResizeObserverOptions, cb: ResizeCallback): void {\n let callbacks = this.#registry.get(target);\n if (!callbacks) {\n callbacks = new Set();\n this.#registry.set(target, callbacks);\n this.#finalizer.register(target, new WeakRef(target), target);\n this.#observer.observe(target, options);\n this.#size++;\n }\n callbacks.add(cb);\n }\n\n /** Stop a specific callback from observing the target. */\n unobserve(target: Element, cb: ResizeCallback): void {\n const callbacks = this.#registry.get(target);\n if (!callbacks) return;\n callbacks.delete(cb);\n if (callbacks.size === 0) {\n this.#registry.delete(target);\n this.#finalizer.unregister(target);\n this.#observer.unobserve(target);\n this.#size--;\n }\n }\n\n /** Number of currently observed elements. */\n get observedCount(): number {\n return this.#size;\n }\n\n /** Disposable contract (ES2026 explicit resource management). */\n [Symbol.dispose](): void {\n this.#observer.disconnect();\n this.#scheduler.cancel();\n this.#size = 0;\n }\n}\n\n/**\n * Module-level weak registry of pools per document/shadow root.\n * Ensures a single shared pool per root context.\n */\nconst poolRegistry = new WeakMap<Document | ShadowRoot, ObserverPool>();\n\n/**\n * Get or create the shared observer pool for the given root.\n * Uses `Promise.try()` (ES2026) for safe async-context creation\n * with synchronous return path.\n *\n * @param root - Document or ShadowRoot to scope the pool to.\n * @returns The shared `ObserverPool` for the given root.\n * @internal\n */\nexport const getSharedPool = (root: Document | ShadowRoot): ObserverPool => {\n const existing = poolRegistry.get(root);\n if (existing) return existing;\n\n // Promise.try() (ES2026) — safely wraps synchronous pool creation in a\n // microtask-aware context, catching any constructor exceptions into a\n // rejected promise for diagnostics while returning synchronously.\n Promise.try(() => {\n if (typeof globalThis.ResizeObserver === 'undefined') {\n throw new Error(\n '[@crimson_dev/use-resize-observer] ResizeObserver is not available. ' +\n 'Import the /shim entry or use the /server entry for SSR.',\n );\n }\n }).catch((error: unknown) => {\n console.error(Error.isError(error) ? error : new Error(String(error)));\n });\n\n const pool = new ObserverPool();\n poolRegistry.set(root, pool);\n return pool;\n};\n","import { getSharedPool } from './pool.js';\nimport type {\n CreateResizeObserverOptions,\n ResizeCallback,\n ResizeObserverFactory,\n} from './types.js';\n\n/**\n * Framework-agnostic factory for creating a ResizeObserver subscription\n * using the shared pool architecture.\n *\n * Uses the same pool and scheduler as the React hook — no duplicate observers.\n * Implements cleanup tracking with `Map` for efficient iteration.\n *\n * @param options - Configuration options.\n * @returns An object with `observe`, `unobserve`, and `disconnect` methods.\n *\n * @example\n * ```ts\n * using observer = createResizeObserver({ box: 'border-box' });\n * observer.observe(element, (entry) => {\n * console.log(entry.contentRect.width);\n * });\n * ```\n */\nexport const createResizeObserver = (\n options: CreateResizeObserverOptions = {},\n): ResizeObserverFactory & Disposable => {\n const { box = 'content-box', root = globalThis.document } = options;\n const pool = getSharedPool(root);\n const tracked = new Map<Element, Set<ResizeCallback>>();\n\n const observe = (target: Element, callback: ResizeCallback): void => {\n pool.observe(target, { box }, callback);\n\n let cbs = tracked.get(target);\n if (!cbs) {\n cbs = new Set();\n tracked.set(target, cbs);\n }\n cbs.add(callback);\n };\n\n const unobserve = (target: Element, callback: ResizeCallback): void => {\n pool.unobserve(target, callback);\n const cbs = tracked.get(target);\n if (cbs) {\n cbs.delete(callback);\n if (cbs.size === 0) tracked.delete(target);\n }\n };\n\n const disconnect = (): void => {\n for (const [target, cbs] of tracked) {\n for (const cb of cbs) {\n pool.unobserve(target, cb);\n }\n }\n tracked.clear();\n };\n\n return {\n observe,\n unobserve,\n disconnect,\n [Symbol.dispose](): void {\n disconnect();\n },\n };\n};\n","'use client';\n\nimport { useEffect, useRef, useState } from 'react';\n\nimport { extractDimensions } from './extract.js';\nimport { getSharedPool } from './pool.js';\nimport type {\n ResizeCallback,\n ResizeObserverBoxOptions,\n UseResizeObserverOptions,\n UseResizeObserverResult,\n} from './types.js';\n\n/** Internal state shape — single object to batch width+height+entry in one setState. */\ninterface ObserverState {\n readonly width: number;\n readonly height: number;\n readonly entry: ResizeObserverEntry;\n}\n\n/**\n * Primary React hook for observing element resize events.\n *\n * Features:\n * - Single shared `ResizeObserver` per document root (pool architecture)\n * - `requestAnimationFrame` batching with `startTransition` wrapping\n * - GC-backed cleanup via `FinalizationRegistry`\n * - React Compiler-safe (stable callback identity via ref pattern)\n * - Sub-300B gzip bundle contribution\n *\n * @param options - Configuration options.\n * @returns Ref, width, height, and raw entry.\n *\n * @example\n * ```tsx\n * const { ref, width, height } = useResizeObserver<HTMLDivElement>();\n * return <div ref={ref}>Size: {width} x {height}</div>;\n * ```\n */\nexport const useResizeObserver = <T extends Element = Element>(\n options: UseResizeObserverOptions<T> = {},\n): UseResizeObserverResult<T> => {\n const { ref: externalRef, box = 'content-box', root, onResize } = options;\n\n const internalRef = useRef<T | null>(null);\n const targetRef = externalRef ?? internalRef;\n\n const [state, setState] = useState<ObserverState | undefined>(undefined);\n\n // Stable callback ref — survives re-renders without triggering re-observation.\n // Follows useEffectEvent semantics: latest closure captured, identity stable.\n const onResizeRef = useRef(onResize);\n onResizeRef.current = onResize;\n\n const boxRef = useRef(box);\n boxRef.current = box;\n\n useEffect(() => {\n const element = targetRef.current;\n if (!element) return;\n\n const observerRoot = root ?? element.ownerDocument;\n const pool = getSharedPool(observerRoot);\n\n const callback: ResizeCallback = (resizeEntry) => {\n const { width: w, height: h } = extractDimensions(resizeEntry, boxRef.current);\n setState({ width: w, height: h, entry: resizeEntry });\n onResizeRef.current?.(resizeEntry);\n };\n\n pool.observe(element, { box }, callback);\n\n return () => {\n pool.unobserve(element, callback);\n };\n }, [targetRef, box, root]);\n\n return {\n ref: targetRef,\n width: state?.width,\n height: state?.height,\n entry: state?.entry,\n };\n};\n\nexport type { ResizeObserverBoxOptions, UseResizeObserverOptions, UseResizeObserverResult };\n","'use client';\n\nimport type { RefObject } from 'react';\nimport { useEffect, useRef, useState } from 'react';\n\nimport { extractBoxSize } from './extract.js';\nimport { getSharedPool } from './pool.js';\nimport type { ResizeCallback, ResizeObserverBoxOptions } from './types.js';\n\n/** Entry data for a single observed element in the multi-element hook. */\nexport interface ResizeEntry {\n readonly width: number;\n readonly height: number;\n readonly entry: ResizeObserverEntry;\n}\n\n/** Options for `useResizeObserverEntries`. */\nexport interface UseResizeObserverEntriesOptions {\n /** Which box model to report. @default 'content-box' */\n box?: ResizeObserverBoxOptions;\n /** Document or ShadowRoot scoping the pool. @default document */\n root?: Document | ShadowRoot;\n}\n\n/**\n * Multi-element variant: observe multiple elements simultaneously through\n * a single pool subscription.\n *\n * @param refs - Array of refs pointing to elements to observe.\n * @param options - Configuration options.\n * @returns A `Map<Element, ResizeEntry>` keyed by observed element.\n *\n * @example\n * ```tsx\n * const ref1 = useRef<HTMLDivElement>(null);\n * const ref2 = useRef<HTMLDivElement>(null);\n * const entries = useResizeObserverEntries([ref1, ref2]);\n * ```\n */\nexport const useResizeObserverEntries = (\n refs: ReadonlyArray<RefObject<Element | null>>,\n options: UseResizeObserverEntriesOptions = {},\n): Map<Element, ResizeEntry> => {\n const { box = 'content-box', root } = options;\n const [entries, setEntries] = useState<Map<Element, ResizeEntry>>(new Map());\n const boxRef = useRef(box);\n boxRef.current = box;\n\n useEffect(() => {\n const cleanups: Array<() => void> = [];\n\n for (const ref of refs) {\n const element = ref.current;\n if (!element) continue;\n\n const observerRoot = root ?? element.ownerDocument;\n const pool = getSharedPool(observerRoot);\n const currentBox = boxRef.current;\n\n const callback: ResizeCallback = (resizeEntry) => {\n const sizeEntry = extractBoxSize(resizeEntry, currentBox);\n\n setEntries((prev) => {\n const next = new Map(prev);\n next.set(element, {\n width: sizeEntry?.inlineSize ?? 0,\n height: sizeEntry?.blockSize ?? 0,\n entry: resizeEntry,\n });\n return next;\n });\n };\n\n pool.observe(element, { box: currentBox }, callback);\n cleanups.push(() => pool.unobserve(element, callback));\n }\n\n return () => {\n for (const cleanup of cleanups) {\n cleanup();\n }\n };\n }, [refs, root]);\n\n return entries;\n};\n"],"mappings":"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"}
|
package/dist/server.d.ts
CHANGED
package/dist/shim.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ declare class ResizeObserverShim {
|
|
|
32
32
|
* @param values - Array of numbers to sum precisely.
|
|
33
33
|
* @returns The precise sum.
|
|
34
34
|
*/
|
|
35
|
-
declare const sumPrecise: (values: number[]) => number;
|
|
35
|
+
declare const sumPrecise: (values: readonly number[]) => number;
|
|
36
36
|
//#endregion
|
|
37
37
|
export { ResizeObserverShim, sumPrecise };
|
|
38
38
|
//# sourceMappingURL=shim.d.ts.map
|
package/dist/shim.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shim.js","names":["#callback","#targets","#lastSizes","#startPolling","#stopPolling","#rafId","#checkForChanges"],"sources":["../src/shim.ts"],"sourcesContent":["/**\n * Polyfill shim entry — provides a ResizeObserver polyfill for\n * environments without native support.\n *\n * Installs `globalThis.ResizeObserver` if it's missing.\n * Uses rAF polling as the observation mechanism.\n *\n * For sub-pixel normalization, uses `Math.sumPrecise()` (ES2026)\n * with fallback to iterative addition.\n *\n * @example\n * ```ts\n * // Ensure ResizeObserver exists before using the hook:\n * import '@crimson_dev/use-resize-observer/shim';\n * import { useResizeObserver } from '@crimson_dev/use-resize-observer';\n * ```\n */\n\n/** Minimal ResizeObserver polyfill for environments without native support. */\nclass ResizeObserverShim {\n readonly #callback: ResizeObserverCallback;\n readonly #targets = new Set<Element>();\n #rafId: number | null = null;\n readonly #lastSizes = new WeakMap<Element, { width: number; height: number }>();\n\n constructor(callback: ResizeObserverCallback) {\n this.#callback = callback;\n }\n\n observe(target: Element, _options?: ResizeObserverOptions): void {\n this.#targets.add(target);\n this.#startPolling();\n }\n\n unobserve(target: Element): void {\n this.#targets.delete(target);\n if (this.#targets.size === 0) this.#stopPolling();\n }\n\n disconnect(): void {\n this.#targets.clear();\n this.#stopPolling();\n }\n\n #startPolling(): void {\n if (this.#rafId !== null) return;\n const poll = (): void => {\n this.#checkForChanges();\n this.#rafId = requestAnimationFrame(poll);\n };\n this.#rafId = requestAnimationFrame(poll);\n }\n\n #stopPolling(): void {\n if (this.#rafId !== null) {\n cancelAnimationFrame(this.#rafId);\n this.#rafId = null;\n }\n }\n\n #checkForChanges(): void {\n const entries: ResizeObserverEntry[] = [];\n const dpr = globalThis.devicePixelRatio ?? 1;\n\n for (const target of this.#targets) {\n const rect = target.getBoundingClientRect();\n const last = this.#lastSizes.get(target);\n\n if (!last || last.width !== rect.width || last.height !== rect.height) {\n this.#lastSizes.set(target, { width: rect.width, height: rect.height });\n\n entries.push({\n target,\n contentRect: rect,\n borderBoxSize: [\n { inlineSize: rect.width, blockSize: rect.height },\n ] as unknown as ReadonlyArray<ResizeObserverSize>,\n contentBoxSize: [\n { inlineSize: rect.width, blockSize: rect.height },\n ] as unknown as ReadonlyArray<ResizeObserverSize>,\n devicePixelContentBoxSize: [\n {\n inlineSize: rect.width * dpr,\n blockSize: rect.height * dpr,\n },\n ] as unknown as ReadonlyArray<ResizeObserverSize>,\n } satisfies ResizeObserverEntry);\n }\n }\n\n if (entries.length > 0) {\n this.#callback(entries, this as unknown as ResizeObserver);\n }\n }\n}\n\n/**\n * Normalize sub-pixel coordinates using `Math.sumPrecise()` (ES2026).\n * Falls back to simple addition if unavailable.\n *\n * @param values - Array of numbers to sum precisely.\n * @returns The precise sum.\n */\nexport const sumPrecise = (values: number[]): number => {\n if (typeof Math.sumPrecise === 'function') {\n return Math.sumPrecise(values);\n }\n let sum = 0;\n for (const v of values) sum += v;\n return sum;\n};\n\n// Install shim if native ResizeObserver is unavailable\nif (typeof globalThis.ResizeObserver === 'undefined') {\n Object.defineProperty(globalThis, 'ResizeObserver', {\n value: ResizeObserverShim,\n writable: true,\n configurable: true,\n });\n}\n\nexport { ResizeObserverShim };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAmBA,IAAM,qBAAN,MAAyB;CACvB,CAASA;CACT,CAASC,0BAAW,IAAI,KAAc;CACtC,SAAwB;CACxB,CAASC,4BAAa,IAAI,
|
|
1
|
+
{"version":3,"file":"shim.js","names":["#callback","#targets","#lastSizes","#startPolling","#stopPolling","#rafId","#checkForChanges"],"sources":["../src/shim.ts"],"sourcesContent":["/**\n * Polyfill shim entry — provides a ResizeObserver polyfill for\n * environments without native support.\n *\n * Installs `globalThis.ResizeObserver` if it's missing.\n * Uses rAF polling as the observation mechanism.\n *\n * For sub-pixel normalization, uses `Math.sumPrecise()` (ES2026)\n * with fallback to iterative addition.\n *\n * @example\n * ```ts\n * // Ensure ResizeObserver exists before using the hook:\n * import '@crimson_dev/use-resize-observer/shim';\n * import { useResizeObserver } from '@crimson_dev/use-resize-observer';\n * ```\n */\n\n/** Minimal ResizeObserver polyfill for environments without native support. */\nclass ResizeObserverShim {\n readonly #callback: ResizeObserverCallback;\n readonly #targets = new Set<Element>();\n #rafId: number | null = null;\n readonly #lastSizes = new WeakMap<Element, { readonly width: number; readonly height: number }>();\n\n constructor(callback: ResizeObserverCallback) {\n this.#callback = callback;\n }\n\n observe(target: Element, _options?: ResizeObserverOptions): void {\n this.#targets.add(target);\n this.#startPolling();\n }\n\n unobserve(target: Element): void {\n this.#targets.delete(target);\n if (this.#targets.size === 0) this.#stopPolling();\n }\n\n disconnect(): void {\n this.#targets.clear();\n this.#stopPolling();\n }\n\n #startPolling(): void {\n if (this.#rafId !== null) return;\n const poll = (): void => {\n this.#checkForChanges();\n this.#rafId = requestAnimationFrame(poll);\n };\n this.#rafId = requestAnimationFrame(poll);\n }\n\n #stopPolling(): void {\n if (this.#rafId !== null) {\n cancelAnimationFrame(this.#rafId);\n this.#rafId = null;\n }\n }\n\n #checkForChanges(): void {\n const entries: ResizeObserverEntry[] = [];\n const dpr = globalThis.devicePixelRatio ?? 1;\n\n for (const target of this.#targets) {\n const rect = target.getBoundingClientRect();\n const last = this.#lastSizes.get(target);\n\n if (!last || last.width !== rect.width || last.height !== rect.height) {\n this.#lastSizes.set(target, { width: rect.width, height: rect.height });\n\n entries.push({\n target,\n contentRect: rect,\n borderBoxSize: [\n { inlineSize: rect.width, blockSize: rect.height },\n ] as unknown as ReadonlyArray<ResizeObserverSize>,\n contentBoxSize: [\n { inlineSize: rect.width, blockSize: rect.height },\n ] as unknown as ReadonlyArray<ResizeObserverSize>,\n devicePixelContentBoxSize: [\n {\n inlineSize: rect.width * dpr,\n blockSize: rect.height * dpr,\n },\n ] as unknown as ReadonlyArray<ResizeObserverSize>,\n } satisfies ResizeObserverEntry);\n }\n }\n\n if (entries.length > 0) {\n this.#callback(entries, this as unknown as ResizeObserver);\n }\n }\n}\n\n/**\n * Normalize sub-pixel coordinates using `Math.sumPrecise()` (ES2026).\n * Falls back to simple addition if unavailable.\n *\n * @param values - Array of numbers to sum precisely.\n * @returns The precise sum.\n */\nexport const sumPrecise = (values: readonly number[]): number => {\n if (typeof Math.sumPrecise === 'function') {\n return Math.sumPrecise(values);\n }\n let sum = 0;\n for (const v of values) sum += v;\n return sum;\n};\n\n// Install shim if native ResizeObserver is unavailable\nif (typeof globalThis.ResizeObserver === 'undefined') {\n Object.defineProperty(globalThis, 'ResizeObserver', {\n value: ResizeObserverShim,\n writable: true,\n configurable: true,\n });\n}\n\nexport { ResizeObserverShim };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAmBA,IAAM,qBAAN,MAAyB;CACvB,CAASA;CACT,CAASC,0BAAW,IAAI,KAAc;CACtC,SAAwB;CACxB,CAASC,4BAAa,IAAI,SAAuE;CAEjG,YAAY,UAAkC;AAC5C,QAAKF,WAAY;;CAGnB,QAAQ,QAAiB,UAAwC;AAC/D,QAAKC,QAAS,IAAI,OAAO;AACzB,QAAKE,cAAe;;CAGtB,UAAU,QAAuB;AAC/B,QAAKF,QAAS,OAAO,OAAO;AAC5B,MAAI,MAAKA,QAAS,SAAS,EAAG,OAAKG,aAAc;;CAGnD,aAAmB;AACjB,QAAKH,QAAS,OAAO;AACrB,QAAKG,aAAc;;CAGrB,gBAAsB;AACpB,MAAI,MAAKC,UAAW,KAAM;EAC1B,MAAM,aAAmB;AACvB,SAAKC,iBAAkB;AACvB,SAAKD,QAAS,sBAAsB,KAAK;;AAE3C,QAAKA,QAAS,sBAAsB,KAAK;;CAG3C,eAAqB;AACnB,MAAI,MAAKA,UAAW,MAAM;AACxB,wBAAqB,MAAKA,MAAO;AACjC,SAAKA,QAAS;;;CAIlB,mBAAyB;EACvB,MAAM,UAAiC,EAAE;EACzC,MAAM,MAAM,WAAW,oBAAoB;AAE3C,OAAK,MAAM,UAAU,MAAKJ,SAAU;GAClC,MAAM,OAAO,OAAO,uBAAuB;GAC3C,MAAM,OAAO,MAAKC,UAAW,IAAI,OAAO;AAExC,OAAI,CAAC,QAAQ,KAAK,UAAU,KAAK,SAAS,KAAK,WAAW,KAAK,QAAQ;AACrE,UAAKA,UAAW,IAAI,QAAQ;KAAE,OAAO,KAAK;KAAO,QAAQ,KAAK;KAAQ,CAAC;AAEvE,YAAQ,KAAK;KACX;KACA,aAAa;KACb,eAAe,CACb;MAAE,YAAY,KAAK;MAAO,WAAW,KAAK;MAAQ,CACnD;KACD,gBAAgB,CACd;MAAE,YAAY,KAAK;MAAO,WAAW,KAAK;MAAQ,CACnD;KACD,2BAA2B,CACzB;MACE,YAAY,KAAK,QAAQ;MACzB,WAAW,KAAK,SAAS;MAC1B,CACF;KACF,CAA+B;;;AAIpC,MAAI,QAAQ,SAAS,EACnB,OAAKF,SAAU,SAAS,KAAkC;;;;;;;;;;AAYhE,MAAa,cAAc,WAAsC;AAC/D,KAAI,OAAO,KAAK,eAAe,WAC7B,QAAO,KAAK,WAAW,OAAO;CAEhC,IAAI,MAAM;AACV,MAAK,MAAM,KAAK,OAAQ,QAAO;AAC/B,QAAO;;AAIT,IAAI,OAAO,WAAW,mBAAmB,YACvC,QAAO,eAAe,YAAY,kBAAkB;CAClD,OAAO;CACP,UAAU;CACV,cAAc;CACf,CAAC"}
|
|
@@ -46,4 +46,4 @@ interface ResizeObserverFactory {
|
|
|
46
46
|
}
|
|
47
47
|
//#endregion
|
|
48
48
|
export { UseResizeObserverOptions as a, ResizeObserverFactory as i, ResizeCallback as n, UseResizeObserverResult as o, ResizeObserverBoxOptions as r, CreateResizeObserverOptions as t };
|
|
49
|
-
//# sourceMappingURL=types-
|
|
49
|
+
//# sourceMappingURL=types-ypIHKARt.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types-
|
|
1
|
+
{"version":3,"file":"types-ypIHKARt.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;KAGY,cAAA,IAAkB,KAAA,EAAO,mBAAA;AAArC;AAAA,KAGY,wBAAA;;UAGK,wBAAA,WAAmC,OAAA,GAAU,OAAA;EANzB;EAQnC,GAAA,GAAM,SAAA,CAAU,CAAA;EALN;EAOV,GAAA,GAAM,wBAAA;EAPI;EASV,IAAA,GAAO,QAAA,GAAW,UAAA;EANpB;;;;EAWE,QAAA,IAAY,KAAA,EAAO,mBAAA;AAAA;;UAIJ,uBAAA,WAAkC,OAAA,GAAU,OAAA;;EAE3D,GAAA,EAAK,SAAA,CAAU,CAAA;;EAEf,KAAA;EARmB;EAUnB,MAAA;;EAEA,KAAA,EAAO,mBAAA;AAAA;;UAIQ,2BAAA;;EAEf,GAAA,GAAM,wBAAA;;EAEN,IAAA,GAAO,QAAA,GAAW,UAAA;AAAA;;UAIH,qBAAA;EACf,OAAA,CAAQ,MAAA,EAAQ,OAAA,EAAS,QAAA,EAAU,cAAA;EACnC,SAAA,CAAU,MAAA,EAAQ,OAAA,EAAS,QAAA,EAAU,cAAA;EACrC,UAAA;AAAA"}
|
package/dist/worker.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { o as UseResizeObserverResult, r as ResizeObserverBoxOptions } from "./types-
|
|
2
|
+
import { o as UseResizeObserverResult, r as ResizeObserverBoxOptions } from "./types-ypIHKARt.js";
|
|
3
3
|
import { RefObject } from "react";
|
|
4
4
|
|
|
5
5
|
//#region src/worker/hook.d.ts
|
package/dist/worker.js
CHANGED
|
@@ -195,7 +195,7 @@ const useResizeObserverWorker = (options = {}) => {
|
|
|
195
195
|
});
|
|
196
196
|
startPolling();
|
|
197
197
|
}).catch((error) => {
|
|
198
|
-
console.error("[@crimson_dev/use-resize-observer/worker] Init failed:", error);
|
|
198
|
+
console.error("[@crimson_dev/use-resize-observer/worker] Init failed:", Error.isError(error) ? error : new Error(String(error)));
|
|
199
199
|
});
|
|
200
200
|
return () => {
|
|
201
201
|
cancelled = true;
|
package/dist/worker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.js","names":[],"sources":["../src/worker/protocol.ts","../src/worker/hook.ts"],"sourcesContent":["/**\n * SharedArrayBuffer protocol for Worker-based resize observations.\n *\n * Layout:\n * - 4 Float16 values per element slot (2 bytes each = 8 bytes per slot)\n * - Int32Array overlay for `Atomics.notify()` / `Atomics.waitAsync()` synchronization\n * - Supports up to 256 simultaneous element observations\n *\n * @internal\n */\n\n/** Bytes per observation slot: 4 x Float16 (2 bytes each) = 8 bytes. */\nexport const SLOT_BYTES: number = 8;\n\n/** Maximum number of simultaneously observable elements. */\nexport const MAX_ELEMENTS: number = 256;\n\n/** Total SharedArrayBuffer size in bytes. */\nexport const SAB_SIZE: number = SLOT_BYTES * MAX_ELEMENTS;\n\n/** Offsets within a single Float16Array slot. */\nexport const SlotOffset = {\n InlineSize: 0,\n BlockSize: 1,\n BorderInline: 2,\n BorderBlock: 3,\n} as const;\n\nexport type SlotOffsetKey = keyof typeof SlotOffset;\n\n/** Discriminated union of all Worker protocol messages. */\nexport type WorkerMessage =\n | { readonly op: 'init'; readonly sab: SharedArrayBuffer }\n | { readonly op: 'observe'; readonly slotId: number; readonly elementId: string }\n | { readonly op: 'unobserve'; readonly slotId: number }\n | { readonly op: 'terminate' }\n | { readonly op: 'ready' }\n | { readonly op: 'error'; readonly message: string };\n\n/**\n * Write resize measurements into a SharedArrayBuffer slot.\n * Uses `Float16Array` (ES2026) for compact storage and\n * `Atomics.notify()` for cross-thread signaling.\n *\n * @param sab - SharedArrayBuffer backing the measurement protocol.\n * @param slotId - Zero-based slot index for this element.\n * @param entry - ResizeObserverEntry from the Worker's observer.\n */\nexport const writeSlot = (\n sab: SharedArrayBuffer,\n slotId: number,\n entry: ResizeObserverEntry,\n): void => {\n const view = new Float16Array(sab, slotId * SLOT_BYTES, 4);\n const cs = entry.contentBoxSize[0];\n const bs = entry.borderBoxSize[0];\n view[SlotOffset.InlineSize] = cs?.inlineSize ?? 0;\n view[SlotOffset.BlockSize] = cs?.blockSize ?? 0;\n view[SlotOffset.BorderInline] = bs?.inlineSize ?? 0;\n view[SlotOffset.BorderBlock] = bs?.blockSize ?? 0;\n // Signal main thread that new data is available\n Atomics.notify(new Int32Array(sab), slotId, 1);\n};\n\n/**\n * Read resize measurements from a SharedArrayBuffer slot.\n *\n * @param sab - SharedArrayBuffer backing the measurement protocol.\n * @param slotId - Zero-based slot index for this element.\n * @returns Measurement object with width, height, and border dimensions.\n */\nexport const readSlot = (\n sab: SharedArrayBuffer,\n slotId: number,\n): {\n readonly width: number;\n readonly height: number;\n readonly borderWidth: number;\n readonly borderHeight: number;\n} => {\n const view = new Float16Array(sab, slotId * SLOT_BYTES, 4);\n return {\n width: view[SlotOffset.InlineSize] ?? 0,\n height: view[SlotOffset.BlockSize] ?? 0,\n borderWidth: view[SlotOffset.BorderInline] ?? 0,\n borderHeight: view[SlotOffset.BorderBlock] ?? 0,\n };\n};\n\n/**\n * Allocate a slot from the bitmap tracker.\n * Scans for the first unallocated slot in O(n) worst case.\n *\n * @param bitmap - Int32Array tracking allocated slots (1 = in use).\n * @returns The allocated slot index, or -1 if all slots are in use.\n */\nexport const allocateSlot = (bitmap: Int32Array): number => {\n for (let i = 0; i < MAX_ELEMENTS; i++) {\n if (bitmap[i] === 0) {\n bitmap[i] = 1;\n return i;\n }\n }\n return -1;\n};\n\n/**\n * Release a slot back to the bitmap tracker.\n *\n * @param bitmap - Int32Array tracking allocated slots.\n * @param slotId - The slot index to release.\n */\nexport const releaseSlot = (bitmap: Int32Array, slotId: number): void => {\n if (slotId >= 0 && slotId < MAX_ELEMENTS) {\n bitmap[slotId] = 0;\n }\n};\n","'use client';\n\nimport type { RefObject } from 'react';\nimport { useEffect, useRef, useState } from 'react';\n\nimport type { ResizeObserverBoxOptions, UseResizeObserverResult } from '../types.js';\nimport type { WorkerMessage } from './protocol.js';\nimport { allocateSlot, MAX_ELEMENTS, readSlot, releaseSlot, SAB_SIZE } from './protocol.js';\n\n/** Options for the Worker-based resize observer hook. */\nexport interface UseResizeObserverWorkerOptions<T extends Element = Element> {\n /** Pre-existing ref to observe. If omitted, an internal ref is created. */\n ref?: RefObject<T | null>;\n /** Which box model to report. @default 'content-box' */\n box?: ResizeObserverBoxOptions;\n /**\n * Called on every resize event. Identity is stable across renders\n * (powered by ref pattern) — do NOT wrap in useCallback.\n */\n onResize?: (dimensions: { readonly width: number; readonly height: number }) => void;\n}\n\n/** Shared Worker instance — lazy-initialized, lives until last observer unmounts. */\nlet sharedWorker: Worker | null = null;\nlet sharedSab: SharedArrayBuffer | null = null;\nconst slotBitmap = new Int32Array(MAX_ELEMENTS);\nlet activeObserverCount = 0;\nlet workerReady = false;\n\n/** Promise that resolves when the Worker is initialized and ready. */\nlet initPromise: Promise<void> | null = null;\n\n/**\n * Lazily initialize the shared Worker with `Promise.withResolvers()` (ES2024+).\n * Uses `Error.isError()` (ES2026) for robust error discrimination.\n */\nconst ensureWorker = (): Promise<void> => {\n if (initPromise) return initPromise;\n\n const { promise, resolve, reject } = Promise.withResolvers<void>();\n initPromise = promise;\n\n Promise.try(() => {\n if (!globalThis.crossOriginIsolated) {\n throw new Error(\n '[@crimson_dev/use-resize-observer/worker] ' +\n 'crossOriginIsolated is false. Worker mode requires COOP/COEP headers. ' +\n 'See: https://developer.mozilla.org/en-US/docs/Web/API/crossOriginIsolated',\n );\n }\n\n sharedSab = new SharedArrayBuffer(SAB_SIZE);\n const workerUrl = new URL('./worker.js', import.meta.url);\n sharedWorker = new Worker(workerUrl, { type: 'module' });\n\n sharedWorker.addEventListener('message', (event: MessageEvent<WorkerMessage>) => {\n if (event.data.op === 'ready') {\n workerReady = true;\n resolve();\n } else if (event.data.op === 'error') {\n reject(new Error(event.data.message));\n }\n });\n\n sharedWorker.addEventListener('error', (event) => {\n const errorMessage = event instanceof ErrorEvent ? event.message : 'Worker error';\n reject(new Error(errorMessage));\n\n // Auto-restart on crash\n sharedWorker = null;\n initPromise = null;\n workerReady = false;\n });\n\n sharedWorker.postMessage({ op: 'init', sab: sharedSab } satisfies WorkerMessage);\n }).catch((error: unknown) => {\n reject(Error.isError(error) ? error : new Error(String(error)));\n });\n\n return promise;\n};\n\nconst terminateWorkerIfIdle = (): void => {\n if (activeObserverCount === 0 && sharedWorker) {\n sharedWorker.postMessage({ op: 'terminate' } satisfies WorkerMessage);\n sharedWorker.terminate();\n sharedWorker = null;\n sharedSab = null;\n initPromise = null;\n workerReady = false;\n slotBitmap.fill(0);\n }\n};\n\n/**\n * Worker-based resize observer hook.\n *\n * Moves all `ResizeObserver` measurement off the main thread using\n * `SharedArrayBuffer` + `Float16Array` + `Atomics`.\n *\n * Requires `crossOriginIsolated === true` (COOP/COEP headers).\n *\n * @param options - Configuration options.\n * @returns Ref, width, height, and raw entry (entry is `undefined` in Worker mode).\n */\nexport const useResizeObserverWorker = <T extends Element = Element>(\n options: UseResizeObserverWorkerOptions<T> = {},\n): UseResizeObserverResult<T> => {\n const { ref: externalRef, onResize } = options;\n\n const internalRef = useRef<T | null>(null);\n const targetRef = externalRef ?? internalRef;\n\n const [width, setWidth] = useState<number | undefined>(undefined);\n const [height, setHeight] = useState<number | undefined>(undefined);\n\n const onResizeRef = useRef(onResize);\n onResizeRef.current = onResize;\n\n useEffect(() => {\n const element = targetRef.current;\n if (!element) return;\n\n const slotId = allocateSlot(slotBitmap);\n if (slotId === -1) {\n console.error(\n `[@crimson_dev/use-resize-observer/worker] ` +\n `Maximum ${String(MAX_ELEMENTS)} simultaneous observations exceeded.`,\n );\n return;\n }\n\n activeObserverCount++;\n let cancelled = false;\n let rafId: number | null = null;\n\n const startPolling = (): void => {\n const poll = (): void => {\n if (cancelled || !sharedSab) return;\n\n const { width: w, height: h } = readSlot(sharedSab, slotId);\n setWidth(w);\n setHeight(h);\n onResizeRef.current?.({ width: w, height: h });\n rafId = requestAnimationFrame(poll);\n };\n rafId = requestAnimationFrame(poll);\n };\n\n ensureWorker()\n .then(() => {\n if (cancelled) return;\n sharedWorker?.postMessage({\n op: 'observe',\n slotId,\n elementId: element.id || `slot-${String(slotId)}`,\n } satisfies WorkerMessage);\n startPolling();\n })\n .catch((error: unknown) => {\n console.error('[@crimson_dev/use-resize-observer/worker] Init failed:', error);\n });\n\n return () => {\n cancelled = true;\n if (rafId !== null) cancelAnimationFrame(rafId);\n\n if (workerReady && sharedWorker) {\n sharedWorker.postMessage({\n op: 'unobserve',\n slotId,\n } satisfies WorkerMessage);\n }\n\n releaseSlot(slotBitmap, slotId);\n activeObserverCount--;\n terminateWorkerIfIdle();\n };\n }, [targetRef]);\n\n return { ref: targetRef, width, height, entry: undefined };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"worker.js","names":[],"sources":["../src/worker/protocol.ts","../src/worker/hook.ts"],"sourcesContent":["/**\n * SharedArrayBuffer protocol for Worker-based resize observations.\n *\n * Layout:\n * - 4 Float16 values per element slot (2 bytes each = 8 bytes per slot)\n * - Int32Array overlay for `Atomics.notify()` / `Atomics.waitAsync()` synchronization\n * - Supports up to 256 simultaneous element observations\n *\n * @internal\n */\n\n/** Bytes per observation slot: 4 x Float16 (2 bytes each) = 8 bytes. */\nexport const SLOT_BYTES: number = 8;\n\n/** Maximum number of simultaneously observable elements. */\nexport const MAX_ELEMENTS: number = 256;\n\n/** Total SharedArrayBuffer size in bytes. */\nexport const SAB_SIZE: number = SLOT_BYTES * MAX_ELEMENTS;\n\n/** Offsets within a single Float16Array slot. */\nexport const SlotOffset = {\n InlineSize: 0,\n BlockSize: 1,\n BorderInline: 2,\n BorderBlock: 3,\n} as const;\n\nexport type SlotOffsetKey = keyof typeof SlotOffset;\n\n/** Discriminated union of all Worker protocol messages. */\nexport type WorkerMessage =\n | { readonly op: 'init'; readonly sab: SharedArrayBuffer }\n | { readonly op: 'observe'; readonly slotId: number; readonly elementId: string }\n | { readonly op: 'unobserve'; readonly slotId: number }\n | { readonly op: 'terminate' }\n | { readonly op: 'ready' }\n | { readonly op: 'error'; readonly message: string };\n\n/**\n * Write resize measurements into a SharedArrayBuffer slot.\n * Uses `Float16Array` (ES2026) for compact storage and\n * `Atomics.notify()` for cross-thread signaling.\n *\n * @param sab - SharedArrayBuffer backing the measurement protocol.\n * @param slotId - Zero-based slot index for this element.\n * @param entry - ResizeObserverEntry from the Worker's observer.\n */\nexport const writeSlot = (\n sab: SharedArrayBuffer,\n slotId: number,\n entry: ResizeObserverEntry,\n): void => {\n const view = new Float16Array(sab, slotId * SLOT_BYTES, 4);\n const cs = entry.contentBoxSize[0];\n const bs = entry.borderBoxSize[0];\n view[SlotOffset.InlineSize] = cs?.inlineSize ?? 0;\n view[SlotOffset.BlockSize] = cs?.blockSize ?? 0;\n view[SlotOffset.BorderInline] = bs?.inlineSize ?? 0;\n view[SlotOffset.BorderBlock] = bs?.blockSize ?? 0;\n // Signal main thread that new data is available\n Atomics.notify(new Int32Array(sab), slotId, 1);\n};\n\n/**\n * Read resize measurements from a SharedArrayBuffer slot.\n *\n * @param sab - SharedArrayBuffer backing the measurement protocol.\n * @param slotId - Zero-based slot index for this element.\n * @returns Measurement object with width, height, and border dimensions.\n */\nexport const readSlot = (\n sab: SharedArrayBuffer,\n slotId: number,\n): {\n readonly width: number;\n readonly height: number;\n readonly borderWidth: number;\n readonly borderHeight: number;\n} => {\n const view = new Float16Array(sab, slotId * SLOT_BYTES, 4);\n return {\n width: view[SlotOffset.InlineSize] ?? 0,\n height: view[SlotOffset.BlockSize] ?? 0,\n borderWidth: view[SlotOffset.BorderInline] ?? 0,\n borderHeight: view[SlotOffset.BorderBlock] ?? 0,\n };\n};\n\n/**\n * Allocate a slot from the bitmap tracker.\n * Scans for the first unallocated slot in O(n) worst case.\n *\n * @param bitmap - Int32Array tracking allocated slots (1 = in use).\n * @returns The allocated slot index, or -1 if all slots are in use.\n */\nexport const allocateSlot = (bitmap: Int32Array): number => {\n for (let i = 0; i < MAX_ELEMENTS; i++) {\n if (bitmap[i] === 0) {\n bitmap[i] = 1;\n return i;\n }\n }\n return -1;\n};\n\n/**\n * Release a slot back to the bitmap tracker.\n *\n * @param bitmap - Int32Array tracking allocated slots.\n * @param slotId - The slot index to release.\n */\nexport const releaseSlot = (bitmap: Int32Array, slotId: number): void => {\n if (slotId >= 0 && slotId < MAX_ELEMENTS) {\n bitmap[slotId] = 0;\n }\n};\n","'use client';\n\nimport type { RefObject } from 'react';\nimport { useEffect, useRef, useState } from 'react';\n\nimport type { ResizeObserverBoxOptions, UseResizeObserverResult } from '../types.js';\nimport type { WorkerMessage } from './protocol.js';\nimport { allocateSlot, MAX_ELEMENTS, readSlot, releaseSlot, SAB_SIZE } from './protocol.js';\n\n/** Options for the Worker-based resize observer hook. */\nexport interface UseResizeObserverWorkerOptions<T extends Element = Element> {\n /** Pre-existing ref to observe. If omitted, an internal ref is created. */\n ref?: RefObject<T | null>;\n /** Which box model to report. @default 'content-box' */\n box?: ResizeObserverBoxOptions;\n /**\n * Called on every resize event. Identity is stable across renders\n * (powered by ref pattern) — do NOT wrap in useCallback.\n */\n onResize?: (dimensions: { readonly width: number; readonly height: number }) => void;\n}\n\n/** Shared Worker instance — lazy-initialized, lives until last observer unmounts. */\nlet sharedWorker: Worker | null = null;\nlet sharedSab: SharedArrayBuffer | null = null;\nconst slotBitmap = new Int32Array(MAX_ELEMENTS);\nlet activeObserverCount = 0;\nlet workerReady = false;\n\n/** Promise that resolves when the Worker is initialized and ready. */\nlet initPromise: Promise<void> | null = null;\n\n/**\n * Lazily initialize the shared Worker with `Promise.withResolvers()` (ES2024+).\n * Uses `Error.isError()` (ES2026) for robust error discrimination.\n */\nconst ensureWorker = (): Promise<void> => {\n if (initPromise) return initPromise;\n\n const { promise, resolve, reject } = Promise.withResolvers<void>();\n initPromise = promise;\n\n Promise.try(() => {\n if (!globalThis.crossOriginIsolated) {\n throw new Error(\n '[@crimson_dev/use-resize-observer/worker] ' +\n 'crossOriginIsolated is false. Worker mode requires COOP/COEP headers. ' +\n 'See: https://developer.mozilla.org/en-US/docs/Web/API/crossOriginIsolated',\n );\n }\n\n sharedSab = new SharedArrayBuffer(SAB_SIZE);\n const workerUrl = new URL('./worker.js', import.meta.url);\n sharedWorker = new Worker(workerUrl, { type: 'module' });\n\n sharedWorker.addEventListener('message', (event: MessageEvent<WorkerMessage>) => {\n if (event.data.op === 'ready') {\n workerReady = true;\n resolve();\n } else if (event.data.op === 'error') {\n reject(new Error(event.data.message));\n }\n });\n\n sharedWorker.addEventListener('error', (event) => {\n const errorMessage = event instanceof ErrorEvent ? event.message : 'Worker error';\n reject(new Error(errorMessage));\n\n // Auto-restart on crash\n sharedWorker = null;\n initPromise = null;\n workerReady = false;\n });\n\n sharedWorker.postMessage({ op: 'init', sab: sharedSab } satisfies WorkerMessage);\n }).catch((error: unknown) => {\n reject(Error.isError(error) ? error : new Error(String(error)));\n });\n\n return promise;\n};\n\nconst terminateWorkerIfIdle = (): void => {\n if (activeObserverCount === 0 && sharedWorker) {\n sharedWorker.postMessage({ op: 'terminate' } satisfies WorkerMessage);\n sharedWorker.terminate();\n sharedWorker = null;\n sharedSab = null;\n initPromise = null;\n workerReady = false;\n slotBitmap.fill(0);\n }\n};\n\n/**\n * Worker-based resize observer hook.\n *\n * Moves all `ResizeObserver` measurement off the main thread using\n * `SharedArrayBuffer` + `Float16Array` + `Atomics`.\n *\n * Requires `crossOriginIsolated === true` (COOP/COEP headers).\n *\n * @param options - Configuration options.\n * @returns Ref, width, height, and raw entry (entry is `undefined` in Worker mode).\n */\nexport const useResizeObserverWorker = <T extends Element = Element>(\n options: UseResizeObserverWorkerOptions<T> = {},\n): UseResizeObserverResult<T> => {\n const { ref: externalRef, onResize } = options;\n\n const internalRef = useRef<T | null>(null);\n const targetRef = externalRef ?? internalRef;\n\n const [width, setWidth] = useState<number | undefined>(undefined);\n const [height, setHeight] = useState<number | undefined>(undefined);\n\n const onResizeRef = useRef(onResize);\n onResizeRef.current = onResize;\n\n useEffect(() => {\n const element = targetRef.current;\n if (!element) return;\n\n const slotId = allocateSlot(slotBitmap);\n if (slotId === -1) {\n console.error(\n `[@crimson_dev/use-resize-observer/worker] ` +\n `Maximum ${String(MAX_ELEMENTS)} simultaneous observations exceeded.`,\n );\n return;\n }\n\n activeObserverCount++;\n let cancelled = false;\n let rafId: number | null = null;\n\n const startPolling = (): void => {\n const poll = (): void => {\n if (cancelled || !sharedSab) return;\n\n const { width: w, height: h } = readSlot(sharedSab, slotId);\n setWidth(w);\n setHeight(h);\n onResizeRef.current?.({ width: w, height: h });\n rafId = requestAnimationFrame(poll);\n };\n rafId = requestAnimationFrame(poll);\n };\n\n ensureWorker()\n .then(() => {\n if (cancelled) return;\n sharedWorker?.postMessage({\n op: 'observe',\n slotId,\n elementId: element.id || `slot-${String(slotId)}`,\n } satisfies WorkerMessage);\n startPolling();\n })\n .catch((error: unknown) => {\n console.error(\n '[@crimson_dev/use-resize-observer/worker] Init failed:',\n Error.isError(error) ? error : new Error(String(error)),\n );\n });\n\n return () => {\n cancelled = true;\n if (rafId !== null) cancelAnimationFrame(rafId);\n\n if (workerReady && sharedWorker) {\n sharedWorker.postMessage({\n op: 'unobserve',\n slotId,\n } satisfies WorkerMessage);\n }\n\n releaseSlot(slotBitmap, slotId);\n activeObserverCount--;\n terminateWorkerIfIdle();\n };\n }, [targetRef]);\n\n return { ref: targetRef, width, height, entry: undefined };\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crimson_dev/use-resize-observer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Zero-dependency, Worker-native, ESNext-first React 19 ResizeObserver hook",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
"@vitest/browser-playwright": "4.1.0-beta.5",
|
|
81
81
|
"@vitest/coverage-v8": "4.1.0-beta.5",
|
|
82
82
|
"@vitest/ui": "4.1.0-beta.5",
|
|
83
|
+
"babel-plugin-react-compiler": "^0.0.0-experimental-1371fcb-20260304",
|
|
83
84
|
"concurrently": "9.2.1",
|
|
84
85
|
"happy-dom": "20.8.3",
|
|
85
86
|
"playwright": "1.59.0-alpha-2026-03-05",
|
|
@@ -102,7 +103,7 @@
|
|
|
102
103
|
"vitest": "4.1.0-beta.5"
|
|
103
104
|
},
|
|
104
105
|
"simple-git-hooks": {
|
|
105
|
-
"pre-commit": "npx @biomejs/biome check --staged"
|
|
106
|
+
"pre-commit": "npx @biomejs/biome check --staged || npx @biomejs/biome check --staged 2>&1 | grep -q 'No files were processed'"
|
|
106
107
|
},
|
|
107
108
|
"scripts": {
|
|
108
109
|
"build": "tsdown",
|