react-native-gesture-handler 2.4.1 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/lib/commonjs/components/DrawerLayout.js +38 -11
  2. package/lib/commonjs/components/DrawerLayout.js.map +1 -1
  3. package/lib/commonjs/handlers/ForceTouchGestureHandler.js +2 -1
  4. package/lib/commonjs/handlers/ForceTouchGestureHandler.js.map +1 -1
  5. package/lib/commonjs/handlers/createHandler.js +19 -8
  6. package/lib/commonjs/handlers/createHandler.js.map +1 -1
  7. package/lib/commonjs/handlers/gestureHandlerCommon.js.map +1 -1
  8. package/lib/commonjs/handlers/gestures/GestureDetector.js +83 -63
  9. package/lib/commonjs/handlers/gestures/GestureDetector.js.map +1 -1
  10. package/lib/commonjs/handlers/gestures/gesture.js +13 -2
  11. package/lib/commonjs/handlers/gestures/gesture.js.map +1 -1
  12. package/lib/commonjs/web/utils.js.map +1 -1
  13. package/lib/module/components/DrawerLayout.js +38 -11
  14. package/lib/module/components/DrawerLayout.js.map +1 -1
  15. package/lib/module/handlers/ForceTouchGestureHandler.js +1 -1
  16. package/lib/module/handlers/ForceTouchGestureHandler.js.map +1 -1
  17. package/lib/module/handlers/createHandler.js +20 -8
  18. package/lib/module/handlers/createHandler.js.map +1 -1
  19. package/lib/module/handlers/gestureHandlerCommon.js.map +1 -1
  20. package/lib/module/handlers/gestures/GestureDetector.js +83 -63
  21. package/lib/module/handlers/gestures/GestureDetector.js.map +1 -1
  22. package/lib/module/handlers/gestures/gesture.js +13 -2
  23. package/lib/module/handlers/gestures/gesture.js.map +1 -1
  24. package/lib/module/web/utils.js.map +1 -1
  25. package/lib/typescript/components/DrawerLayout.d.ts +3 -0
  26. package/lib/typescript/handlers/ForceTouchGestureHandler.d.ts +2 -2
  27. package/lib/typescript/handlers/gestureHandlerCommon.d.ts +1 -0
  28. package/lib/typescript/handlers/gestures/GestureDetector.d.ts +2 -1
  29. package/lib/typescript/handlers/gestures/gesture.d.ts +3 -0
  30. package/package.json +1 -1
  31. package/src/components/DrawerLayout.tsx +34 -10
  32. package/src/handlers/ForceTouchGestureHandler.ts +3 -2
  33. package/src/handlers/createHandler.ts +25 -11
  34. package/src/handlers/gestureHandlerCommon.ts +2 -0
  35. package/src/handlers/gestures/GestureDetector.tsx +107 -81
  36. package/src/handlers/gestures/gesture.ts +16 -0
  37. package/src/web/utils.ts +1 -1
@@ -148,6 +148,11 @@ export interface DrawerLayoutProps {
148
148
  onDrawerSlide?: (position: number) => void;
149
149
 
150
150
  onGestureRef?: (ref: PanGestureHandler) => void;
151
+
152
+ // implicit `children` prop has been removed in @types/react^18.0.0
153
+ children?:
154
+ | React.ReactNode
155
+ | ((openValue?: Animated.AnimatedInterpolation) => React.ReactNode);
151
156
  }
152
157
 
