Skip to content

Commit 6eb7261

Browse files
authored
[Web] Fix handling of enabled prop (#3726)
## Description Right now, if one changes `enabled` property of handlers to `false`, they can never activate again. In the following example: ```ts const [enabled, setEnabled] = useState(false); const pan = Gesture.Pan().enabled(enabled) ``` gesture will never become active, even if `enabled` changes to `true`. Same happens when `enabled` is set to `true` and then changed to `false` and `true` again. This happens because we set `enabled` to `true` by default in `setGestureConfig`, thus the following check: ```ts if (config.enabled !== undefined && this.enabled !== config.enabled) { ... } ``` passes only when `config.enabled` is `false`. I've set `enabled` to `null` by default, and then if `config.enabled` is defined, it is assigned given value. Else default will be `true`. To fix the issue, I've removed reset of `enabled` in `resetConfig` method. Alternative approach would be to follow Android implementation and always react to changes in `enabled` even in cases like `false` $\rightarrow$ `false` and `true` $\rightarrow$ `true`. ## Test plan <details> <summary>Tested on the following code:</summary> ```tsx import React from 'react'; import { Button, StyleSheet, View } from 'react-native'; import { Gesture, GestureDetector, GestureHandlerRootView, NativeDetector, usePan, } from 'react-native-gesture-handler'; import Animated, { useAnimatedStyle, useSharedValue, } from 'react-native-reanimated'; export default function EmptyExample() { const [showDetector, setShowDetector] = React.useState(true); const [enablePan1, setEnablePan1] = React.useState(true); const enablePan2 = useSharedValue(true); const pan1 = Gesture.Pan().enabled(enablePan1); const pan2 = usePan({ enabled: enablePan2, }); const as = useAnimatedStyle(() => { return { backgroundColor: enablePan2.value ? 'lightgreen' : 'crimson', }; }); return ( <GestureHandlerRootView style={styles.container}> <Button title="Toggle old API enabled!" onPress={() => { setEnablePan1((prev) => !prev); }} /> <GestureDetector gesture={pan1}> <View style={[ styles.box, { backgroundColor: enablePan1 ? 'lightgreen' : 'crimson' }, ]} /> </GestureDetector> <Button title="Toggle new API enabled!" onPress={() => { enablePan2.value = !enablePan2.value; }} /> <Button title="Toggle NativeDetetor" onPress={() => { setShowDetector((prev) => !prev); }} /> {showDetector && ( <NativeDetector gesture={pan2}> <Animated.View style={[styles.box, as]} /> </NativeDetector> )} </GestureHandlerRootView> ); } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', }, box: { width: 200, height: 200, borderRadius: 20, }, }); ``` </details>
1 parent d2cbb50 commit 6eb7261

File tree

4 files changed

+38
-23
lines changed

4 files changed

+38
-23
lines changed

packages/react-native-gesture-handler/src/web/handlers/GestureHandler.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export default abstract class GestureHandler implements IGestureHandler {
4040
private _state: State = State.UNDETERMINED;
4141

4242
private _shouldCancelWhenOutside = false;
43-
private _enabled = false;
43+
private _enabled: boolean | null = null;
4444

4545
private viewRef: number | null = null;
4646
private propsRef: React.RefObject<PropsRef> | null = null;
@@ -712,16 +712,28 @@ export default abstract class GestureHandler implements IGestureHandler {
712712
// Handling config
713713
//
714714

715+
// Helper function to correctly set enabled property
716+
private updateEnabled(enabled: boolean | undefined) {
717+
if (enabled === undefined) {
718+
if (this._enabled) {
719+
return;
720+
}
721+
722+
this._enabled = true;
723+
this.delegate.onEnabledChange();
724+
} else if (this._enabled !== enabled) {
725+
this._enabled = enabled;
726+
this.delegate.onEnabledChange();
727+
}
728+
}
729+
715730
public setGestureConfig(config: Config) {
716731
this.resetConfig();
717732
this.updateGestureConfig(config);
718733
}
719734

720735
public updateGestureConfig(config: Config): void {
721-
if (config.enabled !== undefined && this.enabled !== config.enabled) {
722-
this._enabled = config.enabled;
723-
this.delegate.onEnabledChange();
724-
}
736+
this.updateEnabled(config.enabled);
725737

726738
if (config.hitSlop !== undefined) {
727739
this.hitSlop = config.hitSlop;
@@ -911,7 +923,6 @@ export default abstract class GestureHandler implements IGestureHandler {
911923
}
912924

913925
protected resetConfig(): void {
914-
this._enabled = true;
915926
this.manualActivation = false;
916927
this.shouldCancelWhenOutside = false;
917928
this.mouseButton = undefined;

packages/react-native-gesture-handler/src/web/handlers/IGestureHandler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export default interface IGestureHandler {
2323
state: State;
2424
shouldCancelWhenOutside: boolean;
2525
shouldResetProgress: boolean;
26-
readonly enabled: boolean;
26+
readonly enabled: boolean | null;
2727
readonly pointerType: PointerType;
2828
enableContextMenu: boolean;
2929
readonly activeCursor?: ActiveCursor;

packages/react-native-gesture-handler/src/web/tools/EventManager.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ export default abstract class EventManager<T> {
9494
this.pointersInBounds.splice(index, 1);
9595
}
9696

97+
public setEnabled(value: boolean | null) {
98+
if (value) {
99+
this.registerListeners();
100+
} else {
101+
this.resetManager();
102+
this.unregisterListeners();
103+
}
104+
}
105+
97106
public resetManager(): void {
98107
// Reseting activePointersCounter is necessary to make gestures such as pinch work properly
99108
// There are gestures that end when there is still one active pointer (like pinch/rotation)

packages/react-native-gesture-handler/src/web/tools/GestureHandlerWebDelegate.ts

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ export class GestureHandlerWebDelegate
3939
);
4040
}
4141

42-
this.isInitialized = true;
43-
4442
this.gestureHandler = handler;
4543
this.view = findNodeHandle(viewRef) as unknown as HTMLElement;
4644

@@ -49,10 +47,6 @@ export class GestureHandlerWebDelegate
4947
touchAction: this.view.style.touchAction,
5048
};
5149

52-
this.setUserSelect();
53-
this.setTouchAction();
54-
this.setContextMenu();
55-
5650
const shouldSendHoverEvents = handler.name === SingleGestureName.Hover;
5751

5852
this.eventManagers.push(
@@ -64,6 +58,8 @@ export class GestureHandlerWebDelegate
6458
this.eventManagers.forEach((manager) =>
6559
this.gestureHandler.attachEventManager(manager)
6660
);
61+
62+
this.isInitialized = true;
6763
}
6864

6965
detach(): void {
@@ -73,11 +69,14 @@ export class GestureHandlerWebDelegate
7369
};
7470

7571
this.eventManagers.forEach((manager) => {
76-
manager.unregisterListeners();
72+
manager.setEnabled(false);
7773
});
74+
7875
this.removeContextMenuListeners();
7976
this._view = null;
8077
this.eventManagers = [];
78+
79+
this.isInitialized = false;
8180
}
8281

8382
isPointerInBounds({ x, y }: { x: number; y: number }): boolean {
@@ -203,15 +202,9 @@ export class GestureHandlerWebDelegate
203202
this.setTouchAction();
204203
this.setContextMenu();
205204

206-
if (this.gestureHandler.enabled) {
207-
this.eventManagers.forEach((manager) => {
208-
manager.registerListeners();
209-
});
210-
} else {
211-
this.eventManagers.forEach((manager) => {
212-
manager.unregisterListeners();
213-
});
214-
}
205+
this.eventManagers.forEach((manager) => {
206+
manager.setEnabled(this.gestureHandler.enabled);
207+
});
215208
}
216209

217210
onBegin(): void {
@@ -246,6 +239,8 @@ export class GestureHandlerWebDelegate
246239
this.eventManagers.forEach((manager) => {
247240
manager.unregisterListeners();
248241
});
242+
243+
this.isInitialized = false;
249244
}
250245

251246
private ensureView(view: any): asserts view is HTMLElement {

0 commit comments

Comments
 (0)