diff --git a/packages/fiber/__mocks__/react-native/index.ts b/packages/fiber/__mocks__/react-native.ts similarity index 85% rename from packages/fiber/__mocks__/react-native/index.ts rename to packages/fiber/__mocks__/react-native.ts index 0a0a257454..5b2aedbf9c 100644 --- a/packages/fiber/__mocks__/react-native/index.ts +++ b/packages/fiber/__mocks__/react-native.ts @@ -15,9 +15,9 @@ const Container = React.memo( }, }, } as LayoutChangeEvent) + }, [onLayout]) - ref = { current: { props } } - }, []) + React.useImperativeHandle(ref, () => props) return null }), @@ -36,6 +36,10 @@ export const StyleSheet = { }, } +export const PanResponder = { + create: () => ({ panHandlers: {} }), +} + export const Image = { getSize(_uri: string, res: Function, rej?: Function) { res(1, 1) diff --git a/packages/fiber/__mocks__/react-native/Libraries/Blob/BlobManager.js b/packages/fiber/__mocks__/react-native/Libraries/Blob/BlobManager.js deleted file mode 100644 index c707dc18f7..0000000000 --- a/packages/fiber/__mocks__/react-native/Libraries/Blob/BlobManager.js +++ /dev/null @@ -1,4 +0,0 @@ -export default class BlobManager { - createFromParts() {} - createFromOptions() {} -} diff --git a/packages/fiber/__mocks__/react-native/Libraries/Pressability/Pressability.ts b/packages/fiber/__mocks__/react-native/Libraries/Pressability/Pressability.ts deleted file mode 100644 index 4cb71fac2e..0000000000 --- a/packages/fiber/__mocks__/react-native/Libraries/Pressability/Pressability.ts +++ /dev/null @@ -1,2 +0,0 @@ -const mock = {} -export default mock diff --git a/packages/fiber/src/native/Canvas.tsx b/packages/fiber/src/native/Canvas.tsx index 5361ed28ce..f1c9f26f85 100644 --- a/packages/fiber/src/native/Canvas.tsx +++ b/packages/fiber/src/native/Canvas.tsx @@ -111,8 +111,7 @@ const CanvasImpl = /*#__PURE__*/ React.forwardRef( // Overwrite onCreated to apply RN bindings onCreated: (state: RootState) => { // Bind events after creation - const handlers = state.events.connect?.(viewRef.current) - setBind(handlers) + setBind(state.events.handlers) // Bind render to RN bridge const context = state.gl.getContext() as ExpoWebGLRenderingContext diff --git a/packages/fiber/src/native/events.ts b/packages/fiber/src/native/events.ts index 19e1f88ddb..0e4ef1bb36 100644 --- a/packages/fiber/src/native/events.ts +++ b/packages/fiber/src/native/events.ts @@ -1,39 +1,13 @@ import { UseBoundStore } from 'zustand' import { RootState } from '../core/store' import { createEvents, DomEvent, EventManager, Events } from '../core/events' -import { GestureResponderEvent } from 'react-native' -/* eslint-disable import/default, import/no-named-as-default, import/no-named-as-default-member */ -// @ts-ignore -import Pressability from 'react-native/Libraries/Pressability/Pressability' -/* eslint-enable import/default, import/no-named-as-default, import/no-named-as-default-member */ - -const EVENTS = { - PRESS: 'onPress', - PRESSIN: 'onPressIn', - PRESSOUT: 'onPressOut', - LONGPRESS: 'onLongPress', - - HOVERIN: 'onHoverIn', - HOVEROUT: 'onHoverOut', - PRESSMOVE: 'onPressMove', -} - -const DOM_EVENTS = { - [EVENTS.PRESS]: 'onClick', - [EVENTS.PRESSIN]: 'onPointerDown', - [EVENTS.PRESSOUT]: 'onPointerUp', - [EVENTS.LONGPRESS]: 'onDoubleClick', - - [EVENTS.HOVERIN]: 'onPointerOver', - [EVENTS.HOVEROUT]: 'onPointerOut', - [EVENTS.PRESSMOVE]: 'onPointerMove', -} +import { type GestureResponderEvent, PanResponder } from 'react-native' /** Default R3F event manager for react-native */ export function createTouchEvents(store: UseBoundStore): EventManager { const { handlePointer } = createEvents(store) - const handleTouch = (event: GestureResponderEvent, name: keyof typeof EVENTS) => { + const handleTouch = (event: GestureResponderEvent, name: string): true => { event.persist() // Apply offset @@ -41,10 +15,29 @@ export function createTouchEvents(store: UseBoundStore): EventManager ;(event as any).nativeEvent.offsetY = event.nativeEvent.locationY // Emulate DOM event - const callback = handlePointer(DOM_EVENTS[name]) - return callback(event.nativeEvent as any) + const callback = handlePointer(name) + callback(event.nativeEvent as any) + + return true } + const responder = PanResponder.create({ + onStartShouldSetPanResponder: () => true, + onMoveShouldSetPanResponder: () => true, + onMoveShouldSetPanResponderCapture: () => true, + onPanResponderTerminationRequest: () => true, + onStartShouldSetPanResponderCapture: (e) => handleTouch(e, 'onPointerCapture'), + onPanResponderStart: (e) => handleTouch(e, 'onPointerDown'), + onPanResponderMove: (e) => handleTouch(e, 'onPointerMove'), + onPanResponderEnd: (e, state) => { + handleTouch(e, 'onPointerUp') + if (Math.hypot(state.dx, state.dy) < 20) handleTouch(e, 'onClick') + }, + onPanResponderRelease: (e) => handleTouch(e, 'onPointerLeave'), + onPanResponderTerminate: (e) => handleTouch(e, 'onLostPointerCapture'), + onPanResponderReject: (e) => handleTouch(e, 'onLostPointerCapture'), + }) + return { priority: 1, enabled: true, @@ -56,34 +49,23 @@ export function createTouchEvents(store: UseBoundStore): EventManager }, connected: undefined, - handlers: Object.values(EVENTS).reduce( - (acc, name) => ({ - ...acc, - [name]: (event: GestureResponderEvent) => handleTouch(event, name as keyof typeof EVENTS), - }), - {}, - ) as unknown as Events, + handlers: responder.panHandlers as unknown as Events, update: () => { const { events, internal } = store.getState() - if (internal.lastEvent?.current && events.handlers) events.handlers.onPointerMove(internal.lastEvent.current) + if (internal.lastEvent?.current && events.handlers) { + handlePointer('onPointerMove')(internal.lastEvent.current) + } }, connect: () => { const { set, events } = store.getState() events.disconnect?.() - const connected = new Pressability(events.handlers) - set((state) => ({ events: { ...state.events, connected } })) - - const handlers = connected.getEventHandlers() - return handlers + set((state) => ({ events: { ...state.events, connected: true } })) }, disconnect: () => { - const { set, events } = store.getState() + const { set } = store.getState() - if (events.connected) { - events.connected.reset() - set((state) => ({ events: { ...state.events, connected: undefined } })) - } + set((state) => ({ events: { ...state.events, connected: false } })) }, } }