view-anchor 1.0.0-beta.1 → 1.0.0-beta.2
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 +38 -21
- package/README.zh-CN.md +34 -22
- package/dist/react.d.ts +21 -1
- package/dist/react.d.ts.map +1 -1
- package/dist/react.js +53 -7
- package/dist/view-anchor.d.ts +14 -19
- package/dist/view-anchor.d.ts.map +1 -1
- package/dist/view-anchor.js +82 -89
- package/docs/index.html +57 -70
- package/docs/mechanism.md +5 -3
- package/package.json +1 -1
- package/src/react.ts +92 -10
- package/src/view-anchor.ts +100 -105
package/README.md
CHANGED
|
@@ -68,15 +68,19 @@ const publish = (placement) => {
|
|
|
68
68
|
const handle = createViewAnchor(target, {
|
|
69
69
|
visible: true,
|
|
70
70
|
publish,
|
|
71
|
-
followScroll: true, // re-measure
|
|
72
|
-
followGeometry: true, // poll animation frames during scrolls /
|
|
71
|
+
followScroll: true, // re-measure on scroll (window capture fallback + ancestor listeners)
|
|
72
|
+
followGeometry: true, // poll animation frames during scrolls / pulse(), stop when steady
|
|
73
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
74
|
dedupe: true, // default; set false to receive every measurement, even unchanged ones
|
|
76
75
|
})
|
|
77
76
|
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
// update() applies new options; omitted fields reset to defaults (e.g. omitting
|
|
78
|
+
// followScroll resets it to false).
|
|
79
|
+
handle.update({ visible: true, publish, followScroll: true, followGeometry: true })
|
|
80
|
+
// pulse() opens frame-following for a limited duration; use it to track animation
|
|
81
|
+
// after a move no observer sees. For splitter drags, call pulse() after each
|
|
82
|
+
// pointermove that changes layout; a single pointerdown pulse stops once steady.
|
|
83
|
+
handle.pulse()
|
|
80
84
|
handle.dispose() // stop observing; never publishes again
|
|
81
85
|
```
|
|
82
86
|
|
|
@@ -94,7 +98,7 @@ controller.abort() // same cleanup as handle.dispose()
|
|
|
94
98
|
### React
|
|
95
99
|
|
|
96
100
|
```tsx
|
|
97
|
-
import { useViewAnchor } from 'view-anchor/react'
|
|
101
|
+
import { useViewAnchor, useSizeAnchor } from 'view-anchor/react'
|
|
98
102
|
|
|
99
103
|
function DebugPanel({ visible }: { visible: boolean }) {
|
|
100
104
|
const ref = useViewAnchor({
|
|
@@ -107,9 +111,20 @@ function DebugPanel({ visible }: { visible: boolean }) {
|
|
|
107
111
|
// sends { visible: false }, without deciding how the surface is stored.
|
|
108
112
|
return <div ref={ref} className="h-full w-full" />
|
|
109
113
|
}
|
|
114
|
+
|
|
115
|
+
function ContentSizer() {
|
|
116
|
+
const ref = useSizeAnchor({
|
|
117
|
+
axis: 'block', // report height; use 'inline' for width
|
|
118
|
+
publish: updatePlaceholderHeight,
|
|
119
|
+
})
|
|
120
|
+
// The ref attaches to the element whose content size drives the host placeholder.
|
|
121
|
+
return <div ref={ref}>{/* dynamic content */}</div>
|
|
122
|
+
}
|
|
110
123
|
```
|
|
111
124
|
|
|
112
|
-
|
|
125
|
+
For a splitter controlled by React, call `ref.pulse()` after each active pointer move updates the layout. The `useViewAnchor` ref remains a callback ref; its `pulse()` method forwards to the current handle and does nothing when detached or when `followGeometry` is off. A single pulse on pointerdown does not keep following through a pause.
|
|
126
|
+
|
|
127
|
+
Both hooks survive React 18 and 19 StrictMode double-mounting without publishing stale frames. They use `update()` semantics: an omitted option resets to its default, not the previous value. An omitted `treatZeroAreaAsHidden`, `followScroll`, or `followGeometry` counts as `false`; an omitted `dedupe` resets to `true`.
|
|
113
128
|
|
|
114
129
|
### Let content drive the size
|
|
115
130
|
|
|
@@ -165,20 +180,22 @@ The full contract is in [docs/protocol.md](./docs/protocol.md).
|
|
|
165
180
|
|
|
166
181
|
## API
|
|
167
182
|
|
|
168
|
-
| Export | Kind | Purpose
|
|
169
|
-
| ----------------------------------------------------------------- | ----------------- |
|
|
170
|
-
| `createViewAnchor(target, opts)` | function | Publish a `Placement`, with opt-in `followScroll` / `followGeometry` / `treatZeroAreaAsHidden` / `
|
|
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
|
-
| `
|
|
175
|
-
| `
|
|
176
|
-
| `
|
|
177
|
-
| `
|
|
178
|
-
| `
|
|
179
|
-
| `
|
|
180
|
-
| `
|
|
181
|
-
| `
|
|
183
|
+
| Export | Kind | Purpose |
|
|
184
|
+
| ----------------------------------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
185
|
+
| `createViewAnchor(target, opts)` | function | Publish a `Placement`, with opt-in `followScroll` / `followGeometry` / `treatZeroAreaAsHidden` / `dedupe`, and `pulse()`. |
|
|
186
|
+
| `measurePlacement(target)` | function | Pure measurement: wraps the target rect as `{ visible: true, bounds }`. |
|
|
187
|
+
| `createSizeAnchor(target, opts)` | function | Call `publish({ axis, extent })` with one content-size axis. |
|
|
188
|
+
| `useViewAnchor(opts)` from `view-anchor/react` | hook | React adapter for `createViewAnchor`, returning a ref callback for a placeholder element. |
|
|
189
|
+
| `useSizeAnchor(opts)` from `view-anchor/react` | hook | React adapter for `createSizeAnchor`, returning a ref callback for a content element. |
|
|
190
|
+
| `Bounds` | type | `{ x, y, width, height }` in CSS pixels. |
|
|
191
|
+
| `Placement` | type | `{ visible: true; bounds } \| { visible: false }`. |
|
|
192
|
+
| `Publisher<T>` / `PublishResult` | type | Signature of the `publish` callback and its return value (`void \| boolean`). |
|
|
193
|
+
| `ViewAnchorOptions` / `ViewAnchorHandle` | type | Options and handle for `createViewAnchor`. Handle includes `update()`, `pulse()`, and `dispose()`. |
|
|
194
|
+
| `UseViewAnchorOptions` / `ViewAnchorRef` from `view-anchor/react` | type | Options and callback ref for `useViewAnchor`; the ref also has `pulse()`. |
|
|
195
|
+
| `UseSizeAnchorOptions` / `SizeAnchorRef` from `view-anchor/react` | type | Options and ref shape for `useSizeAnchor`. |
|
|
196
|
+
| `SizeAxis` / `SizeMeasurement` | type | Axis and payload types for the reverse direction. |
|
|
197
|
+
| `SizeAnchorOptions` / `SizeAnchorHandle` | type | Options and handle for `createSizeAnchor`. Handle includes `update()` and `dispose()`. |
|
|
198
|
+
| `view-anchor/protocol` | functions + types | Versioned messages, strict decoding, sequence guards, message publishers, and microtask batching. |
|
|
182
199
|
|
|
183
200
|
## Versioning
|
|
184
201
|
|
package/README.zh-CN.md
CHANGED
|
@@ -52,15 +52,16 @@ const publish = (placement) => {
|
|
|
52
52
|
const handle = createViewAnchor(placeholderEl, {
|
|
53
53
|
visible: true,
|
|
54
54
|
publish,
|
|
55
|
-
followScroll: true, //
|
|
56
|
-
followGeometry: true, //
|
|
55
|
+
followScroll: true, // 滚动时重新测量(window 捕获阶段 + 祖先监听)
|
|
56
|
+
followGeometry: true, // 滚动或 pulse() 期间逐帧测量;稳定后停止
|
|
57
57
|
treatZeroAreaAsHidden: true, // 零面积或 display:none 时发布 { visible: false }
|
|
58
|
-
holdSelector: '[role="separator"]', // 默认值;命中该选择器的 pointerdown 按住期间保持 followGeometry 开启,传 null 关闭
|
|
59
58
|
dedupe: true, // 默认值;传 false 让每次测量都发布,即使值没变
|
|
60
59
|
})
|
|
61
60
|
|
|
62
|
-
|
|
63
|
-
handle.
|
|
61
|
+
// update() 应用新选项;省略的字段重置为默认值(例如省略 followScroll 会重置为 false)。
|
|
62
|
+
handle.update({ visible: true, publish, followScroll: true, followGeometry: true })
|
|
63
|
+
// pulse() 开启一段时间的帧跟踪;用于跟踪观察器无法感知的变化。分隔条拖拽时,每次 pointermove 更新布局后都要调用 pulse();仅在 pointerdown 调用一次会在几何稳定后停止跟随。
|
|
64
|
+
handle.pulse()
|
|
64
65
|
handle.dispose() // 停止观察;之后不会再发布
|
|
65
66
|
```
|
|
66
67
|
|
|
@@ -82,7 +83,7 @@ controller.abort()
|
|
|
82
83
|
### React
|
|
83
84
|
|
|
84
85
|
```tsx
|
|
85
|
-
import { useViewAnchor } from 'view-anchor/react'
|
|
86
|
+
import { useViewAnchor, useSizeAnchor } from 'view-anchor/react'
|
|
86
87
|
|
|
87
88
|
function ExternalSurfaceContainer({ visible }: { visible: boolean }) {
|
|
88
89
|
const ref = useViewAnchor({
|
|
@@ -94,11 +95,20 @@ function ExternalSurfaceContainer({ visible }: { visible: boolean }) {
|
|
|
94
95
|
|
|
95
96
|
return <div ref={ref} className="h-full w-full" />
|
|
96
97
|
}
|
|
98
|
+
|
|
99
|
+
function ContentSizer() {
|
|
100
|
+
const ref = useSizeAnchor({
|
|
101
|
+
axis: 'block', // 报告高度;使用 'inline' 报告宽度
|
|
102
|
+
publish: updatePlaceholderHeight,
|
|
103
|
+
})
|
|
104
|
+
// ref 附加到内容尺寸驱动宿主占位的元素
|
|
105
|
+
return <div ref={ref}>{/* 动态内容 */}</div>
|
|
106
|
+
}
|
|
97
107
|
```
|
|
98
108
|
|
|
99
|
-
|
|
109
|
+
React 分隔条每次拖动更新布局后可调用 `ref.pulse()`。`useViewAnchor` 返回的仍是回调 ref,额外提供 `pulse()`;元素未挂载或 `followGeometry` 关闭时调用不会起作用。只在按下时调用一次无法覆盖停顿后的移动。
|
|
100
110
|
|
|
101
|
-
|
|
111
|
+
两个 hook 都能在 React 18 和 19 的 StrictMode 重挂载中正常工作,不会发布过期帧。它们使用 `update()` 语义:省略的选项重置为默认值,而不是沿用上次的值。省略 `treatZeroAreaAsHidden`、`followScroll` 或 `followGeometry` 会重置为 `false`;省略 `dedupe` 会重置为 `true`。
|
|
102
112
|
|
|
103
113
|
### 由内容决定占位尺寸
|
|
104
114
|
|
|
@@ -159,20 +169,22 @@ if (decoded.ok) {
|
|
|
159
169
|
|
|
160
170
|
## API
|
|
161
171
|
|
|
162
|
-
| 导出 | 类型 | 用途
|
|
163
|
-
| ---------------------------------------- | ----------- |
|
|
164
|
-
| `createViewAnchor(target, opts)` | 函数 | 发布带可见性的 `Placement`,可选 `followScroll`、`followGeometry`、`treatZeroAreaAsHidden`、`
|
|
165
|
-
| `measurePlacement(target)` | 函数 | 读取当前矩形,返回 `{ visible: true, bounds }`。
|
|
166
|
-
| `createSizeAnchor(target, opts)` | 函数 | 用 `publish({ axis, extent })` 上报一个内容尺寸轴。
|
|
167
|
-
| `useViewAnchor(opts)` | Hook | `createViewAnchor` 的 React 适配,返回占位元素的 ref 回调。
|
|
168
|
-
| `
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
171
|
-
| `
|
|
172
|
-
| `
|
|
173
|
-
| `
|
|
174
|
-
| `
|
|
175
|
-
| `
|
|
172
|
+
| 导出 | 类型 | 用途 |
|
|
173
|
+
| ---------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
174
|
+
| `createViewAnchor(target, opts)` | 函数 | 发布带可见性的 `Placement`,可选 `followScroll`、`followGeometry`、`treatZeroAreaAsHidden`、`dedupe`,含 `pulse()`。 |
|
|
175
|
+
| `measurePlacement(target)` | 函数 | 读取当前矩形,返回 `{ visible: true, bounds }`。 |
|
|
176
|
+
| `createSizeAnchor(target, opts)` | 函数 | 用 `publish({ axis, extent })` 上报一个内容尺寸轴。 |
|
|
177
|
+
| `useViewAnchor(opts)` | Hook | `createViewAnchor` 的 React 适配,返回占位元素的 ref 回调。 |
|
|
178
|
+
| `useSizeAnchor(opts)` | Hook | `createSizeAnchor` 的 React 适配,返回内容元素的 ref 回调。 |
|
|
179
|
+
| `Bounds` | 类型 | `{ x, y, width, height }`,单位为 CSS 像素。 |
|
|
180
|
+
| `Placement` | 类型 | `{ visible: true; bounds } \| { visible: false }`。 |
|
|
181
|
+
| `Publisher<T>` / `PublishResult` | 类型 | `publish` 回调的签名及其返回值(`void \| boolean`)。 |
|
|
182
|
+
| `ViewAnchorOptions` / `ViewAnchorHandle` | 类型 | `createViewAnchor` 的选项与句柄。句柄包含 `update()`、`pulse()` 和 `dispose()`。 |
|
|
183
|
+
| `UseViewAnchorOptions` / `ViewAnchorRef` | 类型 | `useViewAnchor` 的选项与回调 ref 类型;ref 还提供 `pulse()`。 |
|
|
184
|
+
| `UseSizeAnchorOptions` / `SizeAnchorRef` | 类型 | `useSizeAnchor` 的选项与 ref 类型。 |
|
|
185
|
+
| `SizeAxis` / `SizeMeasurement` | 类型 | 内容尺寸上报的轴和数据。 |
|
|
186
|
+
| `SizeAnchorOptions` / `SizeAnchorHandle` | 类型 | `createSizeAnchor` 的选项与句柄。句柄包含 `update()` 和 `dispose()`。 |
|
|
187
|
+
| `view-anchor/protocol` | 函数 + 类型 | 消息封装、校验、排序和批处理。 |
|
|
176
188
|
|
|
177
189
|
## 版本承诺
|
|
178
190
|
|
package/dist/react.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ViewAnchorOptions } from './view-anchor.js';
|
|
2
|
+
import type { SizeAnchorOptions, SizeAnchorHandle, SizeAxis, SizeMeasurement } from './types.js';
|
|
3
|
+
export type { SizeAnchorOptions, SizeAnchorHandle, SizeAxis, SizeMeasurement };
|
|
2
4
|
export interface UseViewAnchorOptions extends ViewAnchorOptions {
|
|
3
5
|
/**
|
|
4
6
|
* Values that re-apply the anchor when changed. Keep this array's length
|
|
@@ -7,7 +9,25 @@ export interface UseViewAnchorOptions extends ViewAnchorOptions {
|
|
|
7
9
|
deps?: ReadonlyArray<unknown>;
|
|
8
10
|
}
|
|
9
11
|
/** Compatible with React 18's null callback and React 19's ref cleanup. */
|
|
10
|
-
|
|
12
|
+
type AnchorRef = (el: HTMLElement | null) => void | (() => void);
|
|
13
|
+
/** Callback ref with an imperative trigger for geometry changes React cannot observe. */
|
|
14
|
+
export type ViewAnchorRef = AnchorRef & {
|
|
15
|
+
pulse(durationMs?: number): void;
|
|
16
|
+
};
|
|
11
17
|
/** Bind the explicit Placement API to a DOM element callback ref. */
|
|
12
18
|
export declare function useViewAnchor(options: UseViewAnchorOptions): ViewAnchorRef;
|
|
19
|
+
export interface UseSizeAnchorOptions extends Omit<SizeAnchorOptions, 'signal'> {
|
|
20
|
+
/**
|
|
21
|
+
* Values that re-apply the anchor when changed. Keep this array's length
|
|
22
|
+
* stable across renders.
|
|
23
|
+
*/
|
|
24
|
+
deps?: ReadonlyArray<unknown>;
|
|
25
|
+
}
|
|
26
|
+
/** Compatible with React 18's null callback and React 19's ref cleanup. */
|
|
27
|
+
export type SizeAnchorRef = AnchorRef;
|
|
28
|
+
/**
|
|
29
|
+
* Bind a size anchor to a DOM element callback ref. Reports the target's
|
|
30
|
+
* content size on the specified axis back to the provided publish callback.
|
|
31
|
+
*/
|
|
32
|
+
export declare function useSizeAnchor(options: UseSizeAnchorOptions): SizeAnchorRef;
|
|
13
33
|
//# sourceMappingURL=react.d.ts.map
|
package/dist/react.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAEzD,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D;;;OAGG;IACH,IAAI,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;CAC9B;AAED,2EAA2E;AAC3E,
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAEzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEhG,YAAY,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAA;AAE9E,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D;;;OAGG;IACH,IAAI,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;CAC9B;AAED,2EAA2E;AAC3E,KAAK,SAAS,GAAG,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,CAAA;AAEhE,yFAAyF;AACzF,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG;IAAE,KAAK,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAA;AA8K5E,qEAAqE;AACrE,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CAwB1E;AAMD,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAAC,iBAAiB,EAAE,QAAQ,CAAC;IAC7E;;;OAGG;IACH,IAAI,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;CAC9B;AAED,2EAA2E;AAC3E,MAAM,MAAM,aAAa,GAAG,SAAS,CAAA;AA0BrC;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CAM1E"}
|
package/dist/react.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { useCallback, useEffect, useInsertionEffect, useRef, version } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useInsertionEffect, useMemo, useRef, version } from 'react';
|
|
2
2
|
import { createViewAnchor } from './view-anchor.js';
|
|
3
|
+
import { createSizeAnchor } from './size-anchor.js';
|
|
3
4
|
// Callback refs own the imperative anchor because React invokes them during commit.
|
|
4
5
|
// React 19 may call the cleanup returned from a ref and immediately reattach the
|
|
5
6
|
// same element in development mode. Collapse is deferred by one microtask so that
|
|
@@ -86,9 +87,20 @@ function useAnchorRef(options, applied, adapter) {
|
|
|
86
87
|
return;
|
|
87
88
|
appliedRef.current = applied;
|
|
88
89
|
const handle = handleRef.current;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
const element = elementRef.current;
|
|
91
|
+
if (handle && element) {
|
|
92
|
+
const adapter = adapterRef.current;
|
|
93
|
+
const prevOpts = lastAppliedOptionsRef.current;
|
|
94
|
+
const nextOpts = optionsRef.current;
|
|
95
|
+
// Check if we need to recreate the handle (e.g. axis change in size anchor)
|
|
96
|
+
if (adapter.shouldRecreate?.(prevOpts, nextOpts)) {
|
|
97
|
+
handle.dispose();
|
|
98
|
+
handleRef.current = adapter.create(element, nextOpts);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
adapter.update(handle, nextOpts);
|
|
102
|
+
}
|
|
103
|
+
lastAppliedOptionsRef.current = nextOpts;
|
|
92
104
|
}
|
|
93
105
|
// oxlint-disable-next-line react/exhaustive-deps
|
|
94
106
|
}, applied);
|
|
@@ -100,7 +112,7 @@ function useAnchorRef(options, applied, adapter) {
|
|
|
100
112
|
deferDetach(element);
|
|
101
113
|
};
|
|
102
114
|
}, []);
|
|
103
|
-
return ref;
|
|
115
|
+
return { ref, handleRef };
|
|
104
116
|
}
|
|
105
117
|
const hideStates = new WeakMap();
|
|
106
118
|
const trackHide = (options, state) => ({
|
|
@@ -135,14 +147,48 @@ const viewAdapter = {
|
|
|
135
147
|
};
|
|
136
148
|
/** Bind the explicit Placement API to a DOM element callback ref. */
|
|
137
149
|
export function useViewAnchor(options) {
|
|
138
|
-
|
|
150
|
+
const { ref: attach, handleRef } = useAnchorRef(options, [
|
|
139
151
|
options.visible,
|
|
140
152
|
options.publish,
|
|
141
153
|
options.treatZeroAreaAsHidden,
|
|
142
154
|
options.followScroll,
|
|
143
155
|
options.followGeometry,
|
|
144
|
-
options.holdSelector,
|
|
145
156
|
options.dedupe,
|
|
146
157
|
...(options.deps ?? []),
|
|
147
158
|
], viewAdapter);
|
|
159
|
+
const pulse = useCallback((durationMs) => handleRef.current?.pulse(durationMs), [handleRef]);
|
|
160
|
+
// The new callback captures the handle, but only reads it when pulse() is called.
|
|
161
|
+
return useMemo(
|
|
162
|
+
// oxlint-disable-next-line react/refs
|
|
163
|
+
() => Object.assign((element) => attach(element), { pulse }), [attach, pulse]);
|
|
164
|
+
}
|
|
165
|
+
const sizeAdapter = {
|
|
166
|
+
create(target, options) {
|
|
167
|
+
return createSizeAnchor(target, options);
|
|
168
|
+
},
|
|
169
|
+
update(handle, options) {
|
|
170
|
+
// Options are forwarded as-is; the hook applies the same "omitted resets
|
|
171
|
+
// to default" rule as the core's update().
|
|
172
|
+
handle.update(options);
|
|
173
|
+
},
|
|
174
|
+
collapse(_handle, _options) {
|
|
175
|
+
// Size anchors do not have a "hidden" state to collapse to; they simply
|
|
176
|
+
// stop publishing when disposed. No action needed here.
|
|
177
|
+
},
|
|
178
|
+
isCollapsed(_handle, _options) {
|
|
179
|
+
// Size anchors are never in a "collapsed" state that needs to be sent
|
|
180
|
+
// before unmount; always return true to skip the collapse call.
|
|
181
|
+
return true;
|
|
182
|
+
},
|
|
183
|
+
shouldRecreate(prevOptions, nextOptions) {
|
|
184
|
+
// axis is frozen at creation; changing it requires a new handle
|
|
185
|
+
return prevOptions.axis !== nextOptions.axis;
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
/**
|
|
189
|
+
* Bind a size anchor to a DOM element callback ref. Reports the target's
|
|
190
|
+
* content size on the specified axis back to the provided publish callback.
|
|
191
|
+
*/
|
|
192
|
+
export function useSizeAnchor(options) {
|
|
193
|
+
return useAnchorRef(options, [options.axis, options.publish, options.dedupe, ...(options.deps ?? [])], sizeAdapter).ref;
|
|
148
194
|
}
|
package/dist/view-anchor.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Placement, Publisher } from './types.js';
|
|
2
|
-
export declare const DEFAULT_HOLD_SELECTOR = "[role=\"separator\"]";
|
|
3
2
|
export interface ViewAnchorOptions {
|
|
4
3
|
/**
|
|
5
4
|
* Whether the native view should be visible. When true, publishes
|
|
@@ -23,27 +22,23 @@ export interface ViewAnchorOptions {
|
|
|
23
22
|
*/
|
|
24
23
|
treatZeroAreaAsHidden?: boolean;
|
|
25
24
|
/**
|
|
26
|
-
* When true,
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* When true, re-measures on scroll events. Uses a hybrid approach:
|
|
26
|
+
* - A capture-phase window scroll listener provides a reliable fallback that
|
|
27
|
+
* catches all scrolls (including overflow:hidden + programmatic scrollLeft/Top,
|
|
28
|
+
* and works even before the element is connected or after reparenting).
|
|
29
|
+
* - Scrollable ancestor listeners catch scrolls in detached subtrees or shadow
|
|
30
|
+
* roots that do not reach window. Events reaching window are handled there
|
|
31
|
+
* only once, even when dedupe is disabled.
|
|
32
|
+
* Ancestors are re-collected on update().
|
|
33
|
+
* Default is false. update() applies the same default: omitting it resets to false.
|
|
29
34
|
*/
|
|
30
35
|
followScroll?: boolean;
|
|
31
36
|
/**
|
|
32
|
-
* When true, polls geometry per animation frame during active motion (scrolls
|
|
33
|
-
*
|
|
37
|
+
* When true, polls geometry per animation frame during active motion (scrolls
|
|
38
|
+
* or pulse()) and auto-closes when steady.
|
|
34
39
|
* update() applies the same default: omitting it resets to false.
|
|
35
40
|
*/
|
|
36
41
|
followGeometry?: boolean;
|
|
37
|
-
/**
|
|
38
|
-
* CSS selector for the press-and-hold target that keeps followGeometry open
|
|
39
|
-
* for the duration of a pointer press. Only meaningful when followGeometry
|
|
40
|
-
* is true; the pointer listeners are mounted only while both are set. A capture-phase pointerdown whose
|
|
41
|
-
* target matches `closest(holdSelector)` opens frame following until release.
|
|
42
|
-
* Default is `[role="separator"]`. Pass null to disable. update() applies
|
|
43
|
-
* the same default when omitted. An invalid selector throws synchronously
|
|
44
|
-
* (SyntaxError) before any option is applied.
|
|
45
|
-
*/
|
|
46
|
-
holdSelector?: string | null;
|
|
47
42
|
/**
|
|
48
43
|
* When true (the default), a measurement identical to the last accepted
|
|
49
44
|
* Placement is not published again. When false, every usable frame publishes
|
|
@@ -58,7 +53,7 @@ export interface ViewAnchorHandle {
|
|
|
58
53
|
/**
|
|
59
54
|
* Apply a full set of options and re-publish immediately. Omitted options
|
|
60
55
|
* reset to defaults: treatZeroAreaAsHidden/followScroll/followGeometry → false,
|
|
61
|
-
*
|
|
56
|
+
* dedupe → true.
|
|
62
57
|
* `signal` is not accepted here — it cannot be changed after creation.
|
|
63
58
|
*/
|
|
64
59
|
update(opts: Omit<ViewAnchorOptions, 'signal'>): void;
|
|
@@ -68,8 +63,8 @@ export interface ViewAnchorHandle {
|
|
|
68
63
|
* Open a frame-following window. Auto-closes once stable
|
|
69
64
|
* or after durationMs. No-op if followGeometry is false.
|
|
70
65
|
* durationMs is an upper bound, not a minimum: the window closes as soon as
|
|
71
|
-
* two frames measure the same rect, so a transition that starts
|
|
72
|
-
* (sub-pixel movement in its first frames) may not be followed to the end.
|
|
66
|
+
* two consecutive frames measure the same rect, so a transition that starts
|
|
67
|
+
* slowly (sub-pixel movement in its first frames) may not be followed to the end.
|
|
73
68
|
*/
|
|
74
69
|
pulse(durationMs?: number): void;
|
|
75
70
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"view-anchor.d.ts","sourceRoot":"","sources":["../src/view-anchor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"view-anchor.d.ts","sourceRoot":"","sources":["../src/view-anchor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAgB9D,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,OAAO,EAAE,OAAO,CAAA;IAChB,wCAAwC;IACxC,OAAO,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;IAC7B;;;OAGG;IACH,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B;;;;;;;;;;OAUG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;;OAKG;IACH,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,iBAAiB,EAAE,QAAQ,CAAC,GAAG,IAAI,CAAA;IACrD,2CAA2C;IAC3C,OAAO,IAAI,IAAI,CAAA;IACf;;;;;;OAMG;IACH,KAAK,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,CAM/D;AAyDD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,iBAAiB,GAAG,gBAAgB,CAuT/F"}
|