@crimson_dev/use-resize-observer 0.1.1

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/dist/index.js ADDED
@@ -0,0 +1,386 @@
1
+ 'use client';
2
+ 'use client';
3
+
4
+ import { createContext, startTransition, useEffect, useRef, useState } from "react";
5
+
6
+ //#region src/context.ts
7
+ /**
8
+ * Context for injecting a custom `ResizeObserver` constructor.
9
+ *
10
+ * Useful for:
11
+ * - **Testing**: Inject a mock `ResizeObserver` for deterministic tests.
12
+ * - **SSR**: Inject a no-op implementation to avoid `ReferenceError`.
13
+ * - **Polyfills**: Inject a polyfill without modifying `globalThis`.
14
+ *
15
+ * @example
16
+ * ```tsx
17
+ * // In tests:
18
+ * <ResizeObserverContext.Provider value={MockResizeObserver}>
19
+ * <ComponentThatUsesResize />
20
+ * </ResizeObserverContext.Provider>
21
+ * ```
22
+ */
23
+ const ResizeObserverContext = createContext(null);
24
+ ResizeObserverContext.displayName = "ResizeObserverContext";
25
+
26
+ //#endregion
27
+ //#region src/scheduler.ts
28
+ /**
29
+ * Batching scheduler that coalesces all ResizeObserver callbacks into a single
30
+ * `requestAnimationFrame` flush, wrapped in React `startTransition` for
31
+ * non-urgent update scheduling.
32
+ *
33
+ * Uses a `Map<Element, FlushEntry>` with last-write-wins semantics so that
34
+ * 100 simultaneous resize events produce exactly 1 React render cycle.
35
+ *
36
+ * Implements `Disposable` for ES2026 `using` declarations.
37
+ *
38
+ * @internal
39
+ */
40
+ var RafScheduler = class {
41
+ #queue = /* @__PURE__ */ new Map();
42
+ #rafId = null;
43
+ /** Enqueue a resize observation for the next rAF flush. */
44
+ schedule(target, entry, cbs) {
45
+ this.#queue.set(target, {
46
+ callbacks: cbs,
47
+ entry
48
+ });
49
+ this.#requestFlush();
50
+ }
51
+ #requestFlush() {
52
+ if (this.#rafId !== null) return;
53
+ this.#rafId = requestAnimationFrame(() => {
54
+ this.#rafId = null;
55
+ this.#flush();
56
+ });
57
+ }
58
+ #flush() {
59
+ const snapshot = new Map(this.#queue);
60
+ this.#queue.clear();
61
+ startTransition(() => {
62
+ for (const { callbacks, entry } of snapshot.values()) for (const cb of callbacks) cb(entry);
63
+ });
64
+ }
65
+ /** Cancel any pending rAF and clear the queue. */
66
+ cancel() {
67
+ if (this.#rafId !== null) {
68
+ cancelAnimationFrame(this.#rafId);
69
+ this.#rafId = null;
70
+ }
71
+ this.#queue.clear();
72
+ }
73
+ /** Disposable contract (ES2026 explicit resource management). */
74
+ [Symbol.dispose]() {
75
+ this.cancel();
76
+ }
77
+ };
78
+ /** Create a new scheduler instance. @internal */
79
+ const createScheduler = () => new RafScheduler();
80
+
81
+ //#endregion
82
+ //#region src/pool.ts
83
+ /**
84
+ * Shared observer pool that multiplexes many element observations through a
85
+ * single `ResizeObserver` instance per document root.
86
+ *
87
+ * Uses `WeakMap` + `FinalizationRegistry` for GC-backed cleanup of detached
88
+ * elements, and `RafScheduler` for batched, non-urgent React state updates.
89
+ *
90
+ * Implements `Disposable` for ES2026 `using` declarations.
91
+ *
92
+ * @internal
93
+ */
94
+ var ObserverPool = class {
95
+ #scheduler;
96
+ #registry = /* @__PURE__ */ new WeakMap();
97
+ #finalizer = new FinalizationRegistry((ref) => {
98
+ const el = ref.deref();
99
+ if (el) {
100
+ this.#observer.unobserve(el);
101
+ this.#size--;
102
+ }
103
+ });
104
+ #observer;
105
+ #size = 0;
106
+ constructor(scheduler) {
107
+ this.#scheduler = scheduler ?? createScheduler();
108
+ this.#observer = new ResizeObserver((entries) => {
109
+ for (const entry of entries) {
110
+ const callbacks = this.#registry.get(entry.target);
111
+ if (callbacks?.size) this.#scheduler.schedule(entry.target, entry, callbacks);
112
+ }
113
+ });
114
+ }
115
+ /** Begin observing an element with the given options and callback. */
116
+ observe(target, options, cb) {
117
+ let callbacks = this.#registry.get(target);
118
+ if (!callbacks) {
119
+ callbacks = /* @__PURE__ */ new Set();
120
+ this.#registry.set(target, callbacks);
121
+ this.#finalizer.register(target, new WeakRef(target), target);
122
+ this.#observer.observe(target, options);
123
+ this.#size++;
124
+ }
125
+ callbacks.add(cb);
126
+ }
127
+ /** Stop a specific callback from observing the target. */
128
+ unobserve(target, cb) {
129
+ const callbacks = this.#registry.get(target);
130
+ if (!callbacks) return;
131
+ callbacks.delete(cb);
132
+ if (callbacks.size === 0) {
133
+ this.#registry.delete(target);
134
+ this.#finalizer.unregister(target);
135
+ this.#observer.unobserve(target);
136
+ this.#size--;
137
+ }
138
+ }
139
+ /** Number of currently observed elements. */
140
+ get observedCount() {
141
+ return this.#size;
142
+ }
143
+ /** Disposable contract (ES2026 explicit resource management). */
144
+ [Symbol.dispose]() {
145
+ this.#observer.disconnect();
146
+ this.#scheduler.cancel();
147
+ this.#size = 0;
148
+ }
149
+ };
150
+ /**
151
+ * Module-level weak registry of pools per document/shadow root.
152
+ * Ensures a single shared pool per root context.
153
+ */
154
+ const poolRegistry = /* @__PURE__ */ new WeakMap();
155
+ /**
156
+ * Get or create the shared observer pool for the given root.
157
+ * Uses `Promise.try()` (ES2026) for safe async-context creation
158
+ * with synchronous return path.
159
+ *
160
+ * @param root - Document or ShadowRoot to scope the pool to.
161
+ * @returns The shared `ObserverPool` for the given root.
162
+ * @internal
163
+ */
164
+ const getSharedPool = (root) => {
165
+ const existing = poolRegistry.get(root);
166
+ if (existing) return existing;
167
+ Promise.try(() => {
168
+ 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
+ }).catch((error) => {
170
+ console.error(error);
171
+ });
172
+ const pool = new ObserverPool();
173
+ poolRegistry.set(root, pool);
174
+ return pool;
175
+ };
176
+
177
+ //#endregion
178
+ //#region src/factory.ts
179
+ /**
180
+ * Framework-agnostic factory for creating a ResizeObserver subscription
181
+ * using the shared pool architecture.
182
+ *
183
+ * Uses the same pool and scheduler as the React hook — no duplicate observers.
184
+ * Implements cleanup tracking with `Map` for efficient iteration.
185
+ *
186
+ * @param options - Configuration options.
187
+ * @returns An object with `observe`, `unobserve`, and `disconnect` methods.
188
+ *
189
+ * @example
190
+ * ```ts
191
+ * using observer = createResizeObserver({ box: 'border-box' });
192
+ * observer.observe(element, (entry) => {
193
+ * console.log(entry.contentRect.width);
194
+ * });
195
+ * ```
196
+ */
197
+ const createResizeObserver = (options = {}) => {
198
+ const { box = "content-box", root = globalThis.document } = options;
199
+ const pool = getSharedPool(root);
200
+ const tracked = /* @__PURE__ */ new Map();
201
+ const observe = (target, callback) => {
202
+ pool.observe(target, { box }, callback);
203
+ let cbs = tracked.get(target);
204
+ if (!cbs) {
205
+ cbs = /* @__PURE__ */ new Set();
206
+ tracked.set(target, cbs);
207
+ }
208
+ cbs.add(callback);
209
+ };
210
+ const unobserve = (target, callback) => {
211
+ pool.unobserve(target, callback);
212
+ const cbs = tracked.get(target);
213
+ if (cbs) {
214
+ cbs.delete(callback);
215
+ if (cbs.size === 0) tracked.delete(target);
216
+ }
217
+ };
218
+ const disconnect = () => {
219
+ for (const [target, cbs] of tracked) for (const cb of cbs) pool.unobserve(target, cb);
220
+ tracked.clear();
221
+ };
222
+ return {
223
+ observe,
224
+ unobserve,
225
+ disconnect,
226
+ [Symbol.dispose]() {
227
+ disconnect();
228
+ }
229
+ };
230
+ };
231
+
232
+ //#endregion
233
+ //#region src/hook.ts
234
+ /**
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
+ * Primary React hook for observing element resize events.
266
+ *
267
+ * Features:
268
+ * - Single shared `ResizeObserver` per document root (pool architecture)
269
+ * - `requestAnimationFrame` batching with `startTransition` wrapping
270
+ * - GC-backed cleanup via `FinalizationRegistry`
271
+ * - React Compiler-safe (stable callback identity via ref pattern)
272
+ * - Sub-300B gzip bundle contribution
273
+ *
274
+ * @param options - Configuration options.
275
+ * @returns Ref, width, height, and raw entry.
276
+ *
277
+ * @example
278
+ * ```tsx
279
+ * const { ref, width, height } = useResizeObserver<HTMLDivElement>();
280
+ * return <div ref={ref}>Size: {width} x {height}</div>;
281
+ * ```
282
+ */
283
+ const useResizeObserver = (options = {}) => {
284
+ const { ref: externalRef, box = "content-box", root, onResize } = options;
285
+ const internalRef = useRef(null);
286
+ const targetRef = externalRef ?? internalRef;
287
+ const [width, setWidth] = useState(void 0);
288
+ const [height, setHeight] = useState(void 0);
289
+ const [entry, setEntry] = useState(void 0);
290
+ const onResizeRef = useRef(onResize);
291
+ onResizeRef.current = onResize;
292
+ const boxRef = useRef(box);
293
+ boxRef.current = box;
294
+ useEffect(() => {
295
+ const element = targetRef.current;
296
+ if (!element) return;
297
+ const pool = getSharedPool(root ?? element.ownerDocument);
298
+ const callback = (resizeEntry) => {
299
+ const { width: w, height: h } = extractDimensions(resizeEntry, boxRef.current);
300
+ setWidth(w);
301
+ setHeight(h);
302
+ setEntry(resizeEntry);
303
+ onResizeRef.current?.(resizeEntry);
304
+ };
305
+ pool.observe(element, { box }, callback);
306
+ return () => {
307
+ pool.unobserve(element, callback);
308
+ };
309
+ }, [
310
+ targetRef,
311
+ box,
312
+ root
313
+ ]);
314
+ return {
315
+ ref: targetRef,
316
+ width,
317
+ height,
318
+ entry
319
+ };
320
+ };
321
+
322
+ //#endregion
323
+ //#region src/hook-multi.ts
324
+ /**
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
+ * Multi-element variant: observe multiple elements simultaneously through
337
+ * a single pool subscription.
338
+ *
339
+ * @param refs - Array of refs pointing to elements to observe.
340
+ * @param options - Configuration options.
341
+ * @returns A `Map<Element, ResizeEntry>` keyed by observed element.
342
+ *
343
+ * @example
344
+ * ```tsx
345
+ * const ref1 = useRef<HTMLDivElement>(null);
346
+ * const ref2 = useRef<HTMLDivElement>(null);
347
+ * const entries = useResizeObserverEntries([ref1, ref2]);
348
+ * ```
349
+ */
350
+ const useResizeObserverEntries = (refs, options = {}) => {
351
+ const { box = "content-box", root } = options;
352
+ const [entries, setEntries] = useState(/* @__PURE__ */ new Map());
353
+ const boxRef = useRef(box);
354
+ boxRef.current = box;
355
+ useEffect(() => {
356
+ const cleanups = [];
357
+ for (const ref of refs) {
358
+ const element = ref.current;
359
+ if (!element) continue;
360
+ const pool = getSharedPool(root ?? element.ownerDocument);
361
+ const currentBox = boxRef.current;
362
+ const callback = (resizeEntry) => {
363
+ const sizeEntry = extractSize(resizeEntry, currentBox);
364
+ setEntries((prev) => {
365
+ const next = new Map(prev);
366
+ next.set(element, {
367
+ width: sizeEntry?.inlineSize ?? 0,
368
+ height: sizeEntry?.blockSize ?? 0,
369
+ entry: resizeEntry
370
+ });
371
+ return next;
372
+ });
373
+ };
374
+ pool.observe(element, { box: currentBox }, callback);
375
+ cleanups.push(() => pool.unobserve(element, callback));
376
+ }
377
+ return () => {
378
+ for (const cleanup of cleanups) cleanup();
379
+ };
380
+ }, [refs, root]);
381
+ return entries;
382
+ };
383
+
384
+ //#endregion
385
+ export { ResizeObserverContext, createResizeObserver, useResizeObserver, useResizeObserverEntries };
386
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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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"}
@@ -0,0 +1,28 @@
1
+
2
+ import { o as UseResizeObserverResult } from "./types-ASPFw2w_.js";
3
+
4
+ //#region src/server.d.ts
5
+ /**
6
+ * Server-safe mock result for SSR/RSC environments.
7
+ *
8
+ * Returns `undefined` for all measurement values and a no-op ref,
9
+ * preventing `ReferenceError` when `ResizeObserver` is unavailable.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * // In an RSC or SSR context:
14
+ * const result = createServerResizeObserverMock<HTMLDivElement>();
15
+ * // result.width === undefined
16
+ * // result.height === undefined
17
+ * // result.entry === undefined
18
+ * ```
19
+ */
20
+ declare const createServerResizeObserverMock: <T extends Element = Element>() => UseResizeObserverResult<T>;
21
+ /**
22
+ * Check whether the current environment supports ResizeObserver.
23
+ * Safe to call on server — returns `false` without throwing.
24
+ */
25
+ declare const isResizeObserverSupported: () => boolean;
26
+ //#endregion
27
+ export { createServerResizeObserverMock, isResizeObserverSupported };
28
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","names":[],"sources":["../src/server.ts"],"mappings":";;;;;;AAiBA;;;;;;;;;;;;;cAAa,8BAAA,aACD,OAAA,GAAU,OAAA,OACjB,uBAAA,CAAwB,CAAA;;;AAW7B;;cAAa,yBAAA"}
package/dist/server.js ADDED
@@ -0,0 +1,32 @@
1
+ 'use client';
2
+ //#region src/server.ts
3
+ /**
4
+ * Server-safe mock result for SSR/RSC environments.
5
+ *
6
+ * Returns `undefined` for all measurement values and a no-op ref,
7
+ * preventing `ReferenceError` when `ResizeObserver` is unavailable.
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * // In an RSC or SSR context:
12
+ * const result = createServerResizeObserverMock<HTMLDivElement>();
13
+ * // result.width === undefined
14
+ * // result.height === undefined
15
+ * // result.entry === undefined
16
+ * ```
17
+ */
18
+ const createServerResizeObserverMock = () => ({
19
+ ref: { current: null },
20
+ width: void 0,
21
+ height: void 0,
22
+ entry: void 0
23
+ });
24
+ /**
25
+ * Check whether the current environment supports ResizeObserver.
26
+ * Safe to call on server — returns `false` without throwing.
27
+ */
28
+ const isResizeObserverSupported = () => typeof globalThis !== "undefined" && typeof globalThis.ResizeObserver !== "undefined";
29
+
30
+ //#endregion
31
+ export { createServerResizeObserverMock, isResizeObserverSupported };
32
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","names":[],"sources":["../src/server.ts"],"sourcesContent":["import type { UseResizeObserverResult } from './types.js';\n\n/**\n * Server-safe mock result for SSR/RSC environments.\n *\n * Returns `undefined` for all measurement values and a no-op ref,\n * preventing `ReferenceError` when `ResizeObserver` is unavailable.\n *\n * @example\n * ```tsx\n * // In an RSC or SSR context:\n * const result = createServerResizeObserverMock<HTMLDivElement>();\n * // result.width === undefined\n * // result.height === undefined\n * // result.entry === undefined\n * ```\n */\nexport const createServerResizeObserverMock = <\n T extends Element = Element,\n>(): UseResizeObserverResult<T> => ({\n ref: { current: null },\n width: undefined,\n height: undefined,\n entry: undefined,\n});\n\n/**\n * Check whether the current environment supports ResizeObserver.\n * Safe to call on server — returns `false` without throwing.\n */\nexport const isResizeObserverSupported = (): boolean =>\n typeof globalThis !== 'undefined' && typeof globalThis.ResizeObserver !== 'undefined';\n"],"mappings":";;;;;;;;;;;;;;;;;AAiBA,MAAa,wCAEuB;CAClC,KAAK,EAAE,SAAS,MAAM;CACtB,OAAO;CACP,QAAQ;CACR,OAAO;CACR;;;;;AAMD,MAAa,kCACX,OAAO,eAAe,eAAe,OAAO,WAAW,mBAAmB"}
package/dist/shim.d.ts ADDED
@@ -0,0 +1,38 @@
1
+
2
+ //#region src/shim.d.ts
3
+ /**
4
+ * Polyfill shim entry — provides a ResizeObserver polyfill for
5
+ * environments without native support.
6
+ *
7
+ * Installs `globalThis.ResizeObserver` if it's missing.
8
+ * Uses rAF polling as the observation mechanism.
9
+ *
10
+ * For sub-pixel normalization, uses `Math.sumPrecise()` (ES2026)
11
+ * with fallback to iterative addition.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * // Ensure ResizeObserver exists before using the hook:
16
+ * import '@crimson_dev/use-resize-observer/shim';
17
+ * import { useResizeObserver } from '@crimson_dev/use-resize-observer';
18
+ * ```
19
+ */
20
+ /** Minimal ResizeObserver polyfill for environments without native support. */
21
+ declare class ResizeObserverShim {
22
+ #private;
23
+ constructor(callback: ResizeObserverCallback);
24
+ observe(target: Element, _options?: ResizeObserverOptions): void;
25
+ unobserve(target: Element): void;
26
+ disconnect(): void;
27
+ }
28
+ /**
29
+ * Normalize sub-pixel coordinates using `Math.sumPrecise()` (ES2026).
30
+ * Falls back to simple addition if unavailable.
31
+ *
32
+ * @param values - Array of numbers to sum precisely.
33
+ * @returns The precise sum.
34
+ */
35
+ declare const sumPrecise: (values: number[]) => number;
36
+ //#endregion
37
+ export { ResizeObserverShim, sumPrecise };
38
+ //# sourceMappingURL=shim.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shim.d.ts","names":[],"sources":["../src/shim.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;cAmBM,kBAAA;EAAA;EAMJ,WAAA,CAAY,QAAA,EAAU,sBAAA;EAItB,OAAA,CAAQ,MAAA,EAAQ,OAAA,EAAS,QAAA,GAAW,qBAAA;EAKpC,SAAA,CAAU,MAAA,EAAQ,OAAA;EAKlB,UAAA,CAAA;AAAA;AAgEF;;;;;;;AAAA,cAAa,UAAA,GAAc,MAAA"}
package/dist/shim.js ADDED
@@ -0,0 +1,108 @@
1
+ 'use client';
2
+ //#region src/shim.ts
3
+ /**
4
+ * Polyfill shim entry — provides a ResizeObserver polyfill for
5
+ * environments without native support.
6
+ *
7
+ * Installs `globalThis.ResizeObserver` if it's missing.
8
+ * Uses rAF polling as the observation mechanism.
9
+ *
10
+ * For sub-pixel normalization, uses `Math.sumPrecise()` (ES2026)
11
+ * with fallback to iterative addition.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * // Ensure ResizeObserver exists before using the hook:
16
+ * import '@crimson_dev/use-resize-observer/shim';
17
+ * import { useResizeObserver } from '@crimson_dev/use-resize-observer';
18
+ * ```
19
+ */
20
+ /** Minimal ResizeObserver polyfill for environments without native support. */
21
+ var ResizeObserverShim = class {
22
+ #callback;
23
+ #targets = /* @__PURE__ */ new Set();
24
+ #rafId = null;
25
+ #lastSizes = /* @__PURE__ */ new WeakMap();
26
+ constructor(callback) {
27
+ this.#callback = callback;
28
+ }
29
+ observe(target, _options) {
30
+ this.#targets.add(target);
31
+ this.#startPolling();
32
+ }
33
+ unobserve(target) {
34
+ this.#targets.delete(target);
35
+ if (this.#targets.size === 0) this.#stopPolling();
36
+ }
37
+ disconnect() {
38
+ this.#targets.clear();
39
+ this.#stopPolling();
40
+ }
41
+ #startPolling() {
42
+ if (this.#rafId !== null) return;
43
+ const poll = () => {
44
+ this.#checkForChanges();
45
+ this.#rafId = requestAnimationFrame(poll);
46
+ };
47
+ this.#rafId = requestAnimationFrame(poll);
48
+ }
49
+ #stopPolling() {
50
+ if (this.#rafId !== null) {
51
+ cancelAnimationFrame(this.#rafId);
52
+ this.#rafId = null;
53
+ }
54
+ }
55
+ #checkForChanges() {
56
+ const entries = [];
57
+ const dpr = globalThis.devicePixelRatio ?? 1;
58
+ for (const target of this.#targets) {
59
+ const rect = target.getBoundingClientRect();
60
+ const last = this.#lastSizes.get(target);
61
+ if (!last || last.width !== rect.width || last.height !== rect.height) {
62
+ this.#lastSizes.set(target, {
63
+ width: rect.width,
64
+ height: rect.height
65
+ });
66
+ entries.push({
67
+ target,
68
+ contentRect: rect,
69
+ borderBoxSize: [{
70
+ inlineSize: rect.width,
71
+ blockSize: rect.height
72
+ }],
73
+ contentBoxSize: [{
74
+ inlineSize: rect.width,
75
+ blockSize: rect.height
76
+ }],
77
+ devicePixelContentBoxSize: [{
78
+ inlineSize: rect.width * dpr,
79
+ blockSize: rect.height * dpr
80
+ }]
81
+ });
82
+ }
83
+ }
84
+ if (entries.length > 0) this.#callback(entries, this);
85
+ }
86
+ };
87
+ /**
88
+ * Normalize sub-pixel coordinates using `Math.sumPrecise()` (ES2026).
89
+ * Falls back to simple addition if unavailable.
90
+ *
91
+ * @param values - Array of numbers to sum precisely.
92
+ * @returns The precise sum.
93
+ */
94
+ const sumPrecise = (values) => {
95
+ if (typeof Math.sumPrecise === "function") return Math.sumPrecise(values);
96
+ let sum = 0;
97
+ for (const v of values) sum += v;
98
+ return sum;
99
+ };
100
+ if (typeof globalThis.ResizeObserver === "undefined") Object.defineProperty(globalThis, "ResizeObserver", {
101
+ value: ResizeObserverShim,
102
+ writable: true,
103
+ configurable: true
104
+ });
105
+
106
+ //#endregion
107
+ export { ResizeObserverShim, sumPrecise };
108
+ //# sourceMappingURL=shim.js.map
@@ -0,0 +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,SAAqD;CAE/E,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,WAA6B;AACtD,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"}