153
158
  export type DrawerLayoutState = {
@@ -155,6 +160,8 @@ export type DrawerLayoutState = {
155
160
  touchX: Animated.Value;
156
161
  drawerTranslation: Animated.Value;
157
162
  containerWidth: number;
163
+ drawerState: DrawerState;
164
+ drawerOpened: boolean;
158
165
  };
159
166
 
160
167
  export type DrawerMovementOption = {
@@ -189,6 +196,8 @@ export default class DrawerLayout extends Component<
189
196
  touchX,
190
197
  drawerTranslation,
191
198
  containerWidth: 0,
199
+ drawerState: IDLE,
200
+ drawerOpened: false,
192
201
  };
193
202
 
194
203
  this.updateAnimatedEvent(props, this.state);
@@ -349,6 +358,7 @@ export default class DrawerLayout extends Component<
349
358
  this.handleRelease({ nativeEvent });
350
359
  } else if (nativeEvent.state === State.ACTIVE) {
351
360
  this.emitStateChanged(DRAGGING, false);
361
+ this.setState({ drawerState: DRAGGING });
352
362
  if (this.props.keyboardDismissMode === 'on-drag') {
353
363
  Keyboard.dismiss();
354
364
  }
@@ -464,6 +474,7 @@ export default class DrawerLayout extends Component<
464
474
  const willShow = toValue !== 0;
465
475
  this.updateShowing(willShow);
466
476
  this.emitStateChanged(SETTLING, willShow);
477
+ this.setState({ drawerState: SETTLING });
467
478
  if (this.props.hideStatusBar) {
468
479
  StatusBar.setHidden(willShow, this.props.statusBarAnimation || 'slide');
469
480
  }
@@ -476,6 +487,12 @@ export default class DrawerLayout extends Component<
476
487
  }).start(({ finished }) => {
477
488
  if (finished) {
478
489
  this.emitStateChanged(IDLE, willShow);
490
+ this.setState({ drawerOpened: willShow });
491
+ if (this.state.drawerState !== DRAGGING) {
492
+ // it's possilbe that user started drag while the drawer
493
+ // was settling, don't override state in this case
494
+ this.setState({ drawerState: IDLE });
495
+ }
479
496
  if (willShow) {
480
497
  this.props.onDrawerOpen?.();
481
498
  } else {
@@ -516,11 +533,14 @@ export default class DrawerLayout extends Component<
516
533
  private renderOverlay = () => {
517
534
  /* Overlay styles */
518
535
  invariant(this.openValue, 'should be set');
519
- const overlayOpacity = this.openValue.interpolate({
520
- inputRange: [0, 1],
521
- outputRange: [0, 1],
522
- extrapolate: 'clamp',
523
- });
536
+ let overlayOpacity;
537
+
538
+ if (this.state.drawerState !== IDLE) {
539
+ overlayOpacity = this.openValue;
540
+ } else {
541
+ overlayOpacity = this.state.drawerOpened ? 1 : 0;
542
+ }
543
+
524
544
  const dynamicOverlayStyles = {
525
545
  opacity: overlayOpacity,
526
546
  backgroundColor: this.props.overlayColor,
@@ -579,11 +599,15 @@ export default class DrawerLayout extends Component<
579
599
  let drawerTranslateX: number | Animated.AnimatedInterpolation = 0;
580
600
  if (drawerSlide) {
581
601
  const closedDrawerOffset = fromLeft ? -drawerWidth! : drawerWidth!;
582
- drawerTranslateX = openValue.interpolate({
583
- inputRange: [0, 1],
584
- outputRange: [closedDrawerOffset, 0],
585
- extrapolate: 'clamp',
586
- });
602
+ if (this.state.drawerState !== IDLE) {
603
+ drawerTranslateX = openValue.interpolate({
604
+ inputRange: [0, 1],
605
+ outputRange: [closedDrawerOffset, 0],
606
+ extrapolate: 'clamp',
607
+ });
608
+ } else {
609
+ drawerTranslateX = this.state.drawerOpened ? 0 : closedDrawerOffset;
610
+ }
587
611
  }
588
612
  const drawerStyles: {
589
613
  transform: { translateX: number | Animated.AnimatedInterpolation }[];
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { PropsWithChildren } from 'react';
2
2
  import { tagMessage } from '../utils';
3
3
  import PlatformConstants from '../PlatformConstants';
4
4
  import createHandler from './createHandler';
@@ -13,7 +13,8 @@ export const forceTouchGestureHandlerProps = [
13
13
  'feedbackOnActivation',
14
14
  ] as const;
15
15
 
16
- class ForceTouchFallback extends React.Component {
16
+ // implicit `children` prop has been removed in @types/react^18.0.0
17
+ class ForceTouchFallback extends React.Component<PropsWithChildren<unknown>> {
17
18
  static forceTouchAvailable = false;
18
19
  componentDidMount() {
19
20
  console.warn(
@@ -148,6 +148,8 @@ type InternalEventHandlers = {
148
148
  onGestureHandlerStateChange?: (event: any) => void;
149
149
  };
150
150
 
151
+ const UNRESOLVED_REFS_RETRY_LIMIT = 1;
152
+
151
153
  // TODO(TS) - make sure that BaseGestureHandlerProps doesn't need other generic parameter to work with custom properties.
152
154
  export default function createHandler<
153
155
  T extends BaseGestureHandlerProps<U>,
@@ -198,7 +200,7 @@ export default function createHandler<
198
200
  'toggleElementInspector',
199
201
  () => {
200
202
  this.setState((_) => ({ allowTouches }));
201
- this.update();
203
+ this.update(UNRESOLVED_REFS_RETRY_LIMIT);
202
204
  }
203
205
  );
204
206
  }
@@ -211,7 +213,7 @@ export default function createHandler<
211
213
  // be resolved by then.
212
214
  this.updateEnqueued = setImmediate(() => {
213
215
  this.updateEnqueued = null;
214
- this.update();
216
+ this.update(UNRESOLVED_REFS_RETRY_LIMIT);
215
217
  });
216
218
  }
217
219
 
@@ -231,7 +233,7 @@ export default function createHandler<
231
233
  if (this.viewTag !== viewTag) {
232
234
  this.attachGestureHandler(viewTag as number); // TODO(TS) - check interaction between _viewTag & findNodeHandle
233
235
  }
234
- this.update();
236
+ this.update(UNRESOLVED_REFS_RETRY_LIMIT);
235
237
  }
236
238
 
237
239
  componentWillUnmount() {
@@ -361,14 +363,26 @@ export default function createHandler<
361
363
  scheduleFlushOperations();
362
364
  };
363
365
 
364
- private update() {
365
- const newConfig = filterConfig(
366
- transformProps ? transformProps(this.props) : this.props,
367
- [...allowedProps, ...customNativeProps],
368
- config
369
- );
370
- if (!deepEqual(this.config, newConfig)) {
371
- this.updateGestureHandler(newConfig);
366
+ private update(remainingTries: number) {
367
+ const props: HandlerProps<U> = this.props;
368
+
369
+ // When ref is set via a function i.e. `ref={(r) => refObject.current = r}` instead of
370
+ // `ref={refObject}` it's possible that it won't be resolved in time. Seems like trying
371
+ // again is easy enough fix.
372
+ if (hasUnresolvedRefs(props) && remainingTries > 0) {
373
+ this.updateEnqueued = setImmediate(() => {
374
+ this.updateEnqueued = null;
375
+ this.update(remainingTries - 1);
376
+ });
377
+ } else {
378
+ const newConfig = filterConfig(
379
+ transformProps ? transformProps(this.props) : this.props,
380
+ [...allowedProps, ...customNativeProps],
381
+ config
382
+ );
383
+ if (!deepEqual(this.config, newConfig)) {
384
+ this.updateGestureHandler(newConfig);
385
+ }
372
386
  }
373
387
  }
374
388
 
@@ -126,6 +126,8 @@ export type BaseGestureHandlerProps<
126
126
  onHandlerStateChange?: (
127
127
  event: HandlerStateChangeEvent<ExtraEventPayloadT>
128
128
  ) => void;
129
+ // implicit `children` prop has been removed in @types/react^18.0.0
130
+ children?: React.ReactNode;
129
131
  };
130
132
 
131
133
  function isConfigParam(param: unknown, name: string) {
@@ -268,11 +268,34 @@ function updateHandlers(
268
268
  }
269
269
 
270
270
  if (preparedGesture.animatedHandlers) {
271
- preparedGesture.animatedHandlers.value = (preparedGesture.config
271
+ const previousHandlersValue =
272
+ preparedGesture.animatedHandlers.value ?? [];
273
+ const newHandlersValue = (preparedGesture.config
272
274
  .filter((g) => g.shouldUseReanimated) // ignore gestures that shouldn't run on UI
273
275
  .map((g) => g.handlers) as unknown) as HandlerCallbacks<
274
276
  Record<string, unknown>
275
277
  >[];
278
+
279
+ // if amount of gesture configs changes, we need to update the callbacks in shared value
280
+ let shouldUpdateSharedValue =
281
+ previousHandlersValue.length !== newHandlersValue.length;
282
+
283
+ if (!shouldUpdateSharedValue) {
284
+ // if the amount is the same, we need to check if any of the configs inside has changed
285
+ for (let i = 0; i < newHandlersValue.length; i++) {
286
+ if (
287
+ // we can use the `gestureId` prop as it's unique for every config instance
288
+ newHandlersValue[i].gestureId !== previousHandlersValue[i].gestureId
289
+ ) {
290
+ shouldUpdateSharedValue = true;
291
+ break;
292
+ }
293
+ }
294
+ }
295
+
296
+ if (shouldUpdateSharedValue) {
297
+ preparedGesture.animatedHandlers.value = newHandlersValue;
298
+ }
276
299
  }
277
300
 
278
301
  scheduleFlushOperations();
@@ -299,90 +322,90 @@ function needsToReattach(
299
322
  return false;
300
323
  }
301
324
 
302
- function useAnimatedGesture(
303
- preparedGesture: GestureConfigReference,
304
- needsRebuild: boolean
305
- ) {
306
- if (!Reanimated) {
307
- return;
308
- }
325
+ function isStateChangeEvent(
326
+ event: GestureUpdateEvent | GestureStateChangeEvent | GestureTouchEvent
327
+ ): event is GestureStateChangeEvent {
328
+ 'worklet';
329
+ // @ts-ignore Yes, the oldState prop is missing on GestureTouchEvent, that's the point
330
+ return event.oldState != null;
331
+ }
309
332
 
310
- function isStateChangeEvent(
311
- event: GestureUpdateEvent | GestureStateChangeEvent | GestureTouchEvent
312
- ): event is GestureStateChangeEvent {
313
- 'worklet';
314
- // @ts-ignore Yes, the oldState prop is missing on GestureTouchEvent, that's the point
315
- return event.oldState != null;
316
- }
333
+ function isTouchEvent(
334
+ event: GestureUpdateEvent | GestureStateChangeEvent | GestureTouchEvent
335
+ ): event is GestureTouchEvent {
336
+ 'worklet';
337
+ return event.eventType != null;
338
+ }
317
339
 
318
- function isTouchEvent(
319
- event: GestureUpdateEvent | GestureStateChangeEvent | GestureTouchEvent
320
- ): event is GestureTouchEvent {
321
- 'worklet';
322
- return event.eventType != null;
340
+ function getHandler(
341
+ type: CALLBACK_TYPE,
342
+ gesture: HandlerCallbacks<Record<string, unknown>>
343
+ ) {
344
+ 'worklet';
345
+ switch (type) {
346
+ case CALLBACK_TYPE.BEGAN:
347
+ return gesture.onBegin;
348
+ case CALLBACK_TYPE.START:
349
+ return gesture.onStart;
350
+ case CALLBACK_TYPE.UPDATE:
351
+ return gesture.onUpdate;
352
+ case CALLBACK_TYPE.CHANGE:
353
+ return gesture.onChange;
354
+ case CALLBACK_TYPE.END:
355
+ return gesture.onEnd;
356
+ case CALLBACK_TYPE.FINALIZE:
357
+ return gesture.onFinalize;
358
+ case CALLBACK_TYPE.TOUCHES_DOWN:
359
+ return gesture.onTouchesDown;
360
+ case CALLBACK_TYPE.TOUCHES_MOVE:
361
+ return gesture.onTouchesMove;
362
+ case CALLBACK_TYPE.TOUCHES_UP:
363
+ return gesture.onTouchesUp;
364
+ case CALLBACK_TYPE.TOUCHES_CANCELLED:
365
+ return gesture.onTouchesCancelled;
323
366
  }
367
+ }
324
368
 
325
- function getHandler(
326
- type: CALLBACK_TYPE,
327
- gesture: HandlerCallbacks<Record<string, unknown>>
328
- ) {
329
- 'worklet';
330
- switch (type) {
331
- case CALLBACK_TYPE.BEGAN:
332
- return gesture.onBegin;
333
- case CALLBACK_TYPE.START:
334
- return gesture.onStart;
335
- case CALLBACK_TYPE.UPDATE:
336
- return gesture.onUpdate;
337
- case CALLBACK_TYPE.CHANGE:
338
- return gesture.onChange;
339
- case CALLBACK_TYPE.END:
340
- return gesture.onEnd;
341
- case CALLBACK_TYPE.FINALIZE:
342
- return gesture.onFinalize;
343
- case CALLBACK_TYPE.TOUCHES_DOWN:
344
- return gesture.onTouchesDown;
345
- case CALLBACK_TYPE.TOUCHES_MOVE:
346
- return gesture.onTouchesMove;
347
- case CALLBACK_TYPE.TOUCHES_UP:
348
- return gesture.onTouchesUp;
349
- case CALLBACK_TYPE.TOUCHES_CANCELLED:
350
- return gesture.onTouchesCancelled;
351
- }
369
+ function touchEventTypeToCallbackType(
370
+ eventType: TouchEventType
371
+ ): CALLBACK_TYPE {
372
+ 'worklet';
373
+ switch (eventType) {
374
+ case TouchEventType.TOUCHES_DOWN:
375
+ return CALLBACK_TYPE.TOUCHES_DOWN;
376
+ case TouchEventType.TOUCHES_MOVE:
377
+ return CALLBACK_TYPE.TOUCHES_MOVE;
378
+ case TouchEventType.TOUCHES_UP:
379
+ return CALLBACK_TYPE.TOUCHES_UP;
380
+ case TouchEventType.TOUCHES_CANCELLED:
381
+ return CALLBACK_TYPE.TOUCHES_CANCELLED;
352
382
  }
383
+ return CALLBACK_TYPE.UNDEFINED;
384
+ }
353
385
 
354
- function touchEventTypeToCallbackType(
355
- eventType: TouchEventType
356
- ): CALLBACK_TYPE {
357
- 'worklet';
358
- switch (eventType) {
359
- case TouchEventType.TOUCHES_DOWN:
360
- return CALLBACK_TYPE.TOUCHES_DOWN;
361
- case TouchEventType.TOUCHES_MOVE:
362
- return CALLBACK_TYPE.TOUCHES_MOVE;
363
- case TouchEventType.TOUCHES_UP:
364
- return CALLBACK_TYPE.TOUCHES_UP;
365
- case TouchEventType.TOUCHES_CANCELLED:
366
- return CALLBACK_TYPE.TOUCHES_CANCELLED;
367
- }
368
- return CALLBACK_TYPE.UNDEFINED;
386
+ function runWorklet(
387
+ type: CALLBACK_TYPE,
388
+ gesture: HandlerCallbacks<Record<string, unknown>>,
389
+ event: GestureStateChangeEvent | GestureUpdateEvent | GestureTouchEvent,
390
+ ...args: any[]
391
+ ) {
392
+ 'worklet';
393
+ const handler = getHandler(type, gesture);
394
+ if (gesture.isWorklet[type]) {
395
+ // @ts-ignore Logic below makes sure the correct event is send to the
396
+ // correct handler.
397
+ handler?.(event, ...args);
398
+ } else if (handler) {
399
+ console.warn(tagMessage('Animated gesture callback must be a worklet'));
369
400
  }
401
+ }
370
402
 
371
- function runWorklet(
372
- type: CALLBACK_TYPE,
373
- gesture: HandlerCallbacks<Record<string, unknown>>,
374
- event: GestureStateChangeEvent | GestureUpdateEvent | GestureTouchEvent,
375
- ...args: any[]
376
- ) {
377
- 'worklet';
378
- const handler = getHandler(type, gesture);
379
- if (gesture.isWorklet[type]) {
380
- // @ts-ignore Logic below makes sure the correct event is send to the
381
- // correct handler.
382
- handler?.(event, ...args);
383
- } else if (handler) {
384
- console.warn(tagMessage('Animated gesture callback must be a worklet'));
385
- }
403
+ function useAnimatedGesture(
404
+ preparedGesture: GestureConfigReference,
405
+ needsRebuild: boolean
406
+ ) {
407
+ if (!Reanimated) {
408
+ return;
386
409
  }
387
410
 
388
411
  // Hooks are called conditionally, but the condition is whether the
@@ -490,10 +513,9 @@ function useAnimatedGesture(
490
513
 
491
514
  interface GestureDetectorProps {
492
515
  gesture?: ComposedGesture | GestureType;
516
+ children?: React.ReactNode;
493
517
  }
494
- export const GestureDetector: React.FunctionComponent<GestureDetectorProps> = (
495
- props
496
- ) => {
518
+ export const GestureDetector = (props: GestureDetectorProps) => {
497
519
  const gestureConfig = props.gesture;
498
520
  const gesture = gestureConfig?.toGestureArray?.() ?? [];
499
521
  const useReanimatedHook = gesture.some((g) => g.shouldUseReanimated);
@@ -605,7 +627,11 @@ export const GestureDetector: React.FunctionComponent<GestureDetectorProps> = (
605
627
  }
606
628
  };
607
629
 
608
- class Wrap extends React.Component<{ onGestureHandlerEvent?: unknown }> {
630
+ class Wrap extends React.Component<{
631
+ onGestureHandlerEvent?: unknown;
632
+ // implicit `children` prop has been removed in @types/react^18.0.0
633
+ children?: React.ReactNode;
634
+ }> {
609
635
  render() {
610
636
  // I don't think that fighting with types over such a simple function is worth it
611
637
  // The only thing it does is add 'collapsable: false' to the child component
@@ -53,6 +53,7 @@ type TouchEventHandlerType = (
53
53
  ) => void;
54
54
 
55
55
  export type HandlerCallbacks<EventPayloadT extends Record<string, unknown>> = {
56
+ gestureId: number;
56
57
  handlerTag: number;
57
58
  onBegin?: (event: GestureStateChangeEvent<EventPayloadT>) => void;
58
59
  onStart?: (event: GestureStateChangeEvent<EventPayloadT>) => void;
@@ -115,17 +116,32 @@ export abstract class Gesture {
115
116
  abstract prepare(): void;
116
117
  }
117
118
 
119
+ let nextGestureId = 0;
118
120
  export abstract class BaseGesture<
119
121
  EventPayloadT extends Record<string, unknown>
120
122
  > extends Gesture {
123
+ private gestureId = -1;
121
124
  public handlerTag = -1;
122
125
  public handlerName = '';
123
126
  public config: BaseGestureConfig = {};
124
127
  public handlers: HandlerCallbacks<EventPayloadT> = {
128
+ gestureId: -1,
125
129
  handlerTag: -1,
126
130
  isWorklet: [],
127
131
  };
128
132
 
133
+ constructor() {
134
+ super();
135
+
136
+ // Used to check whether the gesture config has been updated when wrapping it
137
+ // with `useMemo`. Since every config will have a unique id, when the dependencies
138
+ // don't change, the config won't be recreated and the id will stay the same.
139
+ // If the id is different, it means that the config has changed and the gesture
140
+ // needs to be updated.
141
+ this.gestureId = nextGestureId++;
142
+ this.handlers.gestureId = this.gestureId;
143
+ }
144
+
129
145
  private addDependency(
130
146
  key: 'simultaneousWith' | 'requireToFail',
131
147
  gesture: Exclude<GestureRef, number>
package/src/web/utils.ts CHANGED
@@ -20,5 +20,5 @@ export function fireAfterInterval(
20
20
  method();
21
21
  return null;
22
22
  }
23
- return setTimeout(() => method(), interval);
23
+ return setTimeout(() => method(), interval as number);
24
24
  }