react-native-reanimated 4.1.2 → 4.1.4

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 (128) hide show
  1. package/Common/cpp/reanimated/RuntimeDecorators/RNRuntimeDecorator.cpp +24 -4
  2. package/Common/cpp/reanimated/RuntimeDecorators/RNRuntimeDecorator.h +7 -0
  3. package/android/build.gradle +1 -0
  4. package/compatibility.json +2 -2
  5. package/lib/module/Colors.js +5 -8
  6. package/lib/module/Colors.js.map +1 -1
  7. package/lib/module/ConfigHelper.js +5 -3
  8. package/lib/module/ConfigHelper.js.map +1 -1
  9. package/lib/module/ReanimatedModule/NativeReanimated.js +1 -1
  10. package/lib/module/ReanimatedModule/NativeReanimated.js.map +1 -1
  11. package/lib/module/ReanimatedModule/js-reanimated/index.js.map +1 -1
  12. package/lib/module/ViewDescriptorsSet.js +5 -1
  13. package/lib/module/ViewDescriptorsSet.js.map +1 -1
  14. package/lib/module/animation/spring/spring.js +3 -4
  15. package/lib/module/animation/spring/spring.js.map +1 -1
  16. package/lib/module/animation/spring/springUtils.js +12 -0
  17. package/lib/module/animation/spring/springUtils.js.map +1 -1
  18. package/lib/module/common/logger.js +15 -11
  19. package/lib/module/common/logger.js.map +1 -1
  20. package/lib/module/common/processors/colors.js +1 -4
  21. package/lib/module/common/processors/colors.js.map +1 -1
  22. package/lib/module/createAnimatedComponent/AnimatedComponent.js +19 -7
  23. package/lib/module/createAnimatedComponent/AnimatedComponent.js.map +1 -1
  24. package/lib/module/createAnimatedComponent/InlinePropManager.js +13 -14
  25. package/lib/module/createAnimatedComponent/InlinePropManager.js.map +1 -1
  26. package/lib/module/css/index.js +1 -1
  27. package/lib/module/css/index.js.map +1 -1
  28. package/lib/module/css/native/normalization/common/settings.js +2 -12
  29. package/lib/module/css/native/normalization/common/settings.js.map +1 -1
  30. package/lib/module/css/native/style/config.js +2 -2
  31. package/lib/module/css/native/style/config.js.map +1 -1
  32. package/lib/module/css/utils/parsers.js +11 -0
  33. package/lib/module/css/utils/parsers.js.map +1 -1
  34. package/lib/module/css/utils/props.js +0 -5
  35. package/lib/module/css/utils/props.js.map +1 -1
  36. package/lib/module/css/web/managers/CSSAnimationsManager.js +68 -42
  37. package/lib/module/css/web/managers/CSSAnimationsManager.js.map +1 -1
  38. package/lib/module/fabricUtils.js +9 -11
  39. package/lib/module/fabricUtils.js.map +1 -1
  40. package/lib/module/hook/useAnimatedRef.js +12 -6
  41. package/lib/module/hook/useAnimatedRef.js.map +1 -1
  42. package/lib/module/index.js +1 -0
  43. package/lib/module/index.js.map +1 -1
  44. package/lib/module/initializers.js +1 -3
  45. package/lib/module/initializers.js.map +1 -1
  46. package/lib/module/interpolateColor.js +75 -24
  47. package/lib/module/interpolateColor.js.map +1 -1
  48. package/lib/module/layoutReanimation/web/Easing.web.js +3 -0
  49. package/lib/module/layoutReanimation/web/Easing.web.js.map +1 -1
  50. package/lib/module/layoutReanimation/web/componentUtils.js +4 -3
  51. package/lib/module/layoutReanimation/web/componentUtils.js.map +1 -1
  52. package/lib/module/layoutReanimation/web/domUtils.js +1 -1
  53. package/lib/module/layoutReanimation/web/domUtils.js.map +1 -1
  54. package/lib/module/layoutReanimation/web/transition/Curved.web.js +1 -1
  55. package/lib/module/layoutReanimation/web/transition/Curved.web.js.map +1 -1
  56. package/lib/module/platform-specific/jsVersion.js +1 -1
  57. package/lib/module/platformFunctions/dispatchCommand.js +6 -0
  58. package/lib/module/platformFunctions/dispatchCommand.js.map +1 -1
  59. package/lib/module/platformFunctions/scrollTo.web.js +12 -8
  60. package/lib/module/platformFunctions/scrollTo.web.js.map +1 -1
  61. package/lib/typescript/Colors.d.ts +1 -1
  62. package/lib/typescript/Colors.d.ts.map +1 -1
  63. package/lib/typescript/ConfigHelper.d.ts.map +1 -1
  64. package/lib/typescript/ReanimatedModule/js-reanimated/index.d.ts +2 -1
  65. package/lib/typescript/ReanimatedModule/js-reanimated/index.d.ts.map +1 -1
  66. package/lib/typescript/ViewDescriptorsSet.d.ts +1 -0
  67. package/lib/typescript/ViewDescriptorsSet.d.ts.map +1 -1
  68. package/lib/typescript/animation/spring/spring.d.ts.map +1 -1
  69. package/lib/typescript/animation/spring/springUtils.d.ts +1 -0
  70. package/lib/typescript/animation/spring/springUtils.d.ts.map +1 -1
  71. package/lib/typescript/common/logger.d.ts +5 -5
  72. package/lib/typescript/common/logger.d.ts.map +1 -1
  73. package/lib/typescript/common/processors/colors.d.ts.map +1 -1
  74. package/lib/typescript/commonTypes.d.ts +1 -1
  75. package/lib/typescript/commonTypes.d.ts.map +1 -1
  76. package/lib/typescript/createAnimatedComponent/AnimatedComponent.d.ts.map +1 -1
  77. package/lib/typescript/createAnimatedComponent/InlinePropManager.d.ts.map +1 -1
  78. package/lib/typescript/css/index.d.ts +1 -1
  79. package/lib/typescript/css/index.d.ts.map +1 -1
  80. package/lib/typescript/css/native/normalization/common/settings.d.ts.map +1 -1
  81. package/lib/typescript/css/native/style/config.d.ts.map +1 -1
  82. package/lib/typescript/css/utils/parsers.d.ts +2 -1
  83. package/lib/typescript/css/utils/parsers.d.ts.map +1 -1
  84. package/lib/typescript/css/web/managers/CSSAnimationsManager.d.ts +2 -0
  85. package/lib/typescript/css/web/managers/CSSAnimationsManager.d.ts.map +1 -1
  86. package/lib/typescript/fabricUtils.d.ts.map +1 -1
  87. package/lib/typescript/hook/useAnimatedRef.d.ts.map +1 -1
  88. package/lib/typescript/index.d.ts +1 -0
  89. package/lib/typescript/index.d.ts.map +1 -1
  90. package/lib/typescript/initializers.d.ts.map +1 -1
  91. package/lib/typescript/interpolateColor.d.ts.map +1 -1
  92. package/lib/typescript/layoutReanimation/web/Easing.web.d.ts.map +1 -1
  93. package/lib/typescript/layoutReanimation/web/componentUtils.d.ts +1 -1
  94. package/lib/typescript/layoutReanimation/web/componentUtils.d.ts.map +1 -1
  95. package/lib/typescript/platform-specific/jsVersion.d.ts +1 -1
  96. package/lib/typescript/platformFunctions/scrollTo.web.d.ts.map +1 -1
  97. package/package.json +1 -1
  98. package/src/Colors.ts +7 -10
  99. package/src/ConfigHelper.ts +9 -3
  100. package/src/ReanimatedModule/NativeReanimated.ts +1 -1
  101. package/src/ReanimatedModule/js-reanimated/index.ts +2 -1
  102. package/src/ViewDescriptorsSet.ts +8 -0
  103. package/src/animation/spring/spring.ts +11 -6
  104. package/src/animation/spring/springUtils.ts +19 -0
  105. package/src/common/logger.ts +18 -11
  106. package/src/common/processors/colors.ts +1 -4
  107. package/src/commonTypes.ts +1 -1
  108. package/src/createAnimatedComponent/AnimatedComponent.tsx +23 -7
  109. package/src/createAnimatedComponent/InlinePropManager.ts +14 -15
  110. package/src/css/index.ts +1 -1
  111. package/src/css/native/normalization/common/settings.ts +2 -17
  112. package/src/css/native/style/config.ts +1 -1
  113. package/src/css/utils/parsers.ts +13 -1
  114. package/src/css/utils/props.ts +0 -8
  115. package/src/css/web/managers/CSSAnimationsManager.ts +85 -40
  116. package/src/fabricUtils.ts +12 -26
  117. package/src/hook/useAnimatedRef.ts +12 -9
  118. package/src/index.ts +1 -0
  119. package/src/initializers.ts +1 -9
  120. package/src/interpolateColor.ts +112 -43
  121. package/src/layoutReanimation/web/Easing.web.ts +4 -0
  122. package/src/layoutReanimation/web/componentUtils.ts +6 -4
  123. package/src/layoutReanimation/web/domUtils.ts +1 -1
  124. package/src/layoutReanimation/web/transition/Curved.web.ts +1 -1
  125. package/src/platform-specific/jsVersion.ts +1 -1
  126. package/src/platformFunctions/dispatchCommand.ts +15 -2
  127. package/src/platformFunctions/scrollTo.web.ts +10 -4
  128. package/src/privateGlobals.d.ts +1 -1
