From 2aeb3e96109360641687e2bd7c8915096e2f65c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 22 Jul 2026 11:30:07 +0200 Subject: [PATCH 1/6] Fix wrong comparison --- .../java/com/swmansion/gesturehandler/core/PanGestureHandler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/PanGestureHandler.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/PanGestureHandler.kt index afb8feedff..fdb057568f 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/PanGestureHandler.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/PanGestureHandler.kt @@ -122,7 +122,7 @@ class PanGestureHandler(context: Context?) : GestureHandler() { } val vy = velocityY if (minVelocityY != MIN_VALUE_IGNORE && - (minVelocityY < 0 && vx <= minVelocityY || minVelocityY in 0.0f..vx) + (minVelocityY < 0 && vy <= minVelocityY || minVelocityY in 0.0f..vy) ) { return true } From cb5088809b9451f57e490374ee2262bc07d48835 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 22 Jul 2026 12:31:25 +0200 Subject: [PATCH 2/6] Android abs --- .../swmansion/gesturehandler/core/PanGestureHandler.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/PanGestureHandler.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/PanGestureHandler.kt index fdb057568f..ffc0158cc3 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/PanGestureHandler.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/PanGestureHandler.kt @@ -11,6 +11,7 @@ import com.facebook.react.uimanager.PixelUtil import com.swmansion.gesturehandler.core.GestureUtils.getLastPointerX import com.swmansion.gesturehandler.core.GestureUtils.getLastPointerY import com.swmansion.gesturehandler.react.events.eventbuilders.PanGestureHandlerEventDataBuilder +import kotlin.math.abs class PanGestureHandler(context: Context?) : GestureHandler() { override val isContinuous = true @@ -115,15 +116,11 @@ class PanGestureHandler(context: Context?) : GestureHandler() { return true } val vx = velocityX - if (minVelocityX != MIN_VALUE_IGNORE && - (minVelocityX < 0 && vx <= minVelocityX || minVelocityX in 0.0f..vx) - ) { + if (minVelocityX != MIN_VALUE_IGNORE && abs(vx) >= abs(minVelocityX)) { return true } val vy = velocityY - if (minVelocityY != MIN_VALUE_IGNORE && - (minVelocityY < 0 && vy <= minVelocityY || minVelocityY in 0.0f..vy) - ) { + if (minVelocityY != MIN_VALUE_IGNORE && abs(vy) >= abs(minVelocityY)) { return true } val velocitySq = vx * vx + vy * vy From 8e5b34bff6c7b8d6aabbd506f1b4e5bea7829f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 22 Jul 2026 12:31:47 +0200 Subject: [PATCH 3/6] Apple abs --- .../apple/Handlers/RNPanHandler.m | 4 ++-- .../react-native-gesture-handler/apple/RNGestureHandler.h | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/react-native-gesture-handler/apple/Handlers/RNPanHandler.m b/packages/react-native-gesture-handler/apple/Handlers/RNPanHandler.m index 921ac4b186..dec9f9490f 100644 --- a/packages/react-native-gesture-handler/apple/Handlers/RNPanHandler.m +++ b/packages/react-native-gesture-handler/apple/Handlers/RNPanHandler.m @@ -337,10 +337,10 @@ - (BOOL)shouldActivateUnderCustomCriteria } CGPoint velocity = [self velocityInView:self.view]; - if (TEST_MIN_IF_NOT_NAN(velocity.x, _minVelocityX)) { + if (TEST_ABS_MIN_IF_NOT_NAN(velocity.x, _minVelocityX)) { return YES; } - if (TEST_MIN_IF_NOT_NAN(velocity.y, _minVelocityY)) { + if (TEST_ABS_MIN_IF_NOT_NAN(velocity.y, _minVelocityY)) { return YES; } if (TEST_MIN_IF_NOT_NAN(VEC_LEN_SQ(velocity), _minVelocitySq)) { diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandler.h b/packages/react-native-gesture-handler/apple/RNGestureHandler.h index c342cb48cb..8f522b4ecb 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandler.h +++ b/packages/react-native-gesture-handler/apple/RNGestureHandler.h @@ -15,6 +15,8 @@ #define TEST_MIN_IF_NOT_NAN(value, limit) \ (!isnan(limit) && ((limit < 0 && value <= limit) || (limit >= 0 && value >= limit))) +#define TEST_ABS_MIN_IF_NOT_NAN(value, limit) (!isnan(limit) && fabs(value) >= fabs(limit)) + #define TEST_MAX_IF_NOT_NAN(value, max) (!isnan(max) && ((max < 0 && value < max) || (max >= 0 && value > max))) #define APPLY_PROP(recognizer, config, type, prop, propName) \ From cab55ae4013911b4ff9e0e7f37f065fa1e86d392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 22 Jul 2026 12:36:17 +0200 Subject: [PATCH 4/6] Web abs --- .../src/web/handlers/PanGestureHandler.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/react-native-gesture-handler/src/web/handlers/PanGestureHandler.ts b/packages/react-native-gesture-handler/src/web/handlers/PanGestureHandler.ts index d874ee05c0..0354e56e8e 100644 --- a/packages/react-native-gesture-handler/src/web/handlers/PanGestureHandler.ts +++ b/packages/react-native-gesture-handler/src/web/handlers/PanGestureHandler.ts @@ -75,8 +75,7 @@ export default class PanGestureHandler extends GestureHandler { } if (config.minVelocity !== undefined) { - this.minVelocityX = config.minVelocity; - this.minVelocityY = config.minVelocity; + this.minVelocitySq = config.minVelocity * config.minVelocity; this.hasCustomActivationCriteria = true; } @@ -432,8 +431,7 @@ export default class PanGestureHandler extends GestureHandler { if ( this.minVelocityX !== Number.MAX_SAFE_INTEGER && - ((this.minVelocityX < 0 && vx <= this.minVelocityX) || - (this.minVelocityX >= 0 && this.minVelocityX <= vx)) + Math.abs(vx) >= Math.abs(this.minVelocityX) ) { return true; } @@ -441,8 +439,7 @@ export default class PanGestureHandler extends GestureHandler { const vy: number = this.velocityY; if ( this.minVelocityY !== Number.MAX_SAFE_INTEGER && - ((this.minVelocityY < 0 && vy <= this.minVelocityY) || - (this.minVelocityY >= 0 && this.minVelocityY <= vy)) + Math.abs(vy) >= Math.abs(this.minVelocityY) ) { return true; } From 69fe199a70560029f3e2427bb849f5cf794d5c2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 22 Jul 2026 12:45:53 +0200 Subject: [PATCH 5/6] Docs --- .../docs/gestures/use-pan-gesture.mdx | 24 +++++++++++++++++++ .../version-2.x/gesture-handlers/pan-gh.md | 12 ++++++++++ .../version-2.x/gestures/pan-gesture.md | 12 ++++++++++ .../src/handlers/PanGestureHandler.ts | 15 ++++++++++++ .../src/handlers/gestures/panGesture.ts | 9 ++++--- .../src/v3/hooks/gestures/pan/PanTypes.ts | 15 ++++++++++++ 6 files changed, 84 insertions(+), 3 deletions(-) diff --git a/packages/docs-gesture-handler/docs/gestures/use-pan-gesture.mdx b/packages/docs-gesture-handler/docs/gestures/use-pan-gesture.mdx index 55a43fdb61..6c07cc0f64 100644 --- a/packages/docs-gesture-handler/docs/gestures/use-pan-gesture.mdx +++ b/packages/docs-gesture-handler/docs/gestures/use-pan-gesture.mdx @@ -125,6 +125,30 @@ minDistance: number | SharedValue; Minimum distance the finger (or multiple fingers) need to travel before the gesture activates. Expressed in points. +### minVelocity + +```ts +minVelocity: number | SharedValue; +``` + +Minimum speed the pointer has to reach in order to activate the gesture. Expressed in points per second. + +### minVelocityX + +```ts +minVelocityX: number | SharedValue; +``` + +Minimum speed along X axis the pointer has to reach in order to activate the gesture. Expressed in points per second. + +### minVelocityY + +```ts +minVelocityY: number | SharedValue; +``` + +Minimum speed along Y axis the pointer has to reach in order to activate the gesture. Expressed in points per second. + ### minPointers ```ts diff --git a/packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/pan-gh.md b/packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/pan-gh.md index 58c6cf7407..364a0e7d54 100644 --- a/packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/pan-gh.md +++ b/packages/docs-gesture-handler/versioned_docs/version-2.x/gesture-handlers/pan-gh.md @@ -52,6 +52,18 @@ See [set of properties inherited from base handler class](/docs/2.x/gesture-hand Minimum distance the finger (or multiple finger) need to travel before the handler [activates](/docs/2.x/under-the-hood/state#active). Expressed in points. +### `minVelocity` + +Minimum speed the pointer has to reach in order for the handler to [activate](/docs/2.x/under-the-hood/state#active). Expressed in points per second. + +### `minVelocityX` + +Minimum speed along X axis the pointer has to reach in order for the handler to [activate](/docs/2.x/under-the-hood/state#active). Expressed in points per second. + +### `minVelocityY` + +Minimum speed along Y axis the pointer has to reach in order for the handler to [activate](/docs/2.x/under-the-hood/state#active). Expressed in points per second. + ### `minPointers` A number of fingers that is required to be placed before handler can [activate](/docs/2.x/under-the-hood/state#active). Should be an integer greater than or equal to 0. diff --git a/packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/pan-gesture.md b/packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/pan-gesture.md index 487e15a4a5..f33232c769 100644 --- a/packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/pan-gesture.md +++ b/packages/docs-gesture-handler/versioned_docs/version-2.x/gestures/pan-gesture.md @@ -117,6 +117,18 @@ If you wish to track the "center of mass" virtual pointer and account for its ch Minimum distance the finger (or multiple fingers) need to travel before the gesture [activates](/docs/2.x/fundamentals/states-events#active). Expressed in points. +### `minVelocity(value: number)` + +Minimum speed the pointer has to reach in order for the gesture to [activate](/docs/2.x/fundamentals/states-events#active). Expressed in points per second. + +### `minVelocityX(value: number)` + +Minimum speed along X axis the pointer has to reach in order for the gesture to [activate](/docs/2.x/fundamentals/states-events#active). Expressed in points per second. + +### `minVelocityY(value: number)` + +Minimum speed along Y axis the pointer has to reach in order for the gesture to [activate](/docs/2.x/fundamentals/states-events#active). Expressed in points per second. + ### `minPointers(value: number)` A number of fingers that is required to be placed before the gesture can [activate](/docs/2.x/fundamentals/states-events#active). Should be an integer greater than or equal to 0. diff --git a/packages/react-native-gesture-handler/src/handlers/PanGestureHandler.ts b/packages/react-native-gesture-handler/src/handlers/PanGestureHandler.ts index 19aced91d3..c9fea9d9eb 100644 --- a/packages/react-native-gesture-handler/src/handlers/PanGestureHandler.ts +++ b/packages/react-native-gesture-handler/src/handlers/PanGestureHandler.ts @@ -63,9 +63,24 @@ interface CommonPanProperties { */ maxPointers?: number; + /** + * Minimum speed the pointer has to reach in order to activate the handler. + * Expressed in points per second. + */ minVelocity?: number; + + /** + * Minimum speed along X axis the pointer has to reach in order to activate + * the handler. Expressed in points per second. + */ minVelocityX?: number; + + /** + * Minimum speed along Y axis the pointer has to reach in order to activate + * the handler. Expressed in points per second. + */ minVelocityY?: number; + activateAfterLongPress?: number; } diff --git a/packages/react-native-gesture-handler/src/handlers/gestures/panGesture.ts b/packages/react-native-gesture-handler/src/handlers/gestures/panGesture.ts index 47d361cdc7..1413e4b5f9 100644 --- a/packages/react-native-gesture-handler/src/handlers/gestures/panGesture.ts +++ b/packages/react-native-gesture-handler/src/handlers/gestures/panGesture.ts @@ -154,7 +154,8 @@ export class PanGesture extends ContinousBaseGesture< } /** - * Minimum velocity the finger has to reach in order to activate handler. + * Minimum speed the pointer has to reach in order to activate handler. + * Expressed in points per second. * @param velocity */ minVelocity(velocity: number) { @@ -163,7 +164,8 @@ export class PanGesture extends ContinousBaseGesture< } /** - * Minimum velocity along X axis the finger has to reach in order to activate handler. + * Minimum speed along X axis the pointer has to reach in order to activate handler. + * Expressed in points per second. * @param velocity */ minVelocityX(velocity: number) { @@ -172,7 +174,8 @@ export class PanGesture extends ContinousBaseGesture< } /** - * Minimum velocity along Y axis the finger has to reach in order to activate handler. + * Minimum speed along Y axis the pointer has to reach in order to activate handler. + * Expressed in points per second. * @param velocity */ minVelocityY(velocity: number) { diff --git a/packages/react-native-gesture-handler/src/v3/hooks/gestures/pan/PanTypes.ts b/packages/react-native-gesture-handler/src/v3/hooks/gestures/pan/PanTypes.ts index db5b879912..86a859c42f 100644 --- a/packages/react-native-gesture-handler/src/v3/hooks/gestures/pan/PanTypes.ts +++ b/packages/react-native-gesture-handler/src/v3/hooks/gestures/pan/PanTypes.ts @@ -40,9 +40,24 @@ type CommonPanGestureProperties = { */ maxPointers?: number; + /** + * Minimum speed the pointer has to reach in order to activate the gesture. + * Expressed in points per second. + */ minVelocity?: number; + + /** + * Minimum speed along X axis the pointer has to reach in order to activate + * the gesture. Expressed in points per second. + */ minVelocityX?: number; + + /** + * Minimum speed along Y axis the pointer has to reach in order to activate + * the gesture. Expressed in points per second. + */ minVelocityY?: number; + activateAfterLongPress?: number; }; From 73983fbf96a52a8a8605947f52dc351a7af3d17d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Mon, 27 Jul 2026 11:38:37 +0200 Subject: [PATCH 6/6] Fix wrong reset --- .../gesturehandler/core/PanGestureHandler.kt | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/PanGestureHandler.kt b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/PanGestureHandler.kt index ffc0158cc3..77aaabb05c 100644 --- a/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/PanGestureHandler.kt +++ b/packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/PanGestureHandler.kt @@ -27,6 +27,12 @@ class PanGestureHandler(context: Context?) : GestureHandler() { private val defaultMinDist: Float private var minDist = MAX_VALUE_IGNORE + + // Config updates may be partial (e.g. a single key sent when a shared value used in the config + // changes), so both of these need to persist between updateConfig calls - deriving them from the + // keys present in a single update would reset minDist based on an incomplete picture. + private var hasCustomActivationCriteria = false + private var hasExplicitMinDist = false private var activeOffsetXStart = MIN_VALUE_IGNORE private var activeOffsetXEnd = MAX_VALUE_IGNORE private var failOffsetXStart = MAX_VALUE_IGNORE @@ -90,6 +96,8 @@ class PanGestureHandler(context: Context?) : GestureHandler() { minVelocityY = DEFAULT_MIN_VELOCITY_Y minVelocity = DEFAULT_MIN_VELOCITY minDist = defaultMinDist + hasCustomActivationCriteria = false + hasExplicitMinDist = false minPointers = DEFAULT_MIN_POINTERS maxPointers = DEFAULT_MAX_POINTERS activateAfterLongPress = DEFAULT_ACTIVATE_AFTER_LONG_PRESS @@ -270,7 +278,6 @@ class PanGestureHandler(context: Context?) : GestureHandler() { override fun updateConfig(handler: PanGestureHandler, config: ReadableMap) { super.updateConfig(handler, config) - var hasCustomActivationCriteria = false if (config.hasKey(KEY_ACTIVE_OFFSET_X_START)) { handler.activeOffsetXStart = PixelUtil.toPixelFromDIP( @@ -278,7 +285,7 @@ class PanGestureHandler(context: Context?) : GestureHandler() { KEY_ACTIVE_OFFSET_X_START, ), ) - hasCustomActivationCriteria = true + handler.hasCustomActivationCriteria = true } if (config.hasKey(KEY_ACTIVE_OFFSET_X_END)) { handler.activeOffsetXEnd = @@ -287,7 +294,7 @@ class PanGestureHandler(context: Context?) : GestureHandler() { KEY_ACTIVE_OFFSET_X_END, ), ) - hasCustomActivationCriteria = true + handler.hasCustomActivationCriteria = true } if (config.hasKey(KEY_FAIL_OFFSET_RANGE_X_START)) { handler.failOffsetXStart = @@ -296,7 +303,7 @@ class PanGestureHandler(context: Context?) : GestureHandler() { KEY_FAIL_OFFSET_RANGE_X_START, ), ) - hasCustomActivationCriteria = true + handler.hasCustomActivationCriteria = true } if (config.hasKey(KEY_FAIL_OFFSET_RANGE_X_END)) { handler.failOffsetXEnd = @@ -305,7 +312,7 @@ class PanGestureHandler(context: Context?) : GestureHandler() { KEY_FAIL_OFFSET_RANGE_X_END, ), ) - hasCustomActivationCriteria = true + handler.hasCustomActivationCriteria = true } if (config.hasKey(KEY_ACTIVE_OFFSET_Y_START)) { handler.activeOffsetYStart = @@ -314,7 +321,7 @@ class PanGestureHandler(context: Context?) : GestureHandler() { KEY_ACTIVE_OFFSET_Y_START, ), ) - hasCustomActivationCriteria = true + handler.hasCustomActivationCriteria = true } if (config.hasKey(KEY_ACTIVE_OFFSET_Y_END)) { handler.activeOffsetYEnd = @@ -323,7 +330,7 @@ class PanGestureHandler(context: Context?) : GestureHandler() { KEY_ACTIVE_OFFSET_Y_END, ), ) - hasCustomActivationCriteria = true + handler.hasCustomActivationCriteria = true } if (config.hasKey(KEY_FAIL_OFFSET_RANGE_Y_START)) { handler.failOffsetYStart = @@ -332,7 +339,7 @@ class PanGestureHandler(context: Context?) : GestureHandler() { KEY_FAIL_OFFSET_RANGE_Y_START, ), ) - hasCustomActivationCriteria = true + handler.hasCustomActivationCriteria = true } if (config.hasKey(KEY_FAIL_OFFSET_RANGE_Y_END)) { handler.failOffsetYEnd = @@ -341,28 +348,30 @@ class PanGestureHandler(context: Context?) : GestureHandler() { KEY_FAIL_OFFSET_RANGE_Y_END, ), ) - hasCustomActivationCriteria = true + handler.hasCustomActivationCriteria = true } if (config.hasKey(KEY_MIN_VELOCITY)) { // This value is actually in DPs/ms, but we can use the same function as for converting // from DPs to pixels as the unit we're converting is in the numerator handler.minVelocity = PixelUtil.toPixelFromDIP(config.getDouble(KEY_MIN_VELOCITY)) - hasCustomActivationCriteria = true + handler.hasCustomActivationCriteria = true } if (config.hasKey(KEY_MIN_VELOCITY_X)) { handler.minVelocityX = PixelUtil.toPixelFromDIP(config.getDouble(KEY_MIN_VELOCITY_X)) - hasCustomActivationCriteria = true + handler.hasCustomActivationCriteria = true } if (config.hasKey(KEY_MIN_VELOCITY_Y)) { handler.minVelocityY = PixelUtil.toPixelFromDIP(config.getDouble(KEY_MIN_VELOCITY_Y)) - hasCustomActivationCriteria = true + handler.hasCustomActivationCriteria = true } // PanGestureHandler sets minDist by default, if there are custom criteria specified we want - // to reset that setting and use provided criteria instead. + // to reset that setting and use provided criteria instead - unless minDist was explicitly + // configured, in which case it must survive partial config updates of other criteria. if (config.hasKey(KEY_MIN_DIST)) { handler.minDist = PixelUtil.toPixelFromDIP(config.getDouble(KEY_MIN_DIST)) - } else if (hasCustomActivationCriteria) { + handler.hasExplicitMinDist = true + } else if (handler.hasCustomActivationCriteria && !handler.hasExplicitMinDist) { handler.minDist = Float.MAX_VALUE } if (config.hasKey(KEY_MIN_POINTERS)) {