From 46230937e0b53c47877b76fee4d12d96cc0826ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82?= Date: Wed, 9 Sep 2026 12:02:56 +0200 Subject: [PATCH] Fix button disabling mid-gesture --- .../apple/RNGestureHandlerButton.mm | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.mm b/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.mm index a69a0123e2..6eb3dd5dcf 100644 --- a/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.mm +++ b/packages/react-native-gesture-handler/apple/RNGestureHandlerButton.mm @@ -330,6 +330,13 @@ - (void)setUserEnabled:(BOOL)userEnabled } _userEnabled = userEnabled; + +#if !TARGET_OS_OSX + if (!userEnabled) { + [self cancelActivePress]; + } +#endif + [self dispatchHoverEventIfNeeded]; if (_isHovered && !_isPressed) { @@ -337,6 +344,28 @@ - (void)setUserEnabled:(BOOL)userEnabled } } +#if !TARGET_OS_OSX +// Ends a press that is in progress when the button gets disabled underneath +// it, so JS receives the press-out +- (void)cancelActivePress +{ + if (!self.tracking) { + return; + } + + [self cancelTrackingWithEvent:nil]; + [self rngh_sendActionsForControlEvents:UIControlEventTouchCancel withEvent:nil]; + + // No finger lifted, so skip the press-out fade: it would overlap the restyle + // that usually comes with disabling and show as a flash. + [self cancelPendingPressOutAnimation]; + [self animateToOpacity:self.restingOpacity + scale:self.restingScale + underlayOpacity:self.restingUnderlayOpacity + duration:0]; +} +#endif + - (void)setUnderlayColor:(RNGHColor *)underlayColor { _underlayColor = underlayColor; @@ -1197,7 +1226,6 @@ - (void)mouseDown:(NSEvent *)event return; } - _isTouchInsideBounds = YES; [self handleAnimatePressIn]; [super mouseDown:event]; @@ -1208,7 +1236,7 @@ - (void)mouseUp:(NSEvent *)event if (!_userEnabled) { return; } - + NSPoint locationInView = [self convertPoint:[event locationInWindow] fromView:nil]; _isHovered = NSPointInRect(locationInView, self.bounds); [self recordHoverSampleForMouseEvent:event]; @@ -1224,7 +1252,7 @@ - (void)mouseDragged:(NSEvent *)event if (!_userEnabled) { return; } - + NSPoint locationInWindow = [event locationInWindow]; NSPoint locationInView = [self convertPoint:locationInWindow fromView:nil]; BOOL currentlyInside = NSPointInRect(locationInView, self.bounds); @@ -1260,7 +1288,7 @@ - (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event if (!_userEnabled) { return NO; } - + _isTouchInsideBounds = YES; // A pencil's hover-out arrives just before touch-down but only schedules the // clear, so `_isHovered` still reflects the open hover. Under Reduce Motion