view-anchor 0.2.2 → 1.0.0-beta.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.
Files changed (46) hide show
  1. package/README.md +71 -87
  2. package/README.zh-CN.md +59 -64
  3. package/dist/index.d.ts +5 -7
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +2 -3
  6. package/dist/protocol-publisher.d.ts +7 -9
  7. package/dist/protocol-publisher.d.ts.map +1 -1
  8. package/dist/protocol-publisher.js +2 -8
  9. package/dist/protocol-types.d.ts +5 -8
  10. package/dist/protocol-types.d.ts.map +1 -1
  11. package/dist/protocol-types.js +3 -6
  12. package/dist/protocol.d.ts +2 -2
  13. package/dist/protocol.d.ts.map +1 -1
  14. package/dist/protocol.js +10 -2
  15. package/dist/react.d.ts +2 -15
  16. package/dist/react.d.ts.map +1 -1
  17. package/dist/react.js +43 -38
  18. package/dist/{size-advertiser.d.ts → size-anchor.d.ts} +6 -5
  19. package/dist/size-anchor.d.ts.map +1 -0
  20. package/dist/size-anchor.js +131 -0
  21. package/dist/types.d.ts +26 -32
  22. package/dist/types.d.ts.map +1 -1
  23. package/dist/view-anchor.d.ts +52 -33
  24. package/dist/view-anchor.d.ts.map +1 -1
  25. package/dist/view-anchor.js +186 -265
  26. package/docs/bidirectional-design.md +29 -34
  27. package/docs/index.html +656 -366
  28. package/docs/mechanism.md +35 -57
  29. package/docs/performance-report.md +12 -54
  30. package/docs/protocol.md +12 -3
  31. package/package.json +4 -6
  32. package/src/index.ts +5 -19
  33. package/src/protocol-publisher.ts +10 -16
  34. package/src/protocol-types.ts +5 -8
  35. package/src/protocol.ts +13 -7
  36. package/src/react.ts +51 -64
  37. package/src/size-anchor.ts +131 -0
  38. package/src/types.ts +29 -37
  39. package/src/view-anchor.ts +221 -287
  40. package/dist/measure-loop.d.ts +0 -24
  41. package/dist/measure-loop.d.ts.map +0 -1
  42. package/dist/measure-loop.js +0 -89
  43. package/dist/size-advertiser.d.ts.map +0 -1
  44. package/dist/size-advertiser.js +0 -94
  45. package/src/measure-loop.ts +0 -101
  46. package/src/size-advertiser.ts +0 -108
