react-native-reanimated 3.18.0 → 3.18.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 (84) hide show
  1. package/Common/cpp/reanimated/Fabric/ReanimatedCommitHook.cpp +14 -2
  2. package/Common/cpp/reanimated/Fabric/ReanimatedCommitHook.h +6 -1
  3. package/Common/cpp/reanimated/Fabric/ReanimatedMountHook.cpp +6 -1
  4. package/Common/cpp/reanimated/Fabric/ReanimatedMountHook.h +6 -1
  5. package/Common/cpp/reanimated/Fabric/ShadowTreeCloner.cpp +3 -2
  6. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy.cpp +86 -24
  7. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy.h +29 -4
  8. package/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h +4 -5
  9. package/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp +45 -15
  10. package/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h +4 -1
  11. package/Common/cpp/reanimated/Tools/ReanimatedSystraceSection.h +1 -1
  12. package/README.md +1 -13
  13. package/RNReanimated.podspec +1 -1
  14. package/android/src/main/cpp/reanimated/android/NativeProxy.cpp +25 -21
  15. package/android/src/main/cpp/reanimated/android/NativeProxy.h +5 -3
  16. package/apple/reanimated/apple/LayoutReanimation/REASharedTransitionManager.m +1 -2
  17. package/apple/reanimated/apple/LayoutReanimation/REASwizzledUIManager.mm +8 -7
  18. package/lib/module/PlatformChecker.js +6 -0
  19. package/lib/module/PlatformChecker.js.map +1 -1
  20. package/lib/module/animation/util.js +7 -4
  21. package/lib/module/animation/util.js.map +1 -1
  22. package/lib/module/component/FlatList.js +9 -3
  23. package/lib/module/component/FlatList.js.map +1 -1
  24. package/lib/module/core.js +0 -2
  25. package/lib/module/core.js.map +1 -1
  26. package/lib/module/createAnimatedComponent/NativeEventsManager.js +1 -1
  27. package/lib/module/createAnimatedComponent/NativeEventsManager.js.map +1 -1
  28. package/lib/module/createAnimatedComponent/createAnimatedComponent.js +24 -37
  29. package/lib/module/createAnimatedComponent/createAnimatedComponent.js.map +1 -1
  30. package/lib/module/hook/useAnimatedRef.js +70 -35
  31. package/lib/module/hook/useAnimatedRef.js.map +1 -1
  32. package/lib/module/hook/useHandler.js +7 -0
  33. package/lib/module/hook/useHandler.js.map +1 -1
  34. package/lib/module/hook/useScrollViewOffset.js +28 -26
  35. package/lib/module/hook/useScrollViewOffset.js.map +1 -1
  36. package/lib/module/hook/utils.js +20 -9
  37. package/lib/module/hook/utils.js.map +1 -1
  38. package/lib/module/index.js +3 -0
  39. package/lib/module/index.js.map +1 -1
  40. package/lib/module/platform-specific/jsVersion.js +1 -1
  41. package/lib/module/platformFunctions/setNativeProps.js +1 -1
  42. package/lib/module/platformFunctions/setNativeProps.js.map +1 -1
  43. package/lib/module/processBoxShadow.js.map +1 -1
  44. package/lib/typescript/PlatformChecker.d.ts +2 -0
  45. package/lib/typescript/PlatformChecker.d.ts.map +1 -1
  46. package/lib/typescript/animation/util.d.ts.map +1 -1
  47. package/lib/typescript/component/FlatList.d.ts +10 -2
  48. package/lib/typescript/component/FlatList.d.ts.map +1 -1
  49. package/lib/typescript/core.d.ts.map +1 -1
  50. package/lib/typescript/createAnimatedComponent/commonTypes.d.ts +1 -0
  51. package/lib/typescript/createAnimatedComponent/commonTypes.d.ts.map +1 -1
  52. package/lib/typescript/createAnimatedComponent/createAnimatedComponent.d.ts.map +1 -1
  53. package/lib/typescript/helperTypes.d.ts +1 -3
  54. package/lib/typescript/helperTypes.d.ts.map +1 -1
  55. package/lib/typescript/hook/commonTypes.d.ts +7 -4
  56. package/lib/typescript/hook/commonTypes.d.ts.map +1 -1
  57. package/lib/typescript/hook/useAnimatedRef.d.ts +3 -1
  58. package/lib/typescript/hook/useAnimatedRef.d.ts.map +1 -1
  59. package/lib/typescript/hook/useHandler.d.ts.map +1 -1
  60. package/lib/typescript/hook/useScrollViewOffset.d.ts.map +1 -1
  61. package/lib/typescript/hook/utils.d.ts.map +1 -1
  62. package/lib/typescript/index.d.ts +1 -1
  63. package/lib/typescript/index.d.ts.map +1 -1
  64. package/lib/typescript/platform-specific/jsVersion.d.ts +1 -1
  65. package/lib/typescript/processBoxShadow.d.ts +6 -3
  66. package/lib/typescript/processBoxShadow.d.ts.map +1 -1
  67. package/package.json +2 -2
  68. package/src/PlatformChecker.ts +8 -0
  69. package/src/animation/util.ts +7 -4
  70. package/src/component/FlatList.tsx +44 -6
  71. package/src/core.ts +0 -3
  72. package/src/createAnimatedComponent/NativeEventsManager.ts +1 -1
  73. package/src/createAnimatedComponent/commonTypes.ts +9 -0
  74. package/src/createAnimatedComponent/createAnimatedComponent.tsx +61 -73
  75. package/src/helperTypes.ts +1 -6
  76. package/src/hook/commonTypes.ts +9 -4
  77. package/src/hook/useAnimatedRef.ts +107 -48
  78. package/src/hook/useHandler.ts +10 -0
  79. package/src/hook/useScrollViewOffset.ts +34 -29
  80. package/src/hook/utils.ts +34 -9
  81. package/src/index.ts +4 -1
  82. package/src/platform-specific/jsVersion.ts +1 -1
  83. package/src/platformFunctions/setNativeProps.ts +1 -1
  84. package/src/processBoxShadow.ts +8 -4
