view-anchor 0.2.0 → 0.2.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/README.md +32 -44
- package/README.zh-CN.md +32 -44
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/measure-loop.d.ts.map +1 -1
- package/dist/measure-loop.js +22 -3
- package/dist/protocol-publisher.d.ts.map +1 -1
- package/dist/protocol-publisher.js +19 -3
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +2 -5
- package/dist/react.d.ts.map +1 -1
- package/dist/size-advertiser.d.ts.map +1 -1
- package/dist/size-advertiser.js +9 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/view-anchor.d.ts.map +1 -1
- package/dist/view-anchor.js +29 -12
- package/docs/bidirectional-design.md +19 -15
- package/docs/index.html +413 -398
- package/docs/{mechanism.mdx → mechanism.md} +1 -10
- package/docs/performance-report.md +30 -30
- package/docs/protocol.md +31 -2
- package/package.json +9 -12
- package/src/index.ts +2 -9
- package/src/measure-loop.ts +22 -3
- package/src/protocol-publisher.ts +21 -3
- package/src/protocol.ts +8 -20
- package/src/react.ts +3 -12
- package/src/size-advertiser.ts +9 -0
- package/src/types.ts +1 -3
- package/src/view-anchor.ts +41 -34
package/dist/view-anchor.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
const ZERO = { x: 0, y: 0, width: 0, height: 0 };
|
|
2
|
+
// Replaces a disposed instance's publish callback so a retained handle does
|
|
3
|
+
// not keep the caller's original callback (and whatever it captured) alive.
|
|
4
|
+
const NOOP_PUBLISH = () => false;
|
|
2
5
|
// Round to integer pixels. Width and height are clamped to >= 0 (0 represents
|
|
3
6
|
// a collapsed rect). Coordinates (x, y) can be negative when an element is
|
|
4
7
|
// scrolled out of view; clamping them to 0 would pin the view to the screen edge.
|
|
@@ -27,13 +30,16 @@ export function createViewAnchor(target, opts) {
|
|
|
27
30
|
let present = opts.present;
|
|
28
31
|
let publish = opts.publish;
|
|
29
32
|
let observer = null;
|
|
33
|
+
// Local, clearable alias for the `target` parameter so dispose() can drop
|
|
34
|
+
// the strong reference without widening the public parameter's type.
|
|
35
|
+
let targetRef = target;
|
|
30
36
|
// Last rect sent to publish. Reset on apply() so state changes (such as zoom)
|
|
31
37
|
// force a re-publish even if the geometry did not change.
|
|
32
38
|
let lastPublished = null;
|
|
33
39
|
let publicationRevision = 0;
|
|
34
40
|
let disposed = false;
|
|
35
41
|
const measure = () => {
|
|
36
|
-
const r =
|
|
42
|
+
const r = targetRef.getBoundingClientRect();
|
|
37
43
|
// Drop ticks with non-finite values (NaN / Infinity cannot be sent over IPC).
|
|
38
44
|
if (!Number.isFinite(r.left) ||
|
|
39
45
|
!Number.isFinite(r.top) ||
|
|
@@ -49,12 +55,14 @@ export function createViewAnchor(target, opts) {
|
|
|
49
55
|
lastPublished = candidate;
|
|
50
56
|
try {
|
|
51
57
|
const accepted = publish(candidate) !== false;
|
|
52
|
-
|
|
58
|
+
// A reentrant dispose() during publish() already cleared lastPublished;
|
|
59
|
+
// do not resurrect the pre-dispose value over that terminal state.
|
|
60
|
+
if (!accepted && publicationRevision === attempt && !disposed)
|
|
53
61
|
lastPublished = previous;
|
|
54
62
|
return accepted;
|
|
55
63
|
}
|
|
56
64
|
catch (error) {
|
|
57
|
-
if (publicationRevision === attempt)
|
|
65
|
+
if (publicationRevision === attempt && !disposed)
|
|
58
66
|
lastPublished = previous;
|
|
59
67
|
throw error;
|
|
60
68
|
}
|
|
@@ -75,7 +83,7 @@ export function createViewAnchor(target, opts) {
|
|
|
75
83
|
if (observer)
|
|
76
84
|
return;
|
|
77
85
|
observer = new ResizeObserver(emit);
|
|
78
|
-
observer.observe(
|
|
86
|
+
observer.observe(targetRef);
|
|
79
87
|
window.addEventListener('resize', emit);
|
|
80
88
|
};
|
|
81
89
|
const stopObserving = () => {
|
|
@@ -114,6 +122,9 @@ export function createViewAnchor(target, opts) {
|
|
|
114
122
|
return;
|
|
115
123
|
disposed = true;
|
|
116
124
|
stopObserving();
|
|
125
|
+
targetRef = null;
|
|
126
|
+
publish = NOOP_PUBLISH;
|
|
127
|
+
lastPublished = null;
|
|
117
128
|
},
|
|
118
129
|
};
|
|
119
130
|
}
|
|
@@ -148,6 +159,9 @@ export function createPlacementAnchor(target, opts) {
|
|
|
148
159
|
let guardDisplayNone = opts.guardDisplayNone ?? false;
|
|
149
160
|
let followScroll = opts.followScroll ?? false;
|
|
150
161
|
let followGeometry = opts.followGeometry ?? false;
|
|
162
|
+
// Local, clearable alias for the `target` parameter so dispose() can drop
|
|
163
|
+
// the strong reference without widening the public parameter's type.
|
|
164
|
+
let targetRef = target;
|
|
151
165
|
let observer = null;
|
|
152
166
|
let io = null;
|
|
153
167
|
let scrollListening = false;
|
|
@@ -171,16 +185,14 @@ export function createPlacementAnchor(target, opts) {
|
|
|
171
185
|
let activePointerId = null;
|
|
172
186
|
let sentinelDeadline = null;
|
|
173
187
|
const computePlacement = () => {
|
|
174
|
-
const p = measurePlacement(
|
|
188
|
+
const p = measurePlacement(targetRef);
|
|
175
189
|
if (p.visible &&
|
|
176
190
|
(!Number.isFinite(p.bounds.x) ||
|
|
177
191
|
!Number.isFinite(p.bounds.y) ||
|
|
178
192
|
!Number.isFinite(p.bounds.width) ||
|
|
179
193
|
!Number.isFinite(p.bounds.height)))
|
|
180
194
|
return null;
|
|
181
|
-
if (guardDisplayNone &&
|
|
182
|
-
p.visible &&
|
|
183
|
-
(p.bounds.width === 0 || p.bounds.height === 0)) {
|
|
195
|
+
if (guardDisplayNone && p.visible && (p.bounds.width === 0 || p.bounds.height === 0)) {
|
|
184
196
|
return { visible: false };
|
|
185
197
|
}
|
|
186
198
|
return p;
|
|
@@ -191,12 +203,14 @@ export function createPlacementAnchor(target, opts) {
|
|
|
191
203
|
lastPublished = candidate;
|
|
192
204
|
try {
|
|
193
205
|
const accepted = publish(candidate) !== false;
|
|
194
|
-
|
|
206
|
+
// A reentrant dispose() during publish() already cleared lastPublished;
|
|
207
|
+
// do not resurrect the pre-dispose value over that terminal state.
|
|
208
|
+
if (!accepted && publicationRevision === attempt && !disposed)
|
|
195
209
|
lastPublished = previous;
|
|
196
210
|
return accepted;
|
|
197
211
|
}
|
|
198
212
|
catch (error) {
|
|
199
|
-
if (publicationRevision === attempt)
|
|
213
|
+
if (publicationRevision === attempt && !disposed)
|
|
200
214
|
lastPublished = previous;
|
|
201
215
|
throw error;
|
|
202
216
|
}
|
|
@@ -328,7 +342,7 @@ export function createPlacementAnchor(target, opts) {
|
|
|
328
342
|
const startOptionalObserving = () => {
|
|
329
343
|
if (guardDisplayNone && !io && typeof IntersectionObserver !== 'undefined') {
|
|
330
344
|
io = new IntersectionObserver(emit);
|
|
331
|
-
io.observe(
|
|
345
|
+
io.observe(targetRef);
|
|
332
346
|
}
|
|
333
347
|
if (followScroll && !scrollListening) {
|
|
334
348
|
window.addEventListener('scroll', onScroll, passiveCapture);
|
|
@@ -382,7 +396,7 @@ export function createPlacementAnchor(target, opts) {
|
|
|
382
396
|
if (observer)
|
|
383
397
|
return;
|
|
384
398
|
observer = new ResizeObserver(emit);
|
|
385
|
-
observer.observe(
|
|
399
|
+
observer.observe(targetRef);
|
|
386
400
|
window.addEventListener('resize', emit);
|
|
387
401
|
startOptionalObserving();
|
|
388
402
|
};
|
|
@@ -431,6 +445,9 @@ export function createPlacementAnchor(target, opts) {
|
|
|
431
445
|
return;
|
|
432
446
|
disposed = true;
|
|
433
447
|
stopObserving();
|
|
448
|
+
targetRef = null;
|
|
449
|
+
publish = NOOP_PUBLISH;
|
|
450
|
+
lastPublished = null;
|
|
434
451
|
},
|
|
435
452
|
pulse(durationMs) {
|
|
436
453
|
if (disposed || !followGeometry)
|
|
@@ -79,21 +79,25 @@ export function createSizeAdvertiser(
|
|
|
79
79
|
- 下游的 `createSizeAdvertiser` 将内容尺寸通知宿主,宿主更新占位元素的高度。
|
|
80
80
|
- 宿主的 `createViewAnchor` 监听占位元素的矩形变化,将新位置同步给外部视图。
|
|
81
81
|
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
[
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
82
|
+
```mermaid
|
|
83
|
+
flowchart LR
|
|
84
|
+
subgraph DOWN["下游渲染进程(如工具栏页面)"]
|
|
85
|
+
C["内容容器<br/>高度由自身内容决定"]
|
|
86
|
+
end
|
|
87
|
+
subgraph HOST["宿主渲染进程"]
|
|
88
|
+
H["宿主消息处理器<br/>校验来源、clamp 数值"]
|
|
89
|
+
DIV["占位 div"]
|
|
90
|
+
VA["createViewAnchor"]
|
|
91
|
+
end
|
|
92
|
+
subgraph MAIN["宿主主进程"]
|
|
93
|
+
NV["WebContentsView / 原生视图"]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
C -->|"① publish(size)"| H
|
|
97
|
+
H -->|"② clamp 后写入 style.height"| DIV
|
|
98
|
+
DIV -->|"ResizeObserver 观测"| VA
|
|
99
|
+
VA -->|"③ 测出新矩形,publish(bounds)"| NV
|
|
100
|
+
NV -->|"④ setBounds 后下游视口变化,内容重排"| C
|
|
97
101
|
```
|
|
98
102
|
|
|
99
103
|
1. **下游**:通过 `createSizeAdvertiser` 测量高度并通过 IPC 发给宿主。
|