package/README.md CHANGED
@@ -7,43 +7,41 @@
7
7
  [![npm version](https://img.shields.io/npm/v/view-anchor)](https://www.npmjs.com/package/view-anchor)
8
8
  [![npm downloads](https://img.shields.io/npm/dm/view-anchor)](https://www.npmjs.com/package/view-anchor)
9
9
  [![License](https://img.shields.io/npm/l/view-anchor)](./LICENSE)
10
- [![Node](https://img.shields.io/badge/node-%3E%3D24-339933)](https://nodejs.org/)
11
10
 
12
11
  [English](./README.md) · [简体中文](./README.zh-CN.md)
13
12
 
14
- > 🎮 **Live demo**: the [3D interactive demo](https://lbb00.github.io/view-anchor/) runs the real core in your browser. Drag the splitter, toggle the panel, and watch the native view follow.
13
+ > 🎮 **Live demo**: [3D interactive demo](https://lbb00.github.io/view-anchor/) runs the real core in your browser. Drag the splitters, scroll the page, and click its buttons directly in the tilted scene: surface A follows its placeholder, and surface B reports its content height back so the page resizes its placeholder.
15
14
 
16
15
  ## The problem
17
16
 
18
- Some surfaces are positioned by application code rather than by CSS. They may be a canvas, a video overlay, an embedded document, or any other thing you can position from a rectangle. Your layout moves DOM elements, but it cannot move that external surface by itself.
17
+ Some surfaces are positioned by application code rather than by CSS a canvas, a video overlay, an embedded document, or anything you can position from a rectangle. DOM layout moves placeholder elements, but cannot move those external surfaces.
19
18
 
20
- Point `view-anchor` at a placeholder element. It measures the element on creation, on `ResizeObserver` notifications, and on window `resize`, then calls your `publish` function. Your function applies, stores, or sends that value using the mechanism your application already has.
19
+ Point `view-anchor` at a placeholder element. It measures it on creation, on `ResizeObserver` notifications, and on window `resize`, then calls your `publish` function. Your function applies, stores, or sends that value however your application already does.
21
20
 
22
- The core has no dependency on a host runtime, React, or a layout library. It only uses `ResizeObserver`, `requestAnimationFrame`, and `getBoundingClientRect`. React support lives in a separate `view-anchor/react` entry.
21
+ The core has no dependency on a host runtime, React, or a layout library, and uses only `ResizeObserver`, `requestAnimationFrame`, and `getBoundingClientRect`. React support lives in a separate `view-anchor/react` entry.
23
22
 
24
23
  ## What `publish` receives
25
24
 
26
- `publish` is not a built-in transport. It is a synchronous function that the library calls with one of these plain values:
25
+ `publish` is a synchronous function that the library calls with one of these plain values:
27
26
 
28
- - `createViewAnchor` calls `publish(bounds)`, where `bounds` is `{ x, y, width, height }` in CSS pixels from `getBoundingClientRect()`. When `present` becomes `false`, it calls `publish({ x: 0, y: 0, width: 0, height: 0 })` once and stops observing.
29
- - `createPlacementAnchor` calls `publish({ visible: true, bounds })` while shown, or `publish({ visible: false })` while hidden. Use it when a visible zero-sized element differs from a hidden one.
30
- - `createSizeAdvertiser` calls `publish({ axis, extent })`, where `axis` is `block` (height) or `inline` (width), and `extent` is the rounded non-negative content size in CSS pixels.
27
+ - `createViewAnchor` calls `publish({ visible: true, bounds })` while shown, where `bounds` is `{ x, y, width, height }` in CSS pixels from `getBoundingClientRect()`, or `publish({ visible: false })` while hidden. The explicit `visible` flag distinguishes a visible-but-zero-sized element from one that is hidden or unmounted.
28
+ - `createSizeAnchor` calls `publish({ axis, extent })`, where `axis` is `block` (height) or `inline` (width), and `extent` is the target's rounded, non-negative border-box size on that axis in CSS pixels (padding and border included; content-box only where the browser does not report a border box).
31
29
 
32
- Return `false` only when the value was not accepted; a later measurement may retry it. Return `true` or nothing after accepting or queueing it.
30
+ Return `false` only when the value was not accepted; a later measurement may retry it. Return `true` or nothing after accepting or queueing it. A hidden anchor measures nothing, so a rejected `{ visible: false }` is not retried on its own: call `update()` again to resend it. `useViewAnchor` does this for you on unmount.
33
31
 
34
- ## Built for the hot path
32
+ ## Performance
35
33
 
36
- Geometry updates fire on every resize and, when following a drag, on every animation frame. The library keeps that work predictable:
34
+ Geometry updates fire on every resize and, when following a drag, on every animation frame.
37
35
 
38
36
  - **Synchronous delivery.** Measurement and publish happen inside the same `ResizeObserver` callback. No timers, no extra frame of lag.
39
- - **Dedupe outgoing updates.** A rectangle identical to the last accepted one is not passed to `publish` again.
40
- - **Frame following only when needed.** `followGeometry` polls `requestAnimationFrame` during a scroll burst, a splitter drag, or an explicit `pulse()`, then closes itself once the rectangle settles. Idle cost is zero, and hidden or invalid targets are capped at 30 frames.
37
+ - **Dedupe by default.** A `Placement` identical to the last accepted one is not passed to `publish` again; set `dedupe: false` to receive every measurement.
38
+ - **Frame following on demand.** `followGeometry` polls `requestAnimationFrame` during a scroll burst, a splitter drag, or an explicit `pulse()`, then stops once the rectangle settles. Idle cost is zero; hidden or invalid targets are capped at 30 frames.
41
39
  - **O(1) generation changes.** In the protocol layer, moving an anchor to a new generation or clearing it does not touch other anchors.
42
- - **Latest-wins batching.** Messages queued in the same task are merged in a microtask. The newest placement and size for each anchor are sent separately.
40
+ - **Latest-wins batching.** Messages queued in the same task are merged in a microtask; the newest placement and size for each anchor are sent separately.
43
41
  - **Release on disposal.** Disposed handles stop observing and release their target and callback references, even when the caller keeps the handle.
44
- - **Compact, tree-shakeable core.** Functions are separate exports with `sideEffects: false`; the complete core export is under 3 KB gzipped.
42
+ - **Tree-shakeable core.** Functions are separate exports with `sideEffects: false`; the complete core export is under 3 KB gzipped.
45
43
 
46
- The [performance report](./docs/performance-report.md) contains the reproducible measurements and their environment. They are useful for comparing changes on the same machine, not for predicting browser layout, serialization, delivery, or your app's workload.
44
+ See the [performance report](./docs/performance-report.md) for a comparison with 0.2.2 and export sizes.
47
45
 
48
46
  ## Installation
49
47
 
@@ -53,7 +51,7 @@ pnpm add view-anchor
53
51
  npm install view-anchor
54
52
  ```
55
53
 
56
- React is an optional peer dependency. Import the hooks from `view-anchor/react`. The root entry also re-exports `useViewAnchor` for compatibility with `v0.1.2`, so any app that imports `view-anchor` needs React installed. `view-anchor/protocol` does not.
54
+ React is an optional peer dependency, needed only if you import from `view-anchor/react`. The root entry (`view-anchor`) and `view-anchor/protocol` have no React dependency and load without it installed.
57
55
 
58
56
  ## Usage
59
57
 
@@ -62,28 +60,33 @@ React is an optional peer dependency. Import the hooks from `view-anchor/react`.
62
60
  ```ts
63
61
  import { createViewAnchor } from 'view-anchor'
64
62
 
65
- const publish = (bounds) => {
66
- applyBounds(bounds)
63
+ const publish = (placement) => {
64
+ if (placement.visible) applyBounds(placement.bounds)
65
+ else hideSurface()
67
66
  }
68
67
 
69
68
  const handle = createViewAnchor(target, {
70
- present: true,
69
+ visible: true,
71
70
  publish,
71
+ followScroll: true, // re-measure when any ancestor scrolls
72
+ followGeometry: true, // poll animation frames during scrolls / drags, stop when steady
73
+ treatZeroAreaAsHidden: true, // zero-area or display:none target → { visible: false }
74
+ holdSelector: '[role="separator"]', // default; pointerdown on a match keeps followGeometry open while held, null disables
75
+ dedupe: true, // default; set false to receive every measurement, even unchanged ones
72
76
  })
73
77
 
74
- handle.update({ present: true, publish }) // apply new options and publish right away
78
+ handle.update({ visible: true, publish }) // apply new options and publish right away
79
+ handle.pulse() // re-measure after a move no observer sees (e.g. a class toggle moved the target without resizing it); closes once steady
75
80
  handle.dispose() // stop observing; never publishes again
76
81
  ```
77
82
 
78
- Set `present: false` to collapse the surface. The core publishes a zero rectangle and stops observing. Your `publish` function decides whether that removes, hides, or retains the external surface.
83
+ Set `visible: false` to collapse the surface. The core publishes `{ visible: false }` and stops observing. Your `publish` function decides whether that removes, hides, or retains the external surface.
79
84
 
80
- For ancestor scroll, transforms, or other position-only changes, use `createPlacementAnchor` with `followScroll` or `followGeometry` as needed.
81
-
82
- `createViewAnchor`, `createPlacementAnchor`, `createSizeAdvertiser`, and `createGeometryBatcher` accept `signal`. Aborting it is equivalent to `dispose()`; an already-aborted signal does not measure, publish, or install listeners.
85
+ `createViewAnchor`, `createSizeAnchor`, and `createGeometryBatcher` accept `signal`. Aborting it is equivalent to `dispose()`; an already-aborted signal does not measure, publish, or install listeners.
83
86
 
84
87
  ```ts
85
88
  const controller = new AbortController()
86
- const handle = createViewAnchor(target, { present: true, publish, signal: controller.signal })
89
+ const handle = createViewAnchor(target, { visible: true, publish, signal: controller.signal })
87
90
 
88
91
  controller.abort() // same cleanup as handle.dispose()
89
92
  ```
@@ -95,62 +98,44 @@ import { useViewAnchor } from 'view-anchor/react'
95
98
 
96
99
  function DebugPanel({ visible }: { visible: boolean }) {
97
100
  const ref = useViewAnchor({
98
- present: visible,
99
- publish: publishPanelBounds,
101
+ visible,
102
+ publish: publishPanelPlacement,
103
+ followScroll: true,
104
+ followGeometry: true,
100
105
  })
101
106
  // The external surface follows this placeholder. Hiding or unmounting it
102
- // sends the collapsed value, without deciding how the surface is stored.
107
+ // sends { visible: false }, without deciding how the surface is stored.
103
108
  return <div ref={ref} className="h-full w-full" />
104
109
  }
105
110
  ```
106
111
 
107
- The hook survives React 18 and 19 StrictMode double-mounting without publishing stale frames.
108
-
109
- ### Explicit visibility
110
-
111
- A zero rectangle cannot tell a hidden view from one that is visible but currently 0×0. When that distinction matters, use the `Placement` API. It publishes `{ visible: true, bounds }` or `{ visible: false }` and adds opt-in scroll and geometry following:
112
-
113
- ```ts
114
- import { createPlacementAnchor } from 'view-anchor'
115
-
116
- const handle = createPlacementAnchor(target, {
117
- visible: true,
118
- publish(placement) {
119
- applyPlacement(placement)
120
- },
121
- followScroll: true, // re-measure when any ancestor scrolls
122
- followGeometry: true, // poll animation frames during scrolls / drags, stop when steady
123
- guardDisplayNone: true, // zero-area or display:none target → { visible: false }
124
- })
125
-
126
- handle.pulse() // open a short frame-following window, e.g. during a CSS transition
127
- ```
128
-
129
- The React version is `usePlacementAnchor` from `view-anchor/react`.
112
+ The hook survives React 18 and 19 StrictMode double-mounting without publishing stale frames. It re-applies options on every update with the same reset semantics as `createViewAnchor` and `update()`: an omitted option resets to its default, not the previous value. An omitted `treatZeroAreaAsHidden`, `followScroll`, or `followGeometry` counts as `false`; an omitted `holdSelector` resets to `[role="separator"]`; an omitted `dedupe` resets to `true`. Pass `null` / `false` to turn `holdSelector` / `dedupe` off.
130
113
 
131
114
  ### Let content drive the size
132
115
 
133
- Sometimes the hosted surface's size should come from its own content, for example a toolbar rendered by downstream code. Run `createSizeAdvertiser` inside the hosted document. It reports the content size back so a DOM placeholder in the host can grow to match:
116
+ Sometimes the hosted surface's size should come from its own content, for example a toolbar rendered by downstream code. Run `createSizeAnchor` inside the hosted document. It reports the content size back so a DOM placeholder in the host can grow to match:
134
117
 
135
118
  ```ts
136
- import { createSizeAdvertiser } from 'view-anchor'
119
+ import { createSizeAnchor } from 'view-anchor'
120
+
121
+ const publish = (size) => {
122
+ updatePlaceholderSize(size)
123
+ }
137
124
 
138
- const handle = createSizeAdvertiser(contentWrapper, {
139
- axis: 'block', // one axis per advertiser: block = height, inline = width
140
- publish(size) {
141
- updatePlaceholderSize(size)
142
- },
125
+ const handle = createSizeAnchor(contentWrapper, {
126
+ axis: 'block', // one axis per size anchor: block = height, inline = width
127
+ publish,
143
128
  })
144
129
 
145
- handle.update(publish) // swap the publish channel and report the current size again
130
+ handle.update({ publish }) // apply new options and report the current size again; an omitted dedupe resets to true
146
131
  handle.dispose() // stop observing; never reports again
147
132
  ```
148
133
 
149
- > **Warning:** the target must shrink to fit its content on the owned axis. If the host sets that size instead, the two sides keep reacting to each other and never settle. See [docs/bidirectional-design.md](./docs/bidirectional-design.md).
134
+ > The target must shrink to fit its content on the owned axis. If the host sets that size instead, the two sides keep reacting to each other and never settle. See [docs/bidirectional-design.md](./docs/bidirectional-design.md).
150
135
 
151
136
  ### Versioned transport across a boundary
152
137
 
153
- The core hands you plain `Bounds`, `Placement`, and `AdvertisedSize` values. If an application passes them through an asynchronous or untrusted channel, it usually needs validation and ordering. The optional `view-anchor/protocol` entry adds versioned message envelopes, bounded decoding, a per-anchor sequence guard that drops stale messages, and a microtask batcher:
138
+ The core hands you plain `Bounds`, `Placement`, and `SizeMeasurement` values. If an application passes them through an asynchronous or untrusted channel, it usually needs validation and ordering. The optional `view-anchor/protocol` entry adds versioned message envelopes, bounded decoding, a per-anchor sequence guard that drops stale messages, and a microtask batcher:
154
139
 
155
140
  ```ts
156
141
  import {
@@ -173,45 +158,44 @@ if (decoded.ok) {
173
158
  }
174
159
  ```
175
160
 
176
- Two rules keep the ordering correct:
177
-
178
161
  - **Keep one publisher per `{ anchorId, generation }`.** The batcher and `createGeometrySequenceGuard` remember the highest sequence number for each message kind per anchor. A publisher rebuilt for the same address restarts at sequence 1 and its messages are dropped as stale. In React, hold it in `useMemo` or `useRef`. Bump `generation` when you really want a fresh start.
179
- - **A synchronous publisher returns `false` to say "not accepted".** The core then retries the same geometry on the next trigger. A batching publisher returns `true` once queued and owns any later retries.
162
+ - **A synchronous publisher returns `false` to say "not accepted".** The core then retries the same geometry on the next trigger (a rejected `{ visible: false }` waits for the next `update()`). A batching publisher returns `true` once queued and owns any later retries.
180
163
 
181
164
  The full contract is in [docs/protocol.md](./docs/protocol.md).
182
165
 
183
166
  ## API
184
167
 
185
- | Export | Kind | Purpose |
186
- | --------------------------------------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------- |
187
- | `createViewAnchor(target, opts)` | function | Measure a DOM element and call `publish({ x, y, width, height })`. A zero rect means collapsed. |
188
- | `createPlacementAnchor(target, opts)` | function | Same core with explicit `Placement` visibility, opt-in `followScroll` / `followGeometry` / `guardDisplayNone`, and `pulse()`. |
189
- | `measurePlacement(target)` | function | Pure measurement: wraps the target rect as `{ visible: true, bounds }`. |
190
- | `createSizeAdvertiser(target, opts)` | function | Call `publish({ axis, extent })` with one content-size axis. |
191
- | `useViewAnchor(opts)` from `view-anchor/react` | hook | Returns a ref callback for a placeholder element. |
192
- | `usePlacementAnchor(opts)` from `view-anchor/react` | hook | React adapter for the `Placement` API, including `followScroll` and `followGeometry`. |
193
- | `Bounds` | type | `{ x, y, width, height }` in CSS pixels. |
194
- | `Placement` | type | `{ visible: true; bounds } \| { visible: false }`. |
195
- | `ViewAnchorOptions` / `ViewAnchorHandle` | type | Options and handle for `createViewAnchor`. |
196
- | `PlacementAnchorOptions` / `PlacementAnchorHandle` | type | Options and handle for `createPlacementAnchor`. |
197
- | `UseViewAnchorOptions` / `ViewAnchorRef` from `view-anchor/react` | type | Options and ref shape for `useViewAnchor`. |
198
- | `UsePlacementAnchorOptions` / `PlacementAnchorRef` from `view-anchor/react` | type | Options and ref shape for `usePlacementAnchor`. |
199
- | `AdvertisedAxis` / `AdvertisedSize` | type | Axis and payload types for the reverse direction. |
200
- | `SizeAdvertiserOptions` / `SizeAdvertiserHandle` | type | Options and handle for `createSizeAdvertiser`. |
201
- | `view-anchor/protocol` | functions + types | Versioned messages, strict decoding, sequence guards, message publishers, and microtask batching. |
168
+ | Export | Kind | Purpose |
169
+ | ----------------------------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
170
+ | `createViewAnchor(target, opts)` | function | Publish a `Placement`, with opt-in `followScroll` / `followGeometry` / `treatZeroAreaAsHidden` / `holdSelector` / `dedupe`, and `pulse()`. |
171
+ | `measurePlacement(target)` | function | Pure measurement: wraps the target rect as `{ visible: true, bounds }`. |
172
+ | `createSizeAnchor(target, opts)` | function | Call `publish({ axis, extent })` with one content-size axis. |
173
+ | `useViewAnchor(opts)` from `view-anchor/react` | hook | React adapter for `createViewAnchor`, returning a ref callback for a placeholder element. |
174
+ | `Bounds` | type | `{ x, y, width, height }` in CSS pixels. |
175
+ | `Placement` | type | `{ visible: true; bounds } \| { visible: false }`. |
176
+ | `Publisher<T>` / `PublishResult` | type | Signature of the `publish` callback and its return value (`void \| boolean`). |
177
+ | `ViewAnchorOptions` / `ViewAnchorHandle` | type | Options and handle for `createViewAnchor`. |
178
+ | `UseViewAnchorOptions` / `ViewAnchorRef` from `view-anchor/react` | type | Options and ref shape for `useViewAnchor`. |
179
+ | `SizeAxis` / `SizeMeasurement` | type | Axis and payload types for the reverse direction. |
180
+ | `SizeAnchorOptions` / `SizeAnchorHandle` | type | Options and handle for `createSizeAnchor`. |
181
+ | `view-anchor/protocol` | functions + types | Versioned messages, strict decoding, sequence guards, message publishers, and microtask batching. |
182
+
183
+ ## Versioning
184
+
185
+ Within 1.x, `view-anchor` does not remove or rename any public export from `.`, `view-anchor/react`, or `view-anchor/protocol`, and does not change the default behavior for input that is already valid. Minor releases add functionality; patch releases fix bugs. An interface slated for removal is deprecated first and only removed in 2.0.
202
186
 
203
187
  ## Documentation
204
188
 
205
- - [docs/mechanism.md](./docs/mechanism.md): how the forward direction works. Synchronous publishing, stale-frame safety, the `present` / zero-rect / unmount contract, StrictMode behaviour. Includes the interactive 3D demo at [docs/index.html](./docs/index.html).
189
+ - [docs/mechanism.md](./docs/mechanism.md): how the forward direction works. Synchronous publishing, default dedup and `dedupe: false`, the `Placement` / visibility / unmount contract, StrictMode behaviour. Includes the interactive 3D demo at [docs/index.html](./docs/index.html).
206
190
  - [docs/bidirectional-design.md](./docs/bidirectional-design.md): running both directions at once. Why the forward path is synchronous while the reverse path uses animation frames, single-axis ownership, and where the trust boundary sits.
207
191
  - [docs/protocol.md](./docs/protocol.md): message envelopes, validation, ordering, batching, and what happens on failure.
208
- - [docs/performance-report.md](./docs/performance-report.md): reproducible CPU, heap, RSS, extreme-case, V8, and export-size measurements.
192
+ - [docs/performance-report.md](./docs/performance-report.md): performance summary and export sizes.
209
193
 
210
194
  ## Contributing
211
195
 
212
- Issues and pull requests are welcome. Before submitting, run `pnpm lint`, `pnpm format:check`, `pnpm check-types`, `pnpm test`, and `pnpm build`. `pnpm benchmark` prints the data used to update the performance report.
196
+ Before submitting, run `pnpm lint`, `pnpm format:check`, `pnpm check-types`, `pnpm test`, and `pnpm build`. `pnpm benchmark` prints the data used to update the performance report.
213
197
 
214
- Changes that affect a published version must include a Changeset. Run `pnpm changeset`, select the version bump, and describe the user-visible change. Merging it into `main` opens a version PR; merging that PR publishes the package.
198
+ Changes that affect a published version must include a Changeset. Run `pnpm changeset`, select the version bump, and describe the user-visible change. Merging into `main` opens a version PR; merging that PR publishes the package.
215
199
 
216
200
  ## License
217
201
 
package/README.zh-CN.md CHANGED
@@ -7,27 +7,25 @@
7
7
  [![npm version](https://img.shields.io/npm/v/view-anchor)](https://www.npmjs.com/package/view-anchor)
8
8
  [![npm downloads](https://img.shields.io/npm/dm/view-anchor)](https://www.npmjs.com/package/view-anchor)
9
9
  [![License](https://img.shields.io/npm/l/view-anchor)](./LICENSE)
10
- [![Node](https://img.shields.io/badge/node-%3E%3D24-339933)](https://nodejs.org/)
11
10
 
12
11
  [English](./README.md) · [简体中文](./README.zh-CN.md)
13
12
 
14
- > [在线演示](https://lbb00.github.io/view-anchor/):拖动分栏、切换面板或 3D 视角,查看外部画面如何跟随占位元素。
13
+ > [在线演示](https://lbb00.github.io/view-anchor/):直接在倾斜的 3D 场景里拖动分隔条、滚动页面、点击按钮。画面 A 跟随占位元素;画面 B 把自己的内容高度报回页面,页面据此调整它的占位。
15
14
 
16
15
  ## 它解决什么问题
17
16
 
18
- 有些画面的位置不是由 CSS 直接控制:例如应用自己摆放的叠层、嵌入式文档,或能根据矩形定位的渲染表面。DOM 布局会移动占位元素,却不会自动更新这些画面的位置。
17
+ 有些画面的位置由应用自己摆放(叠层、嵌入式文档或能根据矩形定位的渲染表面)。DOM 布局会移动占位元素,但不会自动更新这些画面的位置。
19
18
 
20
- `view-anchor` 负责测量占位元素。元素尺寸变化或窗口大小变化时,它同步调用你提供的 `publish` 函数。函数怎么使用这个值由应用决定。
19
+ `view-anchor` 负责测量占位元素,在尺寸变化或窗口大小变化时同步调用你提供的 `publish` 函数。函数如何使用这个值由应用决定。
21
20
 
22
21
  ## `publish` 收到什么
23
22
 
24
- `publish` 不是内置传输通道,而是一个同步回调。不同接口会传入不同的普通对象:
23
+ `publish` 是一个同步回调,不同接口会传入不同的普通对象:
25
24
 
26
- - `createViewAnchor` 调用 `publish(bounds)`。`bounds` 是相对视口、取整后的 CSS 像素矩形:`{ x, y, width, height }`。`present` 变为 `false` 时,会发布一次全零矩形,然后停止观察。
27
- - `createPlacementAnchor` 在显示时调用 `publish({ visible: true, bounds })`,隐藏时调用 `publish({ visible: false })`。
28
- - `createSizeAdvertiser` 调用 `publish({ axis, extent })`。`axis` 为 `block`(高度)或 `inline`(宽度),`extent` 是取整后且不小于零的内容尺寸。
25
+ - `createViewAnchor` 在显示时调用 `publish({ visible: true, bounds })`(`bounds` 是相对视口、取整后的 CSS 像素矩形 `{ x, y, width, height }`),隐藏时调用 `publish({ visible: false })`。显式的 `visible` 用于区分“可见但恰好是 0×0”和“已隐藏或未挂载”。
26
+ - `createSizeAnchor` 调用 `publish({ axis, extent })`。`axis` `block`(高度)或 `inline`(宽度),`extent` 是目标元素在该轴上的 border-box 尺寸(包含 padding 和 border;浏览器不提供 border-box 时退回 content-box),单位 CSS 像素,取整且不小于零。
29
27
 
30
- 数据已接收或已经入队时,返回 `true` 或不返回值;当前不能接收时才返回 `false`。下一次测量会重试被拒绝的值。
28
+ 数据已接收或已经入队时,返回 `true` 或不返回值;当前不能接收时才返回 `false`,下一次测量会重试被拒绝的值。隐藏状态下不再测量,所以被拒绝的 `{ visible: false }` 不会自动重发,需要再调用一次 `update()`;`useViewAnchor` 会在卸载时替你补发。
31
29
 
32
30
  ## 安装
33
31
 
@@ -37,7 +35,7 @@ pnpm add view-anchor
37
35
  npm install view-anchor
38
36
  ```
39
37
 
40
- React 是可选 peer dependency。Hook `view-anchor/react` 导入;根入口为了兼容 `v0.1.2` 也会导出 `useViewAnchor`,因此导入根入口时需要安装 React。`view-anchor/protocol` 不需要 React
38
+ React 是可选 peer dependency,只有从 `view-anchor/react` 导入时才需要。根入口(`view-anchor`)和 `view-anchor/protocol` 不依赖 React,未安装 React 也能加载。
41
39
 
42
40
  ## 使用
43
41
 
@@ -46,29 +44,34 @@ React 是可选 peer dependency。Hook 从 `view-anchor/react` 导入;根入
46
44
  ```ts
47
45
  import { createViewAnchor } from 'view-anchor'
48
46
 
49
- const publish = (bounds) => {
50
- applyBoundsToExternalSurface(bounds)
47
+ const publish = (placement) => {
48
+ if (placement.visible) applyBoundsToExternalSurface(placement.bounds)
49
+ else hideExternalSurface()
51
50
  }
52
51
 
53
52
  const handle = createViewAnchor(placeholderEl, {
54
- present: true,
53
+ visible: true,
55
54
  publish,
55
+ followScroll: true, // 祖先容器滚动时重新测量
56
+ followGeometry: true, // 拖拽或滚动期间逐帧测量;稳定后停止
57
+ treatZeroAreaAsHidden: true, // 零面积或 display:none 时发布 { visible: false }
58
+ holdSelector: '[role="separator"]', // 默认值;命中该选择器的 pointerdown 按住期间保持 followGeometry 开启,传 null 关闭
59
+ dedupe: true, // 默认值;传 false 让每次测量都发布,即使值没变
56
60
  })
57
61
 
58
- handle.update({ present: true, publish }) // 更新选项并立即重新发布
62
+ handle.update({ visible: true, publish }) // 更新选项并立即重新发布
63
+ handle.pulse() // 位置变了但没有任何观察器能感知时(如一次类名切换让目标移动了但尺寸没变)主动重测;稳定后自动停止
59
64
  handle.dispose() // 停止观察;之后不会再发布
60
65
  ```
61
66
 
62
- 默认的 `createViewAnchor` 监听 `ResizeObserver` 和窗口 `resize`。如果祖先滚动、transform 或拖拽会移动元素而不改变它自身尺寸,请使用下面的 `createPlacementAnchor`,按需开启 `followScroll` 或 `followGeometry`。
67
+ `visible` 设为 `false` 会发布 `{ visible: false }` 并停止观察。你的 `publish` 函数可以把它解释为隐藏、移除,或保留外部画面。
63
68
 
64
- `present` 设为 `false` 会发布全零矩形并停止观察。你的 `publish` 函数可以把它解释为隐藏、移除,或保留外部画面。
65
-
66
- `createViewAnchor`、`createPlacementAnchor`、`createSizeAdvertiser` 和 `createGeometryBatcher` 都支持 `signal`。调用 `AbortController.abort()` 的清理效果等同于 `dispose()`;创建前已经 abort 的 signal 不会开始测量或安装监听。
69
+ `createViewAnchor`、`createSizeAnchor` `createGeometryBatcher` 都支持 `signal`。调用 `AbortController.abort()` 的清理效果等同于 `dispose()`;创建前已经 abort 的 signal 不会开始测量或安装监听。
67
70
 
68
71
  ```ts
69
72
  const controller = new AbortController()
70
73
  const handle = createViewAnchor(placeholderEl, {
71
- present: true,
74
+ visible: true,
72
75
  publish,
73
76
  signal: controller.signal,
74
77
  })
@@ -83,49 +86,35 @@ import { useViewAnchor } from 'view-anchor/react'
83
86
 
84
87
  function ExternalSurfaceContainer({ visible }: { visible: boolean }) {
85
88
  const ref = useViewAnchor({
86
- present: visible,
87
- publish: applyBoundsToExternalSurface,
89
+ visible,
90
+ publish: applyPlacementToExternalSurface,
91
+ followScroll: true,
92
+ followGeometry: true,
88
93
  })
89
94
 
90
95
  return <div ref={ref} className="h-full w-full" />
91
96
  }
92
97
  ```
93
98
 
94
- 元素卸载时,Hook 会先发布收起值,再释放监听。它兼容 React 18 和 19 的 StrictMode 重挂载。
95
-
96
- ### 显式可见性
97
-
98
- 全零矩形无法区分“已隐藏”和“可见但恰好是 0×0”。需要这个区分时使用 `Placement` API:
99
-
100
- ```ts
101
- import { createPlacementAnchor } from 'view-anchor'
102
-
103
- const handle = createPlacementAnchor(target, {
104
- visible: true,
105
- publish: applyPlacement,
106
- followScroll: true, // 祖先容器滚动时重新测量
107
- followGeometry: true, // 拖拽或滚动期间逐帧测量;稳定后停止
108
- guardDisplayNone: true, // 零面积或 display:none 时发布 { visible: false }
109
- })
99
+ 元素卸载时,Hook 会先发布 `{ visible: false }`,再释放监听。它兼容 React 18 和 19 的 StrictMode 重挂载,不会因此发布过期帧。
110
100
 
111
- handle.pulse() // 例如在 CSS 过渡期间主动打开一段逐帧测量
112
- ```
113
-
114
- React 版本为 `view-anchor/react` 导出的 `usePlacementAnchor`。
101
+ 它每次调用都会重新应用选项,规则与 `createViewAnchor` 和 `update()` 一致:省略任何一项都会重置为默认值,而不是沿用上次的值。省略 `treatZeroAreaAsHidden`、`followScroll` `followGeometry` 会重置为 `false`;省略 `holdSelector` 会重置为 `[role="separator"]`;省略 `dedupe` 会重置为 `true`。要关闭 `holdSelector` / `dedupe`,传 `null` / `false`。
115
102
 
116
103
  ### 由内容决定占位尺寸
117
104
 
118
- 当外部画面的内容尺寸需要反过来调整占位元素时,在内容所在的文档中使用 `createSizeAdvertiser`:
105
+ 当外部画面的内容尺寸需要反过来调整占位元素时,在内容所在的文档中使用 `createSizeAnchor`:
119
106
 
120
107
  ```ts
121
- import { createSizeAdvertiser } from 'view-anchor'
108
+ import { createSizeAnchor } from 'view-anchor'
122
109
 
123
- const handle = createSizeAdvertiser(contentWrapper, {
124
- axis: 'block', // 一个 advertiser 只负责一个轴:block = 高度,inline = 宽度
125
- publish: updatePlaceholderSize,
110
+ const publish = updatePlaceholderSize
111
+
112
+ const handle = createSizeAnchor(contentWrapper, {
113
+ axis: 'block', // 一个 size anchor 只负责一个轴:block = 高度,inline = 宽度
114
+ publish,
126
115
  })
127
116
 
128
- handle.update(updatePlaceholderSize) // 有当前尺寸时会立即重新报告
117
+ handle.update({ publish }) // 应用新选项并立即重新报告当前尺寸;省略 dedupe 会重置为 true
129
118
  handle.dispose()
130
119
  ```
131
120
 
@@ -133,7 +122,7 @@ handle.dispose()
133
122
 
134
123
  ### 需要传递、校验和排序时
135
124
 
136
- 核心只交付 `Bounds`、`Placement` 和 `AdvertisedSize`。如果应用需要把这些值交给别的页面、环境或异步通道,可选的 `view-anchor/protocol` 提供消息版本、输入校验、乱序过滤和微任务合并。
125
+ 核心只交付 `Bounds`、`Placement` 和 `SizeMeasurement`。如果应用需要把这些值交给别的页面、环境或异步通道,可选的 `view-anchor/protocol` 提供消息版本、输入校验、乱序过滤和微任务合并。
137
126
 
138
127
  ```ts
139
128
  import {
@@ -161,34 +150,40 @@ if (decoded.ok) {
161
150
 
162
151
  ## 性能边界
163
152
 
164
- - 相同的已发布矩形不会再次调用 `publish`。
153
+ - 默认去重:与上一次已发布的 `Placement`/尺寸完全相同不会再次调用 `publish`;传 `dedupe: false` 可以让每次测量都发布。
165
154
  - `followGeometry` 只在需要时启动动画帧轮询,稳定后停止。
166
155
  - `createGeometryBatcher` 会合并同一微任务内的更新;每个锚点的位置和尺寸各保留最新值。
167
156
  - `dispose()` 或 abort 会停止监听并释放长期持有的元素和回调引用。
168
157
 
169
- 性能数据和测量方式见 [性能报告](./docs/performance-report.md)。这些数据只用于比较同一台机器上的改动,不能代表浏览器布局、传递或应用业务的耗时。
158
+ 0.2.2 的对比和导出体积见 [性能报告](./docs/performance-report.md)
170
159
 
171
160
  ## API
172
161
 
173
- | 导出 | 类型 | 用途 |
174
- | ------------------------------------- | ----------- | -------------------------------------------------------------------------- |
175
- | `createViewAnchor(target, opts)` | 函数 | 测量 DOM 元素并调用 `publish({ x, y, width, height })`。全零矩形表示收起。 |
176
- | `createPlacementAnchor(target, opts)` | 函数 | 发布带可见性的 `Placement`,可选滚动和几何跟随。 |
177
- | `measurePlacement(target)` | 函数 | 读取当前矩形,返回 `{ visible: true, bounds }`。 |
178
- | `createSizeAdvertiser(target, opts)` | 函数 | `publish({ axis, extent })` 上报一个内容尺寸轴。 |
179
- | `useViewAnchor(opts)` | Hook | 返回占位元素的 ref 回调。 |
180
- | `usePlacementAnchor(opts)` | Hook | `Placement` API React 适配。 |
181
- | `Bounds` | 类型 | `{ x, y, width, height }`,单位为 CSS 像素。 |
182
- | `Placement` | 类型 | `{ visible: true; bounds } \| { visible: false }`。 |
183
- | `AdvertisedAxis` / `AdvertisedSize` | 类型 | 内容尺寸上报的轴和数据。 |
184
- | `view-anchor/protocol` | 函数 + 类型 | 消息封装、校验、排序和批处理。 |
162
+ | 导出 | 类型 | 用途 |
163
+ | ---------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------ |
164
+ | `createViewAnchor(target, opts)` | 函数 | 发布带可见性的 `Placement`,可选 `followScroll`、`followGeometry`、`treatZeroAreaAsHidden`、`holdSelector`、`dedupe`,含 `pulse()`。 |
165
+ | `measurePlacement(target)` | 函数 | 读取当前矩形,返回 `{ visible: true, bounds }`。 |
166
+ | `createSizeAnchor(target, opts)` | 函数 | `publish({ axis, extent })` 上报一个内容尺寸轴。 |
167
+ | `useViewAnchor(opts)` | Hook | `createViewAnchor` React 适配,返回占位元素的 ref 回调。 |
168
+ | `Bounds` | 类型 | `{ x, y, width, height }`,单位为 CSS 像素。 |
169
+ | `Placement` | 类型 | `{ visible: true; bounds } \| { visible: false }`。 |
170
+ | `Publisher<T>` / `PublishResult` | 类型 | `publish` 回调的签名及其返回值(`void \| boolean`)。 |
171
+ | `ViewAnchorOptions` / `ViewAnchorHandle` | 类型 | `createViewAnchor` 的选项与句柄。 |
172
+ | `UseViewAnchorOptions` / `ViewAnchorRef` | 类型 | `useViewAnchor` 的选项与 ref 类型。 |
173
+ | `SizeAxis` / `SizeMeasurement` | 类型 | 内容尺寸上报的轴和数据。 |
174
+ | `SizeAnchorOptions` / `SizeAnchorHandle` | 类型 | `createSizeAnchor` 的选项与句柄。 |
175
+ | `view-anchor/protocol` | 函数 + 类型 | 消息封装、校验、排序和批处理。 |
176
+
177
+ ## 版本承诺
178
+
179
+ 1.x 版本内,`view-anchor` 不会删除或重命名 `.`、`view-anchor/react`、`view-anchor/protocol` 的任何公开导出,也不会改变已有合法输入的默认行为。minor 版本只新增功能,patch 版本只修 bug。要移除的接口会先标记为废弃,等到 2.0 才真正删除。
185
180
 
186
181
  ## 文档
187
182
 
188
- - [机制说明](./docs/mechanism.md):正向测量的触发条件、收起和 React 生命周期。
183
+ - [机制说明](./docs/mechanism.md):正向测量的触发条件、默认去重与 `dedupe: false`、可见性与 React 生命周期。
189
184
  - [双向几何设计](./docs/bidirectional-design.md):内容尺寸反向调整占位元素时的单轴约束。
190
185
  - [通信协议](./docs/protocol.md):消息格式、校验、排序和批处理。
191
- - [性能报告](./docs/performance-report.md):可复现的性能数据和测量环境。
186
+ - [性能报告](./docs/performance-report.md):性能结论和导出体积。
192
187
 
193
188
  ## 参与贡献
194
189
 
package/dist/index.d.ts CHANGED
@@ -1,11 +1,9 @@
1
1
  /**
2
2
  * view-anchor: keeps an external surface aligned with a DOM element's geometry.
3
3
  */
4
- export { createViewAnchor, measurePlacement, createPlacementAnchor } from './view-anchor.js';
5
- export type { PlacementAnchorOptions, PlacementAnchorHandle } from './view-anchor.js';
6
- export type { Bounds, Placement, Publisher, PublishResult, ViewAnchorOptions, ViewAnchorHandle, } from './types.js';
7
- export { createSizeAdvertiser } from './size-advertiser.js';
8
- export { useViewAnchor } from './react.js';
9
- export type { AdvertisedAxis, AdvertisedSize, SizeAdvertiserOptions, SizeAdvertiserHandle, } from './types.js';
10
- export type { UseViewAnchorOptions, ViewAnchorRef } from './react.js';
4
+ export { createViewAnchor, measurePlacement } from './view-anchor.js';
5
+ export type { ViewAnchorOptions, ViewAnchorHandle } from './view-anchor.js';
6
+ export type { Bounds, Placement, Publisher, PublishResult } from './types.js';
7
+ export { createSizeAnchor } from './size-anchor.js';
8
+ export type { SizeAxis, SizeMeasurement, SizeAnchorOptions, SizeAnchorHandle } from './types.js';
11
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA;AAC5F,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAA;AACrF,YAAY,EACV,MAAM,EACN,SAAS,EACT,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAA;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC1C,YAAY,EACV,cAAc,EACd,cAAc,EACd,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,YAAY,CAAA;AACnB,YAAY,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACrE,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAC3E,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AACnD,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * view-anchor: keeps an external surface aligned with a DOM element's geometry.
3
3
  */
4
- export { createViewAnchor, measurePlacement, createPlacementAnchor } from './view-anchor.js';
5
- export { createSizeAdvertiser } from './size-advertiser.js';
6
- export { useViewAnchor } from './react.js';
4
+ export { createViewAnchor, measurePlacement } from './view-anchor.js';
5
+ export { createSizeAnchor } from './size-anchor.js';
@@ -1,7 +1,7 @@
1
- import type { AdvertisedSize, Placement, Publisher } from './types.js';
1
+ import type { SizeMeasurement, Placement, Publisher } from './types.js';
2
2
  import { type GeometryAddress, type GeometryBatch, type GeometryMessage } from './protocol-types.js';
3
- export type GeometrySend = Publisher<GeometryMessage>;
4
- export type GeometryBatchSend = Publisher<GeometryBatch>;
3
+ export type GeometryMessageSender = Publisher<GeometryMessage>;
4
+ export type GeometryBatchSender = Publisher<GeometryBatch>;
5
5
  export interface GeometryBatcherOptions {
6
6
  /** Observes every batch-delivery error, including explicit flushes; it must not throw. */
7
7
  onError?: (error: unknown) => void;
@@ -27,17 +27,15 @@ export interface GeometryBatcher {
27
27
  * recreating a publisher for the same address causes its messages to be dropped.
28
28
  * Increment `generation` when intentionally resetting the publisher.
29
29
  */
30
- export declare function createPlacementMessagePublisher(address: GeometryAddress, send: GeometrySend): (placement: Placement) => boolean;
30
+ export declare function createPlacementMessagePublisher(address: GeometryAddress, send: GeometryMessageSender): (placement: Placement) => boolean;
31
31
  /**
32
32
  * Wraps size updates in a versioned protocol message.
33
33
  * Follows the same stability rule: keep one publisher per `{ anchorId, generation }`,
34
34
  * and increment `generation` when resetting.
35
35
  */
36
- export declare function createSizeMessagePublisher(address: GeometryAddress, send: GeometrySend): (size: AdvertisedSize) => boolean;
36
+ export declare function createSizeMessagePublisher(address: GeometryAddress, send: GeometryMessageSender): (size: SizeMeasurement) => boolean;
37
37
  /**
38
- * Coalesces same-task messages without adding a rendering-frame delay. It owns
39
- * no authorization policy: callers must associate addresses with trusted
40
- * sources before accepting a delivered batch.
38
+ * Coalesces same-task messages without adding a rendering-frame delay.
41
39
  */
42
- export declare function createGeometryBatcher(send: GeometryBatchSend, options?: GeometryBatcherOptions): GeometryBatcher;
40
+ export declare function createGeometryBatcher(send: GeometryBatchSender, options?: GeometryBatcherOptions): GeometryBatcher;
43
41
  //# sourceMappingURL=protocol-publisher.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"protocol-publisher.d.ts","sourceRoot":"","sources":["../src/protocol-publisher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAEtE,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,eAAe,EAGrB,MAAM,qBAAqB,CAAA;AAE5B,MAAM,MAAM,YAAY,GAAG,SAAS,CAAC,eAAe,CAAC,CAAA;AACrD,MAAM,MAAM,iBAAiB,GAAG,SAAS,CAAC,aAAa,CAAC,CAAA;AAExD,MAAM,WAAW,sBAAsB;IACrC,0FAA0F;IAC1F,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IAClC,iFAAiF;IACjF,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,OAAO,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAA;IAC1C,4FAA4F;IAC5F,KAAK,IAAI,OAAO,CAAA;IAChB,gEAAgE;IAChE,KAAK,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,uEAAuE;IACvE,OAAO,IAAI,IAAI,CAAA;CAChB;AAED;;;;;;;;GAQG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,YAAY,GACjB,CAAC,SAAS,EAAE,SAAS,KAAK,OAAO,CAcnC;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,YAAY,GACjB,CAAC,IAAI,EAAE,cAAc,KAAK,OAAO,CAcnC;AAMD;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,iBAAiB,EACvB,OAAO,GAAE,sBAA2B,GACnC,eAAe,CAyKjB"}
1
+ {"version":3,"file":"protocol-publisher.d.ts","sourceRoot":"","sources":["../src/protocol-publisher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAEvE,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,eAAe,EAGrB,MAAM,qBAAqB,CAAA;AAE5B,MAAM,MAAM,qBAAqB,GAAG,SAAS,CAAC,eAAe,CAAC,CAAA;AAC9D,MAAM,MAAM,mBAAmB,GAAG,SAAS,CAAC,aAAa,CAAC,CAAA;AAE1D,MAAM,WAAW,sBAAsB;IACrC,0FAA0F;IAC1F,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IAClC,iFAAiF;IACjF,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,OAAO,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAA;IAC1C,4FAA4F;IAC5F,KAAK,IAAI,OAAO,CAAA;IAChB,gEAAgE;IAChE,KAAK,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,uEAAuE;IACvE,OAAO,IAAI,IAAI,CAAA;CAChB;AAED;;;;;;;;GAQG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,qBAAqB,GAC1B,CAAC,SAAS,EAAE,SAAS,KAAK,OAAO,CAcnC;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,eAAe,EACxB,IAAI,EAAE,qBAAqB,GAC1B,CAAC,IAAI,EAAE,eAAe,KAAK,OAAO,CAcpC;AAMD;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,mBAAmB,EACzB,OAAO,GAAE,sBAA2B,GACnC,eAAe,CAqKjB"}
@@ -46,9 +46,7 @@ export function createSizeMessagePublisher(address, send) {
46
46
  // keep the caller's transport closure (and whatever it captured) alive.
47
47
  const NOOP_SEND = () => false;
48
48
  /**
49
- * Coalesces same-task messages without adding a rendering-frame delay. It owns
50
- * no authorization policy: callers must associate addresses with trusted
51
- * sources before accepting a delivered batch.
49
+ * Coalesces same-task messages without adding a rendering-frame delay.
52
50
  */
53
51
  export function createGeometryBatcher(send, options = {}) {
54
52
  const anchors = new Map();
@@ -154,11 +152,7 @@ export function createGeometryBatcher(send, options = {}) {
154
152
  removeAbortListener = () => { };
155
153
  anchors.clear();
156
154
  pendingAnchors.clear();
157
- // Callers may still mutate the original options object after
158
- // construction (e.g. reassigning onError); flush() captures its own
159
- // reference before send() runs, so an in-flight error report keeps
160
- // reading that object even though dispose() drops the instance's
161
- // long-lived reference here.
155
+ // Drop the long-lived reference; flush() captures its own before send() runs.
162
156
  options = {};
163
157
  send = NOOP_SEND;
164
158
  };