react-native-smooth-clip-view 0.2.2 → 0.2.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.
- package/README.md +52 -14
- package/android/CMakeLists.txt +4 -0
- package/android/build.gradle +1 -0
- package/android/src/main/cpp/SmoothClipRegistry.cpp +72 -53
- package/cpp/SmoothClipVelocityTracker.h +101 -0
- package/ios/SmoothClipView.mm +32 -74
- package/lib/module/drivers.android.js +3 -1
- package/lib/module/drivers.android.js.map +1 -1
- package/lib/module/drivers.ios.js +3 -377
- package/lib/module/drivers.ios.js.map +1 -1
- package/lib/module/drivers.js +8 -0
- package/lib/module/drivers.js.map +1 -1
- package/lib/module/drivers.native.js +401 -0
- package/lib/module/drivers.native.js.map +1 -0
- package/lib/module/easings.js +35 -0
- package/lib/module/easings.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +1 -0
- package/lib/module/index.web.js.map +1 -1
- package/lib/typescript/src/driverTypes.d.ts +34 -3
- package/lib/typescript/src/driverTypes.d.ts.map +1 -1
- package/lib/typescript/src/drivers.android.d.ts +2 -2
- package/lib/typescript/src/drivers.android.d.ts.map +1 -1
- package/lib/typescript/src/drivers.d.ts.map +1 -1
- package/lib/typescript/src/drivers.ios.d.ts +2 -4
- package/lib/typescript/src/drivers.ios.d.ts.map +1 -1
- package/lib/typescript/src/drivers.native.d.ts +5 -0
- package/lib/typescript/src/drivers.native.d.ts.map +1 -0
- package/lib/typescript/src/easings.d.ts +33 -0
- package/lib/typescript/src/easings.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/index.web.d.ts +1 -0
- package/lib/typescript/src/index.web.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/driverTypes.ts +53 -19
- package/src/drivers.android.ts +4 -2
- package/src/drivers.ios.ts +4 -661
- package/src/drivers.native.ts +698 -0
- package/src/drivers.ts +11 -0
- package/src/easings.ts +32 -0
- package/src/index.ts +1 -0
- package/src/index.web.ts +1 -0
package/src/drivers.ios.ts
CHANGED
|
@@ -1,665 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import NativeSmoothClipModule from './smoothClipNative';
|
|
5
|
-
import type {
|
|
6
|
-
ClipReduceMotion,
|
|
7
|
-
KeyframedClipAnimation,
|
|
8
|
-
SmoothClipAnimation,
|
|
9
|
-
SmoothClipDriver,
|
|
10
|
-
SmoothClipDriverOptions,
|
|
11
|
-
SpringClipAnimation,
|
|
12
|
-
TimingClipAnimation,
|
|
13
|
-
} from './driverTypes';
|
|
14
|
-
import {
|
|
15
|
-
allocateDriverId,
|
|
16
|
-
attachDriverState,
|
|
17
|
-
createDriverState,
|
|
18
|
-
detachDriverState,
|
|
19
|
-
getDriverState,
|
|
20
|
-
setDriverState,
|
|
21
|
-
} from './driverState';
|
|
22
|
-
import {
|
|
23
|
-
completeNativeAnimation,
|
|
24
|
-
synchronizeNativeCompletion,
|
|
25
|
-
} from './nativeCompletion';
|
|
26
|
-
import {
|
|
27
|
-
clipPresentationEquals,
|
|
28
|
-
createClipPresentation,
|
|
29
|
-
isFiniteClipPresentation,
|
|
30
|
-
type ClipGeometry,
|
|
31
|
-
type SmoothClipPresentation,
|
|
32
|
-
} from './geometry';
|
|
33
|
-
import {
|
|
34
|
-
createReactRequest,
|
|
35
|
-
rejectDriverRequests,
|
|
36
|
-
resolveReactRequest,
|
|
37
|
-
} from './reactRequests';
|
|
38
|
-
|
|
39
|
-
const INTERACTIVE = 0;
|
|
40
|
-
const NATIVE = 1;
|
|
41
|
-
|
|
42
|
-
const setPresentationHostFunction = NativeSmoothClipModule.setClipPresentation;
|
|
43
|
-
const beginInteractionHostFunction = NativeSmoothClipModule.beginInteraction;
|
|
44
|
-
const animateTimingHostFunction = NativeSmoothClipModule.animateTiming;
|
|
45
|
-
const animateSpringHostFunction = NativeSmoothClipModule.animateSpring;
|
|
46
|
-
const animateKeyframesHostFunction = NativeSmoothClipModule.animateKeyframes;
|
|
47
|
-
const rejectAnimationHostFunction = NativeSmoothClipModule.rejectAnimation;
|
|
48
|
-
const cancelAnimationHostFunction = NativeSmoothClipModule.cancelAnimation;
|
|
49
|
-
const destroyDriverHostFunction = NativeSmoothClipModule.destroyDriver;
|
|
50
|
-
|
|
51
|
-
// Workaround for react-native-worklets 0.10: the RN-side serialization cache
|
|
52
|
-
// hands out one remote-function registry id per function forever, but the
|
|
53
|
-
// registry entry is deleted as soon as any transient UI-side proxy of that
|
|
54
|
-
// function is garbage collected. A later scheduleOnRN for the same function
|
|
55
|
-
// then reads `undefined` from the registry and aborts in Value::getObject.
|
|
56
|
-
// Pinning one UI-side reference for the app lifetime keeps the proxy — and
|
|
57
|
-
// with it the registry entry — alive. Remove once worklets decouples registry
|
|
58
|
-
// cleanup from proxy lifetime.
|
|
59
|
-
scheduleOnUI((resolver: typeof resolveReactRequest) => {
|
|
60
|
-
'worklet';
|
|
61
|
-
(globalThis as Record<string, unknown>).__smoothClipPinnedResolver = resolver;
|
|
62
|
-
}, resolveReactRequest);
|
|
63
|
-
|
|
64
|
-
function reduceMotionCode(value: ClipReduceMotion): number {
|
|
65
|
-
switch (value) {
|
|
66
|
-
case 'always':
|
|
67
|
-
return 1;
|
|
68
|
-
case 'never':
|
|
69
|
-
return 2;
|
|
70
|
-
default:
|
|
71
|
-
return 0;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function animationIsFinite(
|
|
76
|
-
target: SmoothClipPresentation,
|
|
77
|
-
animation: SmoothClipAnimation
|
|
78
|
-
): boolean {
|
|
79
|
-
'worklet';
|
|
80
|
-
if (animation.type === 'timing') {
|
|
81
|
-
return (
|
|
82
|
-
Number.isFinite(animation.duration) &&
|
|
83
|
-
animation.controlPoints.every(Number.isFinite)
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
if (animation.type === 'spring') {
|
|
87
|
-
return [
|
|
88
|
-
animation.mass ?? 1,
|
|
89
|
-
animation.stiffness ?? 100,
|
|
90
|
-
animation.damping ?? 10,
|
|
91
|
-
animation.initialVelocity === 'inherit'
|
|
92
|
-
? 0
|
|
93
|
-
: (animation.initialVelocity ?? 0),
|
|
94
|
-
].every(Number.isFinite);
|
|
95
|
-
}
|
|
96
|
-
if (!Number.isFinite(animation.duration) || animation.frames.length < 2) {
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
let previousOffset = -1;
|
|
100
|
-
for (const frame of animation.frames) {
|
|
101
|
-
if (
|
|
102
|
-
!Number.isFinite(frame.offset) ||
|
|
103
|
-
frame.offset < 0 ||
|
|
104
|
-
frame.offset > 1 ||
|
|
105
|
-
frame.offset <= previousOffset ||
|
|
106
|
-
!isFiniteClipPresentation(frame.presentation)
|
|
107
|
-
) {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
previousOffset = frame.offset;
|
|
111
|
-
}
|
|
112
|
-
return (
|
|
113
|
-
animation.frames[0]?.offset === 0 &&
|
|
114
|
-
previousOffset === 1 &&
|
|
115
|
-
clipPresentationEquals(
|
|
116
|
-
animation.frames[animation.frames.length - 1]?.presentation ?? null,
|
|
117
|
-
target
|
|
118
|
-
)
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function presentationFromNative(
|
|
123
|
-
values: ReadonlyArray<number>,
|
|
124
|
-
fallback: SmoothClipPresentation,
|
|
125
|
-
offset = 0
|
|
126
|
-
): SmoothClipPresentation {
|
|
127
|
-
'worklet';
|
|
128
|
-
if (values.length < offset + 7) return fallback;
|
|
129
|
-
const presentation = {
|
|
130
|
-
clip: {
|
|
131
|
-
x: values[offset] as number,
|
|
132
|
-
y: values[offset + 1] as number,
|
|
133
|
-
width: values[offset + 2] as number,
|
|
134
|
-
height: values[offset + 3] as number,
|
|
135
|
-
radius: values[offset + 4] as number,
|
|
136
|
-
},
|
|
137
|
-
contentTranslateX: values[offset + 5] as number,
|
|
138
|
-
contentTranslateY: values[offset + 6] as number,
|
|
139
|
-
};
|
|
140
|
-
return isFiniteClipPresentation(presentation) ? presentation : fallback;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function flattenedKeyframes(animation: KeyframedClipAnimation): number[] {
|
|
144
|
-
'worklet';
|
|
145
|
-
const values: number[] = [];
|
|
146
|
-
for (const frame of animation.frames) {
|
|
147
|
-
const { clip, contentTranslateX, contentTranslateY } = frame.presentation;
|
|
148
|
-
values.push(
|
|
149
|
-
frame.offset,
|
|
150
|
-
clip.x,
|
|
151
|
-
clip.y,
|
|
152
|
-
clip.width,
|
|
153
|
-
clip.height,
|
|
154
|
-
clip.radius,
|
|
155
|
-
contentTranslateX,
|
|
156
|
-
contentTranslateY
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
return values;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function uiOnly(): never {
|
|
163
|
-
'worklet';
|
|
164
|
-
throw new Error(
|
|
165
|
-
'[SmoothClipView] driver.ui methods must run on the UI runtime. Use driver.react from React code.'
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export function useSmoothClipDriver(
|
|
170
|
-
initialValue: ClipGeometry | SmoothClipPresentation,
|
|
171
|
-
options: SmoothClipDriverOptions = {}
|
|
172
|
-
): SmoothClipDriver {
|
|
173
|
-
const initialPresentation =
|
|
174
|
-
'clip' in initialValue
|
|
175
|
-
? initialValue
|
|
176
|
-
: createClipPresentation(initialValue);
|
|
177
|
-
const presentation = useSharedValue(initialPresentation);
|
|
178
|
-
const ownership = useSharedValue(INTERACTIVE);
|
|
179
|
-
const suppressDeliveries = useSharedValue(0);
|
|
180
|
-
const activeAnimationId = useSharedValue(0);
|
|
181
|
-
// Non-zero after a setScalars hot write: driver.presentation is then stale,
|
|
182
|
-
// so animateTo must start from the native registry's latest value instead
|
|
183
|
-
// of snapping back to the stale SharedValue.
|
|
184
|
-
const scalarsStale = useSharedValue(0);
|
|
185
|
-
const callbackRef = useRef(options.onAnimationComplete);
|
|
186
|
-
const driverRef = useRef<SmoothClipDriver | null>(null);
|
|
187
|
-
callbackRef.current = options.onAnimationComplete;
|
|
188
|
-
|
|
189
|
-
if (driverRef.current === null) {
|
|
190
|
-
const driverId = allocateDriverId();
|
|
191
|
-
const reduceMotion = reduceMotionCode(options.reduceMotion ?? 'system');
|
|
192
|
-
|
|
193
|
-
const seedPresentation = (next: SmoothClipPresentation) => {
|
|
194
|
-
'worklet';
|
|
195
|
-
if (!clipPresentationEquals(presentation.value, next)) {
|
|
196
|
-
suppressDeliveries.value += 1;
|
|
197
|
-
presentation.value = next;
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
const beginOnUI = (): SmoothClipPresentation => {
|
|
202
|
-
'worklet';
|
|
203
|
-
const current = presentationFromNative(
|
|
204
|
-
beginInteractionHostFunction(driverId),
|
|
205
|
-
presentation.value
|
|
206
|
-
);
|
|
207
|
-
activeAnimationId.value = 0;
|
|
208
|
-
ownership.value = INTERACTIVE;
|
|
209
|
-
scalarsStale.value = 0;
|
|
210
|
-
seedPresentation(current);
|
|
211
|
-
return current;
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
const setOnUI = (next: SmoothClipPresentation): void => {
|
|
215
|
-
'worklet';
|
|
216
|
-
if (!isFiniteClipPresentation(next)) return;
|
|
217
|
-
activeAnimationId.value = 0;
|
|
218
|
-
ownership.value = INTERACTIVE;
|
|
219
|
-
scalarsStale.value = 0;
|
|
220
|
-
const { clip, contentTranslateX, contentTranslateY } = next;
|
|
221
|
-
setPresentationHostFunction(
|
|
222
|
-
driverId,
|
|
223
|
-
clip.x,
|
|
224
|
-
clip.y,
|
|
225
|
-
clip.width,
|
|
226
|
-
clip.height,
|
|
227
|
-
clip.radius,
|
|
228
|
-
contentTranslateX,
|
|
229
|
-
contentTranslateY,
|
|
230
|
-
true
|
|
231
|
-
);
|
|
232
|
-
seedPresentation(next);
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
const setScalarsOnUI = (
|
|
236
|
-
x: number,
|
|
237
|
-
y: number,
|
|
238
|
-
width: number,
|
|
239
|
-
height: number,
|
|
240
|
-
radius: number,
|
|
241
|
-
contentTranslateX: number,
|
|
242
|
-
contentTranslateY: number
|
|
243
|
-
): void => {
|
|
244
|
-
'worklet';
|
|
245
|
-
if (
|
|
246
|
-
!Number.isFinite(x) ||
|
|
247
|
-
!Number.isFinite(y) ||
|
|
248
|
-
!Number.isFinite(width) ||
|
|
249
|
-
!Number.isFinite(height) ||
|
|
250
|
-
!Number.isFinite(radius) ||
|
|
251
|
-
!Number.isFinite(contentTranslateX) ||
|
|
252
|
-
!Number.isFinite(contentTranslateY)
|
|
253
|
-
) {
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
if (activeAnimationId.value !== 0) activeAnimationId.value = 0;
|
|
257
|
-
if (ownership.value !== INTERACTIVE) ownership.value = INTERACTIVE;
|
|
258
|
-
if (scalarsStale.value === 0) scalarsStale.value = 1;
|
|
259
|
-
setPresentationHostFunction(
|
|
260
|
-
driverId,
|
|
261
|
-
x,
|
|
262
|
-
y,
|
|
263
|
-
width,
|
|
264
|
-
height,
|
|
265
|
-
radius,
|
|
266
|
-
contentTranslateX,
|
|
267
|
-
contentTranslateY,
|
|
268
|
-
true
|
|
269
|
-
);
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
const animateOnUI = (
|
|
273
|
-
target: SmoothClipPresentation,
|
|
274
|
-
animation: SmoothClipAnimation
|
|
275
|
-
): number => {
|
|
276
|
-
'worklet';
|
|
277
|
-
if (
|
|
278
|
-
!isFiniteClipPresentation(target) ||
|
|
279
|
-
!animationIsFinite(target, animation)
|
|
280
|
-
) {
|
|
281
|
-
return rejectAnimationHostFunction(driverId);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// After a setScalars hot write the SharedValue no longer matches what
|
|
285
|
-
// is on screen; passing it as the start would snap the animation back.
|
|
286
|
-
// Without an interactive start, native resolves the start from its own
|
|
287
|
-
// latest value instead.
|
|
288
|
-
const scalarsWereStale = scalarsStale.value !== 0;
|
|
289
|
-
const hasInteractiveStart =
|
|
290
|
-
ownership.value === INTERACTIVE && !scalarsWereStale;
|
|
291
|
-
const start = presentation.value;
|
|
292
|
-
ownership.value = NATIVE;
|
|
293
|
-
presentation.value = target;
|
|
294
|
-
scalarsStale.value = 0;
|
|
295
|
-
const startClip = start.clip;
|
|
296
|
-
const targetClip = target.clip;
|
|
297
|
-
let animationId: number;
|
|
298
|
-
|
|
299
|
-
if (animation.type === 'timing') {
|
|
300
|
-
const [x1, y1, x2, y2] = animation.controlPoints;
|
|
301
|
-
animationId = animateTimingHostFunction(
|
|
302
|
-
driverId,
|
|
303
|
-
hasInteractiveStart,
|
|
304
|
-
startClip.x,
|
|
305
|
-
startClip.y,
|
|
306
|
-
startClip.width,
|
|
307
|
-
startClip.height,
|
|
308
|
-
startClip.radius,
|
|
309
|
-
start.contentTranslateX,
|
|
310
|
-
start.contentTranslateY,
|
|
311
|
-
targetClip.x,
|
|
312
|
-
targetClip.y,
|
|
313
|
-
targetClip.width,
|
|
314
|
-
targetClip.height,
|
|
315
|
-
targetClip.radius,
|
|
316
|
-
target.contentTranslateX,
|
|
317
|
-
target.contentTranslateY,
|
|
318
|
-
Math.max(0, animation.duration),
|
|
319
|
-
x1,
|
|
320
|
-
y1,
|
|
321
|
-
x2,
|
|
322
|
-
y2,
|
|
323
|
-
reduceMotion
|
|
324
|
-
);
|
|
325
|
-
} else if (animation.type === 'spring') {
|
|
326
|
-
const inheritVelocity =
|
|
327
|
-
animation.initialVelocity === undefined ||
|
|
328
|
-
animation.initialVelocity === 'inherit';
|
|
329
|
-
animationId = animateSpringHostFunction(
|
|
330
|
-
driverId,
|
|
331
|
-
hasInteractiveStart,
|
|
332
|
-
startClip.x,
|
|
333
|
-
startClip.y,
|
|
334
|
-
startClip.width,
|
|
335
|
-
startClip.height,
|
|
336
|
-
startClip.radius,
|
|
337
|
-
start.contentTranslateX,
|
|
338
|
-
start.contentTranslateY,
|
|
339
|
-
targetClip.x,
|
|
340
|
-
targetClip.y,
|
|
341
|
-
targetClip.width,
|
|
342
|
-
targetClip.height,
|
|
343
|
-
targetClip.radius,
|
|
344
|
-
target.contentTranslateX,
|
|
345
|
-
target.contentTranslateY,
|
|
346
|
-
Math.max(0.0001, animation.mass ?? 1),
|
|
347
|
-
Math.max(0.0001, animation.stiffness ?? 100),
|
|
348
|
-
Math.max(0, animation.damping ?? 10),
|
|
349
|
-
inheritVelocity ? 0 : (animation.initialVelocity as number),
|
|
350
|
-
inheritVelocity,
|
|
351
|
-
reduceMotion
|
|
352
|
-
);
|
|
353
|
-
} else {
|
|
354
|
-
animationId = animateKeyframesHostFunction(
|
|
355
|
-
driverId,
|
|
356
|
-
hasInteractiveStart,
|
|
357
|
-
startClip.x,
|
|
358
|
-
startClip.y,
|
|
359
|
-
startClip.width,
|
|
360
|
-
startClip.height,
|
|
361
|
-
startClip.radius,
|
|
362
|
-
start.contentTranslateX,
|
|
363
|
-
start.contentTranslateY,
|
|
364
|
-
targetClip.x,
|
|
365
|
-
targetClip.y,
|
|
366
|
-
targetClip.width,
|
|
367
|
-
targetClip.height,
|
|
368
|
-
targetClip.radius,
|
|
369
|
-
target.contentTranslateX,
|
|
370
|
-
target.contentTranslateY,
|
|
371
|
-
Math.max(0, animation.duration),
|
|
372
|
-
flattenedKeyframes(animation),
|
|
373
|
-
reduceMotion
|
|
374
|
-
);
|
|
375
|
-
}
|
|
376
|
-
if (animationId === 0) {
|
|
377
|
-
// Native rejected the transition after ownership was transferred.
|
|
378
|
-
// Restore interactive ownership and the pre-animation value, then
|
|
379
|
-
// emit a standalone finished:false completion so completion-driven
|
|
380
|
-
// state machines observe exactly one result per animateTo call.
|
|
381
|
-
ownership.value = INTERACTIVE;
|
|
382
|
-
presentation.value = start;
|
|
383
|
-
if (scalarsWereStale) scalarsStale.value = 1;
|
|
384
|
-
activeAnimationId.value = 0;
|
|
385
|
-
return rejectAnimationHostFunction(driverId);
|
|
386
|
-
}
|
|
387
|
-
activeAnimationId.value = animationId;
|
|
388
|
-
return animationId;
|
|
389
|
-
};
|
|
390
|
-
|
|
391
|
-
const cancelOnUI = (
|
|
392
|
-
animationId = 0,
|
|
393
|
-
behavior: 'current' | 'target' = 'current'
|
|
394
|
-
): SmoothClipPresentation => {
|
|
395
|
-
'worklet';
|
|
396
|
-
const values = cancelAnimationHostFunction(
|
|
397
|
-
driverId,
|
|
398
|
-
animationId,
|
|
399
|
-
behavior === 'target' ? 1 : 0
|
|
400
|
-
);
|
|
401
|
-
if (values.length < 8 || values[0] !== 1) return presentation.value;
|
|
402
|
-
const current = presentationFromNative(values, presentation.value, 1);
|
|
403
|
-
activeAnimationId.value = 0;
|
|
404
|
-
ownership.value = INTERACTIVE;
|
|
405
|
-
scalarsStale.value = 0;
|
|
406
|
-
seedPresentation(current);
|
|
407
|
-
return current;
|
|
408
|
-
};
|
|
409
|
-
|
|
410
|
-
const driver: SmoothClipDriver = {
|
|
411
|
-
kind: 'hybrid',
|
|
412
|
-
presentation,
|
|
413
|
-
ui: {
|
|
414
|
-
beginInteraction() {
|
|
415
|
-
'worklet';
|
|
416
|
-
if (isRNRuntime()) return uiOnly();
|
|
417
|
-
return beginOnUI();
|
|
418
|
-
},
|
|
419
|
-
set(next) {
|
|
420
|
-
'worklet';
|
|
421
|
-
if (isRNRuntime()) return uiOnly();
|
|
422
|
-
setOnUI(next);
|
|
423
|
-
},
|
|
424
|
-
setScalars(
|
|
425
|
-
x,
|
|
426
|
-
y,
|
|
427
|
-
width,
|
|
428
|
-
height,
|
|
429
|
-
radius,
|
|
430
|
-
contentTranslateX,
|
|
431
|
-
contentTranslateY
|
|
432
|
-
) {
|
|
433
|
-
'worklet';
|
|
434
|
-
if (isRNRuntime()) return uiOnly();
|
|
435
|
-
setScalarsOnUI(
|
|
436
|
-
x,
|
|
437
|
-
y,
|
|
438
|
-
width,
|
|
439
|
-
height,
|
|
440
|
-
radius,
|
|
441
|
-
contentTranslateX,
|
|
442
|
-
contentTranslateY
|
|
443
|
-
);
|
|
444
|
-
},
|
|
445
|
-
animateTo(target, animation) {
|
|
446
|
-
'worklet';
|
|
447
|
-
if (isRNRuntime()) return uiOnly();
|
|
448
|
-
return animateOnUI(target, animation);
|
|
449
|
-
},
|
|
450
|
-
cancel(animationId = 0, behavior = 'current') {
|
|
451
|
-
'worklet';
|
|
452
|
-
if (isRNRuntime()) return uiOnly();
|
|
453
|
-
return cancelOnUI(animationId, behavior);
|
|
454
|
-
},
|
|
455
|
-
},
|
|
456
|
-
react: {
|
|
457
|
-
beginInteraction() {
|
|
458
|
-
const { requestId, promise } =
|
|
459
|
-
createReactRequest<SmoothClipPresentation>(driverId);
|
|
460
|
-
scheduleOnUI(() => {
|
|
461
|
-
'worklet';
|
|
462
|
-
const result = beginOnUI();
|
|
463
|
-
scheduleOnRN(resolveReactRequest, driverId, requestId, result);
|
|
464
|
-
});
|
|
465
|
-
return promise;
|
|
466
|
-
},
|
|
467
|
-
set(next) {
|
|
468
|
-
const { requestId, promise } = createReactRequest<void>(driverId);
|
|
469
|
-
scheduleOnUI(() => {
|
|
470
|
-
'worklet';
|
|
471
|
-
setOnUI(next);
|
|
472
|
-
scheduleOnRN(resolveReactRequest, driverId, requestId, undefined);
|
|
473
|
-
});
|
|
474
|
-
return promise;
|
|
475
|
-
},
|
|
476
|
-
animateTo(target, animation) {
|
|
477
|
-
const { requestId, promise } = createReactRequest<number>(
|
|
478
|
-
driverId,
|
|
479
|
-
true
|
|
480
|
-
);
|
|
481
|
-
scheduleOnUI(() => {
|
|
482
|
-
'worklet';
|
|
483
|
-
const result = animateOnUI(target, animation);
|
|
484
|
-
scheduleOnRN(
|
|
485
|
-
resolveReactRequest,
|
|
486
|
-
driverId,
|
|
487
|
-
requestId,
|
|
488
|
-
result,
|
|
489
|
-
true
|
|
490
|
-
);
|
|
491
|
-
});
|
|
492
|
-
return promise;
|
|
493
|
-
},
|
|
494
|
-
cancel(animationId = 0, behavior = 'current') {
|
|
495
|
-
const { requestId, promise } =
|
|
496
|
-
createReactRequest<SmoothClipPresentation>(driverId, true);
|
|
497
|
-
scheduleOnUI(() => {
|
|
498
|
-
'worklet';
|
|
499
|
-
const result = cancelOnUI(animationId, behavior);
|
|
500
|
-
scheduleOnRN(
|
|
501
|
-
resolveReactRequest,
|
|
502
|
-
driverId,
|
|
503
|
-
requestId,
|
|
504
|
-
result,
|
|
505
|
-
true
|
|
506
|
-
);
|
|
507
|
-
});
|
|
508
|
-
return promise;
|
|
509
|
-
},
|
|
510
|
-
},
|
|
511
|
-
};
|
|
512
|
-
|
|
513
|
-
setDriverState(
|
|
514
|
-
driver,
|
|
515
|
-
createDriverState(
|
|
516
|
-
driverId,
|
|
517
|
-
initialPresentation,
|
|
518
|
-
presentation,
|
|
519
|
-
callbackRef,
|
|
520
|
-
activeAnimationId,
|
|
521
|
-
ownership
|
|
522
|
-
)
|
|
523
|
-
);
|
|
524
|
-
driverRef.current = driver;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
const driver = driverRef.current;
|
|
528
|
-
|
|
529
|
-
useEffect(() => {
|
|
530
|
-
const currentDriver = driverRef.current;
|
|
531
|
-
if (!currentDriver) return undefined;
|
|
532
|
-
const state = getDriverState(currentDriver);
|
|
533
|
-
const { driverId } = state;
|
|
534
|
-
// Re-attach on every effect run: StrictMode/<Activity> replay the effect
|
|
535
|
-
// after its cleanup detached this state and destroyed the native driver.
|
|
536
|
-
// The authoritative seed below recreates the native entry in that case.
|
|
537
|
-
attachDriverState(state);
|
|
538
|
-
|
|
539
|
-
const subscription = NativeSmoothClipModule.onClipAnimationComplete(
|
|
540
|
-
(result) => {
|
|
541
|
-
if (result.driverId !== driverId) return;
|
|
542
|
-
synchronizeNativeCompletion(
|
|
543
|
-
activeAnimationId,
|
|
544
|
-
ownership,
|
|
545
|
-
result.animationId,
|
|
546
|
-
result.finished
|
|
547
|
-
);
|
|
548
|
-
completeNativeAnimation(driverId, result.animationId, result.finished);
|
|
549
|
-
}
|
|
550
|
-
);
|
|
551
|
-
|
|
552
|
-
scheduleOnUI(
|
|
553
|
-
(
|
|
554
|
-
source: SharedValue<SmoothClipPresentation>,
|
|
555
|
-
listenerId: number,
|
|
556
|
-
nativeDriverId: number,
|
|
557
|
-
owner: SharedValue<number>,
|
|
558
|
-
suppressed: SharedValue<number>,
|
|
559
|
-
active: SharedValue<number>,
|
|
560
|
-
stale: SharedValue<number>,
|
|
561
|
-
setter: typeof setPresentationHostFunction
|
|
562
|
-
) => {
|
|
563
|
-
'worklet';
|
|
564
|
-
let last: SmoothClipPresentation | null = null;
|
|
565
|
-
const deliver = (next: SmoothClipPresentation) => {
|
|
566
|
-
if (
|
|
567
|
-
!isFiniteClipPresentation(next) ||
|
|
568
|
-
clipPresentationEquals(last, next)
|
|
569
|
-
) {
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
// Record `last` only for values native has actually observed:
|
|
573
|
-
// deliveries dropped during native ownership must stay eligible
|
|
574
|
-
// for re-delivery once interactive ownership returns.
|
|
575
|
-
if (owner.value !== INTERACTIVE) return;
|
|
576
|
-
if (suppressed.value > 0) {
|
|
577
|
-
suppressed.value -= 1;
|
|
578
|
-
last = next;
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
last = next;
|
|
582
|
-
if (stale.value !== 0) stale.value = 0;
|
|
583
|
-
const { clip, contentTranslateX, contentTranslateY } = next;
|
|
584
|
-
setter(
|
|
585
|
-
nativeDriverId,
|
|
586
|
-
clip.x,
|
|
587
|
-
clip.y,
|
|
588
|
-
clip.width,
|
|
589
|
-
clip.height,
|
|
590
|
-
clip.radius,
|
|
591
|
-
contentTranslateX,
|
|
592
|
-
contentTranslateY,
|
|
593
|
-
false
|
|
594
|
-
);
|
|
595
|
-
};
|
|
596
|
-
source.addListener(listenerId, deliver);
|
|
597
|
-
// Authoritative take-ownership seed. Creates the native entry before
|
|
598
|
-
// any view mounts on the first run, and revives a driver destroyed by
|
|
599
|
-
// an effect-replay cleanup (StrictMode/<Activity>) on later runs.
|
|
600
|
-
const current = source.value;
|
|
601
|
-
if (isFiniteClipPresentation(current)) {
|
|
602
|
-
active.value = 0;
|
|
603
|
-
owner.value = INTERACTIVE;
|
|
604
|
-
stale.value = 0;
|
|
605
|
-
last = current;
|
|
606
|
-
setter(
|
|
607
|
-
nativeDriverId,
|
|
608
|
-
current.clip.x,
|
|
609
|
-
current.clip.y,
|
|
610
|
-
current.clip.width,
|
|
611
|
-
current.clip.height,
|
|
612
|
-
current.clip.radius,
|
|
613
|
-
current.contentTranslateX,
|
|
614
|
-
current.contentTranslateY,
|
|
615
|
-
true
|
|
616
|
-
);
|
|
617
|
-
}
|
|
618
|
-
},
|
|
619
|
-
presentation,
|
|
620
|
-
driverId,
|
|
621
|
-
driverId,
|
|
622
|
-
ownership,
|
|
623
|
-
suppressDeliveries,
|
|
624
|
-
activeAnimationId,
|
|
625
|
-
scalarsStale,
|
|
626
|
-
setPresentationHostFunction
|
|
627
|
-
);
|
|
628
|
-
|
|
629
|
-
return () => {
|
|
630
|
-
subscription.remove();
|
|
631
|
-
rejectDriverRequests(driverId);
|
|
632
|
-
detachDriverState(state);
|
|
633
|
-
scheduleOnUI(
|
|
634
|
-
(
|
|
635
|
-
source: SharedValue<SmoothClipPresentation>,
|
|
636
|
-
listenerId: number,
|
|
637
|
-
nativeDriverId: number,
|
|
638
|
-
destroy: typeof destroyDriverHostFunction
|
|
639
|
-
) => {
|
|
640
|
-
'worklet';
|
|
641
|
-
source.removeListener(listenerId);
|
|
642
|
-
destroy(nativeDriverId);
|
|
643
|
-
},
|
|
644
|
-
presentation,
|
|
645
|
-
driverId,
|
|
646
|
-
driverId,
|
|
647
|
-
destroyDriverHostFunction
|
|
648
|
-
);
|
|
649
|
-
};
|
|
650
|
-
}, [
|
|
651
|
-
activeAnimationId,
|
|
652
|
-
ownership,
|
|
653
|
-
presentation,
|
|
654
|
-
scalarsStale,
|
|
655
|
-
suppressDeliveries,
|
|
656
|
-
]);
|
|
657
|
-
|
|
658
|
-
return driver;
|
|
659
|
-
}
|
|
660
|
-
|
|
1
|
+
// Platform re-export only — the implementation is shared with Android.
|
|
2
|
+
// Edit drivers.native.ts, not this file.
|
|
3
|
+
export { useSmoothClipDriver } from './drivers.native';
|
|
661
4
|
export type {
|
|
662
5
|
KeyframedClipAnimation,
|
|
663
6
|
SpringClipAnimation,
|
|
664
7
|
TimingClipAnimation,
|
|
665
|
-
};
|
|
8
|
+
} from './drivers.native';
|