view-anchor 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +118 -34
  2. package/README.zh-CN.md +128 -44
  3. package/dist/index.d.ts +4 -16
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +2 -14
  6. package/dist/measure-loop.d.ts +9 -28
  7. package/dist/measure-loop.d.ts.map +1 -1
  8. package/dist/measure-loop.js +37 -16
  9. package/dist/protocol-publisher.d.ts +41 -0
  10. package/dist/protocol-publisher.d.ts.map +1 -0
  11. package/dist/protocol-publisher.js +191 -0
  12. package/dist/protocol-types.d.ts +36 -0
  13. package/dist/protocol-types.d.ts.map +1 -0
  14. package/dist/protocol-types.js +10 -0
  15. package/dist/protocol.d.ts +35 -0
  16. package/dist/protocol.d.ts.map +1 -0
  17. package/dist/protocol.js +131 -0
  18. package/dist/react.d.ts +18 -30
  19. package/dist/react.d.ts.map +1 -1
  20. package/dist/react.js +125 -122
  21. package/dist/size-advertiser.d.ts +9 -14
  22. package/dist/size-advertiser.d.ts.map +1 -1
  23. package/dist/size-advertiser.js +20 -28
  24. package/dist/types.d.ts +29 -73
  25. package/dist/types.d.ts.map +1 -1
  26. package/dist/types.js +1 -15
  27. package/dist/view-anchor.d.ts +36 -77
  28. package/dist/view-anchor.d.ts.map +1 -1
  29. package/dist/view-anchor.js +206 -174
  30. package/docs/bidirectional-design.md +64 -96
  31. package/docs/{anchor-3d.html → index.html} +215 -73
  32. package/docs/mechanism.mdx +55 -49
  33. package/docs/performance-report.md +63 -0
  34. package/docs/protocol.md +79 -0
  35. package/package.json +30 -4
  36. package/src/index.ts +6 -15
  37. package/src/measure-loop.ts +36 -41
  38. package/src/protocol-publisher.ts +236 -0
  39. package/src/protocol-types.ts +43 -0
  40. package/src/protocol.ts +193 -0
  41. package/src/react.ts +186 -141
  42. package/src/size-advertiser.ts +24 -31
  43. package/src/types.ts +34 -79
  44. package/src/view-anchor.ts +228 -212
@@ -1,111 +1,70 @@
1
- import type { Placement, ViewAnchorOptions, ViewAnchorHandle } from './types.js';
1
+ import type { Placement, Publisher, ViewAnchorOptions, ViewAnchorHandle } from './types.js';
2
2
  /**
3
- * Create an anchor binding ONE native view's bounds to `target`'s geometry.
3
+ * Bind a native view or external surface to the geometry of `target`.
4
4
  *
5
- * Imperative core no React, no Electron. Behaviour:
6
- * - `present === true`: publish `target.getBoundingClientRect()` (x/y rounded,
7
- * width/height `Math.max(0, Math.round(...))`) immediately, then re-publish
8
- * SYNCHRONOUSLY on every `ResizeObserver` tick and window `resize`.
9
- * - `present === false`: publish `{0,0,0,0}` immediately; do not observe.
10
- * - `update(opts)`: re-apply synchronously.
11
- * - `dispose()`: stop observing, never publish again.
5
+ * - `present === true`: measures `target.getBoundingClientRect()` and publishes
6
+ * immediately, then re-measures synchronously on ResizeObserver and window resize.
7
+ * - `present === false`: publishes a zero rect ({ x: 0, y: 0, width: 0, height: 0 })
8
+ * and stops observing.
9
+ * - `update(opts)`: re-applies options immediately.
10
+ * - `dispose()`: stops observing and prevents any further publishes.
12
11
  *
13
- * Synchronous, NOT RAF-deferred: the native overlay is a cross-process
14
- * `WebContentsView` whose `setBounds` already lands ~1 compositor frame behind
15
- * the renderer's DOM paint (the two processes composite on different frames).
16
- * Deferring the measure+publish to a RAF stacked a SECOND frame on top — during
17
- * a height/splitter drag that read as the overlay visibly trailing the region
18
- * edge (worst when GROWING, where the not-yet-followed edge exposes background).
19
- * Publishing in the observer tick itself removes that self-inflicted frame and
20
- * leaves only the unavoidable cross-process frame (masked by matching the
21
- * placeholder/desk background colour). The anti-flood role the RAF used to play
22
- * — collapsing a burst of RO+resize ticks in one frame into one publish — is now
23
- * served by `lastPublished` dedup: a tick whose measured rect is byte-identical
24
- * to the last published one is dropped, so a continuous drag still emits at most
25
- * one publish per distinct rect.
26
- *
27
- * Teardown safety: there is no queued frame to outrun a state change — every
28
- * emit reads `disposed`/`present` synchronously, so a tick after
29
- * `update`/`dispose` can never write a stale rect over the live one.
12
+ * Synchronous publishing: measurement and publishing occur directly in the
13
+ * observer tick. Cross-process setBounds calls already have a compositor delay;
14
+ * adding requestAnimationFrame would add a second frame of visual lag during drag
15
+ * operations. High-frequency updates are deduplicated against the last accepted rect.
30
16
  */
