react-native-reanimated 4.4.1 → 4.4.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.
Files changed (77) hide show
  1. package/Common/cpp/reanimated/CSS/interpolation/transforms/TransformOperationInterpolator.cpp +12 -5
  2. package/Common/cpp/reanimated/CSS/misc/ViewStylesRepository.cpp +63 -36
  3. package/Common/cpp/reanimated/CSS/misc/ViewStylesRepository.h +6 -8
  4. package/Common/cpp/reanimated/CSS/svg/values/SVGStrokeDashArray.cpp +5 -0
  5. package/Common/cpp/reanimated/Fabric/ReanimatedMountHook.cpp +25 -12
  6. package/Common/cpp/reanimated/Fabric/ReanimatedMountHook.h +3 -0
  7. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.cpp +6 -2
  8. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h +44 -1
  9. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +125 -43
  10. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp +189 -102
  11. package/Common/cpp/reanimated/LayoutAnimations/SharedTransitions.cpp +8 -3
  12. package/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp +4 -16
  13. package/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h +0 -16
  14. package/android/CMakeLists.txt +6 -0
  15. package/android/build.gradle.kts +12 -1
  16. package/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt +46 -2
  17. package/android/src/main/java/com/swmansion/reanimated/keyboard/KeyboardAnimationCallback.kt +8 -1
  18. package/apple/reanimated/apple/ReanimatedModule.mm +12 -1
  19. package/compatibility.json +1 -1
  20. package/lib/module/common/constants/platform.js +4 -4
  21. package/lib/module/common/constants/platform.js.map +1 -1
  22. package/lib/module/createAnimatedComponent/getViewInfo.js +3 -1
  23. package/lib/module/createAnimatedComponent/getViewInfo.js.map +1 -1
  24. package/lib/module/css/component/AnimatedComponent.js +0 -2
  25. package/lib/module/css/component/AnimatedComponent.js.map +1 -1
  26. package/lib/module/css/native/managers/CSSManager.js +5 -5
  27. package/lib/module/css/native/managers/CSSManager.js.map +1 -1
  28. package/lib/module/css/native/managers/CSSTransitionsManager.js +8 -1
  29. package/lib/module/css/native/managers/CSSTransitionsManager.js.map +1 -1
  30. package/lib/module/css/svg/native/processors/stroke.js +9 -5
  31. package/lib/module/css/svg/native/processors/stroke.js.map +1 -1
  32. package/lib/module/layoutReanimation/animationBuilder/ComplexAnimationBuilder.js +6 -3
  33. package/lib/module/layoutReanimation/animationBuilder/ComplexAnimationBuilder.js.map +1 -1
  34. package/lib/module/layoutReanimation/animationsManager.js +31 -6
  35. package/lib/module/layoutReanimation/animationsManager.js.map +1 -1
  36. package/lib/module/mappers.js +1 -1
  37. package/lib/module/mappers.js.map +1 -1
  38. package/lib/module/platform-specific/findHostInstance.js +8 -5
  39. package/lib/module/platform-specific/findHostInstance.js.map +1 -1
  40. package/lib/module/platform-specific/jsVersion.js +1 -1
  41. package/lib/module/updateProps/updateProps.js +3 -2
  42. package/lib/module/updateProps/updateProps.js.map +1 -1
  43. package/lib/module/valueSetter.js +6 -7
  44. package/lib/module/valueSetter.js.map +1 -1
  45. package/lib/typescript/common/constants/platform.d.ts.map +1 -1
  46. package/lib/typescript/createAnimatedComponent/commonTypes.d.ts +0 -1
  47. package/lib/typescript/createAnimatedComponent/commonTypes.d.ts.map +1 -1
  48. package/lib/typescript/createAnimatedComponent/getViewInfo.d.ts.map +1 -1
  49. package/lib/typescript/css/component/AnimatedComponent.d.ts +0 -1
  50. package/lib/typescript/css/component/AnimatedComponent.d.ts.map +1 -1
  51. package/lib/typescript/css/native/managers/CSSTransitionsManager.d.ts +5 -1
  52. package/lib/typescript/css/native/managers/CSSTransitionsManager.d.ts.map +1 -1
  53. package/lib/typescript/css/svg/native/processors/stroke.d.ts.map +1 -1
  54. package/lib/typescript/layoutReanimation/animationBuilder/ComplexAnimationBuilder.d.ts +17 -14
  55. package/lib/typescript/layoutReanimation/animationBuilder/ComplexAnimationBuilder.d.ts.map +1 -1
  56. package/lib/typescript/layoutReanimation/animationsManager.d.ts.map +1 -1
  57. package/lib/typescript/platform-specific/findHostInstance.d.ts.map +1 -1
  58. package/lib/typescript/platform-specific/jsVersion.d.ts +1 -1
  59. package/lib/typescript/platform-specific/types.d.ts +1 -0
  60. package/lib/typescript/platform-specific/types.d.ts.map +1 -1
  61. package/lib/typescript/updateProps/updateProps.d.ts.map +1 -1
  62. package/package.json +2 -2
  63. package/src/common/constants/platform.ts +4 -4
  64. package/src/createAnimatedComponent/commonTypes.ts +0 -1
  65. package/src/createAnimatedComponent/getViewInfo.ts +3 -1
  66. package/src/css/component/AnimatedComponent.tsx +0 -2
  67. package/src/css/native/managers/CSSManager.ts +8 -8
  68. package/src/css/native/managers/CSSTransitionsManager.ts +9 -2
  69. package/src/css/svg/native/processors/stroke.ts +9 -6
  70. package/src/layoutReanimation/animationBuilder/ComplexAnimationBuilder.ts +39 -36
  71. package/src/layoutReanimation/animationsManager.ts +33 -5
  72. package/src/mappers.ts +1 -1
  73. package/src/platform-specific/findHostInstance.ts +7 -7
  74. package/src/platform-specific/jsVersion.ts +1 -1
  75. package/src/platform-specific/types.ts +2 -0
  76. package/src/updateProps/updateProps.ts +3 -2
  77. package/src/valueSetter.ts +7 -7