@@ -66,6 +66,25 @@ export function checkIfConfigIsValid(config: DefaultSpringConfig): boolean {
66
66
  return errorMessage === '';
67
67
  }
68
68
 
69
+ export function safeMergeConfigs<TConfig extends object>(
70
+ defaults: TConfig,
71
+ userConfig?: Partial<TConfig>
72
+ ): TConfig {
73
+ 'worklet';
74
+ if (!userConfig) {
75
+ return defaults;
76
+ }
77
+
78
+ const filtered = Object.fromEntries(
79
+ Object.entries(userConfig).filter(([, v]) => v !== undefined)
80
+ ) as Partial<TConfig>;
81
+
82
+ return {
83
+ ...defaults,
84
+ ...filtered,
85
+ };
86
+ }
87
+
69
88
  function bisectRoot({
70
89
  min,
71
90
  max,
@@ -39,39 +39,46 @@ function logToConsole(data: LogData) {
39
39
  }
40
40
  }
41
41
 
42
- export const DEFAULT_LOGGER_CONFIG: LoggerConfigInternal = {
42
+ const DEFAULT_LOGGER_CONFIG: LoggerConfigInternal = {
43
43
  logFunction: logToConsole,
44
44
  level: ReanimatedLogLevel.warn,
45
45
  strict: true,
46
46
  };
47
47
 
48
48
  /**
49
- * Registers the logger configuration. use it only for Worklet runtimes.
49
+ * Current logger config getter.
50
50
  *
51
- * @param config - The config to register.
51
+ * @returns The current logger configuration object.
52
52
  */
53
- export function registerLoggerConfig(config: LoggerConfigInternal) {
53
+ export function getLoggerConfig() {
54
54
  'worklet';
55
- global.__reanimatedLoggerConfig = config;
55
+ if (!global.__reanimatedLoggerConfig) {
56
+ global.__reanimatedLoggerConfig = DEFAULT_LOGGER_CONFIG;
57
+ }
58
+ return global.__reanimatedLoggerConfig;
56
59
  }
57
60
 
58
61
  /**
59
62
  * Updates logger configuration.
60
63
  *
64
+ * @param currentConfig - The current logger configuration object.
61
65
  * @param options - The new logger configuration to apply.
62
66
  *
63
67
  * - Level: The minimum log level to display.
64
68
  * - Strict: Whether to log warnings and errors that are not strict. Defaults to
65
69
  * false.
66
70
  */
67
- export function updateLoggerConfig(options?: Partial<LoggerConfig>) {
71
+ export function updateLoggerConfig(
72
+ currentConfig: LoggerConfigInternal,
73
+ options?: Partial<LoggerConfig>
74
+ ) {
68
75
  'worklet';
69
- registerLoggerConfig({
70
- ...global.__reanimatedLoggerConfig,
71
- // Don't reuse previous level and strict values from the global config
76
+ global.__reanimatedLoggerConfig = {
77
+ ...currentConfig,
78
+ // Don't reuse previous level and strict values from the current config
72
79
  level: options?.level ?? DEFAULT_LOGGER_CONFIG.level,
73
80
  strict: options?.strict ?? DEFAULT_LOGGER_CONFIG.strict,
74
- });
81
+ };
75
82
  }
76
83
 
77
84
  type LogOptions = {
@@ -84,7 +91,7 @@ function handleLog(
84
91
  options: LogOptions
85
92
  ) {
86
93
  'worklet';
87
- const config = global.__reanimatedLoggerConfig;
94
+ const config = getLoggerConfig();
88
95
  if (
89
96
  // Don't log if the log is marked as strict-only and the config doesn't
90
97
  // enable strict logging
@@ -6,12 +6,9 @@ import { IS_ANDROID } from '../constants';
6
6
 
7
7
  export function processColor(color: unknown): number | null | undefined {
8
8
  let normalizedColor = processColorInitially(color);
9
- if (normalizedColor === null || normalizedColor === undefined) {
10
- return undefined;
11
- }
12
9
 
13
10
  if (typeof normalizedColor !== 'number') {
14
- return null;
11
+ return normalizedColor;
15
12
  }
16
13
 
17
14
  if (IS_ANDROID) {
@@ -360,7 +360,7 @@ export enum InterfaceOrientation {
360
360
  }
361
361
 
362
362
  export type ShadowNodeWrapper = {
363
- __hostObjectShadowNodeWrapper: never;
363
+ __nativeStateShadowNodeWrapper: never;
364
364
  };
365
365
 
366
366
  export enum KeyboardState {
@@ -120,8 +120,20 @@ export default class AnimatedComponent
120
120
  );
121
121
 
122
122
  if (IS_WEB) {
123
- if (this.props.exiting && this._componentDOMRef) {
124
- saveSnapshot(this._componentDOMRef);
123
+ const element = this._componentDOMRef as ReanimatedHTMLElement;
124
+
125
+ // If the element was cloned (because of the exiting animation), we need bring it
126
+ // back to the DOM
127
+ if (element.dummyClone) {
128
+ const dummyClone = element.dummyClone;
129
+ while (dummyClone.firstChild) {
130
+ element.appendChild(dummyClone.firstChild);
131
+ }
132
+ delete element.dummyClone;
133
+ }
134
+
135
+ if (this.props.exiting) {
136
+ saveSnapshot(element);
125
137
  }
126
138
 
127
139
  if (
@@ -137,11 +149,11 @@ export default class AnimatedComponent
137
149
  if (!skipEntering) {
138
150
  startWebLayoutAnimation(
139
151
  this.props,
140
- this._componentDOMRef as ReanimatedHTMLElement,
152
+ element,
141
153
  LayoutAnimationType.ENTERING
142
154
  );
143
- } else if (this._componentDOMRef) {
144
- this._componentDOMRef.style.visibility = 'initial';
155
+ } else {
156
+ element.style.visibility = 'initial';
145
157
  }
146
158
  }
147
159
 
@@ -207,6 +219,9 @@ export default class AnimatedComponent
207
219
  const { viewTag, shadowNodeWrapper } = this._getViewInfo();
208
220
  const newStyles = new Set<StyleProps>(currentStyles);
209
221
 
222
+ const isStyleAttached = (style: StyleProps) =>
223
+ style.viewDescriptors.has(viewTag);
224
+
210
225
  // remove old styles
211
226
  if (prevStyles) {
212
227
  // in most of the cases, views have only a single animated style and it remains unchanged
@@ -215,14 +230,14 @@ export default class AnimatedComponent
215
230
  prevStyles.length === 1 &&
216
231
  currentStyles[0] === prevStyles[0];
217
232
 
218
- if (hasOneSameStyle) {
233
+ if (hasOneSameStyle && isStyleAttached(prevStyles[0])) {
219
234
  return;
220
235
  }
221
236
 
222
237
  // otherwise, remove each style that is not present in new styles
223
238
  for (const prevStyle of prevStyles) {
224
239
  const isPresent = currentStyles.some((style) => {
225
- if (style === prevStyle) {
240
+ if (style === prevStyle && isStyleAttached(style)) {
226
241
  newStyles.delete(style);
227
242
  return true;
228
243
  }
@@ -233,6 +248,7 @@ export default class AnimatedComponent
233
248
  }
234
249
  }
235
250
  }
251
+
236
252
  newStyles.forEach((style) => {
237
253
  style.viewDescriptors.add(
238
254
  {
@@ -38,23 +38,22 @@ function inlinePropsHasChanged(
38
38
  return false;
39
39
  }
40
40
 
41
- function getInlinePropsUpdate(inlineProps: Record<string, unknown>) {
41
+ function getInlinePropsUpdate(styleValue: StyleProps): unknown {
42
42
  'worklet';
43
- const update: Record<string, unknown> = {};
44
- for (const [key, styleValue] of Object.entries(inlineProps)) {
45
- if (isSharedValue(styleValue)) {
46
- update[key] = styleValue.value;
47
- } else if (Array.isArray(styleValue)) {
48
- update[key] = styleValue.map((item) => {
49
- return getInlinePropsUpdate(item);
50
- });
51
- } else if (typeof styleValue === 'object') {
52
- update[key] = getInlinePropsUpdate(styleValue as Record<string, unknown>);
53
- } else {
54
- update[key] = styleValue;
43
+ if (isSharedValue(styleValue)) {
44
+ return styleValue.value;
45
+ }
46
+ if (Array.isArray(styleValue)) {
47
+ return styleValue.map(getInlinePropsUpdate);
48
+ }
49
+ if (styleValue && typeof styleValue === 'object') {
50
+ const update: Record<string, unknown> = {};
51
+ for (const [key, value] of Object.entries(styleValue)) {
52
+ update[key] = getInlinePropsUpdate(value);
55
53
  }
54
+ return update;
56
55
  }
57
- return update;
56
+ return styleValue;
58
57
  }
59
58
 
60
59
  function extractSharedValuesMapFromProps(
@@ -109,7 +108,7 @@ export function getInlineStyle(
109
108
  isFirstRender: boolean
110
109
  ) {
111
110
  if (isFirstRender) {
112
- return getInlinePropsUpdate(style);
111
+ return getInlinePropsUpdate(style) as Record<string, unknown>;
113
112
  }
114
113
  const newStyle: StyleProps = {};
115
114
  for (const [key, styleValue] of Object.entries(style)) {
package/src/css/index.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  'use strict';
2
- export { createAnimatedComponent } from './component';
2
+ export { createAnimatedComponent as createCSSAnimatedComponent } from './component';
3
3
  export { cubicBezier, linear, steps } from './easing';
4
4
  export * from './stylesheet';
5
5
  export type {
@@ -1,17 +1,13 @@
1
1
  'use strict';
2
2
  import { ReanimatedError } from '../../../../common';
3
- import {
4
- MILLISECONDS_REGEX,
5
- SECONDS_REGEX,
6
- VALID_PREDEFINED_TIMING_FUNCTIONS,
7
- } from '../../../constants';
3
+ import { VALID_PREDEFINED_TIMING_FUNCTIONS } from '../../../constants';
8
4
  import type {
9
5
  CSSTimingFunction,
10
6
  NormalizedCSSTimingFunction,
11
7
  PredefinedTimingFunction,
12
8
  } from '../../../easing';
13
9
  import type { TimeUnit } from '../../../types';
14
- import { isPredefinedTimingFunction } from '../../../utils';
10
+ import { isPredefinedTimingFunction, normalizeTimeUnit } from '../../../utils';
15
11
 
16
12
  export const ERROR_MESSAGES = {
17
13
  invalidDelay: (timeUnit: TimeUnit) =>
@@ -26,17 +22,6 @@ export const ERROR_MESSAGES = {
26
22
  `Invalid parametrized timing function "${timingFunction?.toString()}".`,
27
23
  };
28
24
 
29
- function normalizeTimeUnit(timeUnit: TimeUnit): number | null {
30
- if (typeof timeUnit === 'number') {
31
- return timeUnit;
32
- } else if (MILLISECONDS_REGEX.test(timeUnit)) {
33
- return parseInt(timeUnit, 10);
34
- } else if (SECONDS_REGEX.test(timeUnit)) {
35
- return parseFloat(timeUnit) * 1000;
36
- }
37
- return null;
38
- }
39
-
40
25
  export function normalizeDelay(delay: TimeUnit = 0): number {
41
26
  const delayMs = normalizeTimeUnit(delay);
42
27
  if (delayMs === null) {
@@ -2,12 +2,12 @@
2
2
  import {
3
3
  IS_ANDROID,
4
4
  processBoxShadowNative,
5
- processColor,
6
5
  processTransformOrigin,
7
6
  } from '../../../common';
8
7
  import type { PlainStyle } from '../../types';
9
8
  import {
10
9
  processAspectRatio,
10
+ processColor,
11
11
  processFontWeight,
12
12
  processGap,
13
13
  processInset,
@@ -1,7 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  import { ReanimatedError } from '../../common';
4
- import type { SingleCSSTransitionConfig } from '../types';
4
+ import { MILLISECONDS_REGEX, SECONDS_REGEX } from '../constants';
5
+ import type { SingleCSSTransitionConfig, TimeUnit } from '../types';
5
6
  import { isTimeUnit, smellsLikeTimingFunction } from './guards';
6
7
 
7
8
  export function splitByComma(str: string) {
@@ -80,3 +81,14 @@ export function parseSingleTransitionShorthand(
80
81
 
81
82
  return result;
82
83
  }
84
+
85
+ export function normalizeTimeUnit(timeUnit: TimeUnit): number | null {
86
+ if (typeof timeUnit === 'number') {
87
+ return timeUnit;
88
+ } else if (MILLISECONDS_REGEX.test(timeUnit)) {
89
+ return parseInt(timeUnit, 10);
90
+ } else if (SECONDS_REGEX.test(timeUnit)) {
91
+ return parseFloat(timeUnit) * 1000;
92
+ }
93
+ return null;
94
+ }
@@ -78,14 +78,6 @@ export function filterCSSAndStyleProperties<S extends AnyRecord>(
78
78
  }
79
79
 
80
80
  function validateCSSAnimationProps(props: Partial<CSSAnimationProperties>) {
81
- // Check if any animation properties are present but animationName is missing
82
- if (!('animationName' in props) && Object.keys(props).length > 0) {
83
- logger.warn(
84
- 'CSS animation properties were provided without specifying animationName.\n' +
85
- 'If unintended, add animationName or remove unnecessary animation properties.'
86
- );
87
- }
88
-
89
81
  // Check if animationDuration is missing when animationName is present
90
82
  if (!('animationDuration' in props) && 'animationName' in props) {
91
83
  logger.warn(
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ import { maybeAddSuffix } from '../../../common';
2
3
  import type { ReanimatedHTMLElement } from '../../../ReanimatedModule/js-reanimated';
3
4
  import type {
4
5
  ConvertValuesToArrays,
@@ -7,7 +8,11 @@ import type {
7
8
  ExistingCSSAnimationProperties,
8
9
  ICSSAnimationsManager,
9
10
  } from '../../types';
10
- import { convertPropertiesToArrays, kebabizeCamelCase } from '../../utils';
11
+ import {
12
+ convertPropertiesToArrays,
13
+ kebabizeCamelCase,
14
+ normalizeTimeUnit,
15
+ } from '../../utils';
11
16
  import { processKeyframeDefinitions } from '../animationParser';
12
17
  import {
13
18
  configureWebCSSAnimations,
@@ -25,6 +30,8 @@ const isCSSKeyframesRuleImpl = (
25
30
  type ProcessedAnimation = {
26
31
  keyframesRule: CSSKeyframesRuleImpl;
27
32
  removable: boolean;
33
+ creationTimestamp: number;
34
+ elapsedTime?: number;
28
35
  };
29
36
 
30
37
  type ProcessedSettings = ConvertValuesToArrays<CSSAnimationSettings>;
@@ -34,6 +41,7 @@ export default class CSSAnimationsManager implements ICSSAnimationsManager {
34
41
 
35
42
  // Keys are processed keyframes
36
43
  private attachedAnimations: Record<string, ProcessedAnimation> = {};
44
+ private unmountCleanupCalled = false;
37
45
 
38
46
  constructor(element: ReanimatedHTMLElement) {
39
47
  configureWebCSSAnimations();
@@ -55,44 +63,67 @@ export default class CSSAnimationsManager implements ICSSAnimationsManager {
55
63
  return;
56
64
  }
57
65
 
66
+ const timestamp = Date.now();
58
67
  const processedAnimations = definitions.map((definition) => {
68
+ let processedAnimation: ProcessedAnimation;
69
+
59
70
  // If the CSSKeyframesRule instance was provided, we can just use it
60
71
  if (isCSSKeyframesRuleImpl(definition)) {
61
- return { keyframesRule: definition, removable: false };
62
- }
63
-
64
- // If keyframes was defined as an object, the additional processing is needed
65
- const keyframes = definition as CSSAnimationKeyframes;
66
- const processedKeyframes = processKeyframeDefinitions(keyframes);
67
-
68
- // If the animation with the same keyframes was already attached, we can reuse it
69
- if (this.attachedAnimations[processedKeyframes]) {
70
- return {
71
- keyframesRule:
72
- this.attachedAnimations[processedKeyframes].keyframesRule,
72
+ processedAnimation = this.attachedAnimations[
73
+ definition.processedKeyframes
74
+ ] ?? {
75
+ keyframesRule: definition,
76
+ removable: false,
77
+ creationTimestamp: timestamp,
78
+ };
79
+ } else {
80
+ // If keyframes was defined as an object, the additional processing is needed
81
+ const keyframes = definition as CSSAnimationKeyframes;
82
+ const processedKeyframes = processKeyframeDefinitions(keyframes);
83
+
84
+ // If the animation with the same keyframes was already attached, we can reuse it
85
+ // Otherwise, we need to create a new CSSKeyframesRule object
86
+ processedAnimation = this.attachedAnimations[processedKeyframes] ?? {
87
+ keyframesRule: new CSSKeyframesRuleImpl(
88
+ keyframes,
89
+ processedKeyframes
90
+ ),
73
91
  removable: true,
92
+ creationTimestamp: timestamp,
74
93
  };
75
94
  }
76
95
 
77
- // Otherwise, we need to create a new CSSKeyframesRule object
78
- return {
79
- keyframesRule: new CSSKeyframesRuleImpl(keyframes, processedKeyframes),
80
- removable: true,
81
- };
82
- });
96
+ if (this.unmountCleanupCalled) {
97
+ // unmountCleanup is called not only when the component truly unmounts, but also
98
+ // when display property is set to 'none' (e.g. during navigation between screens)
99
+ // In such a case, we don't want to restart the animation after re-entering the
100
+ // screen so we have to shift its delay by the time elapsed since the animation
101
+ // was started for the first time.
102
+ processedAnimation.elapsedTime =
103
+ timestamp - processedAnimation.creationTimestamp;
104
+ }
83
105
 
84
- const animationNames = processedAnimations.map(
85
- ({ keyframesRule: { name } }) => name
86
- );
106
+ return processedAnimation;
107
+ });
87
108
 
109
+ this.unmountCleanupCalled = false;
88
110
  this.updateAttachedAnimations(processedAnimations);
89
- this.setElementAnimations(animationNames, animationSettings);
111
+ this.setElementAnimations(processedAnimations, animationSettings);
90
112
  }
91
113
 
92
114
  unmountCleanup(): void {
93
- // We use setTimeout to ensure that the animation is removed after the
94
- // component is unmounted (it puts the detach call at the end of the event loop)
95
- setTimeout(this.detach.bind(this));
115
+ if (!this.unmountCleanupCalled) {
116
+ this.unmountCleanupCalled = true;
117
+ // We use setTimeout to ensure that the animation is removed after the
118
+ // component is unmounted (it puts the detach call at the end of the event loop)
119
+ // We just remove the animation definition from the style sheet as there is no
120
+ // need to clean up view props if it is removed from the DOM.
121
+ setTimeout(() => {
122
+ this.removeAnimationsFromStyleSheet(
123
+ Object.values(this.attachedAnimations)
124
+ );
125
+ });
126
+ }
96
127
  }
97
128
 
98
129
  private detach() {
@@ -109,13 +140,8 @@ export default class CSSAnimationsManager implements ICSSAnimationsManager {
109
140
  this.element.style.animationPlayState = '';
110
141
  this.element.style.animationTimingFunction = '';
111
142
 
112
- attachedAnimations.forEach(
113
- ({ keyframesRule: { name, processedKeyframes }, removable }) => {
114
- if (removable && processedKeyframes) {
115
- removeCSSAnimation(name);
116
- }
117
- }
118
- );
143
+ this.removeAnimationsFromStyleSheet(attachedAnimations);
144
+ this.unmountCleanupCalled = false;
119
145
  this.attachedAnimations = {};
120
146
  }
121
147
 
@@ -147,10 +173,12 @@ export default class CSSAnimationsManager implements ICSSAnimationsManager {
147
173
  }
148
174
 
149
175
  private setElementAnimations(
150
- animationNames: string[],
176
+ processedAnimations: ProcessedAnimation[],
151
177
  animationSettings: ProcessedSettings
152
178
  ) {
153
- this.element.style.animationName = animationNames.join(',');
179
+ this.element.style.animationName = processedAnimations
180
+ .map(({ keyframesRule: { name } }) => name)
181
+ .join(',');
154
182
 
155
183
  this.element.style.animationDuration = maybeAddSuffixes(
156
184
  animationSettings,
@@ -158,11 +186,18 @@ export default class CSSAnimationsManager implements ICSSAnimationsManager {
158
186
  'ms'
159
187
  ).join(',');
160
188
 
161
- this.element.style.animationDelay = maybeAddSuffixes(
162
- animationSettings,
163
- 'animationDelay',
164
- 'ms'
165
- ).join(',');
189
+ const animationDelays = animationSettings.animationDelay ?? [];
190
+ this.element.style.animationDelay = processedAnimations
191
+ .map(({ elapsedTime }, i) => {
192
+ const providedDelay = animationDelays[i] ?? 0;
193
+ return maybeAddSuffix(
194
+ elapsedTime
195
+ ? (normalizeTimeUnit(providedDelay) ?? 0) - elapsedTime
196
+ : providedDelay,
197
+ 'ms'
198
+ );
199
+ })
200
+ .join(',');
166
201
 
167
202
  if (animationSettings.animationIterationCount) {
168
203
  this.element.style.animationIterationCount =
@@ -190,4 +225,14 @@ export default class CSSAnimationsManager implements ICSSAnimationsManager {
190
225
  );
191
226
  }
192
227
  }
228
+
229
+ private removeAnimationsFromStyleSheet(animations: ProcessedAnimation[]) {
230
+ animations.forEach(
231
+ ({ keyframesRule: { name, processedKeyframes }, removable }) => {
232
+ if (removable && processedKeyframes) {
233
+ removeCSSAnimation(name);
234
+ }
235
+ }
236
+ );
237
+ }
193
238
  }
@@ -4,37 +4,23 @@ import type { ShadowNodeWrapper, WrapperRef } from './commonTypes';
4
4
  import type { HostInstance } from './platform-specific/findHostInstance';
5
5
  import { findHostInstance } from './platform-specific/findHostInstance';
6
6
 
7
- let getInternalInstanceHandleFromPublicInstance: (ref: unknown) => {
8
- stateNode: { node: unknown };
9
- };
10
-
11
7
  export function getShadowNodeWrapperFromRef(
12
8
  ref: WrapperRef,
13
9
  hostInstance?: HostInstance
14
10
  ): ShadowNodeWrapper {
15
- if (getInternalInstanceHandleFromPublicInstance === undefined) {
16
- try {
17
- getInternalInstanceHandleFromPublicInstance =
18
- // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
19
- require('react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactFabricPublicInstance')
20
- .getInternalInstanceHandleFromPublicInstance ??
21
- ((_ref: any) => _ref._internalInstanceHandle);
22
- } catch {
23
- getInternalInstanceHandleFromPublicInstance = (_ref: any) =>
24
- _ref._internalInstanceHandle;
11
+ let resolvedInstance =
12
+ hostInstance?.__internalInstanceHandle ?? ref?.__internalInstanceHandle;
13
+
14
+ if (!resolvedInstance) {
15
+ if (ref.getNativeScrollRef) {
16
+ resolvedInstance = (ref.getNativeScrollRef() as any)
17
+ .__internalInstanceHandle;
18
+ } else if ((ref as any)._reactInternals) {
19
+ resolvedInstance = findHostInstance(ref).__internalInstanceHandle;
20
+ } else {
21
+ throw new ReanimatedError(`Failed to find host instance for a ref.}`);
25
22
  }
26
23
  }
27
24
 
28
- const resolvedRef =
29
- ref.getScrollResponder?.()?.getNativeScrollRef?.() ??
30
- ref.getNativeScrollRef?.() ??
31
- ref;
32
-
33
- const resolvedInstance =
34
- ref?.__internalInstanceHandle ??
35
- getInternalInstanceHandleFromPublicInstance(
36
- hostInstance ?? findHostInstance(resolvedRef)
37
- );
38
-
39
- return resolvedInstance?.stateNode?.node;
25
+ return resolvedInstance!.stateNode.node as ShadowNodeWrapper;
40
26
  }
@@ -17,10 +17,6 @@ import type {
17
17
  MaybeObserverCleanup,
18
18
  } from './commonTypes';
19
19
 
20
- function getComponentOrScrollable(ref: WrapperRef) {
21
- return ref.getNativeScrollRef?.() ?? ref.getScrollableNode?.() ?? ref;
22
- }
23
-
24
20
  function useAnimatedRefBase<TRef extends WrapperRef>(
25
21
  getWrapper: (ref: TRef) => ShadowNodeWrapper
26
22
  ): AnimatedRef<TRef> {
@@ -36,7 +32,8 @@ function useAnimatedRefBase<TRef extends WrapperRef>(
36
32
  wrapperRef.current = getWrapper(ref);
37
33
 
38
34
  // We have to unwrap the tag from the shadow node wrapper.
39
- fun.getTag = () => findNodeHandle(getComponentOrScrollable(ref));
35
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
+ fun.getTag = () => findNodeHandle(ref as any);
40
37
  fun.current = ref;
41
38
 
42
39
  if (observers.size) {
@@ -81,9 +78,7 @@ function useAnimatedRefNative<
81
78
  );
82
79
 
83
80
  const resultRef = useAnimatedRefBase<TRef>((ref) => {
84
- const currentWrapper = getShadowNodeWrapperFromRef(
85
- getComponentOrScrollable(ref)
86
- );
81
+ const currentWrapper = getShadowNodeWrapperFromRef(ref);
87
82
 
88
83
  sharedWrapper.value = currentWrapper;
89
84
 
@@ -106,7 +101,15 @@ function useAnimatedRefNative<
106
101
  function useAnimatedRefWeb<
107
102
  TRef extends WrapperRef = React.Component,
108
103
  >(): AnimatedRef<TRef> {
109
- return useAnimatedRefBase<TRef>((ref) => getComponentOrScrollable(ref));
104
+ return useAnimatedRefBase<TRef>((ref) => {
105
+ if (ref.getScrollableNode) {
106
+ return ref.getScrollableNode();
107
+ }
108
+ if (ref.getNativeScrollRef) {
109
+ return ref.getNativeScrollRef();
110
+ }
111
+ return ref;
112
+ });
110
113
  }
111
114
 
112
115
  /**
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@ import * as Animated from './Animated';
14
14
 
15
15
  export default Animated;
16
16
 
17
+ export { createAnimatedComponent } from './Animated';
17
18
  export type {
18
19
  DecayAnimation,
19
20
  DelayAnimation,
@@ -1,13 +1,7 @@
1
1
  'use strict';
2
2
  import { executeOnUIRuntimeSync } from 'react-native-worklets';
3
3
 
4
- import {
5
- DEFAULT_LOGGER_CONFIG,
6
- IS_WEB,
7
- ReanimatedError,
8
- registerLoggerConfig,
9
- SHOULD_BE_USE_WEB,
10
- } from './common';
4
+ import { IS_WEB, ReanimatedError, SHOULD_BE_USE_WEB } from './common';
11
5
  import { initSvgCssSupport } from './css/svg';
12
6
  import { getStaticFeatureFlag } from './featureFlags';
13
7
  import type { IReanimatedModule } from './ReanimatedModule';
@@ -25,11 +19,9 @@ export function initializeReanimatedModule(
25
19
  }
26
20
  }
27
21
 
28
- registerLoggerConfig(DEFAULT_LOGGER_CONFIG);
29
22
  if (!SHOULD_BE_USE_WEB) {
30
23
  executeOnUIRuntimeSync(() => {
31
24
  'worklet';
32
25
  global._tagToJSPropNamesMapping = {};
33
- registerLoggerConfig(DEFAULT_LOGGER_CONFIG);
34
26
  })();
35
27
  }