31
17
  export declare function createViewAnchor(target: HTMLElement, opts: ViewAnchorOptions): ViewAnchorHandle;
32
18
  export interface PlacementAnchorOptions {
33
19
  /**
34
- * Caller's INTENT: should the native view be on-screen? `true`
35
- * publish the measured rect as `{ visible:true, bounds }`; `false`
36
- * publish `{ visible:false }`. Crucially, hiddenness comes from this
37
- * flag, not from a measured zero size — so a legitimately 0-sized but
38
- * visible target still publishes `visible:true`.
20
+ * Whether the native view should be visible. When true, publishes
21
+ * { visible: true, bounds }; when false, publishes { visible: false }.
39
22
  */
40
23
  visible: boolean;
41
- /** Receives each explicit Placement. Owns IPC → host. */
42
- publish: (placement: Placement) => void;
24
+ /** Receives each explicit Placement. */
25
+ publish: Publisher<Placement>;
43
26
  /**
44
- * Opt-in geometry detach. When true, a measured zero-area target (no
45
- * geometry box display:none / unmounted / unstable first layout) publishes
46
- * `{ visible:false }` (detach-but-keep) instead of `{ visible:true,
47
- * bounds:0×0 }`, and an IntersectionObserver is attached so a display:none
48
- * transition (which ResizeObserver does not report) re-publishes. Default
49
- * false keeps the legitimate 0×0-visible semantics.
27
+ * When true, targets with zero area (such as display: none or unmounted elements)
28
+ * publish { visible: false } instead of { visible: true, bounds: 0x0 }, and an
29
+ * IntersectionObserver tracks display: none transitions. Default is false.
30
+ * Sticky across update(): omitting it preserves the current setting.
50
31
  */
51
32
  guardDisplayNone?: boolean;
52
33
  /**
53
- * Opt-in capture-phase ancestor-scroll follow. When true, the anchor
54
- * listens for `scroll` on `window` in the CAPTURE phase (scroll events don't
55
- * bubble, but reach `window` while capturing), so an ancestor scroll
56
- * container scrolling the target re-measures and re-publishes. With
57
- * `followGeometry` off, the scroll callback does a single synchronous
58
- * `emit()`; with it on, the scroll OPENS the RAF sentinel window so the
59
- * follow tracks every frame of a scroll burst. Default false.
34
+ * When true, listens for capture-phase scroll events on window to re-measure
35
+ * when an ancestor container scrolls. Default is false.
36
+ * Sticky across update(): omitting it preserves the current setting.
60
37
  */
61
38
  followScroll?: boolean;
62
39
  /**
63
- * Opt-in windowed RAF geometry sentinel. Catches ancestor
64
- * transform / reflow moves that no DOM event reports. The sentinel is
65
- * NON-resident: it is OPENED on demand (a scroll burst, a `[role="separator"]`
66
- * splitter pointerdown, or an explicit `pulse()`), polls geometry once per
67
- * animation frame publishing IN-FRAME, and AUTO-CLOSES once the rect goes
68
- * steady (a few unchanged frames). While closed it schedules no frame, so the
69
- * static cost when idle is exactly zero. Default false.
40
+ * When true, polls geometry per animation frame during active motion (scrolls,
41
+ * splitter dragging, or pulse()) and auto-closes when steady. Zero idle overhead.
42
+ * Sticky across update(): omitting it preserves the current setting.
70
43
  */
71
44
  followGeometry?: boolean;
72
45
  }
