react-native-gesture-handler 3.0.0-beta.4 → 3.0.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt +12 -4
  2. package/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt +6 -2
  3. package/android/src/main/java/com/swmansion/gesturehandler/react/Extensions.kt +21 -0
  4. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt +113 -49
  5. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerDetectorView.kt +75 -98
  6. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerModule.kt +7 -10
  7. package/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRegistry.kt +64 -2
  8. package/apple/RNGestureHandler.mm +50 -27
  9. package/apple/RNGestureHandlerButton.h +4 -2
  10. package/apple/RNGestureHandlerButton.mm +106 -27
  11. package/apple/RNGestureHandlerButtonComponentView.mm +17 -2
  12. package/apple/RNGestureHandlerDetector.mm +99 -75
  13. package/apple/RNGestureHandlerModule.mm +11 -14
  14. package/apple/RNGestureHandlerRegistry.h +14 -0
  15. package/apple/RNGestureHandlerRegistry.m +56 -0
  16. package/lib/module/RNGestureHandlerModule.web.js +5 -1
  17. package/lib/module/RNGestureHandlerModule.web.js.map +1 -1
  18. package/lib/module/components/GestureButtons.js +16 -5
  19. package/lib/module/components/GestureButtons.js.map +1 -1
  20. package/lib/module/components/GestureHandlerButton.js.map +1 -1
  21. package/lib/module/components/GestureHandlerButton.web.js +63 -23
  22. package/lib/module/components/GestureHandlerButton.web.js.map +1 -1
  23. package/lib/module/components/Pressable/Pressable.js +1 -0
  24. package/lib/module/components/Pressable/Pressable.js.map +1 -1
  25. package/lib/module/components/ReanimatedDrawerLayout.js.map +1 -1
  26. package/lib/module/components/ReanimatedSwipeable/ReanimatedSwipeable.js +38 -5
  27. package/lib/module/components/ReanimatedSwipeable/ReanimatedSwipeable.js.map +1 -1
  28. package/lib/module/handlers/gestures/GestureDetector/useDetectorUpdater.js +1 -2
  29. package/lib/module/handlers/gestures/GestureDetector/useDetectorUpdater.js.map +1 -1
  30. package/lib/module/handlers/gestures/GestureDetector/utils.js +0 -47
  31. package/lib/module/handlers/gestures/GestureDetector/utils.js.map +1 -1
  32. package/lib/module/handlers/gestures/reanimatedWrapper.js +14 -2
  33. package/lib/module/handlers/gestures/reanimatedWrapper.js.map +1 -1
  34. package/lib/module/mocks/module.js +3 -2
  35. package/lib/module/mocks/module.js.map +1 -1
  36. package/lib/module/specs/NativeRNGestureHandlerModule.js.map +1 -1
  37. package/lib/module/specs/RNGestureHandlerButtonNativeComponent.ts +28 -13
  38. package/lib/module/v3/NativeProxy.js +5 -3
  39. package/lib/module/v3/NativeProxy.js.map +1 -1
  40. package/lib/module/v3/NativeProxy.web.js +2 -2
  41. package/lib/module/v3/NativeProxy.web.js.map +1 -1
  42. package/lib/module/v3/components/GestureButtons.js +8 -3
  43. package/lib/module/v3/components/GestureButtons.js.map +1 -1
  44. package/lib/module/v3/components/Touchable/Touchable.js +53 -4
  45. package/lib/module/v3/components/Touchable/Touchable.js.map +1 -1
  46. package/lib/module/v3/detectors/HostGestureDetector.web.js +178 -59
  47. package/lib/module/v3/detectors/HostGestureDetector.web.js.map +1 -1
  48. package/lib/module/v3/detectors/NativeDetector.js +3 -2
  49. package/lib/module/v3/detectors/NativeDetector.js.map +1 -1
  50. package/lib/module/v3/detectors/VirtualDetector/InterceptingGestureDetector.js +3 -4
  51. package/lib/module/v3/detectors/VirtualDetector/InterceptingGestureDetector.js.map +1 -1
  52. package/lib/module/v3/detectors/VirtualDetector/VirtualDetector.js +2 -2
  53. package/lib/module/v3/detectors/VirtualDetector/VirtualDetector.js.map +1 -1
  54. package/lib/module/v3/detectors/useGestureRelationsUpdater.js +23 -0
  55. package/lib/module/v3/detectors/useGestureRelationsUpdater.js.map +1 -0
  56. package/lib/module/v3/detectors/utils.js +10 -8
  57. package/lib/module/v3/detectors/utils.js.map +1 -1
  58. package/lib/module/v3/hooks/useGesture.js +3 -18
  59. package/lib/module/v3/hooks/useGesture.js.map +1 -1
  60. package/lib/module/v3/hooks/utils/configUtils.js +1 -3
  61. package/lib/module/v3/hooks/utils/configUtils.js.map +1 -1
  62. package/lib/module/v3/hooks/utils/eventHandlersUtils.js +31 -29
  63. package/lib/module/v3/hooks/utils/eventHandlersUtils.js.map +1 -1
  64. package/lib/module/v3/hooks/utils/reanimatedUtils.js +8 -2
  65. package/lib/module/v3/hooks/utils/reanimatedUtils.js.map +1 -1
  66. package/lib/module/web/tools/NodeManager.js +44 -0
  67. package/lib/module/web/tools/NodeManager.js.map +1 -1
  68. package/lib/typescript/RNGestureHandlerModule.web.d.ts +1 -1
  69. package/lib/typescript/RNGestureHandlerModule.web.d.ts.map +1 -1
  70. package/lib/typescript/components/GestureButtons.d.ts +14 -6
  71. package/lib/typescript/components/GestureButtons.d.ts.map +1 -1
  72. package/lib/typescript/components/GestureHandlerButton.d.ts +62 -8
  73. package/lib/typescript/components/GestureHandlerButton.d.ts.map +1 -1
  74. package/lib/typescript/components/GestureHandlerButton.web.d.ts +10 -3
  75. package/lib/typescript/components/GestureHandlerButton.web.d.ts.map +1 -1
  76. package/lib/typescript/components/Pressable/Pressable.d.ts.map +1 -1
  77. package/lib/typescript/components/Pressable/PressableProps.d.ts +1 -1
  78. package/lib/typescript/components/Pressable/PressableProps.d.ts.map +1 -1
  79. package/lib/typescript/components/ReanimatedDrawerLayout.d.ts +16 -14
  80. package/lib/typescript/components/ReanimatedDrawerLayout.d.ts.map +1 -1
  81. package/lib/typescript/components/ReanimatedSwipeable/ReanimatedSwipeable.d.ts +2 -1
  82. package/lib/typescript/components/ReanimatedSwipeable/ReanimatedSwipeable.d.ts.map +1 -1
  83. package/lib/typescript/components/ReanimatedSwipeable/ReanimatedSwipeableProps.d.ts +30 -34
  84. package/lib/typescript/components/ReanimatedSwipeable/ReanimatedSwipeableProps.d.ts.map +1 -1
  85. package/lib/typescript/handlers/gestures/GestureDetector/useDetectorUpdater.d.ts.map +1 -1
  86. package/lib/typescript/handlers/gestures/GestureDetector/utils.d.ts +0 -1
  87. package/lib/typescript/handlers/gestures/GestureDetector/utils.d.ts.map +1 -1
  88. package/lib/typescript/handlers/gestures/reanimatedWrapper.d.ts.map +1 -1
  89. package/lib/typescript/mocks/module.d.ts +1 -1
  90. package/lib/typescript/mocks/module.d.ts.map +1 -1
  91. package/lib/typescript/specs/NativeRNGestureHandlerModule.d.ts +2 -2
  92. package/lib/typescript/specs/NativeRNGestureHandlerModule.d.ts.map +1 -1
  93. package/lib/typescript/specs/RNGestureHandlerButtonNativeComponent.d.ts +19 -11
  94. package/lib/typescript/specs/RNGestureHandlerButtonNativeComponent.d.ts.map +1 -1
  95. package/lib/typescript/v3/NativeProxy.d.ts +1 -1
  96. package/lib/typescript/v3/NativeProxy.d.ts.map +1 -1
  97. package/lib/typescript/v3/NativeProxy.web.d.ts +1 -1
  98. package/lib/typescript/v3/NativeProxy.web.d.ts.map +1 -1
  99. package/lib/typescript/v3/components/GestureButtons.d.ts +1 -38
  100. package/lib/typescript/v3/components/GestureButtons.d.ts.map +1 -1
  101. package/lib/typescript/v3/components/GestureButtonsProps.d.ts +1 -1
  102. package/lib/typescript/v3/components/GestureButtonsProps.d.ts.map +1 -1
  103. package/lib/typescript/v3/components/Touchable/Touchable.d.ts.map +1 -1
  104. package/lib/typescript/v3/components/Touchable/TouchableProps.d.ts +39 -1
  105. package/lib/typescript/v3/components/Touchable/TouchableProps.d.ts.map +1 -1
  106. package/lib/typescript/v3/detectors/HostGestureDetector.web.d.ts.map +1 -1
  107. package/lib/typescript/v3/detectors/NativeDetector.d.ts.map +1 -1
  108. package/lib/typescript/v3/detectors/VirtualDetector/InterceptingGestureDetector.d.ts.map +1 -1
  109. package/lib/typescript/v3/detectors/useGestureRelationsUpdater.d.ts +3 -0
  110. package/lib/typescript/v3/detectors/useGestureRelationsUpdater.d.ts.map +1 -0
  111. package/lib/typescript/v3/detectors/utils.d.ts +3 -3
  112. package/lib/typescript/v3/detectors/utils.d.ts.map +1 -1
  113. package/lib/typescript/v3/hooks/useGesture.d.ts.map +1 -1
  114. package/lib/typescript/v3/hooks/utils/configUtils.d.ts.map +1 -1
  115. package/lib/typescript/v3/hooks/utils/eventHandlersUtils.d.ts.map +1 -1
  116. package/lib/typescript/v3/hooks/utils/reanimatedUtils.d.ts +1 -0
  117. package/lib/typescript/v3/hooks/utils/reanimatedUtils.d.ts.map +1 -1
  118. package/lib/typescript/web/tools/NodeManager.d.ts +7 -0
  119. package/lib/typescript/web/tools/NodeManager.d.ts.map +1 -1
  120. package/package.json +3 -3
  121. package/src/RNGestureHandlerModule.web.ts +5 -1
  122. package/src/components/GestureButtons.tsx +23 -7
  123. package/src/components/GestureHandlerButton.tsx +70 -8
  124. package/src/components/GestureHandlerButton.web.tsx +97 -29
  125. package/src/components/Pressable/Pressable.tsx +1 -0
  126. package/src/components/Pressable/PressableProps.tsx +2 -1
  127. package/src/components/ReanimatedDrawerLayout.tsx +27 -23
  128. package/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx +51 -5
  129. package/src/components/ReanimatedSwipeable/ReanimatedSwipeableProps.ts +31 -39
  130. package/src/handlers/gestures/GestureDetector/useDetectorUpdater.ts +1 -2
  131. package/src/handlers/gestures/GestureDetector/utils.ts +0 -52
  132. package/src/handlers/gestures/reanimatedWrapper.ts +20 -2
  133. package/src/mocks/module.tsx +4 -2
  134. package/src/specs/NativeRNGestureHandlerModule.ts +2 -4
  135. package/src/specs/RNGestureHandlerButtonNativeComponent.ts +28 -13
  136. package/src/v3/NativeProxy.ts +9 -7
  137. package/src/v3/NativeProxy.web.ts +2 -2
  138. package/src/v3/components/GestureButtons.tsx +13 -5
  139. package/src/v3/components/GestureButtonsProps.ts +1 -0
  140. package/src/v3/components/Touchable/Touchable.tsx +65 -4
  141. package/src/v3/components/Touchable/TouchableProps.ts +49 -1
  142. package/src/v3/detectors/HostGestureDetector.web.tsx +265 -108
  143. package/src/v3/detectors/NativeDetector.tsx +3 -2
  144. package/src/v3/detectors/VirtualDetector/InterceptingGestureDetector.tsx +3 -4
  145. package/src/v3/detectors/VirtualDetector/VirtualDetector.tsx +2 -2
  146. package/src/v3/detectors/useGestureRelationsUpdater.ts +30 -0
  147. package/src/v3/detectors/utils.ts +28 -12
  148. package/src/v3/hooks/useGesture.ts +4 -14
  149. package/src/v3/hooks/utils/configUtils.ts +2 -3
  150. package/src/v3/hooks/utils/eventHandlersUtils.ts +43 -32
  151. package/src/v3/hooks/utils/reanimatedUtils.ts +10 -10
  152. package/src/web/tools/NodeManager.ts +57 -0
  153. package/lib/module/RNRenderer.js +0 -6
  154. package/lib/module/RNRenderer.js.map +0 -1
  155. package/lib/module/RNRenderer.web.js +0 -6
  156. package/lib/module/RNRenderer.web.js.map +0 -1
  157. package/lib/typescript/RNRenderer.d.ts +0 -2
  158. package/lib/typescript/RNRenderer.d.ts.map +0 -1
  159. package/lib/typescript/RNRenderer.web.d.ts +0 -4
  160. package/lib/typescript/RNRenderer.web.d.ts.map +0 -1
  161. package/src/RNRenderer.ts +0 -3
  162. package/src/RNRenderer.web.ts +0 -3
