@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/CHANGELOG.md +52 -0
- package/LICENSE +21 -0
- package/README.md +188 -0
- package/dist/core.d.ts +38 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +58 -0
- package/dist/core.js.map +1 -0
- package/dist/index.d.ts +99 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +386 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +28 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +32 -0
- package/dist/server.js.map +1 -0
- package/dist/shim.d.ts +38 -0
- package/dist/shim.d.ts.map +1 -0
- package/dist/shim.js +108 -0
- package/dist/shim.js.map +1 -0
- package/dist/types-ASPFw2w_.d.ts +49 -0
- package/dist/types-ASPFw2w_.d.ts.map +1 -0
- package/dist/worker.d.ts +103 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +222 -0
- package/dist/worker.js.map +1 -0
- package/package.json +129 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@crimson_dev/use-resize-observer` will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.1] - 2026-03-06
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- API reference page returning 404 on GitHub Pages (`docs/api/` was gitignored)
|
|
12
|
+
- Pre-commit hook failing on non-processable file types (yml, md, gitignore)
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- GitHub Actions updated to latest versions: checkout v6, setup-node v6, upload-pages-artifact v4, github-script v8
|
|
16
|
+
- VitePress theme enhanced with frosted-glass navigation, feature card hover effects, gradient hero text, animated entrance, custom scrollbar, and oklch color system
|
|
17
|
+
|
|
18
|
+
## [0.1.0] - 2026-03-05
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- `useResizeObserver` — primary React hook with shared observer pool
|
|
22
|
+
- `useResizeObserverEntries` — multi-element observation hook
|
|
23
|
+
- `createResizeObserver` — framework-agnostic imperative factory
|
|
24
|
+
- `createResizeObservable` — EventTarget-based observable (`/core`)
|
|
25
|
+
- `createServerResizeObserverMock` — SSR/RSC safe mock (`/server`)
|
|
26
|
+
- `useResizeObserverWorker` — SharedArrayBuffer worker mode (`/worker`)
|
|
27
|
+
- ResizeObserver polyfill shim (`/shim`)
|
|
28
|
+
- `ResizeObserverContext` for dependency injection
|
|
29
|
+
- `ObserverPool` — single shared ResizeObserver per document root
|
|
30
|
+
- `RafScheduler` — requestAnimationFrame batching with `startTransition`
|
|
31
|
+
- `FinalizationRegistry` for automatic GC cleanup of detached elements
|
|
32
|
+
- ES2026 `using` / `Symbol.dispose` support on pool, scheduler, and factory
|
|
33
|
+
- All 3 box models: `content-box`, `border-box`, `device-pixel-content-box`
|
|
34
|
+
- TypeScript 6 strict configuration with `isolatedDeclarations`
|
|
35
|
+
- ESM-only build via tsdown (Rolldown)
|
|
36
|
+
- Biome 2.4.5 linting and formatting
|
|
37
|
+
- Vitest 4 test infrastructure (72 unit tests)
|
|
38
|
+
- Size-limit bundle size guards
|
|
39
|
+
- VitePress 2 documentation site
|
|
40
|
+
- GitHub Actions CI/CD pipeline
|
|
41
|
+
- Changeset-based release pipeline
|
|
42
|
+
- Performance benchmarks with tinybench
|
|
43
|
+
|
|
44
|
+
### Architecture
|
|
45
|
+
- Shared observer pool eliminates per-component ResizeObserver overhead
|
|
46
|
+
- rAF + startTransition batching: 100 elements → 1 render cycle
|
|
47
|
+
- WeakMap-based pool registry scoped per document/shadow root
|
|
48
|
+
- Stable callback identity via ref pattern (React Compiler safe)
|
|
49
|
+
- Worker mode: SharedArrayBuffer + Float16Array + Atomics for off-main-thread
|
|
50
|
+
|
|
51
|
+
[0.1.1]: https://github.com/ABCrimson/use-resize-observer/releases/tag/v0.1.1
|
|
52
|
+
[0.1.0]: https://github.com/ABCrimson/use-resize-observer/releases/tag/v0.1.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Crimson Dev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<br />
|
|
4
|
+
|
|
5
|
+
<picture>
|
|
6
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/ABCrimson/use-resize-observer/main/.github/assets/logo-dark.svg">
|
|
7
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/ABCrimson/use-resize-observer/main/.github/assets/logo-light.svg">
|
|
8
|
+
<img alt="@crimson_dev/use-resize-observer" src="https://raw.githubusercontent.com/ABCrimson/use-resize-observer/main/.github/assets/logo-light.svg" width="480">
|
|
9
|
+
</picture>
|
|
10
|
+
|
|
11
|
+
<br />
|
|
12
|
+
|
|
13
|
+
**Zero-dependency, Worker-native, ESNext-first React 19 ResizeObserver hook**
|
|
14
|
+
|
|
15
|
+
<br />
|
|
16
|
+
|
|
17
|
+
[![npm version][npm-badge]][npm-url]
|
|
18
|
+
[![bundle size][size-badge]][size-url]
|
|
19
|
+
[![CI][ci-badge]][ci-url]
|
|
20
|
+
[![license][license-badge]][license-url]
|
|
21
|
+
[![TypeScript][ts-badge]][ts-url]
|
|
22
|
+
[![docs][docs-badge]][docs-url]
|
|
23
|
+
|
|
24
|
+
[npm-badge]: https://img.shields.io/npm/v/@crimson_dev/use-resize-observer?style=flat-square&color=DC143C&labelColor=0D1117
|
|
25
|
+
[npm-url]: https://npmjs.com/package/@crimson_dev/use-resize-observer
|
|
26
|
+
[size-badge]: https://img.shields.io/bundlephobia/minzip/@crimson_dev/use-resize-observer?style=flat-square&color=DC143C&labelColor=0D1117&label=gzip
|
|
27
|
+
[size-url]: https://bundlephobia.com/package/@crimson_dev/use-resize-observer
|
|
28
|
+
[ci-badge]: https://img.shields.io/github/actions/workflow/status/ABCrimson/use-resize-observer/ci.yml?style=flat-square&labelColor=0D1117&label=CI
|
|
29
|
+
[ci-url]: https://github.com/ABCrimson/use-resize-observer/actions/workflows/ci.yml
|
|
30
|
+
[license-badge]: https://img.shields.io/npm/l/@crimson_dev/use-resize-observer?style=flat-square&color=DC143C&labelColor=0D1117
|
|
31
|
+
[license-url]: https://github.com/ABCrimson/use-resize-observer/blob/main/LICENSE
|
|
32
|
+
[ts-badge]: https://img.shields.io/badge/TypeScript-6.0-3178C6?style=flat-square&labelColor=0D1117
|
|
33
|
+
[ts-url]: https://www.typescriptlang.org/
|
|
34
|
+
[docs-badge]: https://img.shields.io/badge/docs-GitHub%20Pages-DC143C?style=flat-square&labelColor=0D1117
|
|
35
|
+
[docs-url]: https://abcrimson.github.io/use-resize-observer/
|
|
36
|
+
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install @crimson_dev/use-resize-observer
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { useResizeObserver } from '@crimson_dev/use-resize-observer';
|
|
49
|
+
|
|
50
|
+
function ResponsiveCard() {
|
|
51
|
+
const { ref, width, height } = useResizeObserver<HTMLDivElement>();
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div ref={ref}>
|
|
55
|
+
{width} × {height}
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Why This Exists
|
|
62
|
+
|
|
63
|
+
Most resize hooks create **one `ResizeObserver` per component**. At scale, that means hundreds of observers competing for the main thread. This library uses a **shared observer pool** — one `ResizeObserver` per document root — with `requestAnimationFrame` batching and React `startTransition` wrapping. The result: **100 elements resizing = 1 render cycle**.
|
|
64
|
+
|
|
65
|
+
## Highlights
|
|
66
|
+
|
|
67
|
+
<table>
|
|
68
|
+
<tr><td>📦</td><td><strong>< 1.1kB</strong> gzip · zero dependencies</td></tr>
|
|
69
|
+
<tr><td>⚡</td><td>Shared <code>ResizeObserver</code> pool · rAF batching · <code>startTransition</code></td></tr>
|
|
70
|
+
<tr><td>🧵</td><td>Worker mode via <code>SharedArrayBuffer</code> + <code>Float16Array</code></td></tr>
|
|
71
|
+
<tr><td>🎯</td><td>All 3 box models: <code>content-box</code>, <code>border-box</code>, <code>device-pixel-content-box</code></td></tr>
|
|
72
|
+
<tr><td>🧹</td><td><code>FinalizationRegistry</code> for automatic GC cleanup</td></tr>
|
|
73
|
+
<tr><td>🏗️</td><td>ES2026: <code>using</code> / <code>Symbol.dispose</code>, <code>Promise.try()</code></td></tr>
|
|
74
|
+
<tr><td>⚛️</td><td>React 19.3+ · React Compiler verified</td></tr>
|
|
75
|
+
<tr><td>📝</td><td>TypeScript 6 strict · <code>isolatedDeclarations</code></td></tr>
|
|
76
|
+
<tr><td>🌐</td><td>SSR/RSC safe · server entry with mock result</td></tr>
|
|
77
|
+
</table>
|
|
78
|
+
|
|
79
|
+
## Entry Points
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
// Main — primary hook
|
|
83
|
+
import { useResizeObserver } from '@crimson_dev/use-resize-observer';
|
|
84
|
+
|
|
85
|
+
// Multi-element — observe N elements with 1 hook
|
|
86
|
+
import { useResizeObserverEntries } from '@crimson_dev/use-resize-observer';
|
|
87
|
+
|
|
88
|
+
// Factory — framework-agnostic, imperative API
|
|
89
|
+
import { createResizeObserver } from '@crimson_dev/use-resize-observer';
|
|
90
|
+
|
|
91
|
+
// Worker — off-main-thread via SharedArrayBuffer
|
|
92
|
+
import { useResizeObserverWorker } from '@crimson_dev/use-resize-observer/worker';
|
|
93
|
+
|
|
94
|
+
// Core — EventTarget-based, any framework
|
|
95
|
+
import { createResizeObservable } from '@crimson_dev/use-resize-observer/core';
|
|
96
|
+
|
|
97
|
+
// Server — SSR/RSC safe
|
|
98
|
+
import { createServerResizeObserverMock } from '@crimson_dev/use-resize-observer/server';
|
|
99
|
+
|
|
100
|
+
// Shim — polyfill for legacy browsers
|
|
101
|
+
import '@crimson_dev/use-resize-observer/shim';
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## API
|
|
105
|
+
|
|
106
|
+
### `useResizeObserver<T>(options?)`
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
const { ref, width, height, entry } = useResizeObserver<HTMLDivElement>({
|
|
110
|
+
box: 'content-box', // 'content-box' | 'border-box' | 'device-pixel-content-box'
|
|
111
|
+
ref: externalRef, // optional external ref
|
|
112
|
+
root: shadowRoot, // optional Document | ShadowRoot
|
|
113
|
+
onResize: (entry) => {}, // stable callback (no useCallback needed)
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
| Return | Type | Description |
|
|
118
|
+
|--------|------|-------------|
|
|
119
|
+
| `ref` | `RefObject<T \| null>` | Attach to the element to observe |
|
|
120
|
+
| `width` | `number \| undefined` | Inline size of the observed box |
|
|
121
|
+
| `height` | `number \| undefined` | Block size of the observed box |
|
|
122
|
+
| `entry` | `ResizeObserverEntry \| undefined` | Raw entry from the observer |
|
|
123
|
+
|
|
124
|
+
### `useResizeObserverEntries(refs, options?)`
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
const entries = useResizeObserverEntries([ref1, ref2, ref3], {
|
|
128
|
+
box: 'border-box',
|
|
129
|
+
});
|
|
130
|
+
// entries: Map<Element, { width, height, entry }>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### `createResizeObserver(options?)`
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
using observer = createResizeObserver({ box: 'border-box' });
|
|
137
|
+
observer.observe(element, (entry) => console.log(entry));
|
|
138
|
+
// Automatically cleaned up via Symbol.dispose
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Architecture
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
┌─────────────────────────────────────────────────┐
|
|
145
|
+
│ Your Components │
|
|
146
|
+
│ useResizeObserver() useResizeObserverEntries() │
|
|
147
|
+
└──────────────────────┬──────────────────────────┘
|
|
148
|
+
│
|
|
149
|
+
┌────────▼────────┐
|
|
150
|
+
│ ObserverPool │ 1 per document root
|
|
151
|
+
│ (WeakMap) │ FinalizationRegistry
|
|
152
|
+
└────────┬────────┘
|
|
153
|
+
│
|
|
154
|
+
┌────────▼────────┐
|
|
155
|
+
│ RafScheduler │ Map<Element, FlushEntry>
|
|
156
|
+
│ last-write-wins│ requestAnimationFrame
|
|
157
|
+
└────────┬────────┘
|
|
158
|
+
│
|
|
159
|
+
┌────────▼────────┐
|
|
160
|
+
│ startTransition │ Non-urgent batched update
|
|
161
|
+
│ setState() │ 1 render per frame
|
|
162
|
+
└─────────────────┘
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Comparison
|
|
166
|
+
|
|
167
|
+
| | `use-resize-observer@9` | `@crimson_dev/use-resize-observer` |
|
|
168
|
+
|---|---|---|
|
|
169
|
+
| Bundle | ~800B | **< 1.1kB** (pool + scheduler + hook) |
|
|
170
|
+
| React | 16.8+ | **19.3+** with Compiler |
|
|
171
|
+
| Module | CJS + ESM | **ESM only** |
|
|
172
|
+
| TypeScript | 4.x | **6.0 strict** |
|
|
173
|
+
| Observer model | 1 per component | **Shared pool** |
|
|
174
|
+
| Worker mode | — | **SharedArrayBuffer** |
|
|
175
|
+
| Box models | content-box only | **All 3** |
|
|
176
|
+
| GC cleanup | Manual | **Automatic** |
|
|
177
|
+
| Batching | None | **rAF + startTransition** |
|
|
178
|
+
|
|
179
|
+
## Requirements
|
|
180
|
+
|
|
181
|
+
- **Node** ≥ 25.0.0
|
|
182
|
+
- **React** ≥ 19.3.0
|
|
183
|
+
- **TypeScript** ≥ 6.0 (recommended)
|
|
184
|
+
- **Browser** with native `ResizeObserver` (or use the `/shim` entry)
|
|
185
|
+
|
|
186
|
+
## License
|
|
187
|
+
|
|
188
|
+
[MIT](./LICENSE) — Crimson Dev
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
|
|
2
|
+
import { r as ResizeObserverBoxOptions } from "./types-ASPFw2w_.js";
|
|
3
|
+
|
|
4
|
+
//#region src/core.d.ts
|
|
5
|
+
/** Detail payload for resize events dispatched by the observable. */
|
|
6
|
+
interface ResizeEventDetail {
|
|
7
|
+
readonly width: number;
|
|
8
|
+
readonly height: number;
|
|
9
|
+
readonly entry: ResizeObserverEntry;
|
|
10
|
+
}
|
|
11
|
+
/** Custom event type for resize observations. */
|
|
12
|
+
declare class ResizeEvent extends CustomEvent<ResizeEventDetail> {
|
|
13
|
+
constructor(detail: ResizeEventDetail);
|
|
14
|
+
}
|
|
15
|
+
/** Options for the framework-agnostic observable. */
|
|
16
|
+
interface CreateResizeObservableOptions {
|
|
17
|
+
/** Which box model to report. @default 'content-box' */
|
|
18
|
+
box?: ResizeObserverBoxOptions;
|
|
19
|
+
}
|
|
20
|
+
/** Framework-agnostic resize observable with EventTarget-based dispatching. */
|
|
21
|
+
interface ResizeObservable extends EventTarget, Disposable {
|
|
22
|
+
/** Stop observing and clean up resources. */
|
|
23
|
+
disconnect(): void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create a framework-agnostic resize observable for an element.
|
|
27
|
+
*
|
|
28
|
+
* Wraps a `ResizeObserver` with `EventTarget` dispatching — consumers
|
|
29
|
+
* subscribe via `addEventListener('resize', handler)`.
|
|
30
|
+
*
|
|
31
|
+
* @param target - The DOM element to observe.
|
|
32
|
+
* @param options - Configuration options.
|
|
33
|
+
* @returns A `ResizeObservable` with `addEventListener` and `disconnect`.
|
|
34
|
+
*/
|
|
35
|
+
declare const createResizeObservable: (target: Element, options?: CreateResizeObservableOptions) => ResizeObservable;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { CreateResizeObservableOptions, ResizeEvent, ResizeEventDetail, ResizeObservable, createResizeObservable };
|
|
38
|
+
//# sourceMappingURL=core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.d.ts","names":[],"sources":["../src/core.ts"],"mappings":";;;;;UAoBiB,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;EAAA;EAEf,GAAA,GAAM,wBAAA;AAAA;;UAIS,gBAAA,SAAyB,WAAA,EAAa,UAAA;EAAtC;EAEf,UAAA;AAAA;;;;;;AA+BF;;;;;cAAa,sBAAA,GACX,MAAA,EAAQ,OAAA,EACR,OAAA,GAAS,6BAAA,KACR,gBAAA"}
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
//#region src/core.ts
|
|
3
|
+
/** Custom event type for resize observations. */
|
|
4
|
+
var ResizeEvent = class extends CustomEvent {
|
|
5
|
+
constructor(detail) {
|
|
6
|
+
super("resize", { detail });
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
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
|
+
* Create a framework-agnostic resize observable for an element.
|
|
22
|
+
*
|
|
23
|
+
* Wraps a `ResizeObserver` with `EventTarget` dispatching — consumers
|
|
24
|
+
* subscribe via `addEventListener('resize', handler)`.
|
|
25
|
+
*
|
|
26
|
+
* @param target - The DOM element to observe.
|
|
27
|
+
* @param options - Configuration options.
|
|
28
|
+
* @returns A `ResizeObservable` with `addEventListener` and `disconnect`.
|
|
29
|
+
*/
|
|
30
|
+
const createResizeObservable = (target, options = {}) => {
|
|
31
|
+
const { box = "content-box" } = options;
|
|
32
|
+
const eventTarget = new EventTarget();
|
|
33
|
+
const observer = new ResizeObserver((entries) => {
|
|
34
|
+
for (const entry of entries) {
|
|
35
|
+
const sizeEntry = extractBoxSize(entry, box);
|
|
36
|
+
const detail = {
|
|
37
|
+
width: sizeEntry?.inlineSize ?? 0,
|
|
38
|
+
height: sizeEntry?.blockSize ?? 0,
|
|
39
|
+
entry
|
|
40
|
+
};
|
|
41
|
+
eventTarget.dispatchEvent(new ResizeEvent(detail));
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
observer.observe(target, { box });
|
|
45
|
+
const disconnect = () => {
|
|
46
|
+
observer.disconnect();
|
|
47
|
+
};
|
|
48
|
+
return Object.assign(eventTarget, {
|
|
49
|
+
disconnect,
|
|
50
|
+
[Symbol.dispose]() {
|
|
51
|
+
disconnect();
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
//#endregion
|
|
57
|
+
export { ResizeEvent, createResizeObservable };
|
|
58
|
+
//# sourceMappingURL=core.js.map
|
package/dist/core.js.map
ADDED
|
@@ -0,0 +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 * Extract the first size entry for the given box model.\n * @internal\n */\nconst 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 * 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":";;;AA2BA,IAAa,cAAb,cAAiC,YAA+B;CAC9D,YAAY,QAA2B;AACrC,QAAM,UAAU,EAAE,QAAQ,CAAC;;;;;;;AAoB/B,MAAM,kBACJ,OACA,QACmC;AACnC,SAAQ,KAAR;EACE,KAAK,aACH,QAAO,MAAM,cAAc;EAC7B,KAAK,2BACH,SAAQ,MAAM,6BAA6B,MAAM,gBAAgB;EACnE,QACE,QAAO,MAAM,eAAe;;;;;;;;;;;;;AAclC,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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
|
|
2
|
+
import { a as UseResizeObserverOptions, i as ResizeObserverFactory, n as ResizeCallback, o as UseResizeObserverResult, r as ResizeObserverBoxOptions, t as CreateResizeObserverOptions } from "./types-ASPFw2w_.js";
|
|
3
|
+
import React, { RefObject } from "react";
|
|
4
|
+
|
|
5
|
+
//#region src/context.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Context for injecting a custom `ResizeObserver` constructor.
|
|
8
|
+
*
|
|
9
|
+
* Useful for:
|
|
10
|
+
* - **Testing**: Inject a mock `ResizeObserver` for deterministic tests.
|
|
11
|
+
* - **SSR**: Inject a no-op implementation to avoid `ReferenceError`.
|
|
12
|
+
* - **Polyfills**: Inject a polyfill without modifying `globalThis`.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* // In tests:
|
|
17
|
+
* <ResizeObserverContext.Provider value={MockResizeObserver}>
|
|
18
|
+
* <ComponentThatUsesResize />
|
|
19
|
+
* </ResizeObserverContext.Provider>
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
declare const ResizeObserverContext: React.Context<typeof ResizeObserver | null>;
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/factory.d.ts
|
|
25
|
+
/**
|
|
26
|
+
* Framework-agnostic factory for creating a ResizeObserver subscription
|
|
27
|
+
* using the shared pool architecture.
|
|
28
|
+
*
|
|
29
|
+
* Uses the same pool and scheduler as the React hook — no duplicate observers.
|
|
30
|
+
* Implements cleanup tracking with `Map` for efficient iteration.
|
|
31
|
+
*
|
|
32
|
+
* @param options - Configuration options.
|
|
33
|
+
* @returns An object with `observe`, `unobserve`, and `disconnect` methods.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* using observer = createResizeObserver({ box: 'border-box' });
|
|
38
|
+
* observer.observe(element, (entry) => {
|
|
39
|
+
* console.log(entry.contentRect.width);
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
declare const createResizeObserver: (options?: CreateResizeObserverOptions) => ResizeObserverFactory & Disposable;
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/hook.d.ts
|
|
46
|
+
/**
|
|
47
|
+
* Primary React hook for observing element resize events.
|
|
48
|
+
*
|
|
49
|
+
* Features:
|
|
50
|
+
* - Single shared `ResizeObserver` per document root (pool architecture)
|
|
51
|
+
* - `requestAnimationFrame` batching with `startTransition` wrapping
|
|
52
|
+
* - GC-backed cleanup via `FinalizationRegistry`
|
|
53
|
+
* - React Compiler-safe (stable callback identity via ref pattern)
|
|
54
|
+
* - Sub-300B gzip bundle contribution
|
|
55
|
+
*
|
|
56
|
+
* @param options - Configuration options.
|
|
57
|
+
* @returns Ref, width, height, and raw entry.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```tsx
|
|
61
|
+
* const { ref, width, height } = useResizeObserver<HTMLDivElement>();
|
|
62
|
+
* return <div ref={ref}>Size: {width} x {height}</div>;
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
declare const useResizeObserver: <T extends Element = Element>(options?: UseResizeObserverOptions<T>) => UseResizeObserverResult<T>;
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/hook-multi.d.ts
|
|
68
|
+
/** Entry data for a single observed element in the multi-element hook. */
|
|
69
|
+
interface ResizeEntry {
|
|
70
|
+
readonly width: number;
|
|
71
|
+
readonly height: number;
|
|
72
|
+
readonly entry: ResizeObserverEntry;
|
|
73
|
+
}
|
|
74
|
+
/** Options for `useResizeObserverEntries`. */
|
|
75
|
+
interface UseResizeObserverEntriesOptions {
|
|
76
|
+
/** Which box model to report. @default 'content-box' */
|
|
77
|
+
box?: ResizeObserverBoxOptions;
|
|
78
|
+
/** Document or ShadowRoot scoping the pool. @default document */
|
|
79
|
+
root?: Document | ShadowRoot;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Multi-element variant: observe multiple elements simultaneously through
|
|
83
|
+
* a single pool subscription.
|
|
84
|
+
*
|
|
85
|
+
* @param refs - Array of refs pointing to elements to observe.
|
|
86
|
+
* @param options - Configuration options.
|
|
87
|
+
* @returns A `Map<Element, ResizeEntry>` keyed by observed element.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```tsx
|
|
91
|
+
* const ref1 = useRef<HTMLDivElement>(null);
|
|
92
|
+
* const ref2 = useRef<HTMLDivElement>(null);
|
|
93
|
+
* const entries = useResizeObserverEntries([ref1, ref2]);
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
declare const useResizeObserverEntries: (refs: ReadonlyArray<RefObject<Element | null>>, options?: UseResizeObserverEntriesOptions) => Map<Element, ResizeEntry>;
|
|
97
|
+
//#endregion
|
|
98
|
+
export { type CreateResizeObserverOptions, type ResizeCallback, type ResizeEntry, type ResizeObserverBoxOptions, ResizeObserverContext, type ResizeObserverFactory, type UseResizeObserverEntriesOptions, type UseResizeObserverOptions, type UseResizeObserverResult, createResizeObserver, useResizeObserver, useResizeObserverEntries };
|
|
99
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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;;;;cC+Ba,iBAAA,aAA+B,OAAA,GAAU,OAAA,EACpD,OAAA,GAAS,wBAAA,CAAyB,CAAA,MACjC,uBAAA,CAAwB,CAAA;;;;UCjDV,WAAA;EAAA,SACN,KAAA;EAAA,SACA,MAAA;EAAA,SACA,KAAA,EAAO,mBAAA;AAAA;;UAID,+BAAA;;EAEf,GAAA,GAAM,wBAAA;EHGiD;EGDvD,IAAA,GAAO,QAAA,GAAW,UAAA;AAAA;;AFKpB;;;;;;;;;;;;;;cE+Ba,wBAAA,GACX,IAAA,EAAM,aAAA,CAAc,SAAA,CAAU,OAAA,WAC9B,OAAA,GAAS,+BAAA,KACR,GAAA,CAAI,OAAA,EAAS,WAAA"}
|