73
46
  export interface PlacementAnchorHandle {
74
- /** Apply new options; re-publishes immediately (mirrors `createViewAnchor`). */
47
+ /**
48
+ * Apply new options and re-publish immediately.
49
+ * guardDisplayNone, followScroll, and followGeometry are sticky: omitting a flag
50
+ * preserves its current value. Pass an explicit false to disable one.
51
+ */
75
52
  update(opts: PlacementAnchorOptions): void;
76
53
  /** Stop observing; never publish again. */
77
54
  dispose(): void;
78
55
  /**
79
- * Open the RAF sentinel window (animation follow); auto-closes after going
80
- * steady or after `durationMs`. No-op when `followGeometry` is false.
56
+ * Open the animation frame sentinel window. Auto-closes once stable
57
+ * or after durationMs. No-op if followGeometry is false.
81
58
  */
82
59
  pulse(durationMs?: number): void;
83
60
  }
84
61
  /**
85
- * Pure measure: read `target`'s rect and wrap it as an explicit visible
86
- * Placement. Always `{ visible:true }` — hiddenness is a caller decision
87
- * (see `createPlacementAnchor`), so this never returns `{ visible:false }`
88
- * and never infers visibility from a 0 size. A collapsed (0×0) but present
89
- * element therefore yields `{ visible:true, bounds:{...,width:0,height:0} }`,
90
- * distinct from any hidden Placement.
62
+ * Read target's current rect and return { visible: true, bounds }.
63
+ * Does not infer visibility from dimensions.
91
64
  */
92
65
  export declare function measurePlacement(target: HTMLElement): Placement;
93
66
  /**
94
- * The explicit-Placement mirror of `createViewAnchor`. Same observer/dedup/
95
- * teardown machinery, but the sink receives a `Placement`:
96
- * - `visible === true` → publish `measurePlacement(target)` and re-publish
97
- * SYNCHRONOUSLY on every `ResizeObserver`/`resize` tick.
98
- * - `visible === false` → publish `{ visible:false }` (NOT a ZERO bounds);
99
- * do not observe.
100
- *
101
- * Dedup carries the discriminant (`samePlacement`), so a visibility flip is
102
- * never coalesced away.
103
- *
104
- * Opt-in `guardDisplayNone` (default false): when on, a measured zero-area
105
- * target (display:none / unmounted / unstable first layout) publishes
106
- * `{ visible:false }` instead of `{ visible:true, bounds:0×0 }`, and an
107
- * IntersectionObserver is attached so a display:none transition (which
108
- * ResizeObserver does not report) re-publishes.
67
+ * Explicit-visibility variant of createViewAnchor.
109
68
  */
110
69
  export declare function createPlacementAnchor(target: HTMLElement, opts: PlacementAnchorOptions): PlacementAnchorHandle;
111
70
  //# sourceMappingURL=view-anchor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"view-anchor.d.ts","sourceRoot":"","sources":["../src/view-anchor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,SAAS,EACT,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,YAAY,CAAA;AAwBnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,iBAAiB,GACtB,gBAAgB,CA6ElB;AASD,MAAM,WAAW,sBAAsB;IACrC;;;;;;OAMG;IACH,OAAO,EAAE,OAAO,CAAA;IAChB,yDAAyD;IACzD,OAAO,EAAE,CAAC,SAAS,EAAE,SAAS,KAAK,IAAI,CAAA;IACvC;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B;;;;;;;;OAQG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB;AAED,MAAM,WAAW,qBAAqB;IACpC,gFAAgF;IAChF,MAAM,CAAC,IAAI,EAAE,sBAAsB,GAAG,IAAI,CAAA;IAC1C,2CAA2C;IAC3C,OAAO,IAAI,IAAI,CAAA;IACf;;;OAGG;IACH,KAAK,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,CAM/D;AAiBD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,sBAAsB,GAC3B,qBAAqB,CAuPvB"}
1
+ {"version":3,"file":"view-anchor.d.ts","sourceRoot":"","sources":["../src/view-anchor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,SAAS,EACT,SAAS,EACT,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,YAAY,CAAA;AAmBnB;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,iBAAiB,GACtB,gBAAgB,CA6FlB;AAID,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAA;IAChB,wCAAwC;IACxC,OAAO,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;IAC7B;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB;AAED,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,MAAM,CAAC,IAAI,EAAE,sBAAsB,GAAG,IAAI,CAAA;IAC1C,2CAA2C;IAC3C,OAAO,IAAI,IAAI,CAAA;IACf;;;OAGG;IACH,KAAK,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,CAM/D;AAeD;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,sBAAsB,GAC3B,qBAAqB,CA2SvB"}