react-native-reanimated 4.3.0-rc.0 → 4.3.1

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 (176) hide show
  1. package/Common/cpp/reanimated/AnimatedSensor/AnimatedSensorModule.cpp +8 -5
  2. package/Common/cpp/reanimated/AnimatedSensor/AnimatedSensorModule.h +3 -5
  3. package/Common/cpp/reanimated/CSS/common/values/CSSValueVariant.cpp +2 -3
  4. package/Common/cpp/reanimated/CSS/interpolation/PropertyInterpolator.cpp +2 -3
  5. package/Common/cpp/reanimated/CSS/registries/CSSAnimationsRegistry.cpp +7 -3
  6. package/Common/cpp/reanimated/CSS/registries/CSSAnimationsRegistry.h +9 -2
  7. package/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.cpp +6 -2
  8. package/Common/cpp/reanimated/CSS/registries/CSSTransitionsRegistry.h +8 -2
  9. package/Common/cpp/reanimated/CSS/utils/DelayedItemsManager.cpp +2 -2
  10. package/Common/cpp/reanimated/CSS/utils/DelayedItemsManager.h +2 -2
  11. package/Common/cpp/reanimated/Compat/WorkletsApi.h +13 -0
  12. package/Common/cpp/reanimated/Events/UIEventHandler.cpp +1 -1
  13. package/Common/cpp/reanimated/Events/UIEventHandler.h +3 -4
  14. package/Common/cpp/reanimated/Events/UIEventHandlerRegistry.cpp +3 -2
  15. package/Common/cpp/reanimated/Events/UIEventHandlerRegistry.h +1 -1
  16. package/Common/cpp/reanimated/Fabric/ReanimatedCommitHook.cpp +2 -8
  17. package/Common/cpp/reanimated/Fabric/ReanimatedCommitHook.h +2 -6
  18. package/Common/cpp/reanimated/Fabric/ReanimatedMountHook.cpp +1 -6
  19. package/Common/cpp/reanimated/Fabric/ReanimatedMountHook.h +1 -8
  20. package/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.cpp +12 -7
  21. package/Common/cpp/reanimated/Fabric/updates/AnimatedPropsRegistry.h +6 -3
  22. package/Common/cpp/reanimated/Fabric/updates/UpdatesRegistry.cpp +4 -6
  23. package/Common/cpp/reanimated/Fabric/updates/UpdatesRegistry.h +25 -7
  24. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsManager.h +1 -2
  25. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h +1 -1
  26. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +9 -9
  27. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h +2 -3
  28. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp +21 -21
  29. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.h +3 -3
  30. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h +3 -3
  31. package/Common/cpp/reanimated/LayoutAnimations/SharedTransitions.cpp +24 -20
  32. package/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp +35 -53
  33. package/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h +1 -2
  34. package/Common/cpp/reanimated/RuntimeDecorators/UIRuntimeDecorator.cpp +22 -22
  35. package/Common/cpp/reanimated/RuntimeDecorators/UIRuntimeDecorator.h +10 -10
  36. package/Common/cpp/reanimated/Tools/ReaJSIUtils.h +5 -5
  37. package/README.md +1 -1
  38. package/android/build.gradle +0 -21
  39. package/android/src/main/cpp/reanimated/android/NativeProxy.cpp +6 -10
  40. package/android/src/main/cpp/reanimated/android/NativeProxy.h +1 -2
  41. package/android/src/main/java/com/swmansion/reanimated/CopiedEvent.java +66 -9
  42. package/android/src/main/java/com/swmansion/reanimated/NodesManager.java +16 -11
  43. package/android/src/main/java/com/swmansion/reanimated/nativeProxy/EventHandler.java +34 -8
  44. package/android/src/main/java/com/swmansion/reanimated/nativeProxy/NoopEventHandler.java +27 -6
  45. package/apple/reanimated/apple/REANodesManager.mm +0 -8
  46. package/apple/reanimated/apple/ReanimatedModule.mm +6 -12
  47. package/apple/reanimated/apple/native/NativeProxy.h +1 -2
  48. package/apple/reanimated/apple/native/NativeProxy.mm +1 -1
  49. package/compatibility.json +7 -29
  50. package/lib/module/PropsRegistryGarbageCollector.js +3 -0
  51. package/lib/module/PropsRegistryGarbageCollector.js.map +1 -1
  52. package/lib/module/common/style/processors/colors.js +31 -4
  53. package/lib/module/common/style/processors/colors.js.map +1 -1
  54. package/lib/module/common/style/processors/filter.js +103 -61
  55. package/lib/module/common/style/processors/filter.js.map +1 -1
  56. package/lib/module/common/style/processors/font.js +7 -1
  57. package/lib/module/common/style/processors/font.js.map +1 -1
  58. package/lib/module/common/style/processors/insets.js +26 -14
  59. package/lib/module/common/style/processors/insets.js.map +1 -1
  60. package/lib/module/common/style/processors/others.js +15 -5
  61. package/lib/module/common/style/processors/others.js.map +1 -1
  62. package/lib/module/common/style/processors/shadows.js +14 -3
  63. package/lib/module/common/style/processors/shadows.js.map +1 -1
  64. package/lib/module/common/style/processors/transform.js +30 -1
  65. package/lib/module/common/style/processors/transform.js.map +1 -1
  66. package/lib/module/common/style/processors/transformOrigin.js +20 -3
  67. package/lib/module/common/style/processors/transformOrigin.js.map +1 -1
  68. package/lib/module/common/utils/guards.js +27 -7
  69. package/lib/module/common/utils/guards.js.map +1 -1
  70. package/lib/module/common/utils/parsers.js +2 -1
  71. package/lib/module/common/utils/parsers.js.map +1 -1
  72. package/lib/module/createAnimatedComponent/AnimatedComponent.js +1 -7
  73. package/lib/module/createAnimatedComponent/AnimatedComponent.js.map +1 -1
  74. package/lib/module/css/native/managers/CSSManager.js +16 -15
  75. package/lib/module/css/native/managers/CSSManager.js.map +1 -1
  76. package/lib/module/css/native/managers/CSSTransitionsManager.js +47 -44
  77. package/lib/module/css/native/managers/CSSTransitionsManager.js.map +1 -1
  78. package/lib/module/css/native/normalization/transition/config.js +35 -17
  79. package/lib/module/css/native/normalization/transition/config.js.map +1 -1
  80. package/lib/module/css/utils/props.js +6 -0
  81. package/lib/module/css/utils/props.js.map +1 -1
  82. package/lib/module/featureFlags/index.js +1 -1
  83. package/lib/module/featureFlags/staticFlags.json +1 -1
  84. package/lib/module/hook/useAnimatedRef.js +2 -2
  85. package/lib/module/hook/useAnimatedRef.js.map +1 -1
  86. package/lib/module/hook/useAnimatedStyle.js +7 -1
  87. package/lib/module/hook/useAnimatedStyle.js.map +1 -1
  88. package/lib/module/hook/useHandler.js +82 -28
  89. package/lib/module/hook/useHandler.js.map +1 -1
  90. package/lib/module/hook/utils.js +1 -74
  91. package/lib/module/hook/utils.js.map +1 -1
  92. package/lib/module/initializers.js +2 -0
  93. package/lib/module/initializers.js.map +1 -1
  94. package/lib/module/jestUtils/common.js +10 -0
  95. package/lib/module/jestUtils/common.js.map +1 -1
  96. package/lib/module/jestUtils/index.js +2 -5
  97. package/lib/module/jestUtils/index.js.map +1 -1
  98. package/lib/module/jestUtils/index.web.js +1 -1
  99. package/lib/module/jestUtils/index.web.js.map +1 -1
  100. package/lib/module/mutables.js +5 -1
  101. package/lib/module/mutables.js.map +1 -1
  102. package/lib/module/platform-specific/jsVersion.js +1 -1
  103. package/lib/module/platform-specific/jsVersion.js.map +1 -1
  104. package/lib/typescript/PropsRegistryGarbageCollector.d.ts.map +1 -1
  105. package/lib/typescript/common/style/processors/colors.d.ts +3 -3
  106. package/lib/typescript/common/style/processors/colors.d.ts.map +1 -1
  107. package/lib/typescript/common/style/processors/filter.d.ts +0 -4
  108. package/lib/typescript/common/style/processors/filter.d.ts.map +1 -1
  109. package/lib/typescript/common/style/processors/font.d.ts +1 -1
  110. package/lib/typescript/common/style/processors/font.d.ts.map +1 -1
  111. package/lib/typescript/common/style/processors/insets.d.ts.map +1 -1
  112. package/lib/typescript/common/style/processors/others.d.ts +1 -1
  113. package/lib/typescript/common/style/processors/others.d.ts.map +1 -1
  114. package/lib/typescript/common/style/processors/shadows.d.ts.map +1 -1
  115. package/lib/typescript/common/style/processors/transform.d.ts.map +1 -1
  116. package/lib/typescript/common/style/processors/transformOrigin.d.ts +2 -2
  117. package/lib/typescript/common/style/processors/transformOrigin.d.ts.map +1 -1
  118. package/lib/typescript/common/utils/guards.d.ts +0 -1
  119. package/lib/typescript/common/utils/guards.d.ts.map +1 -1
  120. package/lib/typescript/common/utils/parsers.d.ts.map +1 -1
  121. package/lib/typescript/commonTypes.d.ts +1 -1
  122. package/lib/typescript/commonTypes.d.ts.map +1 -1
  123. package/lib/typescript/createAnimatedComponent/AnimatedComponent.d.ts.map +1 -1
  124. package/lib/typescript/css/native/managers/CSSManager.d.ts +7 -1
  125. package/lib/typescript/css/native/managers/CSSManager.d.ts.map +1 -1
  126. package/lib/typescript/css/native/managers/CSSTransitionsManager.d.ts +5 -4
  127. package/lib/typescript/css/native/managers/CSSTransitionsManager.d.ts.map +1 -1
  128. package/lib/typescript/css/native/normalization/transition/config.d.ts.map +1 -1
  129. package/lib/typescript/css/native/types/transition.d.ts +15 -1
  130. package/lib/typescript/css/native/types/transition.d.ts.map +1 -1
  131. package/lib/typescript/css/utils/props.d.ts.map +1 -1
  132. package/lib/typescript/featureFlags/index.d.ts +1 -1
  133. package/lib/typescript/hook/useAnimatedStyle.d.ts.map +1 -1
  134. package/lib/typescript/hook/useHandler.d.ts +8 -9
  135. package/lib/typescript/hook/useHandler.d.ts.map +1 -1
  136. package/lib/typescript/hook/utils.d.ts +0 -5
  137. package/lib/typescript/hook/utils.d.ts.map +1 -1
  138. package/lib/typescript/jestUtils/common.d.ts +2 -0
  139. package/lib/typescript/jestUtils/common.d.ts.map +1 -1
  140. package/lib/typescript/jestUtils/index.d.ts +4 -3
  141. package/lib/typescript/jestUtils/index.d.ts.map +1 -1
  142. package/lib/typescript/mutables.d.ts.map +1 -1
  143. package/lib/typescript/platform-specific/jsVersion.d.ts +1 -1
  144. package/lib/typescript/platform-specific/jsVersion.d.ts.map +1 -1
  145. package/package.json +13 -10
  146. package/src/PropsRegistryGarbageCollector.ts +3 -0
  147. package/src/common/style/processors/colors.ts +20 -7
  148. package/src/common/style/processors/filter.ts +95 -70
  149. package/src/common/style/processors/font.ts +5 -2
  150. package/src/common/style/processors/insets.ts +23 -14
  151. package/src/common/style/processors/others.ts +12 -6
  152. package/src/common/style/processors/shadows.ts +10 -6
  153. package/src/common/style/processors/transform.ts +15 -1
  154. package/src/common/style/processors/transformOrigin.ts +40 -11
  155. package/src/common/utils/guards.ts +21 -16
  156. package/src/common/utils/parsers.ts +1 -1
  157. package/src/commonTypes.ts +1 -1
  158. package/src/createAnimatedComponent/AnimatedComponent.tsx +1 -7
  159. package/src/css/native/managers/CSSManager.ts +29 -14
  160. package/src/css/native/managers/CSSTransitionsManager.ts +52 -54
  161. package/src/css/native/normalization/transition/config.ts +35 -27
  162. package/src/css/native/types/transition.ts +15 -1
  163. package/src/css/utils/props.ts +7 -0
  164. package/src/featureFlags/index.ts +1 -1
  165. package/src/featureFlags/staticFlags.json +1 -1
  166. package/src/hook/useAnimatedRef.ts +2 -2
  167. package/src/hook/useAnimatedStyle.ts +15 -6
  168. package/src/hook/useHandler.ts +150 -64
  169. package/src/hook/utils.ts +1 -127
  170. package/src/initializers.ts +1 -0
  171. package/src/jestUtils/common.ts +10 -0
  172. package/src/jestUtils/index.ts +5 -8
  173. package/src/jestUtils/index.web.ts +1 -1
  174. package/src/mutables.ts +5 -1
  175. package/src/platform-specific/jsVersion.ts +1 -1
  176. package/src/privateGlobals.d.ts +4 -0
