@attio/react-native-bottom-sheet-toolbox
Version:
101 lines (86 loc) • 3.94 kB
text/typescript
import {useAnimatedReaction, useSharedValue} from "react-native-reanimated"
import {useBottomSheetToolboxInternalContext} from "../context"
import {BottomSheetPositionBuiltInLockPriority, snapPoint} from "../utils"
import {useBottomSheetPositionLock} from "./use-bottom-sheet-position-lock"
const LOCK_ID = "useBottomSheetSnapWhenIdle"
/**
* This hook snaps the bottom sheet to the nearest snap point when
* nothing else is interacting with it. It takes into account the
* last known velocity.
*
* A snap is triggered after either of the following two conditions:
* - The position off any snap-point, and has been released.
* - The underlying snap-points have changed.
*/
export function useBottomSheetSnapWhenIdle() {
const {
currentPositionSharedValue,
currentPositionLastVelocitySharedValue,
currentPositionDestinationSharedValue,
normalizedSnapPointsSharedValue,
isReadySharedValue,
currentPositionLockSharedValue,
} = useBottomSheetToolboxInternalContext()
const {canLockSharedValue, hasLockSharedValue, lock, release, setPositionAnimate, setVelocity} =
useBottomSheetPositionLock(LOCK_ID, BottomSheetPositionBuiltInLockPriority.SNAP_WHEN_IDLE)
const lastSnapPointIndexSharedValue = useSharedValue<number>(-1)
useAnimatedReaction(
() => ({
canLock: isReadySharedValue.get() && canLockSharedValue.get(),
hasLock: hasLockSharedValue.get(),
snapPoints: normalizedSnapPointsSharedValue.get(),
}),
({canLock, hasLock, snapPoints}, lastResults) => {
const canLockOrHasLock = canLock || hasLock
if (!snapPoints || !canLockOrHasLock) {
lastSnapPointIndexSharedValue.set(-1)
return
}
const lastCanLockOrHasLock = lastResults?.canLock || lastResults?.hasLock
const didLockStatusChange = lastCanLockOrHasLock !== canLockOrHasLock
const lastSnapPoints = lastResults?.snapPoints || []
const didSnapPointsChange =
snapPoints === null
? lastSnapPoints === null
: lastSnapPoints.length !== snapPoints.length ||
lastSnapPoints.some((v, index) => v !== snapPoints[index])
// Nothing to do
if (!didLockStatusChange && !didSnapPointsChange) return
/**
* If our snap-points change, then try to follow the snap-point we
* were last at. This also supports the case where the snap-points
* are updated to be equal, but then separate again. That case can
* happen when the keyboard opens.
*/
lastSnapPointIndexSharedValue.set(
lastSnapPoints.indexOf(currentPositionDestinationSharedValue.get())
)
const currentPosition = currentPositionSharedValue.get()
const point =
lastSnapPointIndexSharedValue.get() === -1
? snapPoint(
currentPosition,
currentPositionLastVelocitySharedValue.get(),
snapPoints
)
: (snapPoints[lastSnapPointIndexSharedValue.get()] ?? null)
if (point === null || point === currentPosition) return
if (!hasLock) {
lock()
}
setPositionAnimate(point, undefined, (finished) => {
const currentPositionLock = currentPositionLockSharedValue.get()
if (finished || currentPositionLock?.id !== LOCK_ID) {
release()
}
})
setVelocity(0)
},
[
canLockSharedValue,
hasLockSharedValue,
normalizedSnapPointsSharedValue,
isReadySharedValue.get,
]
)
}