@@ -58,6 +58,7 @@ import type {
58
58
  IAnimatedComponentInternal,
59
59
  INativeEventsManager,
60
60
  InitialComponentProps,
61
+ LayoutAnimationOrBuilder,
61
62
  NestedArray,
62
63
  ViewInfo,
63
64
  } from './commonTypes';
@@ -169,22 +170,13 @@ export function createAnimatedComponent(
169
170
  this.jestAnimatedProps = { value: {} };
170
171
  }
171
172
 
172
- const entering = this.props.entering;
173
173
  const skipEntering = this.context?.current;
174
- if (
175
- !entering ||
176
- getReducedMotionFromConfig(entering as CustomConfig) ||
177
- skipEntering ||
178
- !isFabric()
179
- ) {
180
- return;
174
+ if (isFabric() && !skipEntering) {
175
+ this._configureLayoutAnimation(
176
+ LayoutAnimationType.ENTERING,
177
+ this.props.entering
178
+ );
181
179
  }
182
- // This call is responsible for configuring entering animations on Fabric.
183
- updateLayoutAnimations(
184
- this.reanimatedID,
185
- LayoutAnimationType.ENTERING,
186
- maybeBuild(entering, this.props?.style, AnimatedComponent.displayName)
187
- );
188
180
  }
189
181
 
