react-native-gesture-handler 2.4.1 → 2.4.2

Sign up to get free protection for your applications and to get access to all the features.
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
  }