@attio/react-native-bottom-sheet-toolbox
Version:
165 lines (154 loc) • 7.3 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useBottomSheetPositionLock = useBottomSheetPositionLock;
var _react = _interopRequireDefault(require("react"));
var _reactNative = require("react-native");
var _reactNativeReanimated = require("react-native-reanimated");
var _context = require("../context");
var _defaultSheetSnapPointSpringConfig = require("../utils/default-sheet-snap-point-spring-config");
var _useWorkletUiCallback = require("./use-worklet-ui-callback");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
const PIXEL_RATIO = _reactNative.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.
*/
function useBottomSheetPositionLock(id, priority) {
const {
isReadySharedValue,
currentPositionSharedValue,
currentPositionDestinationSharedValue,
currentPositionLastVelocitySharedValue,
currentPositionLockSharedValue
} = (0, _context.useBottomSheetToolboxInternalContext)();
const canLockSharedValue = (0, _reactNativeReanimated.useDerivedValue)(() => {
const isReady = isReadySharedValue.get();
const currentLock = currentPositionLockSharedValue.get();
return isReady && (currentLock === null || currentLock.priority < priority);
}, [isReadySharedValue, currentPositionLockSharedValue, priority]);
const hasLockSharedValue = (0, _reactNativeReanimated.useDerivedValue)(() => id === currentPositionLockSharedValue.get()?.id, [currentPositionLockSharedValue, id]);
const lock = (0, _useWorkletUiCallback.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) {
(0, _reactNativeReanimated.cancelAnimation)(currentPositionSharedValue);
}
}
const newLock = {
id,
priority
};
currentPositionLockSharedValue.set(newLock);
}, [currentPositionLockSharedValue, currentPositionSharedValue, id, priority]);
const release = (0, _useWorkletUiCallback.useWorkletUiCallback)(() => {
"worklet";
const currentLock = currentPositionLockSharedValue.get();
if (currentLock?.id !== id) return;
currentPositionLockSharedValue.set(null);
}, [currentPositionLockSharedValue, id]);
// Release lock when we unmount
const unmountFn = () => {
(0, _reactNativeReanimated.runOnUI)(release)();
};
const onUnmountRef = _react.default.useRef(unmountFn);
onUnmountRef.current = unmountFn;
_react.default.useLayoutEffect(() => () => onUnmountRef.current(), []);
const setPositionInternal = (0, _useWorkletUiCallback.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 = (0, _useWorkletUiCallback.useWorkletUiCallback)((v, animationConfig, callback) => {
"worklet";
const animConfigWithDefault = animationConfig || _defaultSheetSnapPointSpringConfig.DEFAULT_SHEET_SNAP_POINT_SPRING_CONFIG;
const springAnim = (0, _reactNativeReanimated.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 = (0, _useWorkletUiCallback.useWorkletUiCallback)(v => {
"worklet";
setPositionAnimate(v, {
duration: 0
});
}, [setPositionAnimate]);
const setVelocity = (0, _useWorkletUiCallback.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