@sigx/lynx-runtime 0.4.3 → 0.4.5
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.
|
@@ -5,6 +5,16 @@ import type { SharedValue } from './shared-value.js';
|
|
|
5
5
|
export type { BuiltinMapperName, MapperParams };
|
|
6
6
|
/** Reset hook for tests. */
|
|
7
7
|
export declare function resetAnimatedStyleBindingIds(): void;
|
|
8
|
+
/**
|
|
9
|
+
* Reactive binding spec — the `{ sv, mapperName, params }` triple an accessor
|
|
10
|
+
* returns to describe the binding that should currently be live, or `null`
|
|
11
|
+
* for "no binding right now".
|
|
12
|
+
*/
|
|
13
|
+
export interface AnimatedStyleSpec {
|
|
14
|
+
sv: SharedValue<unknown>;
|
|
15
|
+
mapperName: BuiltinMapperName;
|
|
16
|
+
params?: MapperParams[BuiltinMapperName];
|
|
17
|
+
}
|
|
8
18
|
/**
|
|
9
19
|
* Bind a `MainThreadRef`'s element style to a `SharedValue` via a named
|
|
10
20
|
* mapper. The MT-side runtime applies the mapper's output via
|
|
@@ -23,7 +33,24 @@ export declare function resetAnimatedStyleBindingIds(): void;
|
|
|
23
33
|
* ALL of that element's bindings re-run so partial outputs don't drop the
|
|
24
34
|
* unchanged-axis contribution.
|
|
25
35
|
*
|
|
26
|
-
*
|
|
36
|
+
* Two call shapes:
|
|
37
|
+
*
|
|
38
|
+
* - **Static** `useAnimatedStyle(ref, sv, mapperName, params)` — registers the
|
|
39
|
+
* binding once at setup and unregisters at unmount. Use this when the
|
|
40
|
+
* binding never changes for the element's lifetime (the common case:
|
|
41
|
+
* Draggable, Swipeable, drawer offsets, …).
|
|
42
|
+
*
|
|
43
|
+
* - **Reactive** `useAnimatedStyle(ref, () => spec | null)` — runs the
|
|
44
|
+
* accessor in an `effect` and (re)binds whenever the returned spec changes,
|
|
45
|
+
* or unbinds when it returns `null`. The binding always targets the *same*
|
|
46
|
+
* element, so the host element (and its subtree) stays mounted across
|
|
47
|
+
* rebinds. Use this when a single element transitions between animation
|
|
48
|
+
* states at runtime — e.g. a navigator layer that animates during a
|
|
49
|
+
* transition then settles to a static rest position. Re-binding on the
|
|
50
|
+
* same element is what lets `<Stack>` avoid remounting screens per
|
|
51
|
+
* animation phase (see `lynx-navigation`'s `<Layer>`).
|
|
52
|
+
*
|
|
53
|
+
* @example Ghost follower at 0.5× (static)
|
|
27
54
|
* ```tsx
|
|
28
55
|
* const tx = useSharedValue(0);
|
|
29
56
|
* const ghostRef = useMainThreadRef<MainThread.Element | null>(null);
|
|
@@ -33,5 +60,16 @@ export declare function resetAnimatedStyleBindingIds(): void;
|
|
|
33
60
|
* <Draggable translateX={tx} />
|
|
34
61
|
* <view main-thread:ref={ghostRef} style={...} />
|
|
35
62
|
* ```
|
|
63
|
+
*
|
|
64
|
+
* @example Animate-then-rest, no remount (reactive)
|
|
65
|
+
* ```tsx
|
|
66
|
+
* useAnimatedStyle(ref, () =>
|
|
67
|
+
* props.animation
|
|
68
|
+
* ? { sv: props.animation.progress, mapperName: props.animation.axis,
|
|
69
|
+
* params: { inputRange: [...], outputRange: [...] } }
|
|
70
|
+
* : null,
|
|
71
|
+
* );
|
|
72
|
+
* ```
|
|
36
73
|
*/
|
|
37
74
|
export declare function useAnimatedStyle<N extends BuiltinMapperName>(elRef: MainThreadRef<MainThread.Element | null>, sv: SharedValue<unknown>, mapperName: N, params?: MapperParams[N]): void;
|
|
75
|
+
export declare function useAnimatedStyle(elRef: MainThreadRef<MainThread.Element | null>, spec: () => AnimatedStyleSpec | null): void;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { onUnmounted } from '@sigx/runtime-core';
|
|
2
|
+
import { effect } from '@sigx/reactivity';
|
|
2
3
|
import { pushOp, scheduleFlush } from '../op-queue.js';
|
|
3
4
|
import { OP } from '@sigx/lynx-runtime-internal';
|
|
4
5
|
let nextBindingId = 1;
|
|
@@ -14,35 +15,31 @@ function allocBindingId() {
|
|
|
14
15
|
return nextBindingId++;
|
|
15
16
|
}
|
|
16
17
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* `
|
|
20
|
-
*
|
|
21
|
-
* The mapper runs on the **Main Thread** with no thread crossing per frame —
|
|
22
|
-
* the only inputs are the SharedValue's value (already on MT) and the `params` shipped
|
|
23
|
-
* once in the registration op. Because mappers are looked up by *name*, the
|
|
24
|
-
* SWC worklet transform doesn't have to capture a function reference (which
|
|
25
|
-
* it can't), so this fits the existing worklet pipeline cleanly.
|
|
26
|
-
*
|
|
27
|
-
* Multiple bindings on the same element compose into a single
|
|
28
|
-
* `setStyleProperties` call per flush. `transform` outputs concatenate in
|
|
29
|
-
* registration order (e.g. `translateX(50px) translateY(20px)`); other style
|
|
30
|
-
* keys merge by last-write-wins. When ANY binding on an element is dirty,
|
|
31
|
-
* ALL of that element's bindings re-run so partial outputs don't drop the
|
|
32
|
-
* unchanged-axis contribution.
|
|
33
|
-
*
|
|
34
|
-
* @example Ghost follower at 0.5×
|
|
35
|
-
* ```tsx
|
|
36
|
-
* const tx = useSharedValue(0);
|
|
37
|
-
* const ghostRef = useMainThreadRef<MainThread.Element | null>(null);
|
|
38
|
-
*
|
|
39
|
-
* useAnimatedStyle(ghostRef, tx, 'translateX', { factor: 0.5 });
|
|
40
|
-
*
|
|
41
|
-
* <Draggable translateX={tx} />
|
|
42
|
-
* <view main-thread:ref={ghostRef} style={...} />
|
|
43
|
-
* ```
|
|
18
|
+
* Stable identity for a spec so the reactive binder can tell "same binding,
|
|
19
|
+
* different object instance" (no-op) from "actually changed" (rebind). The
|
|
20
|
+
* SharedValue's `_wvid` plus the mapper name + JSON-serialized params uniquely
|
|
21
|
+
* pin the binding the MT side would register.
|
|
44
22
|
*/
|
|
45
|
-
|
|
23
|
+
function specSignature(cfg) {
|
|
24
|
+
if (!cfg)
|
|
25
|
+
return 'none';
|
|
26
|
+
let p = '';
|
|
27
|
+
try {
|
|
28
|
+
p = cfg.params == null ? '' : JSON.stringify(cfg.params);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
p = '';
|
|
32
|
+
}
|
|
33
|
+
return `${cfg.mapperName}:${cfg.sv._wvid}:${p}`;
|
|
34
|
+
}
|
|
35
|
+
export function useAnimatedStyle(elRef, svOrSpec, mapperName, params) {
|
|
36
|
+
// Reactive form: drive register/unregister from an effect on the accessor.
|
|
37
|
+
if (typeof svOrSpec === 'function') {
|
|
38
|
+
bindReactiveAnimatedStyle(elRef, svOrSpec);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
// Static form (unchanged): register once, unregister at unmount.
|
|
42
|
+
const sv = svOrSpec;
|
|
46
43
|
const bindingId = allocBindingId();
|
|
47
44
|
pushOp(OP.REGISTER_AV_STYLE_BINDING, bindingId, elRef._wvid, sv._wvid, mapperName, params ?? null);
|
|
48
45
|
scheduleFlush();
|
|
@@ -51,3 +48,51 @@ export function useAnimatedStyle(elRef, sv, mapperName, params) {
|
|
|
51
48
|
scheduleFlush();
|
|
52
49
|
});
|
|
53
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Reactive-binding implementation. Runs `accessor` in an `effect`; when the
|
|
53
|
+
* resulting spec's *signature* changes, unregisters the previous binding (if
|
|
54
|
+
* any) and registers the new one against the same element. A `null` spec means
|
|
55
|
+
* "no binding right now" — the element keeps the last style the MT applied
|
|
56
|
+
* (mappers are not reset on unregister), which for transition transforms is the
|
|
57
|
+
* resting identity transform.
|
|
58
|
+
*
|
|
59
|
+
* The signature dedupe is load-bearing: callers typically build a *fresh* spec
|
|
60
|
+
* object on every render (e.g. `computeLayers` in `<Stack>`), so the accessor's
|
|
61
|
+
* return value changes identity every render even when the logical binding is
|
|
62
|
+
* unchanged. Without the dedupe the binding would thrash register/unregister on
|
|
63
|
+
* every progress-driven re-render.
|
|
64
|
+
*
|
|
65
|
+
* Ordering note: we `scheduleFlush()` (microtask-deferred), never `flushNow()`,
|
|
66
|
+
* so the REGISTER op lands on the same channel and ordering the static form
|
|
67
|
+
* uses — preserving the "bindings register → SV resets inside the worklet →
|
|
68
|
+
* withTiming starts" sequence the navigator relies on.
|
|
69
|
+
*/
|
|
70
|
+
function bindReactiveAnimatedStyle(elRef, accessor) {
|
|
71
|
+
let bindingId = null;
|
|
72
|
+
let sig = null;
|
|
73
|
+
const runner = effect(() => {
|
|
74
|
+
const cfg = accessor();
|
|
75
|
+
const nextSig = specSignature(cfg);
|
|
76
|
+
if (nextSig === sig)
|
|
77
|
+
return;
|
|
78
|
+
sig = nextSig;
|
|
79
|
+
if (bindingId !== null) {
|
|
80
|
+
pushOp(OP.UNREGISTER_AV_STYLE_BINDING, bindingId);
|
|
81
|
+
bindingId = null;
|
|
82
|
+
}
|
|
83
|
+
if (cfg) {
|
|
84
|
+
const id = allocBindingId();
|
|
85
|
+
bindingId = id;
|
|
86
|
+
pushOp(OP.REGISTER_AV_STYLE_BINDING, id, elRef._wvid, cfg.sv._wvid, cfg.mapperName, cfg.params ?? null);
|
|
87
|
+
}
|
|
88
|
+
scheduleFlush();
|
|
89
|
+
});
|
|
90
|
+
onUnmounted(() => {
|
|
91
|
+
runner.stop();
|
|
92
|
+
if (bindingId !== null) {
|
|
93
|
+
pushOp(OP.UNREGISTER_AV_STYLE_BINDING, bindingId);
|
|
94
|
+
bindingId = null;
|
|
95
|
+
scheduleFlush();
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export { registerBgSink, unregisterBgSink, ingestAvPublishes, resetBgAvBridge, b
|
|
|
18
18
|
export { useSharedValue, SharedValue, } from './animated/shared-value.js';
|
|
19
19
|
export type { SharedValueState } from './animated/shared-value.js';
|
|
20
20
|
export { useAnimatedStyle, resetAnimatedStyleBindingIds, } from './animated/use-animated-style.js';
|
|
21
|
+
export type { AnimatedStyleSpec } from './animated/use-animated-style.js';
|
|
21
22
|
export { useAnimatedValue, AnimatedValue, } from './animated/animated-value.js';
|
|
22
23
|
export type { AnimatedValueState } from './animated/animated-value.js';
|
|
23
24
|
export { runOnMainThread, runOnBackground, resetThreading, transformToWorklet, resetRunOnBackgroundState, } from './threading.js';
|
package/dist/jsx.d.ts
CHANGED
|
@@ -233,6 +233,12 @@ export interface ListAttributes extends LynxCommonAttributes {
|
|
|
233
233
|
}
|
|
234
234
|
export interface ListItemAttributes extends LynxCommonAttributes {
|
|
235
235
|
children?: any;
|
|
236
|
+
/**
|
|
237
|
+
* Unique, stable key identifying this item for the native recycler. Lynx
|
|
238
|
+
* uses it to diff and reuse `<list-item>` views as the list scrolls; it
|
|
239
|
+
* must be unique among siblings within the same `<list>`.
|
|
240
|
+
*/
|
|
241
|
+
'item-key'?: string;
|
|
236
242
|
/** Item type for recycling (items with same item-type share a view pool) */
|
|
237
243
|
'item-type'?: string | number;
|
|
238
244
|
/** Sticky offset from top */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sigx/lynx-runtime",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "Lynx renderer for SignalX (background thread)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@sigx/runtime-core": "^0.4.8",
|
|
43
43
|
"@sigx/reactivity": "^0.4.8",
|
|
44
|
-
"@sigx/lynx-runtime-internal": "^0.4.
|
|
44
|
+
"@sigx/lynx-runtime-internal": "^0.4.5"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@lynx-js/types": "*"
|