UNPKG

react-native-gesture-handler

Version:

Declarative API exposing native platform touch and gesture system to React Native

233 lines (190 loc) 6.37 kB
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; } }