@@ -1,7 +1,10 @@
1
1
  'use strict';
2
- 'worklet';
3
2
  import { ReanimatedError } from '../../errors';
4
- import type { TransformOrigin, ValueProcessor } from '../../types';
3
+ import type {
4
+ NormalizedTransformOrigin,
5
+ TransformOrigin,
6
+ ValueProcessor,
7
+ } from '../../types';
5
8
 
6
9
  type Axis = 'x' | 'y' | 'z';
7
10
  type ConvertedValue = `${number}%` | number;
@@ -21,6 +24,7 @@ const VERTICAL_CONVERSIONS = {
21
24
  } satisfies KeywordConversions;
22
25
 
23
26
  function getAllowedValues(axis: Axis, isArray: boolean): string {
27
+ 'worklet';
24
28
  const allowed: string[] = [];
25
29
 
26
30
  if (isArray) {
@@ -51,20 +55,25 @@ function getAllowedValues(axis: Axis, isArray: boolean): string {
51
55
  }
52
56
 
53
57
  export const ERROR_MESSAGES = {
54
- invalidTransformOrigin: (value: Readonly<TransformOrigin>) =>
55
- `Invalid transformOrigin: ${JSON.stringify(value)}. Expected 1-3 values.`,
58
+ invalidTransformOrigin: (value: Readonly<TransformOrigin>) => {
59
+ 'worklet';
60
+ return `Invalid transformOrigin: ${JSON.stringify(value)}. Expected 1-3 values.`;
61
+ },
56
62
  invalidValue: (
57
63
  value: string | number,
58
64
  axis: Axis,
59
65
  origin: Readonly<TransformOrigin>,
60
66
  isArray: boolean
61
- ) =>
62
- `Invalid value "${value}" for the ${axis}-axis in transformOrigin ${JSON.stringify(
67
+ ) => {
68
+ 'worklet';
69
+ return `Invalid value "${value}" for the ${axis}-axis in transformOrigin ${JSON.stringify(
63
70
  origin
64
- )}. Allowed values: ${getAllowedValues(axis, isArray)}.`,
71
+ )}. Allowed values: ${getAllowedValues(axis, isArray)}.`;
72
+ },
65
73
  };
66
74
 
67
75
  function maybeSwapComponents(components: ReadonlyArray<string | number>) {
76
+ 'worklet';
68
77
  if (
69
78
  components[0] in VERTICAL_CONVERSIONS &&
70
79
  (components[1] === undefined || components[1] in HORIZONTAL_CONVERSIONS)
@@ -77,13 +86,30 @@ function maybeSwapComponents(components: ReadonlyArray<string | number>) {
77
86
  return components;
78
87
  }
79
88
 
89
+ function parseValue(
90
+ value: string | number,
91
+ allowPercentages: true,
92
+ customParse: CustomParse,
93
+ getError: () => string,
94
+ keywordConversions?: KeywordConversions
95
+ ): `${number}%` | number;
96
+
97
+ function parseValue(
98
+ value: string | number,
99
+ allowPercentages: false,
100
+ customParse: CustomParse,
101
+ getError: () => string,
102
+ keywordConversions?: KeywordConversions
103
+ ): number;
104
+
80
105
  function parseValue(
81
106
  value: string | number,
82
107
  allowPercentages: boolean,
83
108
  customParse: CustomParse,
84
109
  getError: () => string,
85
110
  keywordConversions?: KeywordConversions
86
- ) {
111
+ ): `${number}%` | number {
112
+ 'worklet';
87
113
  if (typeof value === 'number') {
88
114
  return value;
89
115
  }
@@ -109,6 +135,7 @@ function parseValue(
109
135
  }
110
136
 
111
137
  function parsePx(component: string) {
138
+ 'worklet';
112
139
  if (component.endsWith('px') || component === '0') {
113
140
  const num = parseFloat(component);
114
141
  if (!isNaN(num)) {
@@ -118,9 +145,11 @@ function parsePx(component: string) {
118
145
  return null;
119
146
  }
120
147
 
121
- export const processTransformOrigin: ValueProcessor<TransformOrigin> = (
122
- value
123
- ) => {
148
+ export const processTransformOrigin: ValueProcessor<
149
+ TransformOrigin,
150
+ NormalizedTransformOrigin
151
+ > = (value) => {
152
+ 'worklet';
124
153
  const isArray = Array.isArray(value);
125
154
  let components = typeof value === 'string' ? value.split(/\s+/) : value;
126
155
  const customParse = isArray ? () => null : parsePx;
@@ -1,5 +1,4 @@
1
1
  'use strict';
2
- 'worklet';
3
2
  import type { AnyRecord, ConfigPropertyAlias, UnknownRecord } from '../types';
4
3
 
5
4
  export const isDefined = <T>(value: T): value is NonNullable<T> =>
@@ -7,31 +6,37 @@ export const isDefined = <T>(value: T): value is NonNullable<T> =>
7
6
 
8
7
  export const isAngle = (
9
8
  value: string | number
10
- ): value is `${number}deg` | `${number}rad` =>
11
- typeof value === 'string' && /^-?\d+(\.\d+)?(deg|rad)$/.test(value);
9
+ ): value is `${number}deg` | `${number}rad` => {
10
+ 'worklet';
11
+ return typeof value === 'string' && /^-?\d+(\.\d+)?(deg|rad)$/.test(value);
12
+ };
12
13
 
13
- export const isNumber = (value: unknown): value is number =>
14
- typeof value === 'number' && !isNaN(value);
14
+ export const isNumber = (value: unknown): value is number => {
15
+ 'worklet';
16
+ return typeof value === 'number' && !isNaN(value);
17
+ };
15
18
 
16
- export const isNumberArray = (value: unknown): value is number[] =>
17
- Array.isArray(value) && value.every(isNumber);
19
+ export const isNumberArray = (value: unknown): value is number[] => {
20
+ 'worklet';
21
+ return Array.isArray(value) && value.every(isNumber);
22
+ };
18
23
 
19
24
  export const isLength = (value: string) => {
25
+ 'worklet';
20
26
  return value.endsWith('px') || !isNaN(Number(value));
21
27
  };
22
28
 
23
- export const isPercentage = (value: unknown): value is `${number}%` =>
24
- typeof value === 'string' && /^-?\d+(\.\d+)?%$/.test(value);
29
+ export const isPercentage = (value: unknown): value is `${number}%` => {
30
+ 'worklet';
31
+ return typeof value === 'string' && /^-?\d+(\.\d+)?%$/.test(value);
32
+ };
25
33
 
26
34
  export const isRecord = <T extends UnknownRecord = UnknownRecord>(
27
35
  value: unknown
28
- ): value is T =>
29
- typeof value === 'object' && value !== null && !Array.isArray(value);
30
-
31
- export const hasProp = <P extends AnyRecord, K extends string>(
32
- obj: P,
33
- key: K
34
- ): obj is P & Record<K, string> => key in obj;
36
+ ): value is T => {
37
+ 'worklet';
38
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
39
+ };
35
40
 
36
41
  export const isConfigPropertyAlias = <P extends AnyRecord>(
37
42
  value: unknown
@@ -1,5 +1,4 @@
1
1
  'use strict';
2
- 'worklet';
3
2
  import type { BoxShadowValue } from 'react-native';
4
3
 
5
4
  import { isLength } from '../utils/guards';
@@ -15,6 +14,7 @@ const SHADOW_PARTS_REGEX = /(?:[^\s()]+|\([^()]*\))+/g;
15
14
  const SHADOW_SPLIT_REGEX = /(?:[^,()]+|\([^)]*\))+(?=\s*,|$)/g;
16
15
 
17
16
  export function parseBoxShadowString(value: string) {
17
+ 'worklet';
18
18
  if (value === 'none') {
19
19
  return [];
20
20
  }
@@ -374,7 +374,7 @@ export type ShadowNodeWrapper = {
374
374
 
375
375
  export type SettledUpdate = {
376
376
  viewTag: number;
377
- styleProps: StyleProps;
377
+ styleProps: StyleProps | null;
378
378
  };
379
379
 
380
380
  export enum KeyboardState {
@@ -3,7 +3,6 @@ import '../layoutReanimation/animationsManager';
3
3
 
4
4
  import type React from 'react';
5
5
  import { Fragment } from 'react';
6
- import { StyleSheet } from 'react-native';
7
6
 
8
7
  import { checkStyleOverwriting, maybeBuild } from '../animationBuilder';
9
8
  import { IS_JEST, IS_WEB, logger } from '../common';
@@ -536,16 +535,11 @@ export default class AnimatedComponent
536
535
  }
537
536
 
538
537
  if (FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS) {
539
- const flatStyles = StyleSheet.flatten(filteredProps.style as object);
540
- const mergedStyles = {
541
- ...flatStyles,
542
- ...this.state.settledProps,
543
- };
544
538
  return super.render({
545
539
  nativeID,
546
540
  ...filteredProps,
547
541
  ...this.state.settledProps,
548
- style: mergedStyles,
542
+ style: [...flattenArray(filteredProps.style), this.state.settledProps],
549
543
  ...jestProps,
550
544
  });
551
545
  }
@@ -1,5 +1,9 @@
1
1
  'use strict';
2
- import { getCompoundComponentName, getPropsBuilder } from '../../../common';
2
+ import {
3
+ getCompoundComponentName,
4
+ getPropsBuilder,
5
+ IS_ANDROID,
6
+ } from '../../../common';
3
7
  import type { ShadowNodeWrapper } from '../../../commonTypes';
4
8
  import type { ViewInfo } from '../../../createAnimatedComponent/commonTypes';
5
9
  import type { CSSStyle } from '../../types';
@@ -14,7 +18,13 @@ export default class CSSManager implements ICSSManager {
14
18
  private readonly cssTransitionsManager: CSSTransitionsManager;
15
19
  private readonly viewTag: number;
16
20
  private readonly propsBuilder: ReturnType<typeof getPropsBuilder>;
17
- private isFirstUpdate: boolean = true;
21
+ /**
22
+ * True if the previous update had CSS transition props attached. On the next
23
+ * update we still need to build `normalizedStyle` only on Android to revert
24
+ * props applied during the transition to correct current values. (fixes
25
+ * https://github.com/software-mansion/react-native-reanimated/issues/9218).
26
+ */
27
+ private hadTransitionLastUpdate = false;
18
28
 
19
29
  constructor(
20
30
  { shadowNodeWrapper, viewTag, reactViewName = 'RCTView' }: ViewInfo,
@@ -41,11 +51,23 @@ export default class CSSManager implements ICSSManager {
41
51
  const [animationProperties, transitionProperties, filteredStyle] =
42
52
  filterCSSAndStyleProperties(style);
43
53
 
44
- const normalizedStyle = this.propsBuilder.build(filteredStyle);
54
+ const hasAnimation = animationProperties !== null;
55
+ const hasTransition = transitionProperties !== null;
45
56
 
46
- // If the update is called during the first css style update, we won't
47
- // trigger CSS transitions and set styles before attaching CSS transitions
48
- if (this.isFirstUpdate && normalizedStyle) {
57
+ const normalizedStyle =
58
+ hasAnimation ||
59
+ hasTransition ||
60
+ (IS_ANDROID && this.hadTransitionLastUpdate)
61
+ ? this.propsBuilder.build(filteredStyle)
62
+ : undefined;
63
+
64
+ if (
65
+ normalizedStyle &&
66
+ (hasAnimation ||
67
+ // We also need to update the current style on Android when the
68
+ // transition is detached.
69
+ (IS_ANDROID && !hasTransition && this.hadTransitionLastUpdate))
70
+ ) {
49
71
  setViewStyle(this.viewTag, normalizedStyle);
50
72
  }
51
73
 
@@ -55,14 +77,7 @@ export default class CSSManager implements ICSSManager {
55
77
  );
56
78
  this.cssAnimationsManager.update(animationProperties);
57
79
 
58
- // If the current update is not the fist one, we want to update CSS
59
- // animations and transitions first and update the style then to make
60
- // sure that the new transition is fired with new settings (like duration)
61
- if (!this.isFirstUpdate && normalizedStyle) {
62
- setViewStyle(this.viewTag, normalizedStyle);
63
- }
64
-
65
- this.isFirstUpdate = false;
80
+ this.hadTransitionLastUpdate = hasTransition;
66
81
  }
67
82
 
68
83
  unmountCleanup(): void {
@@ -17,7 +17,13 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager {
17
17
  private readonly viewTag: number;
18
18
  private readonly shadowNodeWrapper: ShadowNodeWrapper;
19
19
 
20
- private lastAppliedProps: UnknownRecord | null = null;
20
+ // All props from the previous update
21
+ private prevProps: UnknownRecord | null = null;
22
+ // Stores all properties for which transition was triggered before
23
+ // and which haven't been cleaned up yet (null if no transition was attached before)
24
+ private propsWithTransitions = new Set<string>();
25
+ // Indicates whether a CSS transition is currently attached to the view
26
+ private hasTransition = false;
21
27
 
22
28
  constructor(shadowNodeWrapper: ShadowNodeWrapper, viewTag: number) {
23
29
  this.viewTag = viewTag;
@@ -26,38 +32,27 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager {
26
32
 
27
33
  update(
28
34
  transitionProperties: CSSTransitionProperties | null,
29
- props: UnknownRecord = {}
35
+ nextProps: UnknownRecord = {}
30
36
  ): void {
31
37
  const transitionConfig =
32
38
  transitionProperties &&
33
39
  normalizeCSSTransitionProperties(transitionProperties);
34
40
 
35
- if (!transitionConfig) {
36
- this.detach();
37
- return;
38
- }
41
+ const prevProps = this.prevProps;
42
+ this.prevProps = nextProps;
39
43
 
40
- const prevProps = this.lastAppliedProps; // Active props from last update
41
- const nextProps: UnknownRecord = {}; // Active props from this update
42
-
43
- // Filter new props based on the new transition config
44
- if (!transitionConfig.properties) {
45
- Object.assign(nextProps, props);
46
- } else {
47
- for (const property of transitionConfig.properties) {
48
- if (property in props) {
49
- nextProps[property] = props[property];
50
- }
44
+ // If there were no previous props, the view is just mounted so we
45
+ // don't trigger any transitions yet. Also, when there is no transition
46
+ // config, we don't trigger any transitions.
47
+ if (!prevProps || !transitionConfig) {
48
+ if (this.hasTransition) {
49
+ this.detach();
51
50
  }
52
- }
53
-
54
- this.lastAppliedProps = nextProps;
55
-
56
- if (!prevProps) {
57
51
  return;
58
52
  }
59
53
 
60
- const config = this.buildTransitionConfig(
54
+ // Trigger transition for changed properties only
55
+ const config = this.processTransitionConfig(
61
56
  prevProps,
62
57
  nextProps,
63
58
  transitionConfig
@@ -65,6 +60,7 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager {
65
60
 
66
61
  if (Object.keys(config).length) {
67
62
  runCSSTransition(this.shadowNodeWrapper, config);
63
+ this.hasTransition = true;
68
64
  }
69
65
  }
70
66
 
@@ -73,57 +69,59 @@ export default class CSSTransitionsManager implements ICSSTransitionsManager {
73
69
  }
74
70
 
75
71
  private detach() {
76
- if (this.lastAppliedProps) {
77
- unregisterCSSTransition(this.viewTag);
78
- this.lastAppliedProps = null;
79
- }
72
+ unregisterCSSTransition(this.viewTag);
73
+ this.propsWithTransitions.clear();
74
+ this.hasTransition = false;
80
75
  }
81
76
 
82
- private buildTransitionConfig(
77
+ private processTransitionConfig(
83
78
  oldProps: UnknownRecord,
84
79
  newProps: UnknownRecord,
85
80
  newTransitionConfig: NormalizedCSSTransitionConfig
86
81
  ): CSSTransitionConfig {
87
82
  const result: CSSTransitionConfig = {};
88
83
 
89
- const newTransitionProperties = newTransitionConfig.properties;
90
- const allowedProperties = !newTransitionProperties
91
- ? null
92
- : new Set(newTransitionProperties);
84
+ const specificProperties = newTransitionConfig.specificProperties;
85
+
86
+ const isAllowedProperty = (property: string) =>
87
+ !specificProperties || specificProperties.has(property);
88
+
89
+ const getPropertySettings = (property: string) =>
90
+ newTransitionConfig.settings[property] ??
91
+ newTransitionConfig.settings.all;
92
+
93
+ const triggerTransition = (property: string) => {
94
+ result[property] = {
95
+ ...getPropertySettings(property),
96
+ value: [oldProps[property], newProps[property]],
97
+ };
98
+ this.propsWithTransitions.add(property);
99
+ };
93
100
 
94
101
  // Get property changes which we want to trigger transitions for
95
102
  for (const key in newProps) {
96
- const newValue = newProps[key];
97
- const oldValue = oldProps[key];
98
-
99
- if (!deepEqual(oldValue, newValue)) {
100
- result[key] = {
101
- ...this.getPropertySettings(key, newTransitionConfig),
102
- value: [oldValue, newValue],
103
- };
103
+ if (isAllowedProperty(key) && !deepEqual(newProps[key], oldProps[key])) {
104
+ triggerTransition(key);
104
105
  }
105
106
  }
106
107
 
107
- // Clear up old properties that are no longer allowed
108
+ // Handle old props; for no longer allowed ones, cancel the transition
109
+ // immediately; for ones that are allowed but were removed, trigger a transition
110
+ // to undefined (to the default value for the property).
108
111
  for (const key in oldProps) {
109
- if (allowedProperties && !allowedProperties.has(key)) {
110
- result[key] = null;
112
+ if (!isAllowedProperty(key)) {
113
+ if (this.propsWithTransitions.has(key)) {
114
+ // If a property was transitioned before but is no longer allowed,
115
+ // we need to clear it up immediately
116
+ result[key] = null;
117
+ this.propsWithTransitions.delete(key);
118
+ }
111
119
  } else if (!(key in newProps)) {
112
120
  // Property was removed from props but is still allowed
113
- result[key] = {
114
- ...this.getPropertySettings(key, newTransitionConfig),
115
- value: [oldProps[key], undefined],
116
- };
121
+ triggerTransition(key);
117
122
  }
118
123
  }
119
124
 
120
125
  return result;
121
126
  }
122
-
123
- private getPropertySettings(
124
- property: string,
125
- config: NormalizedCSSTransitionConfig
126
- ) {
127
- return config.settings[property] ?? config.settings.all;
128
- }
129
127
  }
@@ -79,9 +79,10 @@ export function normalizeCSSTransitionProperties(
79
79
  transitionDelay,
80
80
  transitionBehavior,
81
81
  } = expandedProperties;
82
- const specificProperties: string[] = [];
83
82
  let allPropertiesTransition = false;
84
83
  const settings: Record<string, NormalizedSingleCSSTransitionSettings> = {};
84
+ const specificProperties = new Set<string>();
85
+ const processedProperties = new Set<string>();
85
86
 
86
87
  if (!transitionProperty.length) {
87
88
  // For cases when transition property hasn't been explicitly specified
@@ -95,42 +96,49 @@ export function normalizeCSSTransitionProperties(
95
96
  // occurrence and ignore remaining ones)
96
97
  for (let i = transitionProperty.length - 1; i >= 0; i--) {
97
98
  const property = transitionProperty[i];
98
- // Continue if there was a prop with the same name specified later
99
- // (we don't want to override the last occurrence of the property)
100
- if (settings?.[property]) {
99
+ // We always respect the last occurrence of a property, even if it gets
100
+ // pruned as inactive. That means earlier occurrences must be ignored.
101
+ if (processedProperties.has(property)) {
101
102
  continue;
102
103
  }
104
+ processedProperties.add(property);
103
105
 
104
- if (property === 'all') {
105
- allPropertiesTransition = true;
106
- } else {
107
- specificProperties.push(property);
106
+ const duration = normalizeDuration(
107
+ transitionDuration[i % transitionDuration.length]
108
+ );
109
+ const delay = normalizeDelay(transitionDelay[i % transitionDelay.length]);
110
+
111
+ // Skip if effective duration is 0 (the transition would be immediate so there is no need
112
+ // to apply it and we can just treat it as a plain render without a transition)
113
+ if (duration + delay <= 0) {
114
+ continue;
108
115
  }
109
116
 
110
- settings[property] = {
111
- duration: normalizeDuration(
112
- transitionDuration[i % transitionDuration.length]
113
- ),
114
- timingFunction: normalizeTimingFunction(
115
- transitionTimingFunction[i % transitionTimingFunction.length]
116
- ),
117
- delay: normalizeDelay(transitionDelay[i % transitionDelay.length]),
118
- allowDiscrete: normalizeTransitionBehavior(
119
- transitionBehavior[i % transitionBehavior.length]
120
- ),
121
- };
117
+ const timingFunction = normalizeTimingFunction(
118
+ transitionTimingFunction[i % transitionTimingFunction.length]
119
+ );
120
+ const allowDiscrete = normalizeTransitionBehavior(
121
+ transitionBehavior[i % transitionBehavior.length]
122
+ );
123
+
124
+ settings[property] = { duration, timingFunction, delay, allowDiscrete };
122
125
 
123
126
  // 'all' transition property overrides all properties before it,
124
127
  // so we don't need to process them
125
- if (allPropertiesTransition) {
128
+ if (property === 'all') {
129
+ allPropertiesTransition = true;
126
130
  break;
127
131
  }
132
+
133
+ specificProperties.add(property);
134
+ }
135
+
136
+ if (allPropertiesTransition) {
137
+ return { specificProperties: undefined, settings };
138
+ }
139
+ if (specificProperties.size) {
140
+ return { specificProperties, settings };
128
141
  }
129
142
 
130
- return {
131
- properties: allPropertiesTransition
132
- ? undefined
133
- : specificProperties.reverse(),
134
- settings,
135
- };
143
+ return null;
136
144
  }
@@ -13,7 +13,21 @@ export type CSSTransitionConfig = Record<
13
13
  (NormalizedSingleCSSTransitionSettings & { value: [unknown, unknown] }) | null
14
14
  >;
15
15
 
16
+ /**
17
+ * `specificProperties`
18
+ *
19
+ * - `undefined`: accept all props (equivalent to `transition-property: all`)
20
+ * - `Set<string>`: accept only props present in the set
21
+ *
22
+ * `settings`
23
+ *
24
+ * - Keys are prop names, plus an optional `all` fallback key
25
+ * - How to read it for a prop `propName`:
26
+ *
27
+ * - Primary: `settings[propName]` (if present)
28
+ * - Fallback: `settings.all` (otherwise)
29
+ */
16
30
  export type NormalizedCSSTransitionConfig = {
17
- properties: string[] | undefined;
31
+ specificProperties: Set<string> | undefined;
18
32
  settings: Record<string, NormalizedSingleCSSTransitionSettings>;
19
33
  };
@@ -27,6 +27,13 @@ export function filterCSSAndStyleProperties<S extends AnyRecord>(
27
27
  const filteredStyle: AnyRecord = {};
28
28
 
29
29
  for (const [prop, value] of Object.entries(style)) {
30
+ if (value === undefined) {
31
+ // If the user explicitly sets a property to undefined (e.g. when they want
32
+ // to remove CSS transition or animation), we treat the property as if it was not
33
+ // present in the style object.
34
+ continue;
35
+ }
36
+
30
37
  if (isAnimationProp(prop)) {
31
38
  // TODO - add support for animation shorthand
32
39
  animationProperties[prop] = value;
@@ -80,7 +80,7 @@ const DefaultStaticFeatureFlags = {
80
80
  USE_SYNCHRONIZABLE_FOR_MUTABLES: true,
81
81
  USE_COMMIT_HOOK_ONLY_FOR_REACT_COMMITS: true,
82
82
  ENABLE_SHARED_ELEMENT_TRANSITIONS: false,
83
- FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS: false,
83
+ FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS: true,
84
84
  } as const satisfies typeof StaticFeatureFlagsJSON;
85
85
 
86
86
  type StaticFeatureFlagsSchema = {
@@ -7,5 +7,5 @@
7
7
  "USE_SYNCHRONIZABLE_FOR_MUTABLES": true,
8
8
  "USE_COMMIT_HOOK_ONLY_FOR_REACT_COMMITS": true,
9
9
  "ENABLE_SHARED_ELEMENT_TRANSITIONS": false,
10
- "FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS": false
10
+ "FORCE_REACT_RENDER_FOR_SETTLED_ANIMATIONS": true
11
11
  }
@@ -37,8 +37,8 @@ function useAnimatedRefBase<TRef extends InstanceOrElement>(
37
37
  wrapperRef.current = getWrapper(ref);
38
38
 
39
39
  // We have to unwrap the tag from the shadow node wrapper.
40
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
- fun.getTag = () => findNodeHandle(ref as any);
40
+ // @ts-expect-error this can't be typed well.
41
+ fun.getTag = () => ref.getScrollableNode?.() || findNodeHandle(ref);
42
42
  fun.current = ref;
43
43
 
44
44
  if (observers.size) {
@@ -30,12 +30,7 @@ import type {
30
30
  JestAnimatedStyleHandle,
31
31
  } from './commonTypes';
32
32
  import { useSharedValue } from './useSharedValue';
33
- import {
34
- buildWorkletsHash,
35
- isAnimated,
36
- shallowEqual,
37
- validateAnimatedStyles,
38
- } from './utils';
33
+ import { isAnimated, shallowEqual, validateAnimatedStyles } from './utils';
39
34
 
40
35
  interface AnimatedState {
41
36
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -450,6 +445,20 @@ function checkSharedValueUsage(
450
445
  }
451
446
  }
452
447
 
448
+ // Builds one big hash from multiple worklets' hashes.
449
+ function buildWorkletsHash<Args extends unknown[], ReturnValue>(
450
+ worklets:
451
+ | Record<string, WorkletFunction<Args, ReturnValue>>
452
+ | WorkletFunction<Args, ReturnValue>[]
453
+ ) {
454
+ // For arrays `Object.values` returns the array itself.
455
+ return Object.values(worklets).reduce(
456
+ (acc, worklet: WorkletFunction<Args, ReturnValue>) =>
457
+ acc + worklet.__workletHash.toString(),
458
+ ''
459
+ );
460
+ }
461
+
453
462
  /**
454
463
  * Lets you create a styles object, similar to StyleSheet styles, which can be
455
464
  * animated using shared values.