react-native-gesture-handler
Version:
Declarative API exposing native platform touch and gesture system to React Native
233 lines (190 loc) • 6.37 kB
text/typescript
import findNodeHandle from '../../findNodeHandle';
import type IGestureHandler from '../handlers/IGestureHandler';
import {
GestureHandlerDelegate,
MeasureResult,
} from './GestureHandlerDelegate';
import PointerEventManager from './PointerEventManager';
import { State } from '../../State';
import { isPointerInBounds } from '../utils';
import EventManager from './EventManager';
import { Config } from '../interfaces';
import { MouseButton } from '../../handlers/gestureHandlerCommon';
import KeyboardEventManager from './KeyboardEventManager';
import WheelEventManager from './WheelEventManager';
interface DefaultViewStyles {
userSelect: string;
touchAction: string;
}
export class GestureHandlerWebDelegate
implements GestureHandlerDelegate<HTMLElement, IGestureHandler>
{
private isInitialized = false;
private _view!: HTMLElement;
private gestureHandler!: IGestureHandler;
private eventManagers: EventManager<unknown>[] = [];
private defaultViewStyles: DefaultViewStyles = {
userSelect: '',
touchAction: '',
};
init(viewRef: number, handler: IGestureHandler): void {
if (!viewRef) {
throw new Error(
`Cannot find HTML Element for handler ${handler.handlerTag}`
);
}
this.isInitialized = true;
this.gestureHandler = handler;
this.view = findNodeHandle(viewRef) as unknown as HTMLElement;
this.defaultViewStyles = {
userSelect: this.view.style.userSelect,
touchAction: this.view.style.touchAction,
};
const config = handler.config;
this.setUserSelect(config.enabled);
this.setTouchAction(config.enabled);
this.setContextMenu(config.enabled);
this.eventManagers.push(new PointerEventManager(this.view));
this.eventManagers.push(new KeyboardEventManager(this.view));
this.eventManagers.push(new WheelEventManager(this.view));
this.eventManagers.forEach((manager) =>
this.gestureHandler.attachEventManager(manager)
);
}
isPointerInBounds({ x, y }: { x: number; y: number }): boolean {
return isPointerInBounds(this.view, { x, y });
}
measureView(): MeasureResult {
const rect = this.view.getBoundingClientRect();
return {
pageX: rect.left,
pageY: rect.top,
width: rect.width,
height: rect.height,
};
}
reset(): void {
this.eventManagers.forEach((manager: EventManager<unknown>) =>
manager.resetManager()
);
}
tryResetCursor() {
const config = this.gestureHandler.config;
if (
config.activeCursor &&
config.activeCursor !== 'auto' &&
this.gestureHandler.state === State.ACTIVE
) {
this.view.style.cursor = 'auto';
}
}
private shouldDisableContextMenu(config: Config) {
return (
(config.enableContextMenu === undefined &&
this.gestureHandler.isButtonInConfig(MouseButton.RIGHT)) ||
config.enableContextMenu === false
);
}
private addContextMenuListeners(config: Config): void {
if (this.shouldDisableContextMenu(config)) {
this.view.addEventListener('contextmenu', this.disableContextMenu);
} else if (config.enableContextMenu) {
this.view.addEventListener('contextmenu', this.enableContextMenu);
}
}
private removeContextMenuListeners(config: Config): void {
if (this.shouldDisableContextMenu(config)) {
this.view.removeEventListener('contextmenu', this.disableContextMenu);
} else if (config.enableContextMenu) {
this.view.removeEventListener('contextmenu', this.enableContextMenu);
}
}
private disableContextMenu(this: void, e: MouseEvent): void {
e.preventDefault();
}
private enableContextMenu(this: void, e: MouseEvent): void {
e.stopPropagation();
}
private setUserSelect(isHandlerEnabled: boolean) {
const { userSelect } = this.gestureHandler.config;
this.view.style['userSelect'] = isHandlerEnabled
? (userSelect ?? 'none')
: this.defaultViewStyles.userSelect;
this.view.style['webkitUserSelect'] = isHandlerEnabled
? (userSelect ?? 'none')
: this.defaultViewStyles.userSelect;
}
private setTouchAction(isHandlerEnabled: boolean) {
const { touchAction } = this.gestureHandler.config;
this.view.style['touchAction'] = isHandlerEnabled
? (touchAction ?? 'none')
: this.defaultViewStyles.touchAction;
// @ts-ignore This one disables default events on Safari
this.view.style['WebkitTouchCallout'] = isHandlerEnabled
? (touchAction ?? 'none')
: this.defaultViewStyles.touchAction;
}
private setContextMenu(isHandlerEnabled: boolean) {
const config = this.gestureHandler.config;
if (isHandlerEnabled) {
this.addContextMenuListeners(config);
} else {
this.removeContextMenuListeners(config);
}
}
onEnabledChange(enabled: boolean): void {
if (!this.isInitialized) {
return;
}
this.setUserSelect(enabled);
this.setTouchAction(enabled);
this.setContextMenu(enabled);
if (enabled) {
this.eventManagers.forEach((manager) => {
// It may look like managers will be registered twice when handler is mounted for the first time.
// However, `init` method is called AFTER `updateGestureConfig` - it means that delegate has not
// been initialized yet, so this code won't be executed.
//
// Also, because we use defined functions, not lambdas, they will not be registered multiple times.
manager.registerListeners();
});
} else {
this.eventManagers.forEach((manager) => {
manager.unregisterListeners();
});
}
}
onBegin(): void {
// no-op for now
}
onActivate(): void {
const config = this.gestureHandler.config;
if (
(!this.view.style.cursor || this.view.style.cursor === 'auto') &&
config.activeCursor
) {
this.view.style.cursor = config.activeCursor;
}
}
onEnd(): void {
this.tryResetCursor();
}
onCancel(): void {
this.tryResetCursor();
}
onFail(): void {
this.tryResetCursor();
}
public destroy(config: Config): void {
this.removeContextMenuListeners(config);
this.eventManagers.forEach((manager) => {
manager.unregisterListeners();
});
}
public get view() {
return this._view;
}
public set view(value: HTMLElement) {
this._view = value;
}
}