Skip to content

Commit 49021d8

Browse files
authored
Add touchAction prop. (#2787)
## Description This PR adds new prop to gesture handlers - `touchAction`. While working on [fix scroll interactions PR](#2631) we've found it difficult to remove `touch-action: none;` and ensure correct behavior of handlers with scroll, especially with swipeable components. What made it even harder were differences between platforms. We decided to kill two birds with one stone - give our users possibility to manipulate `touchAction`, which will also make it easier for us to properly handle scroll. ## Test plan Tested on example app.
1 parent a341f88 commit 49021d8

File tree

5 files changed

+47
-5
lines changed

5 files changed

+47
-5
lines changed

src/components/Swipeable.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,7 @@ export default class Swipeable extends Component<
541541
return (
542542
<PanGestureHandler
543543
activeOffsetX={[-dragOffsetFromRightEdge, dragOffsetFromLeftEdge]}
544+
touchAction="pan-y"
544545
{...this.props}
545546
onGestureEvent={this.onGestureEvent}
546547
onHandlerStateChange={this.onHandlerStateChange}>
@@ -551,6 +552,7 @@ export default class Swipeable extends Component<
551552
{right}
552553
<TapGestureHandler
553554
enabled={rowState !== 0}
555+
touchAction="pan-y"
554556
onHandlerStateChange={this.onTapHandlerStateChange}>
555557
<Animated.View
556558
pointerEvents={rowState === 0 ? 'auto' : 'box-only'}

src/handlers/gestureHandlerCommon.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const commonProps = [
2525
'activeCursor',
2626
'mouseButton',
2727
'enableContextMenu',
28+
'touchAction',
2829
] as const;
2930

3031
const componentInteractionProps = [
@@ -113,6 +114,23 @@ export type ActiveCursor =
113114
| 'zoom-in'
114115
| 'zoom-out';
115116

117+
export type TouchAction =
118+
| 'auto'
119+
| 'none'
120+
| 'pan-x'
121+
| 'pan-left'
122+
| 'pan-right'
123+
| 'pan-y'
124+
| 'pan-up'
125+
| 'pan-down'
126+
| 'pinch-zoom'
127+
| 'manipulation'
128+
| 'inherit'
129+
| 'initial'
130+
| 'revert'
131+
| 'revert-layer'
132+
| 'unset';
133+
116134
//TODO(TS) events in handlers
117135

118136
export interface GestureEvent<ExtraEventPayloadT = Record<string, unknown>> {
@@ -156,6 +174,7 @@ export type CommonGestureConfig = {
156174
activeCursor?: ActiveCursor;
157175
mouseButton?: MouseButton;
158176
enableContextMenu?: boolean;
177+
touchAction?: TouchAction;
159178
};
160179

161180
// Events payloads are types instead of interfaces due to TS limitation.

src/handlers/gestures/GestureDetector.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
HandlerStateChangeEvent,
2020
scheduleFlushOperations,
2121
UserSelect,
22+
TouchAction,
2223
} from '../gestureHandlerCommon';
2324
import {
2425
GestureStateManager,
@@ -614,11 +615,21 @@ const applyEnableContextMenuProp = (
614615
}
615616
};
616617

618+
const applyTouchActionProp = (
619+
touchAction: TouchAction,
620+
gesture: ComposedGesture | GestureType
621+
): void => {
622+
for (const g of gesture.toGestureArray()) {
623+
g.config.touchAction = touchAction;
624+
}
625+
};
626+
617627
interface GestureDetectorProps {
618628
gesture: ComposedGesture | GestureType;
619629
children?: React.ReactNode;
620630
userSelect?: UserSelect;
621631
enableContextMenu?: boolean;
632+
touchAction?: TouchAction;
622633
}
623634
interface GestureDetectorState {
624635
firstRender: boolean;
@@ -644,6 +655,10 @@ export const GestureDetector = (props: GestureDetectorProps) => {
644655
applyEnableContextMenuProp(props.enableContextMenu, gestureConfig);
645656
}
646657

658+
if (props.touchAction !== undefined) {
659+
applyTouchActionProp(props.touchAction, gestureConfig);
660+
}
661+
647662
const gesture = gestureConfig.toGestureArray();
648663
const useReanimatedHook = gesture.some((g) => g.shouldUseReanimated);
649664

src/web/interfaces.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { UserSelect, ActiveCursor } from '../handlers/gestureHandlerCommon';
1+
import {
2+
UserSelect,
3+
ActiveCursor,
4+
TouchAction,
5+
} from '../handlers/gestureHandlerCommon';
26
import { Directions } from '../Directions';
37
import { State } from '../State';
48
import { PointerType } from '../PointerType';
@@ -23,6 +27,7 @@ type ConfigArgs =
2327
| boolean
2428
| HitSlop
2529
| UserSelect
30+
| TouchAction
2631
| ActiveCursor
2732
| Directions
2833
| Handler[]
@@ -40,6 +45,7 @@ export interface Config extends Record<string, ConfigArgs> {
4045
activeCursor?: ActiveCursor;
4146
mouseButton?: MouseButton;
4247
enableContextMenu?: boolean;
48+
touchAction?: TouchAction;
4349

4450
activateAfterLongPress?: number;
4551
failOffsetXStart?: number;

src/web/tools/GestureHandlerWebDelegate.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,6 @@ export class GestureHandlerWebDelegate
3232
this.gestureHandler = handler;
3333
this.view = findNodeHandle(viewRef) as unknown as HTMLElement;
3434

35-
this.view.style['touchAction'] = 'none';
36-
//@ts-ignore This one disables default events on Safari
37-
this.view.style['WebkitTouchCallout'] = 'none';
38-
3935
const config = handler.getConfig();
4036

4137
this.addContextMenuListeners(config);
@@ -48,6 +44,10 @@ export class GestureHandlerWebDelegate
4844
this.view.style['userSelect'] = config.userSelect;
4945
}
5046

47+
this.view.style['touchAction'] = config.touchAction ?? 'none';
48+
//@ts-ignore This one disables default events on Safari
49+
this.view.style['WebkitTouchCallout'] = 'none';
50+
5151
this.eventManagers.push(new PointerEventManager(this.view));
5252
this.eventManagers.push(new TouchEventManager(this.view));
5353

0 commit comments

Comments
 (0)