UNPKG

react-native-gesture-handler

Version:

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

230 lines (225 loc) 9.37 kB
"use strict"; import React, { useEffect, useMemo, useRef } from 'react'; import { View } from 'react-native'; import { ActionType } from '../../ActionType'; import RNGestureHandlerModule from '../../RNGestureHandlerModule.web'; import { tagMessage } from '../../utils'; import NodeManager from '../../web/tools/NodeManager'; import { useNativeGestureRole } from './useNativeGestureRole'; // Bundles all per-instance state passed to the standalone helpers below. Holding everything in // one stable object keeps the helpers pure (no closures over component-render-scoped values), // which means the useEffects don't need them in their deps lists. import { jsx as _jsx } from "react/jsx-runtime"; // Invoked from `NodeManager.observeHandler` once the handler is known to exist. Branches on // handler kind + actionType to pick the right binding flow. May be called multiple times for // the same tag (handler re-registration), so each branch must be idempotent. function attachReadyHandler(refs, tag, actionType, virtualViewTag) { const handler = RNGestureHandlerModule.getGestureHandlerNode(tag); if (actionType === ActionType.NATIVE_DETECTOR && handler.shouldAttachGestureToChildView()) { refs.nativeHandlers.add(tag); if (refs.viewRef.current != null && refs.viewRef.current.childElementCount > 0) { tryAttachNativeHandlersToChildView(refs); } return; } if (actionType === ActionType.VIRTUAL_DETECTOR) { const child = virtualViewTag != null ? refs.virtualChildren.get(virtualViewTag) : undefined; if (child == null || child.viewRef.current == null) { return; } if (!refs.attachedHandlers.has(tag)) { RNGestureHandlerModule.attachGestureHandler(tag, child.viewRef.current, actionType, refs.propsRef); refs.attachedHandlers.add(tag); } RNGestureHandlerModule.updateGestureHandlerConfig(tag, { userSelect: child.userSelect, touchAction: child.touchAction, enableContextMenu: child.enableContextMenu }); return; } if (refs.viewRef.current == null) { return; } if (!refs.attachedHandlers.has(tag)) { RNGestureHandlerModule.attachGestureHandler(tag, refs.viewRef.current, actionType, refs.propsRef); refs.attachedHandlers.add(tag); } RNGestureHandlerModule.updateGestureHandlerConfig(tag, { userSelect: refs.propsRef.current.userSelect, touchAction: refs.propsRef.current.touchAction, enableContextMenu: refs.propsRef.current.enableContextMenu }); } function tryAttachNativeHandlersToChildView(refs) { if (refs.nativeHandlers.size === 0) { return; } const view = refs.viewRef.current; if (view == null) { return; } if (view.childElementCount > 1) { throw new Error(tagMessage('Cannot have more than one child view when native gesture handlers are attached to the detector')); } const target = view.firstElementChild; if (target == null) { return; } for (const tag of refs.nativeHandlers) { // A tag may be in `nativeHandlers` from an earlier ready callback but the underlying // handler may have been dropped since. Skip — a re-registration fires the observation again. if (!NodeManager.hasHandler(tag)) { continue; } if (refs.attachedHandlers.has(tag)) { continue; } RNGestureHandlerModule.attachGestureHandler(tag, target, ActionType.NATIVE_DETECTOR, refs.propsRef); refs.attachedHandlers.add(tag); RNGestureHandlerModule.updateGestureHandlerConfig(tag, { userSelect: refs.propsRef.current.userSelect, touchAction: refs.propsRef.current.touchAction, enableContextMenu: refs.propsRef.current.enableContextMenu }); } } // Reconcile `subscribedSet` against `currentTags`: observe new tags, cancel observation and // detach for tags no longer present. The ready callback set up here is responsible for actually // binding the handler once it exists. function syncSubscriptions(refs, currentTags, subscribedSet, actionType, virtualViewTag) { const toUnsubscribe = new Set(subscribedSet); for (const tag of currentTags) { toUnsubscribe.delete(tag); if (subscribedSet.has(tag)) { continue; } NodeManager.observeHandler(tag, refs.owner, () => { attachReadyHandler(refs, tag, actionType, virtualViewTag); }); subscribedSet.add(tag); } for (const tag of toUnsubscribe) { NodeManager.cancelObservation(tag, refs.owner); if (refs.attachedHandlers.has(tag)) { RNGestureHandlerModule.detachGestureHandler(tag); refs.attachedHandlers.delete(tag); } subscribedSet.delete(tag); refs.nativeHandlers.delete(tag); } } function teardown(refs) { NodeManager.cancelAllObservationsForOwner(refs.owner); for (const tag of refs.attachedHandlers) { RNGestureHandlerModule.detachGestureHandler(tag); } refs.attachedHandlers.clear(); refs.subscribedHandlers.clear(); refs.nativeHandlers.clear(); refs.subscribedVirtualHandlers.clear(); refs.virtualChildren.clear(); } const HostGestureDetector = props => { const { handlerTags, children } = props; const handlerTagsSet = useMemo(() => new Set(handlerTags), [...handlerTags]); const viewRef = useRef(null); const propsRef = useRef(props); // Stable per-instance state const refsRef = useRef(null); if (refsRef.current === null) { refsRef.current = { owner: {}, viewRef, propsRef, subscribedHandlers: new Set(), attachedHandlers: new Set(), nativeHandlers: new Set(), subscribedVirtualHandlers: new Map(), virtualChildren: new Map() }; } const refs = refsRef.current; useNativeGestureRole(viewRef, children); // Keep propsRef in sync and re-apply detector-level DOM props to top-level attached handlers // when they change. Virtual children get their own (potentially different) DOM props applied // in the virtualChildren effect below, so we only touch top-level subscribers here. useEffect(() => { const shouldUpdateDOMProps = propsRef.current.userSelect !== props.userSelect || propsRef.current.touchAction !== props.touchAction || propsRef.current.enableContextMenu !== props.enableContextMenu; propsRef.current = props; if (shouldUpdateDOMProps) { // attachedHandlers ⊆ subscribedHandlers ⋃ subscribedVirtualHandlers, we want to ignore the // handlers attached by the virtual detectors not to overwrite their DOM props. const claimedByVirtual = Array.from(refs.subscribedVirtualHandlers.values()).reduce((acc, current) => acc.union(current), new Set()); const handlersToUpdate = refs.subscribedHandlers.intersection(refs.attachedHandlers).difference(claimedByVirtual); for (const tag of handlersToUpdate) { RNGestureHandlerModule.updateGestureHandlerConfig(tag, { userSelect: props.userSelect, touchAction: props.touchAction, enableContextMenu: props.enableContextMenu }); } } }, [props, refs]); useEffect(() => { syncSubscriptions(refs, handlerTagsSet, refs.subscribedHandlers, ActionType.NATIVE_DETECTOR); }, [handlerTagsSet, refs]); useEffect(() => { // Refresh the snapshot used by the ready callback so re-fires read current child props. refs.virtualChildren.clear(); props.virtualChildren?.forEach(child => { refs.virtualChildren.set(child.viewTag, child); }); const virtualChildrenToDetach = new Set(refs.subscribedVirtualHandlers.keys()); props.virtualChildren?.forEach(child => { virtualChildrenToDetach.delete(child.viewTag); }); for (const viewTag of virtualChildrenToDetach) { const tags = refs.subscribedVirtualHandlers.get(viewTag); if (tags != null) { syncSubscriptions(refs, [], tags, ActionType.VIRTUAL_DETECTOR, viewTag); } refs.subscribedVirtualHandlers.delete(viewTag); } props.virtualChildren?.forEach(child => { if (child.viewRef.current == null) { // We must check whether viewRef is not null as otherwise we get an error when intercepting gesture detector // switches its component based on whether animated/reanimated events should run. return; } let subs = refs.subscribedVirtualHandlers.get(child.viewTag); if (subs == null) { subs = new Set(); refs.subscribedVirtualHandlers.set(child.viewTag, subs); } syncSubscriptions(refs, child.handlerTags, subs, ActionType.VIRTUAL_DETECTOR, child.viewTag); // Re-apply per-child DOM props on every run. Already-attached tags need this when only // the child's props change; tags attached via a sync-fired observer already had it // applied in `attachReadyHandler`, so this is a no-op for them. for (const tag of subs) { if (refs.attachedHandlers.has(tag)) { RNGestureHandlerModule.updateGestureHandlerConfig(tag, { userSelect: child.userSelect, touchAction: child.touchAction, enableContextMenu: child.enableContextMenu }); } } }); }, [props.virtualChildren, refs]); useEffect(() => { return () => teardown(refs); }, [refs]); return /*#__PURE__*/_jsx(View, { style: { display: 'contents' }, ref: viewRef, children: children }); }; export default HostGestureDetector; //# sourceMappingURL=HostGestureDetector.web.js.map