190
182
  componentDidMount() {
@@ -197,10 +189,14 @@ export function createAnimatedComponent(
197
189
  this._attachAnimatedStyles();
198
190
  this._InlinePropManager.attachInlineProps(this, this._getViewInfo());
199
191
 
200
- const layout = this.props.layout;
201
- if (layout) {
202
- this._configureLayoutTransition();
203
- }
192
+ this._configureLayoutAnimation(
193
+ LayoutAnimationType.LAYOUT,
194
+ this.props.layout
195
+ );
196
+ this._configureLayoutAnimation(
197
+ LayoutAnimationType.EXITING,
198
+ this.props.exiting
199
+ );
204
200
 
205
201
  if (IS_WEB) {
206
202
  if (this.props.exiting && this._componentDOMRef) {
@@ -276,15 +272,7 @@ export function createAnimatedComponent(
276
272
  ? getReduceMotionFromConfig(exiting.getReduceMotion())
277
273
  : getReduceMotionFromConfig();
278
274
  if (!reduceMotionInExiting) {
279
- updateLayoutAnimations(
280
- this.getComponentViewTag(),
281
- LayoutAnimationType.EXITING,
282
- maybeBuild(
283
- exiting,
284
- this.props?.style,
285
- AnimatedComponent.displayName
286
- )
287
- );
275
+ this._configureLayoutAnimation(LayoutAnimationType.EXITING, exiting);
288
276
  }
289
277
  }
290
278
 
@@ -465,15 +453,19 @@ export function createAnimatedComponent(
465
453
  componentDidUpdate(
466
454
  prevProps: AnimatedComponentProps<InitialComponentProps>,
467
455
  _prevState: Readonly<unknown>,
468
- // This type comes straight from React
469
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
470
456
  snapshot: DOMRect | null
471
457
  ) {
472
- const layout = this.props.layout;
473
- const oldLayout = prevProps.layout;
474
- if (layout !== oldLayout) {
475
- this._configureLayoutTransition();
476
- }
458
+ this._configureLayoutAnimation(
459
+ LayoutAnimationType.LAYOUT,
460
+ this.props.layout,
461
+ prevProps.layout
462
+ );
463
+ this._configureLayoutAnimation(
464
+ LayoutAnimationType.EXITING,
465
+ this.props.exiting,
466
+ prevProps.exiting
467
+ );
468
+
477
469
  if (
478
470
  this.props.sharedTransitionTag !== undefined ||
479
471
  prevProps.sharedTransitionTag !== undefined
@@ -488,10 +480,9 @@ export function createAnimatedComponent(
488
480
  saveSnapshot(this._componentDOMRef);
489
481
  }
490
482
 
491
- // Snapshot won't be undefined because it comes from getSnapshotBeforeUpdate method
492
483
  if (
493
484
  IS_WEB &&
494
- snapshot !== null &&
485
+ snapshot &&
495
486
  this.props.layout &&
496
487
  !getReducedMotionFromConfig(this.props.layout as CustomConfig)
497
488
  ) {
@@ -503,22 +494,33 @@ export function createAnimatedComponent(
503
494
  }
504
495
  }
505
496
 
506
- _configureLayoutTransition() {
507
- if (IS_WEB) {
497
+ _configureLayoutAnimation(
498
+ type: LayoutAnimationType,
499
+ currentConfig: LayoutAnimationOrBuilder | undefined,
500
+ previousConfig?: LayoutAnimationOrBuilder
501
+ ) {
502
+ if (IS_WEB || currentConfig === previousConfig) {
508
503
  return;
509
504
  }
510
505
 
511
- const layout = this.props.layout;
512
- if (layout && getReducedMotionFromConfig(layout as CustomConfig)) {
513
- return;
506
+ if (this._isReducedMotion(currentConfig)) {
507
+ if (!previousConfig) {
508
+ return;
509
+ }
510
+ currentConfig = undefined;
514
511
  }
512
+
515
513
  updateLayoutAnimations(
516
- this.getComponentViewTag(),
517
- LayoutAnimationType.LAYOUT,
518
- layout &&
514
+ isFabric() && type === LayoutAnimationType.ENTERING
515
+ ? this.reanimatedID
516
+ : this.getComponentViewTag(),
517
+ type,
518
+ currentConfig &&
519
519
  maybeBuild(
520
- layout,
521
- undefined /* We don't have to warn user if style has common properties with animation for LAYOUT */,
520
+ currentConfig,
521
+ type === LayoutAnimationType.LAYOUT
522
+ ? undefined /* We don't have to warn user if style has common properties with animation for LAYOUT */
523
+ : this.props?.style,
522
524
  AnimatedComponent.displayName
523
525
  )
524
526
  );
@@ -584,7 +586,6 @@ export function createAnimatedComponent(
584
586
  // if ref is changed, reset viewInfo
585
587
  this._viewInfo = undefined;
586
588
  }
587
- const tag = this.getComponentViewTag();
588
589
 
589
590
  const { layout, entering, exiting, sharedTransitionTag } = this.props;
590
591
  if (layout || entering || exiting || sharedTransitionTag) {
@@ -595,52 +596,39 @@ export function createAnimatedComponent(
595
596
  if (sharedTransitionTag) {
596
597
  this._configureSharedTransition();
597
598
  }
598
- if (exiting && isFabric()) {
599
- const reduceMotionInExiting =
600
- 'getReduceMotion' in exiting &&
601
- typeof exiting.getReduceMotion === 'function'
602
- ? getReduceMotionFromConfig(exiting.getReduceMotion())
603
- : getReduceMotionFromConfig();
604
- if (!reduceMotionInExiting) {
605
- updateLayoutAnimations(
606
- tag,
607
- LayoutAnimationType.EXITING,
608
- maybeBuild(
609
- exiting,
610
- this.props?.style,
611
- AnimatedComponent.displayName
612
- )
613
- );
614
- }
615
- }
616
599
 
617
600
  const skipEntering = this.context?.current;
618
601
  if (entering && !isFabric() && !skipEntering && !IS_WEB) {
619
- updateLayoutAnimations(
620
- tag,
602
+ this._configureLayoutAnimation(
621
603
  LayoutAnimationType.ENTERING,
622
- maybeBuild(
623
- entering,
624
- this.props?.style,
625
- AnimatedComponent.displayName
626
- )
604
+ this.props.entering
627
605
  );
628
606
  }
629
607
  }
630
608
  },
631
609
  });
632
610
 
611
+ _isReducedMotion(config?: LayoutAnimationOrBuilder): boolean {
612
+ return config &&
613
+ 'getReduceMotion' in config &&
614
+ typeof config.getReduceMotion === 'function'
615
+ ? getReduceMotionFromConfig(config.getReduceMotion())
616
+ : getReduceMotionFromConfig();
617
+ }
618
+
633
619
  // This is a component lifecycle method from React, therefore we are not calling it directly.
634
620
  // It is called before the component gets rerendered. This way we can access components' position before it changed
635
621
  // and later on, in componentDidUpdate, calculate translation for layout transition.
636
622
  getSnapshotBeforeUpdate() {
637
623
  if (
638
624
  IS_WEB &&
639
- this._componentDOMRef?.getBoundingClientRect !== undefined
625
+ this.props.layout &&
626
+ this._componentDOMRef?.getBoundingClientRect
640
627
  ) {
641
628
  return this._componentDOMRef.getBoundingClientRect();
642
629
  }
643
630
 
631
+ // `getSnapshotBeforeUpdate` has to return value which is not `undefined`.
644
632
  return null;
645
633
  }
646
634
 
@@ -7,7 +7,7 @@ until time comes to refactor the code and get necessary types right.
7
7
  This will not be easy though!
8
8
  */
9
9
 
10
- import type { RegisteredStyle, StyleProp } from 'react-native';
10
+ import type { StyleProp } from 'react-native';
11
11
 
12
12
  import type {
13
13
  AnimatedStyle,
@@ -144,10 +144,5 @@ export type AdaptTransforms<T> = {
144
144
  /** @deprecated Please use {@link TransformArrayItem} type instead. */
145
145
  export type TransformStyleTypes = TransformArrayItem;
146
146
 
147
- /** @deprecated This type is no longer relevant. */
148
- export type AnimatedStyleProp<T> =
149
- | AnimatedStyle<T>
150
- | RegisteredStyle<AnimatedStyle<T>>;
151
-
152
147
  /** @deprecated Please use {@link AnimatedProps} type instead. */
153
148
  export type AnimateProps<Props extends object> = AnimatedProps<Props>;
@@ -27,13 +27,18 @@ export interface Descriptor {
27
27
  shadowNodeWrapper: ShadowNodeWrapper;
28
28
  }
29
29
 
30
+ export type MaybeObserverCleanup = (() => void) | undefined;
31
+
32
+ export type AnimatedRefObserver = (tag: number | null) => MaybeObserverCleanup;
33
+
30
34
  export interface AnimatedRef<T extends Component> {
31
35
  (component?: T):
32
36
  | number // Paper
33
37
  | ShadowNodeWrapper // Fabric
34
38
  | HTMLElement; // web
35
39
  current: T | null;
36
- getTag: () => number;
40
+ observe: (observer: AnimatedRefObserver) => void;
41
+ getTag?: () => number | null;
37
42
  }
38
43
 
39
44
  // Might make that type generic if it's ever needed.
@@ -44,10 +49,10 @@ export type AnimatedRefOnUI = {
44
49
  (): number | ShadowNodeWrapper | null;
45
50
  /**
46
51
  * @remarks
47
- * `viewName` is required only on iOS with Paper and it's value is null on
48
- * other platforms.
52
+ * `viewName` is required only on iOS/macOS with Paper and is undefined on
53
+ * other platforms
49
54
  */
50
- viewName: SharedValue<string | null>;
55
+ viewName?: SharedValue<string | null>;
51
56
  };
52
57
 
53
58
  type ReanimatedPayload = {
@@ -1,19 +1,23 @@
1
1
  'use strict';
2
2
  import type { Component } from 'react';
3
- import { useRef } from 'react';
3
+ import { useRef, useState } from 'react';
4
4
  import type { FlatList, ScrollView } from 'react-native';
5
- import { Platform } from 'react-native';
6
5
 
7
6
  import type { ShadowNodeWrapper } from '../commonTypes';
8
7
  import { getShadowNodeWrapperFromRef } from '../fabricUtils';
9
- import { isFabric, isWeb } from '../PlatformChecker';
8
+ import { makeMutable } from '../mutables';
9
+ import { isFabric, isIOS, isMacOS, shouldBeUseWeb } from '../PlatformChecker';
10
10
  import { findNodeHandle } from '../platformFunctions/findNodeHandle';
11
11
  import { shareableMappingCache } from '../shareableMappingCache';
12
12
  import { makeShareableCloneRecursive } from '../shareables';
13
- import type { AnimatedRef, AnimatedRefOnUI } from './commonTypes';
14
- import { useSharedValue } from './useSharedValue';
13
+ import type {
14
+ AnimatedRef,
15
+ AnimatedRefObserver,
16
+ AnimatedRefOnUI,
17
+ MaybeObserverCleanup,
18
+ } from './commonTypes';
15
19
 
16
- const IS_WEB = isWeb();
20
+ const SHOULD_BE_USE_WEB = shouldBeUseWeb();
17
21
 
18
22
  interface MaybeScrollableComponent extends Component {
19
23
  getNativeScrollRef?: FlatList['getNativeScrollRef'];
@@ -26,26 +30,22 @@ interface MaybeScrollableComponent extends Component {
26
30
  }
27
31
 
28
32
  function getComponentOrScrollable(component: MaybeScrollableComponent) {
29
- if (isFabric() && component.getNativeScrollRef) {
33
+ if (component.getNativeScrollRef) {
30
34
  return component.getNativeScrollRef();
31
- } else if (!isFabric() && component.getScrollableNode) {
35
+ }
36
+ if (component.getScrollableNode) {
32
37
  return component.getScrollableNode();
33
38
  }
34
39
  return component;
35
40
  }
36
41
 
37
- /**
38
- * Lets you get a reference of a view that you can use inside a worklet.
39
- *
40
- * @returns An object with a `.current` property which contains an instance of a
41
- * component.
42
- * @see https://docs.swmansion.com/react-native-reanimated/docs/core/useAnimatedRef
43
- */
44
- export function useAnimatedRef<
45
- TComponent extends Component,
46
- >(): AnimatedRef<TComponent> {
47
- const tag = useSharedValue<number | ShadowNodeWrapper | null>(-1);
48
- const viewName = useSharedValue<string | null>(null);
42
+ function useAnimatedRefBase<TComponent extends Component>(
43
+ getWrapper: (component: TComponent) => ShadowNodeWrapper | number | null
44
+ ): AnimatedRef<TComponent> {
45
+ const observers = useRef<Map<AnimatedRefObserver, MaybeObserverCleanup>>(
46
+ new Map()
47
+ ).current;
48
+ const tagOrWrapperRef = useRef<ShadowNodeWrapper | number | null>(-1);
49
49
 
50
50
  const ref = useRef<AnimatedRef<TComponent> | null>(null);
51
51
 
@@ -53,49 +53,108 @@ export function useAnimatedRef<
53
53
  const fun: AnimatedRef<TComponent> = <AnimatedRef<TComponent>>((
54
54
  component
55
55
  ) => {
56
- // enters when ref is set by attaching to a component
57
56
  if (component) {
58
- const getTagValueFunction = isFabric()
59
- ? getShadowNodeWrapperFromRef
60
- : findNodeHandle;
61
-
62
- const getTagOrShadowNodeWrapper = () => {
63
- return IS_WEB
64
- ? getComponentOrScrollable(component)
65
- : getTagValueFunction(getComponentOrScrollable(component));
66
- };
67
-
68
- tag.value = getTagOrShadowNodeWrapper();
69
-
70
- // On Fabric we have to unwrap the tag from the shadow node wrapper
71
- fun.getTag = isFabric()
72
- ? () => findNodeHandle(getComponentOrScrollable(component))
73
- : getTagOrShadowNodeWrapper;
57
+ tagOrWrapperRef.current = getWrapper(component);
74
58
 
59
+ // We have to unwrap the tag from the shadow node wrapper.
60
+ fun.getTag = () => findNodeHandle(getComponentOrScrollable(component));
75
61
  fun.current = component;
76
- // viewName is required only on iOS with Paper
77
- if (Platform.OS === 'ios' && !isFabric()) {
78
- viewName.value =
79
- (component as MaybeScrollableComponent)?.viewConfig
80
- ?.uiViewClassName || 'RCTView';
62
+
63
+ if (observers.size) {
64
+ const currentTag = fun?.getTag?.() ?? null;
65
+ observers.forEach((cleanup, observer) => {
66
+ // Perform the cleanup before calling the observer again.
67
+ // This ensures that all events that were set up in the observer
68
+ // are cleaned up before the observer sets up new events during
69
+ // the next call.
70
+ cleanup?.();
71
+ observers.set(observer, observer(currentTag));
72
+ });
81
73
  }
82
74
  }
83
- return tag.value;
75
+
76
+ return tagOrWrapperRef.current;
84
77
  });
85
78
 
79
+ fun.observe = (observer: AnimatedRefObserver) => {
80
+ // Call observer immediately to get the initial value
81
+ const cleanup = observer(fun?.getTag?.() ?? null);
82
+ observers.set(observer, cleanup);
83
+
84
+ return () => {
85
+ observers.get(observer)?.();
86
+ observers.delete(observer);
87
+ };
88
+ };
89
+
86
90
  fun.current = null;
91
+ ref.current = fun;
92
+ }
93
+
94
+ return ref.current;
95
+ }
96
+
97
+ const IS_APPLE = isIOS() || isMacOS();
87
98
 
99
+ function useAnimatedRefNative<
100
+ TComponent extends Component,
101
+ >(): AnimatedRef<TComponent> {
102
+ const [viewName] = useState(() =>
103
+ // viewName is required only on iOS/MacOS with Paper
104
+ !isFabric() && IS_APPLE ? makeMutable<string | null>(null) : null
105
+ );
106
+ const [tagOrWrapper] = useState(() =>
107
+ makeMutable<ShadowNodeWrapper | number | null>(null)
108
+ );
109
+
110
+ const ref = useAnimatedRefBase<TComponent>((component) => {
111
+ const getTagOrWrapper = isFabric()
112
+ ? getShadowNodeWrapperFromRef
113
+ : findNodeHandle;
114
+
115
+ tagOrWrapper.value = getTagOrWrapper(getComponentOrScrollable(component));
116
+
117
+ if (viewName) {
118
+ viewName.value =
119
+ (component as MaybeScrollableComponent)?.viewConfig?.uiViewClassName ||
120
+ 'RCTView';
121
+ }
122
+
123
+ return tagOrWrapper.value;
124
+ });
125
+
126
+ if (!shareableMappingCache.get(ref)) {
88
127
  const animatedRefShareableHandle = makeShareableCloneRecursive({
89
128
  __init: () => {
90
129
  'worklet';
91
- const f: AnimatedRefOnUI = () => tag.value;
92
- f.viewName = viewName;
130
+ const f: AnimatedRefOnUI = () => tagOrWrapper.value;
131
+ if (viewName) {
132
+ f.viewName = viewName;
133
+ }
93
134
  return f;
94
135
  },
95
136
  });
96
- shareableMappingCache.set(fun, animatedRefShareableHandle);
97
- ref.current = fun;
137
+ shareableMappingCache.set(ref, animatedRefShareableHandle);
98
138
  }
99
139
 
100
- return ref.current;
140
+ return ref;
141
+ }
142
+
143
+ function useAnimatedRefWeb<
144
+ TComponent extends Component,
145
+ >(): AnimatedRef<TComponent> {
146
+ return useAnimatedRefBase<TComponent>((component) =>
147
+ getComponentOrScrollable(component)
148
+ );
101
149
  }
150
+
151
+ /**
152
+ * Lets you get a reference of a view that you can use inside a worklet.
153
+ *
154
+ * @returns An object with a `.current` property which contains an instance of a
155
+ * component.
156
+ * @see https://docs.swmansion.com/react-native-reanimated/docs/core/useAnimatedRef
157
+ */
158
+ export const useAnimatedRef = SHOULD_BE_USE_WEB
159
+ ? useAnimatedRefWeb
160
+ : useAnimatedRefNative;
@@ -2,6 +2,8 @@
2
2
  import { useEffect, useRef } from 'react';
3
3
 
4
4
  import type { WorkletFunction } from '../commonTypes';
5
+ import { isWorkletFunction } from '../commonTypes';
6
+ import { ReanimatedError } from '../errors';
5
7
  import { isJest, isWeb } from '../PlatformChecker';
6
8
  import { makeShareable } from '../shareables';
7
9
  import type { DependencyList, ReanimatedEvent } from './commonTypes';
@@ -83,6 +85,14 @@ export function useHandler<
83
85
 
84
86
  const { context, savedDependencies } = initRef.current;
85
87
 
88
+ for (const handlerName in handlers) {
89
+ if (!isWorkletFunction(handlers[handlerName])) {
90
+ throw new ReanimatedError(
91
+ 'Passed a function that is not a worklet. Please provide a worklet function.'
92
+ );
93
+ }
94
+ }
95
+
86
96
  dependencies = buildDependencies(
87
97
  dependencies,
88
98
  handlers as Record<string, WorkletFunction | undefined>
@@ -3,6 +3,7 @@ import { useCallback, useEffect, useRef } from 'react';
3
3
 
4
4
  import type { SharedValue } from '../commonTypes';
5
5
  import type { AnimatedScrollView } from '../component/ScrollView';
6
+ import { logger } from '../logger';
6
7
  import { isWeb } from '../PlatformChecker';
7
8
  import type {
8
9
  AnimatedRef,
@@ -15,6 +16,9 @@ import { useSharedValue } from './useSharedValue';
15
16
 
16
17
  const IS_WEB = isWeb();
17
18
 
19
+ const NOT_INITIALIZED_WARNING =
20
+ 'animatedRef is not initialized in useScrollViewOffset. Make sure to pass the animated ref to the scrollable component to get scroll offset updates.';
21
+
18
22
  /**
19
23
  * Lets you synchronously get the current offset of a `ScrollView`.
20
24
  *
@@ -43,27 +47,27 @@ function useScrollViewOffsetWeb(
43
47
  offset.value =
44
48
  element.scrollLeft === 0 ? element.scrollTop : element.scrollLeft;
45
49
  }
46
- // eslint-disable-next-line react-hooks/exhaustive-deps
47
- }, [animatedRef, animatedRef?.current]);
50
+ }, [animatedRef, offset]);
48
51
 
49
52
  useEffect(() => {
50
- const element = animatedRef?.current
51
- ? getWebScrollableElement(animatedRef.current)
52
- : null;
53
+ if (!animatedRef) {
54
+ return;
55
+ }
56
+
57
+ return animatedRef.observe((tag) => {
58
+ if (!tag) {
59
+ logger.warn(NOT_INITIALIZED_WARNING);
60
+ return;
61
+ }
53
62
 
54
- if (element) {
63
+ const element = getWebScrollableElement(animatedRef.current);
55
64
  element.addEventListener('scroll', eventHandler);
56
- }
57
- return () => {
58
- if (element) {
65
+
66
+ return () => {
59
67
  element.removeEventListener('scroll', eventHandler);
60
- }
61
- };
62
- // React here has a problem with `animatedRef.current` since a Ref .current
63
- // field shouldn't be used as a dependency. However, in this case we have
64
- // to do it this way.
65
- // eslint-disable-next-line react-hooks/exhaustive-deps
66
- }, [animatedRef, animatedRef?.current, eventHandler]);
68
+ };
69
+ });
70
+ }, [animatedRef, eventHandler]);
67
71
 
68
72
  return offset;
69
73
  }
@@ -89,21 +93,22 @@ function useScrollViewOffsetNative(
89
93
  ) as unknown as EventHandlerInternal<ReanimatedScrollEvent>;
90
94
 
91
95
  useEffect(() => {
92
- const elementTag = animatedRef?.getTag() ?? null;
93
-
94
- if (elementTag) {
95
- eventHandler.workletEventHandler.registerForEvents(elementTag);
96
+ if (!animatedRef) {
97
+ return;
96
98
  }
97
- return () => {
98
- if (elementTag) {
99
- eventHandler.workletEventHandler.unregisterFromEvents(elementTag);
99
+
100
+ return animatedRef.observe((tag) => {
101
+ if (!tag) {
102
+ logger.warn(NOT_INITIALIZED_WARNING);
103
+ return;
100
104
  }
101
- };
102
- // React here has a problem with `animatedRef.current` since a Ref .current
103
- // field shouldn't be used as a dependency. However, in this case we have
104
- // to do it this way.
105
- // eslint-disable-next-line react-hooks/exhaustive-deps
106
- }, [animatedRef, animatedRef?.current, eventHandler]);
105
+
106
+ eventHandler.workletEventHandler.registerForEvents(tag);
107
+ return () => {
108
+ eventHandler.workletEventHandler.unregisterFromEvents(tag);
109
+ };
110
+ });
111
+ }, [animatedRef, eventHandler]);
107
112
 
108
113
  return offset;
109
114
  }
package/src/hook/utils.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
  import type { WorkletFunction } from '../commonTypes';
3
+ import { isWorkletFunction } from '../commonTypes';
3
4
  import { ReanimatedError } from '../errors';
4
5
  import type { DependencyList } from './commonTypes';
5
6
 
@@ -27,19 +28,34 @@ export function buildDependencies(
27
28
  (handler) => handler !== undefined
28
29
  ) as NonNullable<Handler>[];
29
30
  if (!dependencies) {
30
- dependencies = handlersList.map((handler) => {
31
- return {
32
- workletHash: handler.__workletHash,
33
- closure: handler.__closure,
34
- };
35
- });
36
- } else {
37
- dependencies.push(buildWorkletsHash(handlersList));
31
+ return handlersList;
38
32
  }
39
33
 
34
+ dependencies.push(buildWorkletsHash(handlersList));
40
35
  return dependencies;
41
36
  }
42
37
 
38
+ function areWorkletsEqual(
39
+ worklet1: WorkletFunction,
40
+ worklet2: WorkletFunction
41
+ ) {
42
+ if (worklet1.__workletHash === worklet2.__workletHash) {
43
+ const closure1Keys = Object.keys(worklet1.__closure);
44
+ const closure2Keys = Object.keys(worklet2.__closure);
45
+
46
+ return (
47
+ closure1Keys.length === closure2Keys.length &&
48
+ closure1Keys.every(
49
+ (key) =>
50
+ key in worklet2.__closure &&
51
+ worklet1.__closure[key] === worklet2.__closure[key]
52
+ )
53
+ );
54
+ }
55
+
56
+ return false;
57
+ }
58
+
43
59
  // This is supposed to work as useEffect comparison.
44
60
  export function areDependenciesEqual(
45
61
  nextDependencies: DependencyList,
@@ -61,8 +77,17 @@ export function areDependenciesEqual(
61
77
  if (!nextDeps || !prevDeps || prevDeps.length !== nextDeps.length) {
62
78
  return false;
63
79
  }
80
+
64
81
  for (let i = 0; i < prevDeps.length; ++i) {
65
- if (!objectIs(nextDeps[i], prevDeps[i])) {
82
+ const nextDep = nextDeps[i];
83
+ const prevDep = prevDeps[i];
84
+ if (objectIs(nextDep, prevDep)) {
85
+ continue;
86
+ }
87
+ if (!isWorkletFunction(nextDep) || !isWorkletFunction(prevDep)) {
88
+ return false;
89
+ }
90
+ if (!areWorkletsEqual(nextDep, prevDep)) {
66
91
  return false;
67
92
  }
68
93
  }