@@ -30,10 +30,14 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager {
30
30
  this.shadowNodeWrapper = shadowNodeWrapper;
31
31
  }
32
32
 
33
+ /**
34
+ * @returns Whether this update detached a running transition (its props were
35
+ * removed, or normalized to an empty config, e.g. when duration is 0).
36
+ */
33
37
  update(
34
38
  transitionProperties: CSSTransitionProperties | null,
35
39
  nextProps: UnknownRecord = {}
36
- ): void {
40
+ ): boolean {
37
41
  const transitionConfig =
38
42
  transitionProperties &&
39
43
  normalizeCSSTransitionProperties(transitionProperties);
@@ -47,8 +51,9 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager {
47
51
  if (!prevProps || !transitionConfig) {
48
52
  if (this.hasTransition) {
49
53
  this.detach();
54
+ return true;
50
55
  }
51
- return;
56
+ return false;
52
57
  }
53
58
 
54
59
  // Trigger transition for changed properties only
@@ -62,6 +67,8 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager {
62
67
  runCSSTransition(this.shadowNodeWrapper, config);
63
68
  this.hasTransition = true;
64
69
  }
70
+
71
+ return false;
65
72
  }
66
73
 
67
74
  unmountCleanup(): void {
@@ -18,18 +18,21 @@ export const processStrokeDashArray: ValueProcessor<
18
18
  if (isLength(value)) {
19
19
  result = [value];
20
20
  } else if (Array.isArray(value)) {
21
- // We have to repeat the same pattern twice if the number of elements is odd
22
- // "If the number of values is odd, the pattern behaves as if it was duplicated
23
- // to yield an even number of values"
24
- // (https://www.w3.org/TR/fill-stroke-3/#valdef-stroke-dasharray-length-percentage)
25
- result =
26
- value.length % 2 === 0 || value.length < 3 ? value : value.concat(value);
21
+ result = [...value];
27
22
  } else if (value === 'none') {
28
23
  return 'none';
29
24
  } else {
30
25
  throw new Error(`[Reanimated] ${ERROR_MESSAGES.invalidDashArray(value)}`);
31
26
  }
32
27
 
28
+ // Per the SVG spec an odd-length dash array is repeated to an even length.
29
+ // react-native-svg's extractStroke does this, but animated values bypass it
30
+ // and an odd-length array crashes Android's DashPathEffect, so we repeat here.
31
+ // https://www.w3.org/TR/fill-stroke-3/#valdef-stroke-dasharray-length-percentage
32
+ if (result.length % 2 === 1) {
33
+ result = result.concat(result);
34
+ }
35
+
33
36
  if (__DEV__) {
34
37
  isValidDashArray(result);
35
38
  }
@@ -12,9 +12,12 @@ import type { EasingFunctionFactory } from '../../Easing';
12
12
  import { BaseAnimationBuilder } from './BaseAnimationBuilder';
13
13
 
14
14
  /**
15
- * `this` type for static methods on {@link AnimationConfigBuilder}. Represents a
16
- * subclass constructor exposing the shared config-only static API inherited
17
- * from {@link BaseAnimationBuilder}.
15
+ * `this` constraint for static modifiers on {@link AnimationConfigBuilder}.
16
+ * Represents a subclass constructor exposing the shared config-only static API
17
+ * inherited from {@link BaseAnimationBuilder}. The modifiers bind `this` to a
18
+ * concrete subtype of this so the chain preserves the receiver's instance type
19
+ * (e.g. `SharedTransition.springify(...)` stays a `SharedTransition`) instead
20
+ * of collapsing to `AnimationConfigBuilder`.
18
21
  */
19
22
  type AnimationConfigBuilderClass = typeof BaseAnimationBuilder &
20
23
  (new () => AnimationConfigBuilder);
@@ -41,10 +44,10 @@ export class AnimationConfigBuilder extends BaseAnimationBuilder {
41
44
  * @param easingFunction - An easing function which defines the animation
42
45
  * curve.
43
46
  */
44
- static easing(
45
- this: AnimationConfigBuilderClass,
47
+ static easing<T extends AnimationConfigBuilderClass>(
48
+ this: T,
46
49
  easingFunction: EasingFunction | EasingFunctionFactory
47
- ): AnimationConfigBuilder {
50
+ ): InstanceType<T> {
48
51
  const instance = this.createInstance();
49
52
  return instance.easing(easingFunction);
50
53
  }
@@ -64,10 +67,10 @@ export class AnimationConfigBuilder extends BaseAnimationBuilder {
64
67
  *
65
68
  * @param degree - The rotation degree.
66
69
  */
67
- static rotate(
68
- this: AnimationConfigBuilderClass,
70
+ static rotate<T extends AnimationConfigBuilderClass>(
71
+ this: T,
69
72
  degree: string
70
- ): AnimationConfigBuilder {
73
+ ): InstanceType<T> {
71
74
  const instance = this.createInstance();
72
75
  return instance.rotate(degree);
73
76
  }
@@ -85,10 +88,10 @@ export class AnimationConfigBuilder extends BaseAnimationBuilder {
85
88
  * @param duration - An optional duration of the spring animation (in
86
89
  * milliseconds).
87
90
  */
88
- static springify(
89
- this: AnimationConfigBuilderClass,
91
+ static springify<T extends AnimationConfigBuilderClass>(
92
+ this: T,
90
93
  duration?: number
91
- ): AnimationConfigBuilder {
94
+ ): InstanceType<T> {
92
95
  const instance = this.createInstance();
93
96
  return instance.springify(duration);
94
97
  }
@@ -106,10 +109,10 @@ export class AnimationConfigBuilder extends BaseAnimationBuilder {
106
109
  *
107
110
  * @param dampingRatio - How damped the spring is.
108
111
  */
109
- static dampingRatio(
110
- this: AnimationConfigBuilderClass,
112
+ static dampingRatio<T extends AnimationConfigBuilderClass>(
113
+ this: T,
111
114
  dampingRatio: number
112
- ): AnimationConfigBuilder {
115
+ ): InstanceType<T> {
113
116
  const instance = this.createInstance();
114
117
  return instance.dampingRatio(dampingRatio);
115
118
  }
@@ -127,10 +130,10 @@ export class AnimationConfigBuilder extends BaseAnimationBuilder {
127
130
  * @param value - Decides how quickly a spring stops moving. Higher damping
128
131
  * means the spring will come to rest faster.
129
132
  */
130
- static damping(
131
- this: AnimationConfigBuilderClass,
133
+ static damping<T extends AnimationConfigBuilderClass>(
134
+ this: T,
132
135
  value: number
133
- ): AnimationConfigBuilder {
136
+ ): InstanceType<T> {
134
137
  const instance = this.createInstance();
135
138
  return instance.damping(value);
136
139
  }
@@ -148,10 +151,10 @@ export class AnimationConfigBuilder extends BaseAnimationBuilder {
148
151
  * @param mass - The weight of the spring. Reducing this value makes the
149
152
  * animation faster.
150
153
  */
151
- static mass(
152
- this: AnimationConfigBuilderClass,
154
+ static mass<T extends AnimationConfigBuilderClass>(
155
+ this: T,
153
156
  mass: number
154
- ): AnimationConfigBuilder {
157
+ ): InstanceType<T> {
155
158
  const instance = this.createInstance();
156
159
  return instance.mass(mass);
157
160
  }
@@ -168,10 +171,10 @@ export class AnimationConfigBuilder extends BaseAnimationBuilder {
168
171
  *
169
172
  * @param stiffness - How bouncy the spring is.
170
173
  */
171
- static stiffness(
172
- this: AnimationConfigBuilderClass,
174
+ static stiffness<T extends AnimationConfigBuilderClass>(
175
+ this: T,
173
176
  stiffness: number
174
- ): AnimationConfigBuilder {
177
+ ): InstanceType<T> {
175
178
  const instance = this.createInstance();
176
179
  return instance.stiffness(stiffness);
177
180
  }
@@ -189,10 +192,10 @@ export class AnimationConfigBuilder extends BaseAnimationBuilder {
189
192
  * @param overshootClamping - Whether a spring can bounce over the final
190
193
  * position.
191
194
  */
192
- static overshootClamping(
193
- this: AnimationConfigBuilderClass,
195
+ static overshootClamping<T extends AnimationConfigBuilderClass>(
196
+ this: T,
194
197
  overshootClamping: number
195
- ): AnimationConfigBuilder {
198
+ ): InstanceType<T> {
196
199
  const instance = this.createInstance();
197
200
  return instance.overshootClamping(overshootClamping);
198
201
  }
@@ -206,10 +209,10 @@ export class AnimationConfigBuilder extends BaseAnimationBuilder {
206
209
  * @deprecated Use {@link energyThreshold} instead. This method currently does
207
210
  * nothing and will be removed in the upcoming major version.
208
211
  */
209
- static restDisplacementThreshold(
210
- this: AnimationConfigBuilderClass,
212
+ static restDisplacementThreshold<T extends AnimationConfigBuilderClass>(
213
+ this: T,
211
214
  _restDisplacementThreshold: number
212
- ): AnimationConfigBuilder {
215
+ ): InstanceType<T> {
213
216
  return this.createInstance();
214
217
  }
215
218
 
@@ -225,10 +228,10 @@ export class AnimationConfigBuilder extends BaseAnimationBuilder {
225
228
  * @deprecated Use {@link energyThreshold} instead. This method currently does
226
229
  * nothing and will be removed in a future version.
227
230
  */
228
- static restSpeedThreshold(
229
- this: AnimationConfigBuilderClass,
231
+ static restSpeedThreshold<T extends AnimationConfigBuilderClass>(
232
+ this: T,
230
233
  _restSpeedThreshold: number
231
- ): AnimationConfigBuilder {
234
+ ): InstanceType<T> {
232
235
  return this.createInstance();
233
236
  }
234
237
 
@@ -248,10 +251,10 @@ export class AnimationConfigBuilder extends BaseAnimationBuilder {
248
251
  * @param energyThreshold - Relative energy threshold below which the spring
249
252
  * will snap to `toValue` without further oscillations. Defaults to 6e-9.
250
253
  */
251
- static energyThreshold(
252
- this: AnimationConfigBuilderClass,
254
+ static energyThreshold<T extends AnimationConfigBuilderClass>(
255
+ this: T,
253
256
  energyThreshold: number
254
- ): AnimationConfigBuilder {
257
+ ): InstanceType<T> {
255
258
  const instance = this.createInstance();
256
259
  return instance.energyThreshold(energyThreshold);
257
260
  }
@@ -11,28 +11,35 @@ import type {
11
11
  SharedValue,
12
12
  } from '../commonTypes';
13
13
  import { LayoutAnimationType } from '../commonTypes';
14
+ import { getStaticFeatureFlag } from '../featureFlags';
14
15
  import { makeMutableUI } from '../mutables';
15
16
 
16
17
  const TAG_OFFSET = 1e9;
17
18
 
19
+ const USE_ANIMATION_BACKEND = getStaticFeatureFlag('USE_ANIMATION_BACKEND');
20
+
18
21
  function startObservingProgress(
19
22
  tag: number,
20
- sharedValue: SharedValue<Record<string, unknown>>
23
+ sharedValue: SharedValue<Record<string, unknown>>,
24
+ scheduleFlush: () => void
21
25
  ): void {
22
26
  'worklet';
23
27
  sharedValue.addListener(tag + TAG_OFFSET, () => {
24
28
  global._notifyAboutProgress(tag, sharedValue.value);
29
+ scheduleFlush();
25
30
  });
26
31
  }
27
32
 
28
33
  function stopObservingProgress(
29
34
  tag: number,
30
35
  sharedValue: SharedValue<number>,
36
+ scheduleFlush: () => void,
31
37
  removeView = false
32
38
  ): void {
33
39
  'worklet';
34
40
  sharedValue.removeListener(tag + TAG_OFFSET);
35
41
  global._notifyAboutEnd(tag, removeView);
42
+ scheduleFlush();
36
43
  }
37
44
 
38
45
  function createLayoutAnimationManager(): {
@@ -43,6 +50,27 @@ function createLayoutAnimationManager(): {
43
50
  const currentAnimationForTag = new Map();
44
51
  const mutableValuesForTag = new Map();
45
52
 
53
+ // Flush layout-animation progress once per frame via the frame finalizer
54
+ // (after all `requestAnimationFrame` callbacks), reusing the same
55
+ // `_maybeFlushUIUpdatesQueue` path as animated-prop updates.
56
+ // This finalizer runs after the mapper run (which re-queues itself a frame
57
+ // ahead, so it sits earlier in the finalizer queue). When a mapper-driven
58
+ // animation is also active, its flush runs first and already commits the
59
+ // layout-animation updates too, so our `_maybeFlushUIUpdatesQueue` here is a
60
+ // no-op; when only layout animations run, this is the single flush.
61
+ // The backend drives its own flush from `runGrandCallback`, so this is non-backend only.
62
+ let flushRequested = false;
63
+ const scheduleFlush = () => {
64
+ if (USE_ANIMATION_BACKEND || flushRequested) {
65
+ return;
66
+ }
67
+ flushRequested = true;
68
+ globalThis.requestAnimationFrameFinalizer(() => {
69
+ flushRequested = false;
70
+ global._maybeFlushUIUpdatesQueue();
71
+ });
72
+ };
73
+
46
74
  return {
47
75
  start(
48
76
  tag: number,
@@ -70,7 +98,7 @@ function createLayoutAnimationManager(): {
70
98
  value = makeMutableUI(style.initialValues);
71
99
  mutableValuesForTag.set(tag, value);
72
100
  } else {
73
- stopObservingProgress(tag, value);
101
+ stopObservingProgress(tag, value, scheduleFlush);
74
102
  value._value = style.initialValues;
75
103
  }
76
104
 
@@ -81,14 +109,14 @@ function createLayoutAnimationManager(): {
81
109
  currentAnimationForTag.delete(tag);
82
110
  mutableValuesForTag.delete(tag);
83
111
  const shouldRemoveView = type === LayoutAnimationType.EXITING;
84
- stopObservingProgress(tag, value, shouldRemoveView);
112
+ stopObservingProgress(tag, value, scheduleFlush, shouldRemoveView);
85
113
  }
86
114
  if (style.callback) {
87
115
  style.callback(finished === undefined ? false : finished);
88
116
  }
89
117
  };
90
118
 
91
- startObservingProgress(tag, value);
119
+ startObservingProgress(tag, value, scheduleFlush);
92
120
  value.value = animation;
93
121
  },
94
122
  stop(tag: number) {
@@ -96,7 +124,7 @@ function createLayoutAnimationManager(): {
96
124
  if (!value) {
97
125
  return;
98
126
  }
99
- stopObservingProgress(tag, value);
127
+ stopObservingProgress(tag, value, scheduleFlush);
100
128
  },
101
129
  };
102
130
  }
package/src/mappers.ts CHANGED
@@ -103,13 +103,13 @@ function createMapperRegistry() {
103
103
  updateMappersOrder();
104
104
  }
105
105
  if (isAnyMapperDirty) {
106
+ isAnyMapperDirty = false;
106
107
  for (const mapper of sortedMappers) {
107
108
  if (mapper.dirty) {
108
109
  mapper.dirty = false;
109
110
  mapper.worklet();
110
111
  }
111
112
  }
112
- isAnyMapperDirty = false;
113
113
  }
114
114
  } finally {
115
115
  processingMappers = false;
@@ -12,7 +12,9 @@ function findHostInstanceFastPath(maybeNativeRef: HostInstance | undefined) {
12
12
  if (
13
13
  maybeNativeRef.__internalInstanceHandle &&
14
14
  maybeNativeRef.__nativeTag &&
15
- maybeNativeRef._viewConfig
15
+ // ReactFabricHostComponent (e.g. react-native-macos) exposes `_viewConfig`;
16
+ // ReactNativeElement uses `__viewConfig`.
17
+ (maybeNativeRef.__viewConfig || maybeNativeRef._viewConfig)
16
18
  ) {
17
19
  return maybeNativeRef;
18
20
  }
@@ -55,13 +57,11 @@ export function findHostInstance(
55
57
  resolveFindHostInstance_DEPRECATED();
56
58
  /*
57
59
  The Fabric implementation of `findHostInstance_DEPRECATED` requires a React ref as an argument
58
- rather than a native ref. If a component implements the `getAnimatableRef` method, it must use
59
- the ref provided by this method. It is the component's responsibility to ensure that this is
60
- a valid React ref.
60
+ rather than a native ref. Prefer the resolved component ref when available so components can
61
+ forward their ref to the host view that should be animated. Components that expose an animatable
62
+ ref via `getAnimatableRef` already have it resolved into `_componentRef`.
61
63
  */
62
64
  return findHostInstance_DEPRECATED(
63
- (ref as IAnimatedComponentInternalBase)._hasAnimatedRef
64
- ? (ref as IAnimatedComponentInternalBase)._componentRef
65
- : ref
65
+ (ref as IAnimatedComponentInternalBase)._componentRef ?? ref
66
66
  );
67
67
  }
@@ -4,4 +4,4 @@
4
4
  * version used to build the native part of the library in runtime. Remember to
5
5
  * keep this in sync with the version declared in `package.json`
6
6
  */
7
- export const jsVersion = '4.4.1';
7
+ export const jsVersion = '4.4.2';
@@ -3,5 +3,7 @@
3
3
  export type HostInstance = {
4
4
  __internalInstanceHandle?: Record<string, unknown>;
5
5
  __nativeTag?: number;
6
+ __viewConfig?: Record<string, unknown>;
7
+ // Legacy ReactFabricHostComponent key (e.g. react-native-macos).
6
8
  _viewConfig?: Record<string, unknown>;
7
9
  };
@@ -112,7 +112,7 @@ type NativePropsOperation = {
112
112
  function createUpdatePropsManager() {
113
113
  'worklet';
114
114
  const nativeOperations: NativePropsOperation[] = [];
115
- const jsOperations: JSPropsOperation[] = [];
115
+ let jsOperations: JSPropsOperation[] = [];
116
116
 
117
117
  let flushPending = false;
118
118
 
@@ -165,8 +165,9 @@ function createUpdatePropsManager() {
165
165
  nativeOperations.length = 0;
166
166
  }
167
167
  if (jsOperations.length) {
168
+ // Fresh array each flush: scheduleOnRN caches serialized args by identity.
168
169
  scheduleOnRN(updateJSProps, jsOperations);
169
- jsOperations.length = 0;
170
+ jsOperations = [];
170
171
  }
171
172
  flushPending = false;
172
173
  if (!USE_ANIMATION_BACKEND) {
@@ -9,15 +9,19 @@ export function valueSetter<Value>(
9
9
  'worklet';
10
10
  const previousAnimation = mutable._animation;
11
11
  if (previousAnimation) {
12
- previousAnimation.cancelled = true;
13
12
  mutable._animation = null;
13
+ if (!previousAnimation.finished) {
14
+ previousAnimation.cancelled = true;
15
+ previousAnimation.callback?.(false);
16
+ }
14
17
  }
15
18
  if (
16
19
  typeof value === 'function' ||
17
20
  (value !== null &&
18
21
  typeof value === 'object' &&
19
22
  // TODO TYPESCRIPT fix this after fixing AnimationObject type
20
- (value as unknown as AnimationObject).onFrame !== undefined)
23
+ (value as unknown as AnimationObject).onFrame !== undefined &&
24
+ (value as unknown as AnimationObject).onStart !== undefined)
21
25
  ) {
22
26
  const animation: AnimationObject<Value> =
23
27
  typeof value === 'function'
@@ -47,17 +51,13 @@ export function valueSetter<Value>(
47
51
 
48
52
  const step = (timestamp: number) => {
49
53
  if (animation.cancelled) {
50
- animation.callback?.(false /* finished */);
51
54
  return;
52
55
  }
53
56
  const finished = animation.onFrame(animation, timestamp);
54
- animation.finished = true;
55
57
  animation.timestamp = timestamp;
56
- // TODO TYPESCRIPT
57
- // For now I'll assume that `animation.current` is always defined
58
- // but actually need to dive into animations to understand it
59
58
  mutable._value = animation.current!;
60
59
  if (finished) {
60
+ animation.finished = true;
61
61
  animation.callback?.(true /* finished */);
62
62
  } else {
63
63
  requestAnimationFrame(step);