@@ -10,6 +10,7 @@ import type {
10
10
  import RNGestureHandlerModule from '../../RNGestureHandlerModule.web';
11
11
  import { tagMessage } from '../../utils';
12
12
  import { type PropsRef } from '../../web/interfaces';
13
+ import NodeManager from '../../web/tools/NodeManager';
13
14
  import { useNativeGestureRole } from './useNativeGestureRole';
14
15
 
15
16
  export interface GestureHandlerDetectorProps extends PropsRef {
@@ -31,7 +32,193 @@ export interface VirtualChildrenWeb {
31
32
  enableContextMenu?: boolean | undefined;
32
33
  }
33
34
 
34
- const EMPTY_HANDLERS = new Set<number>();
35
+ // Bundles all per-instance state passed to the standalone helpers below. Holding everything in
36
+ // one stable object keeps the helpers pure (no closures over component-render-scoped values),
37
+ // which means the useEffects don't need them in their deps lists.
38
+ type DetectorRefs = {
39
+ owner: object;
40
+ viewRef: RefObject<Element | null>;
41
+ propsRef: RefObject<GestureHandlerDetectorProps>;
42
+ // Tags observed for the detector view (top-level).
43
+ subscribedHandlers: Set<number>;
44
+ // Flat set of tags currently bound to *some* element (top-level or virtual). Mirrors iOS
45
+ // `_attachedHandlers` / Android `attachedHandlers`.
46
+ attachedHandlers: Set<number>;
47
+ // Tags whose handler asked to attach to the detector's child element rather than the detector
48
+ // itself (`shouldAttachGestureToChildView`). Kept here so we can (re)bind them as subviews
49
+ // appear.
50
+ nativeHandlers: Set<number>;
51
+ // For each virtual child's viewTag, the set of currently-observed handler tags.
52
+ subscribedVirtualHandlers: Map<number, Set<number>>;
53
+ // Latest snapshot of virtual children keyed by viewTag. The ready callback reads this so
54
+ // re-fires after a child's per-DOM props change use the up-to-date values.
55
+ virtualChildren: Map<number, VirtualChildrenWeb>;
56
+ };
57
+
58
+ // Invoked from `NodeManager.observeHandler` once the handler is known to exist. Branches on
59
+ // handler kind + actionType to pick the right binding flow. May be called multiple times for
60
+ // the same tag (handler re-registration), so each branch must be idempotent.
61
+ function attachReadyHandler(
62
+ refs: DetectorRefs,
63
+ tag: number,
64
+ actionType: ActionType,
65
+ virtualViewTag?: number
66
+ ) {
67
+ const handler = RNGestureHandlerModule.getGestureHandlerNode(tag);
68
+
69
+ if (
70
+ actionType === ActionType.NATIVE_DETECTOR &&
71
+ handler.shouldAttachGestureToChildView()
72
+ ) {
73
+ refs.nativeHandlers.add(tag);
74
+ if (
75
+ refs.viewRef.current != null &&
76
+ refs.viewRef.current.childElementCount > 0
77
+ ) {
78
+ tryAttachNativeHandlersToChildView(refs);
79
+ }
80
+ return;
81
+ }
82
+
83
+ if (actionType === ActionType.VIRTUAL_DETECTOR) {
84
+ const child =
85
+ virtualViewTag != null
86
+ ? refs.virtualChildren.get(virtualViewTag)
87
+ : undefined;
88
+ if (child == null || child.viewRef.current == null) {
89
+ return;
90
+ }
91
+
92
+ if (!refs.attachedHandlers.has(tag)) {
93
+ RNGestureHandlerModule.attachGestureHandler(
94
+ tag,
95
+ child.viewRef.current,
96
+ actionType,
97
+ refs.propsRef
98
+ );
99
+ refs.attachedHandlers.add(tag);
100
+ }
101
+
102
+ RNGestureHandlerModule.updateGestureHandlerConfig(tag, {
103
+ userSelect: child.userSelect,
104
+ touchAction: child.touchAction,
105
+ enableContextMenu: child.enableContextMenu,
106
+ });
107
+ return;
108
+ }
109
+
110
+ if (refs.viewRef.current == null) {
111
+ return;
112
+ }
113
+
114
+ if (!refs.attachedHandlers.has(tag)) {
115
+ RNGestureHandlerModule.attachGestureHandler(
116
+ tag,
117
+ refs.viewRef.current,
118
+ actionType,
119
+ refs.propsRef
120
+ );
121
+ refs.attachedHandlers.add(tag);
122
+ }
123
+
124
+ RNGestureHandlerModule.updateGestureHandlerConfig(tag, {
125
+ userSelect: refs.propsRef.current.userSelect,
126
+ touchAction: refs.propsRef.current.touchAction,
127
+ enableContextMenu: refs.propsRef.current.enableContextMenu,
128
+ });
129
+ }
130
+
131
+ function tryAttachNativeHandlersToChildView(refs: DetectorRefs) {
132
+ if (refs.nativeHandlers.size === 0) {
133
+ return;
134
+ }
135
+
136
+ const view = refs.viewRef.current;
137
+ if (view == null) {
138
+ return;
139
+ }
140
+
141
+ if (view.childElementCount > 1) {
142
+ throw new Error(
143
+ tagMessage(
144
+ 'Cannot have more than one child view when native gesture handlers are attached to the detector'
145
+ )
146
+ );
147
+ }
148
+
149
+ const target = view.firstElementChild;
150
+ if (target == null) {
151
+ return;
152
+ }
153
+
154
+ for (const tag of refs.nativeHandlers) {
155
+ // A tag may be in `nativeHandlers` from an earlier ready callback but the underlying
156
+ // handler may have been dropped since. Skip — a re-registration fires the observation again.
157
+ if (!NodeManager.hasHandler(tag)) {
158
+ continue;
159
+ }
160
+ if (refs.attachedHandlers.has(tag)) {
161
+ continue;
162
+ }
163
+ RNGestureHandlerModule.attachGestureHandler(
164
+ tag,
165
+ target,
166
+ ActionType.NATIVE_DETECTOR,
167
+ refs.propsRef
168
+ );
169
+ refs.attachedHandlers.add(tag);
170
+ RNGestureHandlerModule.updateGestureHandlerConfig(tag, {
171
+ userSelect: refs.propsRef.current.userSelect,
172
+ touchAction: refs.propsRef.current.touchAction,
173
+ enableContextMenu: refs.propsRef.current.enableContextMenu,
174
+ });
175
+ }
176
+ }
177
+
178
+ // Reconcile `subscribedSet` against `currentTags`: observe new tags, cancel observation and
179
+ // detach for tags no longer present. The ready callback set up here is responsible for actually
180
+ // binding the handler once it exists.
181
+ function syncSubscriptions(
182
+ refs: DetectorRefs,
183
+ currentTags: Iterable<number>,
184
+ subscribedSet: Set<number>,
185
+ actionType: ActionType,
186
+ virtualViewTag?: number
187
+ ) {
188
+ const toUnsubscribe = new Set(subscribedSet);
189
+ for (const tag of currentTags) {
190
+ toUnsubscribe.delete(tag);
191
+ if (subscribedSet.has(tag)) {
192
+ continue;
193
+ }
194
+ NodeManager.observeHandler(tag, refs.owner, () => {
195
+ attachReadyHandler(refs, tag, actionType, virtualViewTag);
196
+ });
197
+ subscribedSet.add(tag);
198
+ }
199
+
200
+ for (const tag of toUnsubscribe) {
201
+ NodeManager.cancelObservation(tag, refs.owner);
202
+ if (refs.attachedHandlers.has(tag)) {
203
+ RNGestureHandlerModule.detachGestureHandler(tag);
204
+ refs.attachedHandlers.delete(tag);
205
+ }
206
+ subscribedSet.delete(tag);
207
+ refs.nativeHandlers.delete(tag);
208
+ }
209
+ }
210
+
211
+ function teardown(refs: DetectorRefs) {
212
+ NodeManager.cancelAllObservationsForOwner(refs.owner);
213
+ for (const tag of refs.attachedHandlers) {
214
+ RNGestureHandlerModule.detachGestureHandler(tag);
215
+ }
216
+ refs.attachedHandlers.clear();
217
+ refs.subscribedHandlers.clear();
218
+ refs.nativeHandlers.clear();
219
+ refs.subscribedVirtualHandlers.clear();
220
+ refs.virtualChildren.clear();
221
+ }
35
222
 
36
223
  const HostGestureDetector = (props: GestureHandlerDetectorProps) => {
37
224
  const { handlerTags, children } = props;
@@ -40,72 +227,28 @@ const HostGestureDetector = (props: GestureHandlerDetectorProps) => {
40
227
 
41
228
  const viewRef = useRef<Element>(null);
42
229
  const propsRef = useRef<GestureHandlerDetectorProps>(props);
43
- const attachedHandlers = useRef<Set<number>>(new Set<number>());
44
- const attachedNativeHandlers = useRef<Set<number>>(new Set<number>());
45
- const attachedVirtualHandlers = useRef<Map<number, Set<number>>>(new Map());
46
-
47
- const detachHandlers = (
48
- currentHandlerTags: Set<number>,
49
- attachedHandlerTags: Set<number>
50
- ) => {
51
- const oldHandlerTags = attachedHandlerTags.difference(currentHandlerTags);
52
- oldHandlerTags.forEach((tag) => {
53
- RNGestureHandlerModule.detachGestureHandler(tag);
54
- attachedHandlerTags.delete(tag);
55
- });
56
- };
57
-
58
- const attachHandlers = (
59
- viewRef: RefObject<Element | null>,
60
- propsRef: RefObject<GestureHandlerDetectorProps>,
61
- currentHandlerTags: Set<number>,
62
- attachedHandlerTags: Set<number>,
63
- actionType: ActionType
64
- ) => {
65
- const newHandlerTags = currentHandlerTags.difference(attachedHandlerTags);
66
-
67
- newHandlerTags.forEach((tag) => {
68
- if (
69
- RNGestureHandlerModule.getGestureHandlerNode(
70
- tag
71
- ).shouldAttachGestureToChildView() &&
72
- actionType === ActionType.NATIVE_DETECTOR
73
- ) {
74
- if (viewRef.current!.childElementCount > 1) {
75
- throw new Error(
76
- tagMessage(
77
- 'Cannot have more than one child view when native gesture handlers are attached to the detector'
78
- )
79
- );
80
- }
81
230
 
82
- RNGestureHandlerModule.attachGestureHandler(
83
- tag,
84
- viewRef.current!.firstChild,
85
- actionType,
86
- propsRef
87
- );
88
- attachedNativeHandlers.current.add(tag);
89
- } else {
90
- RNGestureHandlerModule.attachGestureHandler(
91
- tag,
92
- viewRef.current,
93
- actionType,
94
- propsRef
95
- );
96
- }
97
- attachedHandlerTags.add(tag);
98
-
99
- RNGestureHandlerModule.updateGestureHandlerConfig(tag, {
100
- userSelect: props.userSelect,
101
- touchAction: props.touchAction,
102
- enableContextMenu: props.enableContextMenu,
103
- });
104
- });
105
- };
231
+ // Stable per-instance state
232
+ const refsRef = useRef<DetectorRefs | null>(null);
233
+ if (refsRef.current === null) {
234
+ refsRef.current = {
235
+ owner: {},
236
+ viewRef,
237
+ propsRef,
238
+ subscribedHandlers: new Set<number>(),
239
+ attachedHandlers: new Set<number>(),
240
+ nativeHandlers: new Set<number>(),
241
+ subscribedVirtualHandlers: new Map<number, Set<number>>(),
242
+ virtualChildren: new Map<number, VirtualChildrenWeb>(),
243
+ };
244
+ }
245
+ const refs = refsRef.current;
106
246
 
107
247
  useNativeGestureRole(viewRef, children);
108
248
 
249
+ // Keep propsRef in sync and re-apply detector-level DOM props to top-level attached handlers
250
+ // when they change. Virtual children get their own (potentially different) DOM props applied
251
+ // in the virtualChildren effect below, so we only touch top-level subscribers here.
109
252
  useEffect(() => {
110
253
  const shouldUpdateDOMProps =
111
254
  propsRef.current.userSelect !== props.userSelect ||
@@ -115,7 +258,17 @@ const HostGestureDetector = (props: GestureHandlerDetectorProps) => {
115
258
  propsRef.current = props;
116
259
 
117
260
  if (shouldUpdateDOMProps) {
118
- for (const tag of attachedHandlers.current) {
261
+ // attachedHandlers subscribedHandlers subscribedVirtualHandlers, we want to ignore the
262
+ // handlers attached by the virtual detectors not to overwrite their DOM props.
263
+ const claimedByVirtual = Array.from(
264
+ refs.subscribedVirtualHandlers.values()
265
+ ).reduce((acc, current) => acc.union(current), new Set<number>());
266
+
267
+ const handlersToUpdate = refs.subscribedHandlers
268
+ .intersection(refs.attachedHandlers)
269
+ .difference(claimedByVirtual);
270
+
271
+ for (const tag of handlersToUpdate) {
119
272
  RNGestureHandlerModule.updateGestureHandlerConfig(tag, {
120
273
  userSelect: props.userSelect,
121
274
  touchAction: props.touchAction,
@@ -123,38 +276,38 @@ const HostGestureDetector = (props: GestureHandlerDetectorProps) => {
123
276
  });
124
277
  }
125
278
  }
126
- }, [props]);
279
+ }, [props, refs]);
127
280
 
128
281
  useEffect(() => {
129
- detachHandlers(handlerTagsSet, attachedHandlers.current);
130
-
131
- attachHandlers(
132
- viewRef,
133
- propsRef,
282
+ syncSubscriptions(
283
+ refs,
134
284
  handlerTagsSet,
135
- attachedHandlers.current,
285
+ refs.subscribedHandlers,
136
286
  ActionType.NATIVE_DETECTOR
137
287
  );
138
- return () => {
139
- detachHandlers(EMPTY_HANDLERS, attachedHandlers.current);
140
- attachedVirtualHandlers?.current.forEach((childHandlerTags) => {
141
- detachHandlers(EMPTY_HANDLERS, childHandlerTags);
142
- });
143
- };
144
- }, [handlerTagsSet, viewRef]);
288
+ }, [handlerTagsSet, refs]);
145
289
 
146
290
  useEffect(() => {
147
- const virtualChildrenToDetach: Set<number> = new Set(
148
- attachedVirtualHandlers.current.keys()
149
- );
291
+ // Refresh the snapshot used by the ready callback so re-fires read current child props.
292
+ refs.virtualChildren.clear();
293
+ props.virtualChildren?.forEach((child) => {
294
+ refs.virtualChildren.set(child.viewTag, child);
295
+ });
150
296
 
297
+ const virtualChildrenToDetach = new Set(
298
+ refs.subscribedVirtualHandlers.keys()
299
+ );
151
300
  props.virtualChildren?.forEach((child) => {
152
301
  virtualChildrenToDetach.delete(child.viewTag);
153
302
  });
154
303
 
155
- virtualChildrenToDetach.forEach((tag) => {
156
- detachHandlers(EMPTY_HANDLERS, attachedVirtualHandlers.current.get(tag)!);
157
- });
304
+ for (const viewTag of virtualChildrenToDetach) {
305
+ const tags = refs.subscribedVirtualHandlers.get(viewTag);
306
+ if (tags != null) {
307
+ syncSubscriptions(refs, [], tags, ActionType.VIRTUAL_DETECTOR, viewTag);
308
+ }
309
+ refs.subscribedVirtualHandlers.delete(viewTag);
310
+ }
158
311
 
159
312
  props.virtualChildren?.forEach((child) => {
160
313
  if (child.viewRef.current == null) {
@@ -162,33 +315,37 @@ const HostGestureDetector = (props: GestureHandlerDetectorProps) => {
162
315
  // switches its component based on whether animated/reanimated events should run.
163
316
  return;
164
317
  }
165
- if (!attachedVirtualHandlers.current.has(child.viewTag)) {
166
- attachedVirtualHandlers.current.set(child.viewTag, new Set());
167
- }
168
-
169
- const currentHandlerTags = new Set(child.handlerTags);
170
- detachHandlers(
171
- currentHandlerTags,
172
- attachedVirtualHandlers.current.get(child.viewTag)!
173
- );
174
318
 
175
- attachHandlers(
176
- child.viewRef,
177
- propsRef,
178
- currentHandlerTags,
179
- attachedVirtualHandlers.current.get(child.viewTag)!,
180
- ActionType.VIRTUAL_DETECTOR
319
+ let subs = refs.subscribedVirtualHandlers.get(child.viewTag);
320
+ if (subs == null) {
321
+ subs = new Set();
322
+ refs.subscribedVirtualHandlers.set(child.viewTag, subs);
323
+ }
324
+ syncSubscriptions(
325
+ refs,
326
+ child.handlerTags,
327
+ subs,
328
+ ActionType.VIRTUAL_DETECTOR,
329
+ child.viewTag
181
330
  );
182
-
183
- currentHandlerTags.forEach((tag) => {
184
- RNGestureHandlerModule.updateGestureHandlerConfig(tag, {
185
- userSelect: child.userSelect,
186
- touchAction: child.touchAction,
187
- enableContextMenu: child.enableContextMenu,
188
- });
189
- });
331
+ // Re-apply per-child DOM props on every run. Already-attached tags need this when only
332
+ // the child's props change; tags attached via a sync-fired observer already had it
333
+ // applied in `attachReadyHandler`, so this is a no-op for them.
334
+ for (const tag of subs) {
335
+ if (refs.attachedHandlers.has(tag)) {
336
+ RNGestureHandlerModule.updateGestureHandlerConfig(tag, {
337
+ userSelect: child.userSelect,
338
+ touchAction: child.touchAction,
339
+ enableContextMenu: child.enableContextMenu,
340
+ });
341
+ }
342
+ }
190
343
  });
191
- }, [props.virtualChildren]);
344
+ }, [props.virtualChildren, refs]);
345
+
346
+ useEffect(() => {
347
+ return () => teardown(refs);
348
+ }, [refs]);
192
349
 
193
350
  return (
194
351
  <View style={{ display: 'contents' }} ref={viewRef as Ref<View>}>
@@ -6,7 +6,8 @@ import type { NativeDetectorProps } from './common';
6
6
  import { AnimatedNativeDetector, nativeDetectorStyles } from './common';
7
7
  import HostGestureDetector from './HostGestureDetector';
8
8
  import { ReanimatedNativeDetector } from './ReanimatedNativeDetector';
9
- import { configureRelations, ensureNativeDetectorComponent } from './utils';
9
+ import { useGestureRelationsUpdater } from './useGestureRelationsUpdater';
10
+ import { ensureNativeDetectorComponent } from './utils';
10
11
 
11
12
  export function NativeDetector<
12
13
  TConfig,
@@ -26,7 +27,7 @@ export function NativeDetector<
26
27
  : HostGestureDetector;
27
28
 
28
29
  ensureNativeDetectorComponent(NativeDetectorComponent);
29
- configureRelations(gesture);
30
+ useGestureRelationsUpdater(gesture);
30
31
 
31
32
  const handlerTags = useMemo(() => {
32
33
  return isComposedGesture(gesture)
@@ -18,7 +18,8 @@ import { AnimatedNativeDetector, nativeDetectorStyles } from '../common';
18
18
  import HostGestureDetector from '../HostGestureDetector';
19
19
  import { ReanimatedNativeDetector } from '../ReanimatedNativeDetector';
20
20
  import { useEnsureGestureHandlerRootView } from '../useEnsureGestureHandlerRootView';
21
- import { configureRelations, ensureNativeDetectorComponent } from '../utils';
21
+ import { useGestureRelationsUpdater } from '../useGestureRelationsUpdater';
22
+ import { ensureNativeDetectorComponent } from '../utils';
22
23
  import type { InterceptingDetectorContextValue } from './useInterceptingDetectorContext';
23
24
  import {
24
25
  InterceptingDetectorContext,
@@ -221,9 +222,7 @@ export function InterceptingGestureDetector<
221
222
 
222
223
  ensureNativeDetectorComponent(NativeDetectorComponent);
223
224
 
224
- if (gesture) {
225
- configureRelations(gesture);
226
- }
225
+ useGestureRelationsUpdater(gesture);
227
226
 
228
227
  const handlerTags = useMemo(() => {
229
228
  if (gesture) {
@@ -6,8 +6,8 @@ import { tagMessage } from '../../../utils';
6
6
  import { isComposedGesture } from '../../hooks/utils/relationUtils';
7
7
  import type { DetectorCallbacks, VirtualChild } from '../../types';
8
8
  import type { VirtualDetectorProps } from '../common';
9
+ import { useGestureRelationsUpdater } from '../useGestureRelationsUpdater';
9
10
  import { useNativeGestureRole } from '../useNativeGestureRole';
10
- import { configureRelations } from '../utils';
11
11
  import {
12
12
  InterceptingDetectorMode,
13
13
  useInterceptingDetectorContext,
@@ -104,7 +104,7 @@ export function VirtualDetector<
104
104
  setMode,
105
105
  ]);
106
106
 
107
- configureRelations(props.gesture);
107
+ useGestureRelationsUpdater(props.gesture);
108
108
 
109
109
  return <Wrap ref={handleRef}>{props.children}</Wrap>;
110
110
  }
@@ -0,0 +1,30 @@
1
+ import { useEffect, useMemo } from 'react';
2
+
3
+ import { NativeProxy } from '../NativeProxy';
4
+ import type { Gesture } from '../types';
5
+ import { configureRelations } from './utils';
6
+
7
+ export function useGestureRelationsUpdater<TConfig, THandlerData>(
8
+ gesture?: Gesture<TConfig, THandlerData>
9
+ ) {
10
+ const relations = useMemo(
11
+ () => (gesture ? configureRelations(gesture) : null),
12
+ [gesture]
13
+ );
14
+
15
+ useEffect(() => {
16
+ if (!relations) {
17
+ return;
18
+ }
19
+
20
+ // React runs effects bottom-up, we need to ensure that relations are applied after all effects
21
+ // in the tree have run, so we defer to the next frame.
22
+ const frame = requestAnimationFrame(() => {
23
+ relations.forEach((rel, handlerTag) => {
24
+ NativeProxy.configureRelations(handlerTag, rel);
25
+ });
26
+ });
27
+
28
+ return () => cancelAnimationFrame(frame);
29
+ }, [relations]);
30
+ }
@@ -9,25 +9,26 @@ import {
9
9
  isComposedGesture,
10
10
  prepareRelations,
11
11
  } from '../hooks/utils/relationUtils';
12
- import { NativeProxy } from '../NativeProxy';
13
- import type { Gesture } from '../types';
12
+ import type { Gesture, GestureRelations } from '../types';
14
13
  import { ComposedGestureName } from '../types';
15
14
 
16
15
  // The tree consists of ComposedGestures and NativeGestures. NativeGestures are always leaf nodes.
17
16
  export const traverseAndConfigureRelations = (
18
17
  node: Gesture,
18
+ relationsByTag: Map<number, GestureRelations>,
19
19
  simultaneousHandlers: Set<number>,
20
20
  waitFor: number[] = []
21
21
  ) => {
22
22
  // If we are in the leaf node, we want to fill gesture relations arrays with current
23
- // waitFor and simultaneousHandlers. We also want to configure relations on the native side.
23
+ // waitFor and simultaneousHandlers. We also want to record the resulting relations for
24
+ // this handler so the caller can push them to the native side.
24
25
  if (!isComposedGesture(node)) {
25
26
  node.gestureRelations = prepareRelations(node.config, node.handlerTag);
26
27
 
27
28
  node.gestureRelations.simultaneousHandlers.push(...simultaneousHandlers);
28
29
  node.gestureRelations.waitFor.push(...waitFor);
29
30
 
30
- NativeProxy.configureRelations(node.handlerTag, {
31
+ relationsByTag.set(node.handlerTag, {
31
32
  waitFor: node.gestureRelations.waitFor,
32
33
  simultaneousHandlers: node.gestureRelations.simultaneousHandlers,
33
34
  blocksHandlers: node.gestureRelations.blocksHandlers,
@@ -73,7 +74,12 @@ export const traverseAndConfigureRelations = (
73
74
  const length = waitFor.length;
74
75
 
75
76
  // We traverse the child, passing the current `waitFor` and `simultaneousHandlers`.
76
- traverseAndConfigureRelations(child, simultaneousHandlers, waitFor);
77
+ traverseAndConfigureRelations(
78
+ child,
79
+ relationsByTag,
80
+ simultaneousHandlers,
81
+ waitFor
82
+ );
77
83
 
78
84
  // After traversing the child, we need to update `waitFor` and `simultaneousHandlers`
79
85
 
@@ -122,7 +128,12 @@ export const traverseAndConfigureRelations = (
122
128
  // We don't want to mark gesture as simultaneous with itself, so we remove its tag from the set.
123
129
  const hasRemovedTag = simultaneousHandlers.delete(child.handlerTag);
124
130
 
125
- traverseAndConfigureRelations(child, simultaneousHandlers, waitFor);
131
+ traverseAndConfigureRelations(
132
+ child,
133
+ relationsByTag,
134
+ simultaneousHandlers,
135
+ waitFor
136
+ );
126
137
 
127
138
  if (hasRemovedTag) {
128
139
  simultaneousHandlers.add(child.handlerTag);
@@ -138,7 +149,9 @@ export const traverseAndConfigureRelations = (
138
149
 
139
150
  export function configureRelations<TConfig, THandlerData>(
140
151
  gesture: Gesture<TConfig, THandlerData>
141
- ) {
152
+ ): Map<number, GestureRelations> {
153
+ const relationsByTag = new Map<number, GestureRelations>();
154
+
142
155
  if (isComposedGesture(gesture)) {
143
156
  const simultaneousHandlers = new Set<number>(
144
157
  gesture.externalSimultaneousHandlers
@@ -151,13 +164,16 @@ export function configureRelations<TConfig, THandlerData>(
151
164
  );
152
165
  }
153
166
 
154
- traverseAndConfigureRelations(gesture, simultaneousHandlers);
155
- } else {
156
- NativeProxy.configureRelations(
157
- gesture.handlerTag,
158
- gesture.gestureRelations
167
+ traverseAndConfigureRelations(
168
+ gesture,
169
+ relationsByTag,
170
+ simultaneousHandlers
159
171
  );
172
+ } else {
173
+ relationsByTag.set(gesture.handlerTag, gesture.gestureRelations);
160
174
  }
175
+
176
+ return relationsByTag;
161
177
  }
162
178
 
163
179
  export function ensureNativeDetectorComponent(
@@ -1,4 +1,4 @@
1
- import { useEffect, useMemo, useRef } from 'react';
1
+ import { useEffect, useMemo } from 'react';
2
2
 
3
3
  import { getNextHandlerTag } from '../../handlers/getNextHandlerTag';
4
4
  import {
@@ -61,15 +61,6 @@ export function useGesture<
61
61
  [handlerTag, config.simultaneousWith, config.requireToFail, config.block]
62
62
  );
63
63
 
64
- const currentGestureRef = useRef({ type: '', handlerTag: -1 });
65
- if (
66
- currentGestureRef.current.handlerTag !== handlerTag ||
67
- currentGestureRef.current.type !== (type as string)
68
- ) {
69
- currentGestureRef.current = { type, handlerTag };
70
- NativeProxy.createGestureHandler(type, handlerTag, {});
71
- }
72
-
73
64
  const gesture = useMemo(
74
65
  () => ({
75
66
  handlerTag,
@@ -94,11 +85,10 @@ export function useGesture<
94
85
  );
95
86
 
96
87
  useEffect(() => {
97
- return () => {
98
- if (currentGestureRef.current.handlerTag === handlerTag) {
99
- currentGestureRef.current = { type: '', handlerTag: -1 };
100
- }
88
+ NativeProxy.createGestureHandler(type, handlerTag, {});
89
+ scheduleFlushOperations();
101
90
 
91
+ return () => {
102
92
  NativeProxy.dropGestureHandler(handlerTag);
103
93
  scheduleFlushOperations();
104
94
  };
@@ -87,9 +87,8 @@ export function prepareConfigForNativeSide<
87
87
  for (const [key, value] of Object.entries(config)) {
88
88
  // @ts-ignore That's the point, we want to see if key exists in the whitelists
89
89
  if (allowedNativeProps.has(key) || handlerPropsWhiteList.has(key)) {
90
- Object.assign(filteredConfig, {
91
- [key]: Reanimated?.isSharedValue(value) ? value.value : value,
92
- });
90
+ (filteredConfig as Record<string, unknown>)[key] =
91
+ Reanimated?.isSharedValue(value) ? value.value : value;
93
92
  } else if (PropsToFilter.has(key)) {
94
93
  continue;
95
94
  } else {