UNPKG

@attio/react-native-bottom-sheet-toolbox

Version:
160 lines (149 loc) 6.75 kB
"use strict"; import React from "react"; import { PixelRatio } from "react-native"; import { cancelAnimation, runOnUI, useDerivedValue, withSpring } from "react-native-reanimated"; import { useBottomSheetToolboxInternalContext } from "../context"; import { DEFAULT_SHEET_SNAP_POINT_SPRING_CONFIG } from "../utils/default-sheet-snap-point-spring-config"; import { useWorkletUiCallback } from "./use-worklet-ui-callback"; const PIXEL_RATIO = PixelRatio.get(); const MIN_PERCEIVED_JUMP_DISTANCE = 10; /** * A priority lock for controlling the position of the bottom-sheet. * This allows our bottom-sheet's position to be modified by multiple sources. */ export function useBottomSheetPositionLock(id, priority) { const { isReadySharedValue, currentPositionSharedValue, currentPositionDestinationSharedValue, currentPositionLastVelocitySharedValue, currentPositionLockSharedValue } = useBottomSheetToolboxInternalContext(); const canLockSharedValue = useDerivedValue(() => { const isReady = isReadySharedValue.get(); const currentLock = currentPositionLockSharedValue.get(); return isReady && (currentLock === null || currentLock.priority < priority); }, [isReadySharedValue, currentPositionLockSharedValue, priority]); const hasLockSharedValue = useDerivedValue(() => id === currentPositionLockSharedValue.get()?.id, [currentPositionLockSharedValue, id]); const lock = useWorkletUiCallback(() => { "worklet"; const currentLock = currentPositionLockSharedValue.get(); if (currentLock !== null) { if (priority < currentLock.priority) { // Lock checks should happen separately to lock requests. // So this should never happen. However, there are super // rare race conditions where this can happen in SharedValue // world. console.warn(`Unable to lock '${id}' as '${currentLock.id}' is already engaged.`); return; } if (id !== currentLock.id) { cancelAnimation(currentPositionSharedValue); } } const newLock = { id, priority }; currentPositionLockSharedValue.set(newLock); }, [currentPositionLockSharedValue, currentPositionSharedValue, id, priority]); const release = useWorkletUiCallback(() => { "worklet"; const currentLock = currentPositionLockSharedValue.get(); if (currentLock?.id !== id) return; currentPositionLockSharedValue.set(null); }, [currentPositionLockSharedValue, id]); // Release lock when we unmount const unmountFn = () => { runOnUI(release)(); }; const onUnmountRef = React.useRef(unmountFn); onUnmountRef.current = unmountFn; React.useLayoutEffect(() => () => onUnmountRef.current(), []); const setPositionInternal = useWorkletUiCallback(v => { "worklet"; const currentLock = currentPositionLockSharedValue.get(); if (currentLock !== null && currentLock.id !== id) { console.warn(`Cannot setPosition with '${id}' as '${currentLock.id}' is already engaged.`); return; } currentPositionSharedValue.set(v); if (typeof v === "number") { currentPositionDestinationSharedValue.set(v); } }, [currentPositionLockSharedValue, id, currentPositionSharedValue, currentPositionDestinationSharedValue.set]); const setPositionAnimate = useWorkletUiCallback((v, animationConfig, callback) => { "worklet"; const animConfigWithDefault = animationConfig || DEFAULT_SHEET_SNAP_POINT_SPRING_CONFIG; const springAnim = withSpring(v, animConfigWithDefault, callback); const oldOnStart = springAnim.onStart; /** * This is a fun bit of code. * * Sometimes the destination value we're animating to is itself being * animated. Reanimated does a good job of handling in-flight changes to * animation configs. * * However, we want changes to the destination value to supersede the * sheet's own physical behavior (i.e. its spring config). * * Another way to think of this is we want the sheet to appear as if its * easing consistently despite changes to its destination position. * * A practical example: if the sheet is opening simultaneously with the * keyboard, we want the sheet's travel to compensate for the keyboard's * height so it remains visible at all times. * * To achieve this, we use a rough heuristic to determine if the * destination delta can be stepped without the user noticing. If so, * we perform the step. */ springAnim.onStart = (nextAnimation, current, timestamp, previousAnimation) => { oldOnStart(nextAnimation, current, timestamp, previousAnimation); const tickDelta = // biome-ignore lint/complexity/useLiteralKeys: Property 'lastTickTime' comes from an index signature, so it must be accessed with ['lastTickTime']. Date.now() - (previousAnimation ? previousAnimation["lastTickTime"] : 0); const goodFrameRate = 1000 / 60; const deviatedGoodFrameRate = goodFrameRate * 1.25; const destinationAnimating = tickDelta <= deviatedGoodFrameRate; if (destinationAnimating) { const destinationDelta = nextAnimation.toValue - currentPositionDestinationSharedValue.get(); const physicalDistance = Math.abs(destinationDelta * PIXEL_RATIO); const minPerceivedJumpDistance = MIN_PERCEIVED_JUMP_DISTANCE * PIXEL_RATIO; if (physicalDistance < minPerceivedJumpDistance) { ; nextAnimation.current += destinationDelta; } } currentPositionDestinationSharedValue.set(nextAnimation.toValue); // biome-ignore lint/complexity/useLiteralKeys: Property 'lastTickTime' comes from an index signature, so it must be accessed with ['lastTickTime']. nextAnimation["lastTickTime"] = Date.now(); }; setPositionInternal(springAnim); }, [setPositionInternal, currentPositionDestinationSharedValue.get, currentPositionDestinationSharedValue.set]); const setPosition = useWorkletUiCallback(v => { "worklet"; setPositionAnimate(v, { duration: 0 }); }, [setPositionAnimate]); const setVelocity = useWorkletUiCallback(v => { "worklet"; const currentLock = currentPositionLockSharedValue.get(); if (currentLock !== null && currentLock.id !== id) { console.warn(`Cannot setVelocity with '${id}' as '${currentLock.id}' is already engaged.`); return; } currentPositionLastVelocitySharedValue.set(v); }, [currentPositionLockSharedValue, id, currentPositionLastVelocitySharedValue]); return { canLockSharedValue, hasLockSharedValue, lock, release, setPosition, setPositionAnimate, setVelocity }; } //# sourceMappingURL=use-bottom-sheet-position-lock.js.map