diff --git a/src/components/bottomSheet/BottomSheet.tsx b/src/components/bottomSheet/BottomSheet.tsx deleted file mode 100644 index 4ff41422..00000000 --- a/src/components/bottomSheet/BottomSheet.tsx +++ /dev/null @@ -1,1876 +0,0 @@ -import invariant from 'invariant'; -import React, { - forwardRef, - memo, - useCallback, - useEffect, - useImperativeHandle, - useMemo, -} from 'react'; -import { Dimensions, Platform, StyleSheet } from 'react-native'; -import { State } from 'react-native-gesture-handler'; -import Animated, { - cancelAnimation, - Extrapolation, - interpolate, - ReduceMotion, - runOnJS, - runOnUI, - useAnimatedReaction, - useDerivedValue, - useReducedMotion, - useSharedValue, - type WithSpringConfig, - type WithTimingConfig, -} from 'react-native-reanimated'; -import { - ANIMATION_SOURCE, - ANIMATION_STATUS, - INITIAL_LAYOUT_VALUE, - KEYBOARD_BEHAVIOR, - KEYBOARD_BLUR_BEHAVIOR, - KEYBOARD_INPUT_MODE, - KEYBOARD_STATUS, - SHEET_STATE, - SNAP_POINT_TYPE, -} from '../../constants'; -import { - BottomSheetInternalProvider, - BottomSheetProvider, -} from '../../contexts'; -import { - useAnimatedDetents, - useAnimatedKeyboard, - useAnimatedLayout, - usePropsValidator, - useReactiveSharedValue, - useScrollable, - useStableCallback, -} from '../../hooks'; -import type { AnimationState, BottomSheetMethods } from '../../types'; -import { - animate, - getKeyboardAnimationConfigs, - normalizeSnapPoint, - print, -} from '../../utilities'; -import { BottomSheetBackgroundContainer } from '../bottomSheetBackground'; -// import BottomSheetDebugView from '../bottomSheetDebugView'; -import { BottomSheetFooterContainer } from '../bottomSheetFooter'; -import BottomSheetGestureHandlersProvider from '../bottomSheetGestureHandlersProvider'; -import { - BottomSheetHandle, - BottomSheetHandleContainer, -} from '../bottomSheetHandle'; -import { BottomSheetHostingContainer } from '../bottomSheetHostingContainer'; -import { BottomSheetBody } from './BottomSheetBody'; -import { BottomSheetContent } from './BottomSheetContent'; -import { - DEFAULT_ACCESSIBILITY_LABEL, - DEFAULT_ACCESSIBILITY_ROLE, - DEFAULT_ACCESSIBLE, - DEFAULT_ANIMATE_ON_MOUNT, - DEFAULT_DYNAMIC_SIZING, - DEFAULT_ENABLE_BLUR_KEYBOARD_ON_GESTURE, - DEFAULT_ENABLE_CONTENT_PANNING_GESTURE, - DEFAULT_ENABLE_OVER_DRAG, - DEFAULT_ENABLE_PAN_DOWN_TO_CLOSE, - DEFAULT_KEYBOARD_BEHAVIOR, - DEFAULT_KEYBOARD_BLUR_BEHAVIOR, - DEFAULT_KEYBOARD_INDEX, - DEFAULT_KEYBOARD_INPUT_MODE, - DEFAULT_OVER_DRAG_RESISTANCE_FACTOR, - INITIAL_VALUE, -} from './constants'; -import type { AnimateToPositionType, BottomSheetProps } from './types'; - -Animated.addWhitelistedUIProps({ - decelerationRate: true, -}); - -type BottomSheet = BottomSheetMethods; - -const BottomSheetComponent = forwardRef( - function BottomSheet(props, ref) { - //#region extract props - const { - // animations configurations - animationConfigs: _providedAnimationConfigs, - - // configurations - index: _providedIndex = 0, - snapPoints: _providedSnapPoints, - animateOnMount = DEFAULT_ANIMATE_ON_MOUNT, - enableContentPanningGesture = DEFAULT_ENABLE_CONTENT_PANNING_GESTURE, - enableHandlePanningGesture, - enableOverDrag = DEFAULT_ENABLE_OVER_DRAG, - enablePanDownToClose = DEFAULT_ENABLE_PAN_DOWN_TO_CLOSE, - enableDynamicSizing = DEFAULT_DYNAMIC_SIZING, - overDragResistanceFactor = DEFAULT_OVER_DRAG_RESISTANCE_FACTOR, - overrideReduceMotion: _providedOverrideReduceMotion, - - // styles - style, - containerStyle: _providedContainerStyle, - backgroundStyle: _providedBackgroundStyle, - handleStyle: _providedHandleStyle, - handleIndicatorStyle: _providedHandleIndicatorStyle, - - // hooks - gestureEventsHandlersHook, - - // keyboard - keyboardBehavior = DEFAULT_KEYBOARD_BEHAVIOR, - keyboardBlurBehavior = DEFAULT_KEYBOARD_BLUR_BEHAVIOR, - android_keyboardInputMode = DEFAULT_KEYBOARD_INPUT_MODE, - enableBlurKeyboardOnGesture = DEFAULT_ENABLE_BLUR_KEYBOARD_ON_GESTURE, - - // layout - containerLayoutState, - topInset = 0, - bottomInset = 0, - maxDynamicContentSize, - containerHeight, - containerOffset, - - // animated callback shared values - animatedPosition: _providedAnimatedPosition, - animatedIndex: _providedAnimatedIndex, - - // gestures - simultaneousHandlers: _providedSimultaneousHandlers, - waitFor: _providedWaitFor, - activeOffsetX: _providedActiveOffsetX, - activeOffsetY: _providedActiveOffsetY, - failOffsetX: _providedFailOffsetX, - failOffsetY: _providedFailOffsetY, - - // callbacks - onChange: _providedOnChange, - onClose: _providedOnClose, - onAnimate: _providedOnAnimate, - - // private - $modal = false, - detached = false, - - // components - handleComponent = BottomSheetHandle, - backdropComponent: BackdropComponent, - backgroundComponent, - footerComponent, - children, - - // accessibility - accessible: _providedAccessible = DEFAULT_ACCESSIBLE, - accessibilityLabel: - _providedAccessibilityLabel = DEFAULT_ACCESSIBILITY_LABEL, - accessibilityRole: - _providedAccessibilityRole = DEFAULT_ACCESSIBILITY_ROLE, - } = props; - //#endregion - - //#region validate props - if (__DEV__) { - // biome-ignore lint/correctness/useHookAtTopLevel: used in development only. - usePropsValidator({ - index: _providedIndex, - snapPoints: _providedSnapPoints, - enableDynamicSizing, - topInset, - bottomInset, - containerHeight, - containerOffset, - }); - } - //#endregion - - //#region layout variables - const animatedLayoutState = useAnimatedLayout( - containerLayoutState, - topInset, - bottomInset, - $modal, - handleComponent === null - ); - const animatedDetentsState = useAnimatedDetents( - _providedSnapPoints, - animatedLayoutState, - enableDynamicSizing, - maxDynamicContentSize, - detached, - $modal, - bottomInset - ); - const animatedSheetHeight = useDerivedValue(() => { - const { containerHeight } = animatedLayoutState.get(); - const { highestDetentPosition } = animatedDetentsState.get(); - - if (highestDetentPosition === undefined) { - return INITIAL_LAYOUT_VALUE; - } - - return containerHeight - highestDetentPosition; - }, [animatedLayoutState, animatedDetentsState]); - const animatedCurrentIndex = useReactiveSharedValue( - animateOnMount ? -1 : _providedIndex - ); - const animatedPosition = useSharedValue(Dimensions.get('window').height); - - // conditional - const didAnimateOnMount = useSharedValue( - !animateOnMount || _providedIndex === -1 - ); - const isLayoutCalculated = useDerivedValue(() => { - let isContainerHeightCalculated = false; - const { containerHeight, handleHeight } = animatedLayoutState.get(); - //container height was provided and set not to initial value - if ( - containerHeight !== null && - containerHeight !== undefined && - containerHeight !== INITIAL_LAYOUT_VALUE - ) { - isContainerHeightCalculated = true; - } - - let isHandleHeightCalculated = false; - // handle component is null. - if (handleComponent === null) { - isHandleHeightCalculated = true; - } - // handle height did set. - if (handleHeight !== INITIAL_LAYOUT_VALUE) { - isHandleHeightCalculated = true; - } - - let isSnapPointsNormalized = false; - const { detents } = animatedDetentsState.get(); - // the first snap point did normalized - if (detents) { - isSnapPointsNormalized = true; - } - - return ( - isContainerHeightCalculated && - isHandleHeightCalculated && - isSnapPointsNormalized - ); - }, [animatedLayoutState, animatedDetentsState, handleComponent]); - const isInTemporaryPosition = useSharedValue(false); - const animatedContainerHeightDidChange = useSharedValue(false); - - // gesture - const animatedContentGestureState = useSharedValue( - State.UNDETERMINED - ); - const animatedHandleGestureState = useSharedValue( - State.UNDETERMINED - ); - //#endregion - - //#region hooks variables - // keyboard - const { state: animatedKeyboardState, textInputNodesRef } = - useAnimatedKeyboard(); - const userReduceMotionSetting = useReducedMotion(); - const reduceMotion = useMemo(() => { - return !_providedOverrideReduceMotion || - _providedOverrideReduceMotion === ReduceMotion.System - ? userReduceMotionSetting - : _providedOverrideReduceMotion === ReduceMotion.Always; - }, [userReduceMotionSetting, _providedOverrideReduceMotion]); - //#endregion - - //#region state/dynamic variables - // states - const animatedAnimationState = useSharedValue({ - status: ANIMATION_STATUS.UNDETERMINED, - source: ANIMATION_SOURCE.MOUNT, - }); - const animatedSheetState = useDerivedValue(() => { - const { detents, closedDetentPosition } = animatedDetentsState.get(); - - if ( - !detents || - detents.length === 0 || - closedDetentPosition === undefined - ) { - return SHEET_STATE.CLOSED; - } - - // closed position = position >= container height - if (animatedPosition.value >= closedDetentPosition) { - return SHEET_STATE.CLOSED; - } - - const { containerHeight } = animatedLayoutState.get(); - // extended position = container height - sheet height - const extendedPosition = containerHeight - animatedSheetHeight.value; - if (animatedPosition.value === extendedPosition) { - return SHEET_STATE.EXTENDED; - } - - // extended position with keyboard = - // container height - (sheet height + keyboard height in root container) - const keyboardHeightInContainer = - animatedKeyboardState.get().heightWithinContainer; - const extendedPositionWithKeyboard = Math.max( - 0, - containerHeight - - (animatedSheetHeight.value + keyboardHeightInContainer) - ); - - // detect if keyboard is open and the sheet is in temporary position - if ( - keyboardBehavior === KEYBOARD_BEHAVIOR.interactive && - isInTemporaryPosition.value && - animatedPosition.value === extendedPositionWithKeyboard - ) { - return SHEET_STATE.EXTENDED; - } - - // fill parent = 0 - if (animatedPosition.value === 0) { - return SHEET_STATE.FILL_PARENT; - } - - // detect if position is below extended point - if (animatedPosition.value < extendedPosition) { - return SHEET_STATE.OVER_EXTENDED; - } - - return SHEET_STATE.OPENED; - }, [ - animatedLayoutState, - animatedDetentsState, - animatedKeyboardState, - animatedPosition, - animatedSheetHeight, - isInTemporaryPosition, - keyboardBehavior, - ]); - const { - state: animatedScrollableState, - status: animatedScrollableStatus, - setScrollableRef, - removeScrollableRef, - } = useScrollable( - enableContentPanningGesture, - animatedSheetState, - animatedKeyboardState, - animatedAnimationState - ); - // dynamic - const animatedIndex = useDerivedValue(() => { - const { detents } = animatedDetentsState.get(); - if (!detents || detents.length === 0) { - return -1; - } - - const adjustedSnapPoints = detents.slice().reverse(); - const adjustedSnapPointsIndexes = detents - .slice() - .map((_, index: number) => index) - .reverse(); - - const { containerHeight } = animatedLayoutState.get(); - /** - * we add the close state index `-1` - */ - adjustedSnapPoints.push(containerHeight); - adjustedSnapPointsIndexes.push(-1); - - const currentIndex = isLayoutCalculated.value - ? interpolate( - animatedPosition.value, - adjustedSnapPoints, - adjustedSnapPointsIndexes, - Extrapolation.CLAMP - ) - : -1; - - const { - status: animationStatus, - source: animationSource, - nextIndex, - nextPosition, - } = animatedAnimationState.get(); - /** - * if the sheet is currently running an animation by the keyboard opening, - * then we clamp the index on android with resize keyboard mode. - */ - if ( - android_keyboardInputMode === KEYBOARD_INPUT_MODE.adjustResize && - animationStatus === ANIMATION_STATUS.RUNNING && - animationSource === ANIMATION_SOURCE.KEYBOARD && - isInTemporaryPosition.value - ) { - return Math.max(animatedCurrentIndex.value, currentIndex); - } - - /** - * if the sheet is currently running an animation by snap point change - usually caused - * by dynamic content height -, then we return the next position index. - */ - if ( - animationStatus === ANIMATION_STATUS.RUNNING && - animationSource === ANIMATION_SOURCE.SNAP_POINT_CHANGE && - nextIndex !== undefined && - nextPosition !== undefined - ) { - return nextIndex; - } - - return currentIndex; - }, [ - android_keyboardInputMode, - animatedAnimationState, - animatedLayoutState, - animatedCurrentIndex, - animatedPosition, - animatedDetentsState, - isInTemporaryPosition, - isLayoutCalculated, - ]); - //#endregion - - //#region private methods - const handleOnChange = useCallback( - function handleOnChange(index: number, position: number) { - if (__DEV__) { - print({ - component: 'BottomSheet', - method: 'handleOnChange', - category: 'callback', - params: { - index, - position, - }, - }); - } - - if (!_providedOnChange) { - return; - } - - const { dynamicDetentIndex } = animatedDetentsState.get(); - - _providedOnChange( - index, - position, - index === dynamicDetentIndex - ? SNAP_POINT_TYPE.DYNAMIC - : SNAP_POINT_TYPE.PROVIDED - ); - }, - [_providedOnChange, animatedDetentsState] - ); - const handleOnAnimate = useCallback( - function handleOnAnimate(targetIndex: number, targetPosition: number) { - if (__DEV__) { - print({ - component: 'BottomSheet', - method: 'handleOnAnimate', - category: 'callback', - params: { - toIndex: targetIndex, - toPosition: targetPosition, - fromIndex: animatedCurrentIndex.value, - fromPosition: animatedPosition.value, - }, - }); - } - - if (targetIndex === animatedCurrentIndex.get()) { - return; - } - - if (!_providedOnAnimate) { - return; - } - - _providedOnAnimate( - animatedCurrentIndex.value, - targetIndex, - animatedPosition.value, - targetPosition - ); - }, - [_providedOnAnimate, animatedCurrentIndex, animatedPosition] - ); - const handleOnClose = useCallback( - function handleOnClose() { - if (__DEV__) { - print({ - component: 'BottomSheet', - method: 'handleOnClose', - category: 'callback', - }); - } - - if (!_providedOnClose) { - return; - } - - _providedOnClose(); - }, - [_providedOnClose] - ); - //#endregion - - //#region animation - const stopAnimation = useCallback(() => { - 'worklet'; - cancelAnimation(animatedPosition); - animatedAnimationState.set({ - status: ANIMATION_STATUS.STOPPED, - source: ANIMATION_SOURCE.NONE, - }); - }, [animatedPosition, animatedAnimationState]); - const animateToPositionCompleted = useCallback( - function animateToPositionCompleted(isFinished?: boolean) { - 'worklet'; - if (!isFinished) { - return; - } - - const { nextIndex, nextPosition } = animatedAnimationState.get(); - - if (__DEV__) { - runOnJS(print)({ - component: 'BottomSheet', - method: 'animateToPositionCompleted', - params: { - currentIndex: animatedCurrentIndex.value, - nextIndex, - nextPosition, - }, - }); - } - - if (nextIndex === undefined || nextPosition === undefined) { - return; - } - - // callbacks - if (nextIndex !== animatedCurrentIndex.get()) { - runOnJS(handleOnChange)(nextIndex, nextPosition); - } - - if (nextIndex === -1) { - runOnJS(handleOnClose)(); - } - - animatedCurrentIndex.set(nextIndex); - - // reset values - animatedContainerHeightDidChange.set(false); - didAnimateOnMount.set(true); - animatedAnimationState.set({ - status: ANIMATION_STATUS.STOPPED, - source: ANIMATION_SOURCE.NONE, - nextIndex: undefined, - nextPosition: undefined, - isForcedClosing: undefined, - }); - }, - [ - handleOnChange, - handleOnClose, - animatedCurrentIndex, - animatedAnimationState, - animatedContainerHeightDidChange, - didAnimateOnMount, - ] - ); - const animateToPosition: AnimateToPositionType = useCallback( - function animateToPosition( - position: number, - source: ANIMATION_SOURCE, - velocity = 0, - configs?: WithTimingConfig | WithSpringConfig - ) { - 'worklet'; - if (__DEV__) { - runOnJS(print)({ - component: 'BottomSheet', - method: 'animateToPosition', - params: { - currentPosition: animatedPosition.value, - nextPosition: position, - source, - }, - }); - } - - if (position === undefined) { - return; - } - - if (position === animatedPosition.get()) { - return; - } - - // early exit if there is a running animation to - // the same position - const { status: animationStatus, nextPosition } = - animatedAnimationState.get(); - if ( - animationStatus === ANIMATION_STATUS.RUNNING && - position === nextPosition - ) { - return; - } - - // stop animation if it is running - if (animationStatus === ANIMATION_STATUS.RUNNING) { - stopAnimation(); - } - - /** - * offset the position if keyboard is shown and behavior not extend. - */ - let offset = 0; - - const { status, heightWithinContainer } = animatedKeyboardState.get(); - const sheetState = animatedSheetState.get(); - if ( - status === KEYBOARD_STATUS.SHOWN && - keyboardBehavior !== KEYBOARD_BEHAVIOR.extend && - ([ - ANIMATION_SOURCE.KEYBOARD, - ANIMATION_SOURCE.SNAP_POINT_CHANGE, - ].includes(source) || - sheetState === SHEET_STATE.OVER_EXTENDED) - ) { - offset = heightWithinContainer; - } - const { detents, closedDetentPosition, highestDetentPosition } = - animatedDetentsState.get(); - let index = detents?.indexOf(position + offset) ?? -1; - - /** - * because keyboard position is not part of the detents array, - * we will need to manually set the index to the highest detent index. - */ - if ( - index === -1 && - status === KEYBOARD_STATUS.SHOWN && - position !== closedDetentPosition - ) { - if (detents !== undefined && highestDetentPosition !== undefined) { - index = detents.indexOf(highestDetentPosition); - } - if (index === -1) { - index = DEFAULT_KEYBOARD_INDEX; - } - } - - /** - * set the animation state - */ - animatedAnimationState.set(state => { - 'worklet'; - return { - ...state, - status: ANIMATION_STATUS.RUNNING, - source, - nextIndex: index, - nextPosition: position, - }; - }); - - /** - * fire `onAnimate` callback - */ - runOnJS(handleOnAnimate)(index, position); - - /** - * start animation - */ - animatedPosition.value = animate({ - point: position, - configs: configs || _providedAnimationConfigs, - velocity, - overrideReduceMotion: _providedOverrideReduceMotion, - onComplete: animateToPositionCompleted, - }); - }, - [ - handleOnAnimate, - stopAnimation, - animateToPositionCompleted, - keyboardBehavior, - _providedAnimationConfigs, - _providedOverrideReduceMotion, - animatedDetentsState, - animatedAnimationState, - animatedKeyboardState, - animatedPosition, - animatedSheetState, - ] - ); - /** - * Set to position without animation. - * - * @param targetPosition position to be set. - */ - const setToPosition = useCallback( - function setToPosition(targetPosition: number) { - 'worklet'; - if (!targetPosition) { - return; - } - - if (targetPosition === animatedPosition.get()) { - return; - } - - const { status: animationStatus, nextPosition } = - animatedAnimationState.get(); - - // early exit if there is a running animation to - // the same position - if ( - animationStatus === ANIMATION_STATUS.RUNNING && - targetPosition === nextPosition - ) { - return; - } - - if (__DEV__) { - runOnJS(print)({ - component: 'BottomSheet', - method: 'setToPosition', - params: { - currentPosition: animatedPosition.value, - targetPosition, - }, - }); - } - - /** - * store next position - */ - const { detents } = animatedDetentsState.get(); - const index = detents?.indexOf(targetPosition) ?? -1; - animatedAnimationState.set(state => { - 'worklet'; - return { - ...state, - nextPosition: targetPosition, - nextIndex: index, - }; - }); - - stopAnimation(); - - // set values - animatedPosition.value = targetPosition; - animatedContainerHeightDidChange.value = false; - }, - [ - stopAnimation, - animatedPosition, - animatedContainerHeightDidChange, - animatedAnimationState, - animatedDetentsState, - ] - ); - //#endregion - - //#region private methods - /** - * Calculate and evaluate the current position based on multiple - * local states. - */ - const getEvaluatedPosition = useCallback( - function getEvaluatedPosition(source: ANIMATION_SOURCE) { - 'worklet'; - const currentIndex = animatedCurrentIndex.value; - const { detents, highestDetentPosition, closedDetentPosition } = - animatedDetentsState.get(); - const keyboardStatus = animatedKeyboardState.get().status; - - if ( - detents === undefined || - highestDetentPosition === undefined || - closedDetentPosition === undefined - ) { - return; - } - - /** - * if the keyboard blur behavior is restore and keyboard is hidden, - * then we return the previous snap point. - */ - if ( - source === ANIMATION_SOURCE.KEYBOARD && - keyboardBlurBehavior === KEYBOARD_BLUR_BEHAVIOR.restore && - keyboardStatus === KEYBOARD_STATUS.HIDDEN && - animatedContentGestureState.value !== State.ACTIVE && - animatedHandleGestureState.value !== State.ACTIVE - ) { - isInTemporaryPosition.value = false; - const nextPosition = detents[currentIndex]; - return nextPosition; - } - - /** - * if the keyboard appearance behavior is extend and keyboard is shown, - * then we return the heights snap point. - */ - if ( - keyboardBehavior === KEYBOARD_BEHAVIOR.extend && - keyboardStatus === KEYBOARD_STATUS.SHOWN - ) { - return highestDetentPosition; - } - - /** - * if the keyboard appearance behavior is fill parent and keyboard is shown, - * then we return 0 ( full screen ). - */ - if ( - keyboardBehavior === KEYBOARD_BEHAVIOR.fillParent && - keyboardStatus === KEYBOARD_STATUS.SHOWN - ) { - isInTemporaryPosition.value = true; - return 0; - } - - /** - * if the keyboard appearance behavior is interactive and keyboard is shown, - * then we return the heights points minus the keyboard in container height. - */ - if ( - keyboardBehavior === KEYBOARD_BEHAVIOR.interactive && - keyboardStatus === KEYBOARD_STATUS.SHOWN && - // ensure that this logic does not run on android - // with resize input mode - !( - Platform.OS === 'android' && - android_keyboardInputMode === 'adjustResize' - ) - ) { - isInTemporaryPosition.value = true; - const keyboardHeightInContainer = - animatedKeyboardState.get().heightWithinContainer; - return Math.max(0, highestDetentPosition - keyboardHeightInContainer); - } - - /** - * if the bottom sheet is in temporary position, then we return - * the current position. - */ - if (isInTemporaryPosition.value) { - return animatedPosition.value; - } - - /** - * if the bottom sheet did not animate on mount, - * then we return the provided index or the closed position. - */ - if (!didAnimateOnMount.value) { - return _providedIndex === -1 - ? closedDetentPosition - : detents[_providedIndex]; - } - - const { status, nextIndex, nextPosition } = - animatedAnimationState.get(); - - /** - * if the evaluated position is for a snap change source while the sheet is currently running - * an animation and the next position is different than the detent at next index, - * then we return the detent at next index. - * - * https://github.com/gorhom/react-native-bottom-sheet/issues/2431 - */ - if ( - source === ANIMATION_SOURCE.SNAP_POINT_CHANGE && - status === ANIMATION_STATUS.RUNNING && - nextIndex !== undefined && - nextPosition !== undefined && - detents[nextIndex] !== nextPosition - ) { - return detents[nextIndex]; - } - - /** - * return the current index position. - */ - return detents[currentIndex]; - }, - [ - animatedContentGestureState, - animatedCurrentIndex, - animatedHandleGestureState, - animatedAnimationState, - animatedKeyboardState, - animatedPosition, - animatedDetentsState, - isInTemporaryPosition, - didAnimateOnMount, - keyboardBehavior, - keyboardBlurBehavior, - _providedIndex, - android_keyboardInputMode, - ] - ); - - /** - * Evaluate the bottom sheet position based based on a event source and other local states. - */ - const evaluatePosition = useCallback( - function evaluatePosition( - source: ANIMATION_SOURCE, - animationConfigs?: WithSpringConfig | WithTimingConfig - ) { - 'worklet'; - const { - status: animationStatus, - nextIndex, - isForcedClosing, - } = animatedAnimationState.get(); - - const { detents, closedDetentPosition } = animatedDetentsState.get(); - if ( - detents === undefined || - detents.length === 0 || - closedDetentPosition === undefined - ) { - return; - } - - /** - * if a force closing is running and source not from user, then we early exit - */ - if (isForcedClosing && source !== ANIMATION_SOURCE.USER) { - return; - } - /** - * when evaluating the position while layout is not calculated, then we early exit till it is. - */ - if (!isLayoutCalculated.value) { - return; - } - - const proposedPosition = getEvaluatedPosition(source); - if (proposedPosition === undefined) { - return; - } - - /** - * when evaluating the position while the mount animation not been handled, - * then we evaluate on mount use cases. - */ - if (!didAnimateOnMount.value) { - /** - * if there's a running animation (like force close), respect it and don't - * override with mount animation - */ - if (animationStatus === ANIMATION_STATUS.RUNNING) { - return; - } - /** - * if animate on mount is set to true, then we animate to the propose position, - * else, we set the position with out animation. - */ - if (animateOnMount) { - animateToPosition( - proposedPosition, - ANIMATION_SOURCE.MOUNT, - undefined, - animationConfigs - ); - } else { - setToPosition(proposedPosition); - didAnimateOnMount.value = true; - } - return; - } - - /** - * when evaluating the position while the bottom sheet is animating. - */ - if (animationStatus === ANIMATION_STATUS.RUNNING) { - const nextPositionIndex = nextIndex ?? INITIAL_VALUE; - /** - * when evaluating the position while the bottom sheet is - * closing, then we force closing the bottom sheet with no animation. - */ - if (nextPositionIndex === -1 && !isInTemporaryPosition.value) { - setToPosition(closedDetentPosition); - return; - } - - /** - * when evaluating the position while it's animating to - * a position other than the current position, then we - * restart the animation. - */ - if (nextPositionIndex !== animatedCurrentIndex.value) { - animateToPosition( - detents[nextPositionIndex], - source, - undefined, - animationConfigs - ); - return; - } - } - - /** - * when evaluating the position while the bottom sheet is in closed - * position and not animating, we re-set the position to closed position. - */ - if ( - animationStatus !== ANIMATION_STATUS.RUNNING && - animatedCurrentIndex.value === -1 - ) { - /** - * early exit if reduce motion is enabled and index is out of sync with position. - */ - if ( - reduceMotion && - detents[animatedIndex.value] !== animatedPosition.value - ) { - return; - } - setToPosition(closedDetentPosition); - return; - } - - /** - * when evaluating the position after the container resize, then we - * force the bottom sheet to the proposed position with no - * animation. - */ - if (animatedContainerHeightDidChange.value) { - setToPosition(proposedPosition); - return; - } - - /** - * we fall back to the proposed position. - */ - animateToPosition( - proposedPosition, - source, - undefined, - animationConfigs - ); - }, - [ - getEvaluatedPosition, - animateToPosition, - setToPosition, - reduceMotion, - animateOnMount, - animatedAnimationState, - animatedContainerHeightDidChange, - animatedCurrentIndex, - animatedIndex, - animatedPosition, - animatedDetentsState, - didAnimateOnMount, - isInTemporaryPosition, - isLayoutCalculated, - ] - ); - //#endregion - - //#region public methods - const handleSnapToIndex = useStableCallback(function handleSnapToIndex( - index: number, - animationConfigs?: WithSpringConfig | WithTimingConfig - ) { - const { detents } = animatedDetentsState.get(); - const isLayoutReady = isLayoutCalculated.get(); - - if (detents === undefined || detents.length === 0) { - return; - } - - // early exit if layout is not ready yet. - if (!isLayoutReady) { - return; - } - - invariant( - index >= -1 && index <= detents.length - 1, - `'index' was provided but out of the provided snap points range! expected value to be between -1, ${ - detents.length - 1 - }` - ); - - if (__DEV__) { - print({ - component: 'BottomSheet', - method: 'handleSnapToIndex', - params: { - index, - }, - }); - } - - const targetPosition = detents[index]; - - /** - * exit method if : - * - layout is not calculated. - * - already animating to next position. - * - sheet is forced closing. - */ - const { nextPosition, nextIndex, isForcedClosing } = - animatedAnimationState.get(); - if ( - !isLayoutCalculated.value || - index === nextIndex || - targetPosition === nextPosition || - isForcedClosing - ) { - return; - } - - /** - * reset temporary position boolean. - */ - isInTemporaryPosition.value = false; - - runOnUI(animateToPosition)( - targetPosition, - ANIMATION_SOURCE.USER, - 0, - animationConfigs - ); - }); - const handleSnapToPosition = useCallback( - function handleSnapToPosition( - position: number | string, - animationConfigs?: WithSpringConfig | WithTimingConfig - ) { - 'worklet'; - if (__DEV__) { - print({ - component: 'BottomSheet', - method: 'handleSnapToPosition', - params: { - position, - }, - }); - } - - const { containerHeight } = animatedLayoutState.get(); - /** - * normalized provided position. - */ - const targetPosition = normalizeSnapPoint(position, containerHeight); - - /** - * exit method if : - * - layout is not calculated. - * - already animating to next position. - * - sheet is forced closing. - */ - const { nextPosition, isForcedClosing } = animatedAnimationState.get(); - if ( - !isLayoutCalculated || - targetPosition === nextPosition || - isForcedClosing - ) { - return; - } - - /** - * mark the new position as temporary. - */ - isInTemporaryPosition.value = true; - - runOnUI(animateToPosition)( - targetPosition, - ANIMATION_SOURCE.USER, - 0, - animationConfigs - ); - }, - [ - animateToPosition, - isInTemporaryPosition, - isLayoutCalculated, - animatedLayoutState, - animatedAnimationState, - ] - ); - const handleClose = useCallback( - function handleClose( - animationConfigs?: WithSpringConfig | WithTimingConfig - ) { - if (__DEV__) { - print({ - component: 'BottomSheet', - method: 'handleClose', - }); - } - - const closedDetentPosition = - animatedDetentsState.get().closedDetentPosition; - if (closedDetentPosition === undefined) { - return; - } - - const targetPosition = closedDetentPosition; - - /** - * exit method if : - * - layout is not calculated. - * - already animating to next position. - * - sheet is forced closing. - */ - const { nextPosition, isForcedClosing } = animatedAnimationState.get(); - if ( - !isLayoutCalculated.value || - targetPosition === nextPosition || - isForcedClosing - ) { - return; - } - - /** - * reset temporary position variable. - */ - isInTemporaryPosition.value = false; - - runOnUI(animateToPosition)( - targetPosition, - ANIMATION_SOURCE.USER, - 0, - animationConfigs - ); - }, - [ - animateToPosition, - isLayoutCalculated, - isInTemporaryPosition, - animatedDetentsState, - animatedAnimationState, - ] - ); - const handleForceClose = useCallback( - function handleForceClose( - animationConfigs?: WithSpringConfig | WithTimingConfig - ) { - if (__DEV__) { - print({ - component: 'BottomSheet', - method: 'handleForceClose', - }); - } - - const closedDetentPosition = - animatedDetentsState.get().closedDetentPosition; - if (closedDetentPosition === undefined) { - return; - } - - const targetPosition = closedDetentPosition; - - /** - * exit method if : - * - already animating to next position. - * - sheet is forced closing. - */ - const { nextPosition, isForcedClosing } = animatedAnimationState.get(); - if (targetPosition === nextPosition || isForcedClosing) { - return; - } - - /** - * reset temporary position variable. - */ - isInTemporaryPosition.value = false; - - /** - * set force closing variable. - */ - animatedAnimationState.set(state => { - 'worklet'; - return { - ...state, - isForcedClosing: true, - }; - }); - - runOnUI(animateToPosition)( - targetPosition, - ANIMATION_SOURCE.USER, - 0, - animationConfigs - ); - }, - [ - animateToPosition, - isInTemporaryPosition, - animatedDetentsState, - animatedAnimationState, - ] - ); - const handleExpand = useCallback( - function handleExpand( - animationConfigs?: WithSpringConfig | WithTimingConfig - ) { - if (__DEV__) { - print({ - component: 'BottomSheet', - method: 'handleExpand', - }); - } - - const { detents } = animatedDetentsState.get(); - if (detents === undefined || detents.length === 0) { - return; - } - - const targetIndex = detents.length - 1; - const targetPosition = detents[targetIndex]; - - /** - * exit method if : - * - layout is not calculated. - * - already animating to next position. - * - sheet is forced closing. - */ - const { nextPosition, nextIndex, isForcedClosing } = - animatedAnimationState.get(); - if ( - !isLayoutCalculated.value || - targetIndex === nextIndex || - targetPosition === nextPosition || - isForcedClosing - ) { - return; - } - - /** - * reset temporary position boolean. - */ - isInTemporaryPosition.value = false; - - runOnUI(animateToPosition)( - targetPosition, - ANIMATION_SOURCE.USER, - 0, - animationConfigs - ); - }, - [ - animateToPosition, - isInTemporaryPosition, - isLayoutCalculated, - animatedDetentsState, - animatedAnimationState, - ] - ); - const handleCollapse = useCallback( - function handleCollapse( - animationConfigs?: WithSpringConfig | WithTimingConfig - ) { - if (__DEV__) { - print({ - component: 'BottomSheet', - method: 'handleCollapse', - }); - } - - const { detents } = animatedDetentsState.get(); - if (detents === undefined || detents.length === 0) { - return; - } - - const targetPosition = detents[0]; - - /** - * exit method if : - * - layout is not calculated. - * - already animating to next position. - * - sheet is forced closing. - */ - const { nextPosition, nextIndex, isForcedClosing } = - animatedAnimationState.get(); - if ( - !isLayoutCalculated || - nextIndex === 0 || - targetPosition === nextPosition || - isForcedClosing - ) { - return; - } - - /** - * reset temporary position boolean. - */ - isInTemporaryPosition.value = false; - - runOnUI(animateToPosition)( - targetPosition, - ANIMATION_SOURCE.USER, - 0, - animationConfigs - ); - }, - [ - animateToPosition, - isLayoutCalculated, - isInTemporaryPosition, - animatedDetentsState, - animatedAnimationState, - ] - ); - - useImperativeHandle(ref, () => ({ - snapToIndex: handleSnapToIndex, - snapToPosition: handleSnapToPosition, - expand: handleExpand, - collapse: handleCollapse, - close: handleClose, - forceClose: handleForceClose, - })); - //#endregion - - //#region contexts variables - const internalContextVariables = useMemo( - () => ({ - textInputNodesRef, - enableContentPanningGesture, - enableDynamicSizing, - overDragResistanceFactor, - enableOverDrag, - enablePanDownToClose, - animatedAnimationState, - animatedSheetState, - animatedScrollableState, - animatedScrollableStatus, - animatedContentGestureState, - animatedHandleGestureState, - animatedKeyboardState, - animatedLayoutState, - animatedIndex, - animatedPosition, - animatedSheetHeight, - animatedDetentsState, - isInTemporaryPosition, - simultaneousHandlers: _providedSimultaneousHandlers, - waitFor: _providedWaitFor, - activeOffsetX: _providedActiveOffsetX, - activeOffsetY: _providedActiveOffsetY, - failOffsetX: _providedFailOffsetX, - failOffsetY: _providedFailOffsetY, - enableBlurKeyboardOnGesture, - animateToPosition, - stopAnimation, - setScrollableRef, - removeScrollableRef, - }), - [ - textInputNodesRef, - animatedIndex, - animatedPosition, - animatedSheetHeight, - animatedLayoutState, - animatedContentGestureState, - animatedHandleGestureState, - animatedAnimationState, - animatedKeyboardState, - animatedSheetState, - animatedScrollableState, - animatedScrollableStatus, - animatedDetentsState, - isInTemporaryPosition, - enableContentPanningGesture, - overDragResistanceFactor, - enableOverDrag, - enablePanDownToClose, - enableDynamicSizing, - enableBlurKeyboardOnGesture, - _providedSimultaneousHandlers, - _providedWaitFor, - _providedActiveOffsetX, - _providedActiveOffsetY, - _providedFailOffsetX, - _providedFailOffsetY, - setScrollableRef, - removeScrollableRef, - animateToPosition, - stopAnimation, - ] - ); - const externalContextVariables = useMemo( - () => ({ - animatedIndex, - animatedPosition, - snapToIndex: handleSnapToIndex, - snapToPosition: handleSnapToPosition, - expand: handleExpand, - collapse: handleCollapse, - close: handleClose, - forceClose: handleForceClose, - }), - [ - animatedIndex, - animatedPosition, - handleSnapToIndex, - handleSnapToPosition, - handleExpand, - handleCollapse, - handleClose, - handleForceClose, - ] - ); - //#endregion - - //#region effects - useAnimatedReaction( - () => animatedLayoutState.get().containerHeight, - (result, previous) => { - if (result === INITIAL_LAYOUT_VALUE) { - return; - } - - animatedContainerHeightDidChange.value = result !== previous; - - const { closedDetentPosition } = animatedDetentsState.get(); - if (closedDetentPosition === undefined) { - return; - } - - /** - * When user close the bottom sheet while the keyboard open on Android with - * software keyboard layout mode set to resize, the close position would be - * set to the container height - the keyboard height, and when the keyboard - * closes, the container height and here we restart the animation again. - * - * [read more](https://github.com/gorhom/react-native-bottom-sheet/issues/2163) - */ - const { - status: animationStatus, - source: animationSource, - nextIndex, - } = animatedAnimationState.get(); - if ( - animationStatus === ANIMATION_STATUS.RUNNING && - animationSource === ANIMATION_SOURCE.GESTURE && - nextIndex === -1 - ) { - animateToPosition(closedDetentPosition, ANIMATION_SOURCE.GESTURE); - } - }, - [ - animatedContainerHeightDidChange, - animatedAnimationState, - animatedDetentsState, - ] - ); - - /** - * Reaction to the `snapPoints` change, to insure that the sheet position reflect - * to the current point correctly. - * - * @alias OnSnapPointsChange - */ - useAnimatedReaction( - () => animatedDetentsState.get().detents, - (result, previous) => { - /** - * if values did not change, and did handle on mount animation - * then we early exit the method. - */ - if ( - JSON.stringify(result) === JSON.stringify(previous) && - didAnimateOnMount.value - ) { - return; - } - - /** - * if layout is not calculated yet, then we exit the method. - */ - if (!isLayoutCalculated.value) { - return; - } - - if (__DEV__) { - runOnJS(print)({ - component: 'BottomSheet', - method: 'useAnimatedReaction::OnSnapPointChange', - category: 'effect', - params: { - result, - }, - }); - } - - evaluatePosition(ANIMATION_SOURCE.SNAP_POINT_CHANGE); - }, - [isLayoutCalculated, didAnimateOnMount, animatedDetentsState] - ); - - /** - * Reaction to the keyboard state change. - * - * @alias OnKeyboardStateChange - */ - useAnimatedReaction( - () => - animatedKeyboardState.get().status + animatedKeyboardState.get().height, - (result, _previousResult) => { - /** - * if keyboard state is equal to the previous state, then exit the method - */ - if (result === _previousResult) { - return; - } - - const { status, height, easing, duration, target } = - animatedKeyboardState.get(); - - /** - * if state is undetermined, then we early exit. - */ - if (status === KEYBOARD_STATUS.UNDETERMINED) { - return; - } - - const { status: animationStatus, source: animationSource } = - animatedAnimationState.get(); - /** - * if keyboard is hidden by customer gesture, then we early exit. - */ - if ( - status === KEYBOARD_STATUS.HIDDEN && - animationStatus === ANIMATION_STATUS.RUNNING && - animationSource === ANIMATION_SOURCE.GESTURE - ) { - return; - } - - /** - * Calculate the keyboard height in the container. - */ - const containerOffset = animatedLayoutState.get().containerOffset; - let heightWithinContainer = - height === 0 - ? 0 - : $modal - ? Math.abs( - height - Math.abs(bottomInset - containerOffset.bottom) - ) - : Math.abs(height - containerOffset.bottom); - - if (__DEV__) { - runOnJS(print)({ - component: 'BottomSheet', - method: 'useAnimatedReaction::OnKeyboardStateChange', - category: 'effect', - params: { - status, - height, - heightWithinContainer, - containerOffset, - }, - }); - } - - /** - * if platform is android and the input mode is resize, then exit the method - */ - if ( - Platform.OS === 'android' && - android_keyboardInputMode === KEYBOARD_INPUT_MODE.adjustResize - ) { - heightWithinContainer = 0; - - if (keyboardBehavior === KEYBOARD_BEHAVIOR.interactive) { - animatedKeyboardState.set({ - target, - status, - height, - easing, - duration, - heightWithinContainer, - }); - return; - } - } - animatedKeyboardState.set(state => ({ - ...state, - heightWithinContainer, - })); - - /** - * if user is interacting with sheet, then exit the method - */ - const hasActiveGesture = - animatedContentGestureState.value === State.ACTIVE || - animatedContentGestureState.value === State.BEGAN || - animatedHandleGestureState.value === State.ACTIVE || - animatedHandleGestureState.value === State.BEGAN; - if (hasActiveGesture) { - return; - } - - /** - * if new keyboard state is hidden and blur behavior is none, then exit the method - */ - if ( - status === KEYBOARD_STATUS.HIDDEN && - keyboardBlurBehavior === KEYBOARD_BLUR_BEHAVIOR.none - ) { - return; - } - - const animationConfigs = getKeyboardAnimationConfigs(easing, duration); - evaluatePosition(ANIMATION_SOURCE.KEYBOARD, animationConfigs); - }, - [ - $modal, - bottomInset, - keyboardBehavior, - keyboardBlurBehavior, - android_keyboardInputMode, - animatedKeyboardState, - animatedLayoutState, - getEvaluatedPosition, - ] - ); - - /** - * sets provided animated position - */ - useAnimatedReaction( - () => animatedPosition.value, - _animatedPosition => { - if (_providedAnimatedPosition) { - _providedAnimatedPosition.value = _animatedPosition + topInset; - } - }, - [_providedAnimatedPosition, topInset] - ); - - /** - * sets provided animated index - */ - useAnimatedReaction( - () => animatedIndex.value, - _animatedIndex => { - if (_providedAnimatedIndex) { - _providedAnimatedIndex.value = _animatedIndex; - } - }, - [_providedAnimatedIndex] - ); - - /** - * React to `index` prop to snap the sheet to the new position. - * - * @alias onIndexChange - */ - useEffect(() => { - // early exit, if animate on mount is set and it did not animate yet. - if (animateOnMount && !didAnimateOnMount.value) { - return; - } - - handleSnapToIndex(_providedIndex); - }, [animateOnMount, _providedIndex, didAnimateOnMount, handleSnapToIndex]); - //#endregion - - // render - return ( - - - - {BackdropComponent ? ( - - ) : null} - - - {backgroundComponent === null ? null : ( - - )} - - {children} - {footerComponent ? ( - - ) : null} - - {handleComponent !== null ? ( - - ) : null} - - {/* */} - - - - - ); - } -); - -const BottomSheet = memo(BottomSheetComponent); -BottomSheet.displayName = 'BottomSheet'; - -export default BottomSheet; diff --git a/src/components/bottomSheet/BottomSheetBody.tsx b/src/components/bottomSheet/BottomSheetBody.tsx deleted file mode 100644 index 91b6a6a6..00000000 --- a/src/components/bottomSheet/BottomSheetBody.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React, { memo, useMemo } from 'react'; -import { Platform } from 'react-native'; -import Animated, { useAnimatedStyle } from 'react-native-reanimated'; -import { useBottomSheetInternal } from '../../hooks'; -import type { BottomSheetProps } from '../bottomSheet/types'; -import { styles } from './styles'; - -type BottomSheetBodyProps = { - style?: BottomSheetProps['style']; - children?: React.ReactNode; -}; - -function BottomSheetBodyComponent({ style, children }: BottomSheetBodyProps) { - //#region hooks - const { animatedIndex, animatedPosition } = useBottomSheetInternal(); - //#endregion - - //#region styles - const containerAnimatedStyle = useAnimatedStyle( - () => ({ - opacity: Platform.OS === 'android' && animatedIndex.get() === -1 ? 0 : 1, - transform: [ - { - translateY: animatedPosition.get(), - }, - ], - }), - [animatedPosition, animatedIndex] - ); - const containerStyle = useMemo( - () => [style, styles.container, containerAnimatedStyle], - [style, containerAnimatedStyle] - ); - //#endregion - - return ( - - {children} - - ); -} - -export const BottomSheetBody = memo(BottomSheetBodyComponent); -BottomSheetBody.displayName = 'BottomSheetBody'; diff --git a/src/components/bottomSheet/BottomSheetContent.tsx b/src/components/bottomSheet/BottomSheetContent.tsx deleted file mode 100644 index 4a69a689..00000000 --- a/src/components/bottomSheet/BottomSheetContent.tsx +++ /dev/null @@ -1,254 +0,0 @@ -import React, { memo, useMemo } from 'react'; -import type { ViewProps, ViewStyle } from 'react-native'; -import Animated, { - type AnimatedStyle, - useAnimatedStyle, - useDerivedValue, -} from 'react-native-reanimated'; -import { - INITIAL_LAYOUT_VALUE, - KEYBOARD_BEHAVIOR, - KEYBOARD_STATUS, -} from '../../constants'; -import { useBottomSheetInternal } from '../../hooks'; -import type { NullableAccessibilityProps } from '../../types'; -import { animate } from '../../utilities'; -import BottomSheetDraggableView from '../bottomSheetDraggableView'; -import type { BottomSheetProps } from './types'; - -type BottomSheetContent = { - style?: AnimatedStyle; -} & Pick< - BottomSheetProps, - | 'children' - | 'detached' - | 'animationConfigs' - | 'overrideReduceMotion' - | 'keyboardBehavior' -> & - NullableAccessibilityProps & - ViewProps; - -function BottomSheetContentComponent({ - detached, - animationConfigs, - overrideReduceMotion, - keyboardBehavior, - accessible, - accessibilityLabel, - accessibilityHint, - accessibilityRole, - children, -}: BottomSheetContent) { - //#region hooks - const { - enableDynamicSizing, - overDragResistanceFactor, - enableContentPanningGesture, - animatedPosition, - animatedLayoutState, - animatedDetentsState, - animatedSheetHeight, - animatedKeyboardState, - isInTemporaryPosition, - } = useBottomSheetInternal(); - //#endregion - - //#region variables - const animatedContentHeightMax = useDerivedValue(() => { - const { containerHeight, handleHeight } = animatedLayoutState.get(); - - /** - * if container height is not yet calculated, then we exit the method - */ - if (containerHeight === INITIAL_LAYOUT_VALUE) { - return 0; - } - - const { - status: keyboardStatus, - heightWithinContainer: keyboardHeightWithinContainer, - } = animatedKeyboardState.get(); - - let contentHeight = animatedSheetHeight.get() - Math.max(0, handleHeight); - - switch (keyboardBehavior) { - case KEYBOARD_BEHAVIOR.extend: - if (keyboardStatus === KEYBOARD_STATUS.SHOWN) { - contentHeight = contentHeight - keyboardHeightWithinContainer; - } - break; - - case KEYBOARD_BEHAVIOR.fillParent: - if (!isInTemporaryPosition.get()) { - break; - } - - if (keyboardStatus === KEYBOARD_STATUS.SHOWN) { - contentHeight = - containerHeight - handleHeight - keyboardHeightWithinContainer; - } else { - contentHeight = containerHeight - handleHeight; - } - break; - - case KEYBOARD_BEHAVIOR.interactive: { - if (!isInTemporaryPosition.get()) { - break; - } - const contentWithKeyboardHeight = - contentHeight + keyboardHeightWithinContainer; - - if (keyboardStatus === KEYBOARD_STATUS.SHOWN) { - if ( - keyboardHeightWithinContainer + animatedSheetHeight.get() > - containerHeight - ) { - contentHeight = - containerHeight - keyboardHeightWithinContainer - handleHeight; - } - } else if (contentWithKeyboardHeight + handleHeight > containerHeight) { - contentHeight = containerHeight - handleHeight; - } else { - contentHeight = contentWithKeyboardHeight; - } - break; - } - } - - /** - * before the container is measured, `contentHeight` value will be below zero, - * which will lead to freeze the scrollable. - * - * @link (https://github.com/gorhom/react-native-bottom-sheet/issues/470) - */ - return Math.max(contentHeight, 0); - }, [ - animatedLayoutState, - animatedKeyboardState, - animatedSheetHeight, - isInTemporaryPosition, - keyboardBehavior, - ]); - const animatedPaddingBottom = useDerivedValue(() => { - const containerHeight = animatedLayoutState.get().containerHeight; - /** - * if container height is not yet calculated, then we exit the method - */ - if (containerHeight === INITIAL_LAYOUT_VALUE) { - return 0; - } - - const { highestDetentPosition } = animatedDetentsState.get(); - - const highestSnapPoint = Math.max( - highestDetentPosition ?? 0, - animatedPosition.get() - ); - /** - * added safe area to prevent the sheet from floating above - * the bottom of the screen, when sheet being over dragged or - * when the sheet is resized. - */ - const overDragSafePaddingBottom = - Math.sqrt(highestSnapPoint - containerHeight * -1) * - overDragResistanceFactor; - - let paddingBottom = overDragSafePaddingBottom; - - /** - * if keyboard is open, then we try to add padding to prevent content - * from being covered by the keyboard. - */ - const { - status: keyboardStatus, - heightWithinContainer: keyboardHeightWithinContainer, - } = animatedKeyboardState.get(); - if (keyboardStatus === KEYBOARD_STATUS.SHOWN) { - paddingBottom = overDragSafePaddingBottom + keyboardHeightWithinContainer; - } - - return paddingBottom; - }, [ - overDragResistanceFactor, - animatedPosition, - animatedLayoutState, - animatedDetentsState, - animatedKeyboardState, - ]); - //#endregion - - //#region styles - const contentMaskContainerAnimatedStyle = useAnimatedStyle(() => { - const { containerHeight, contentHeight } = animatedLayoutState.get(); - /** - * if container height is not yet calculated, then we exit the method - */ - if (containerHeight === INITIAL_LAYOUT_VALUE) { - return {}; - } - - /** - * if dynamic sizing is enabled, and content height - * is still not set, then we exit method. - */ - if (enableDynamicSizing && contentHeight === INITIAL_LAYOUT_VALUE) { - return {}; - } - - const paddingBottom = detached ? 0 : animatedPaddingBottom.get(); - const height = animatedContentHeightMax.get() + paddingBottom; - - return { - paddingBottom: animate({ - point: paddingBottom, - configs: animationConfigs, - overrideReduceMotion, - }), - height: animate({ - point: height, - configs: animationConfigs, - overrideReduceMotion, - }), - }; - }, [ - overDragResistanceFactor, - enableDynamicSizing, - detached, - animationConfigs, - overrideReduceMotion, - animatedLayoutState, - animatedContentHeightMax, - animatedLayoutState, - ]); - const contentContainerStyle = useMemo( - () => [ - detached - ? { overflow: 'visible' as const } - : { overflow: 'hidden' as const }, - contentMaskContainerAnimatedStyle, - ], - [contentMaskContainerAnimatedStyle, detached] - ); - //#endregion - - //#region render - const DraggableView = enableContentPanningGesture - ? BottomSheetDraggableView - : Animated.View; - return ( - - {children} - - ); - //#endregion -} - -export const BottomSheetContent = memo(BottomSheetContentComponent); -BottomSheetContent.displayName = 'BottomSheetContent'; diff --git a/src/components/bottomSheet/constants.ts b/src/components/bottomSheet/constants.ts deleted file mode 100644 index 51ff1d9c..00000000 --- a/src/components/bottomSheet/constants.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { - KEYBOARD_BEHAVIOR, - KEYBOARD_BLUR_BEHAVIOR, - KEYBOARD_INPUT_MODE, -} from '../../constants'; - -// default values -const DEFAULT_HANDLE_HEIGHT = 24; -const DEFAULT_OVER_DRAG_RESISTANCE_FACTOR = 2.5; -const DEFAULT_ENABLE_CONTENT_PANNING_GESTURE = true; -const DEFAULT_ENABLE_HANDLE_PANNING_GESTURE = true; -const DEFAULT_ENABLE_OVER_DRAG = true; -const DEFAULT_ENABLE_PAN_DOWN_TO_CLOSE = false; -const DEFAULT_ANIMATE_ON_MOUNT = true; -const DEFAULT_DYNAMIC_SIZING = true; - -// keyboard -const DEFAULT_KEYBOARD_BEHAVIOR = KEYBOARD_BEHAVIOR.interactive; -const DEFAULT_KEYBOARD_BLUR_BEHAVIOR = KEYBOARD_BLUR_BEHAVIOR.none; -const DEFAULT_KEYBOARD_INPUT_MODE = KEYBOARD_INPUT_MODE.adjustPan; -const DEFAULT_ENABLE_BLUR_KEYBOARD_ON_GESTURE = false; -const DEFAULT_KEYBOARD_INDEX = -998; - -// initial values -const INITIAL_VALUE = Number.NEGATIVE_INFINITY; -const INITIAL_SNAP_POINT = -999; - -// accessibility -const DEFAULT_ACCESSIBLE = true; -const DEFAULT_ACCESSIBILITY_LABEL = 'Bottom Sheet'; -const DEFAULT_ACCESSIBILITY_ROLE = 'adjustable'; - -export { - DEFAULT_ACCESSIBILITY_LABEL, - DEFAULT_ACCESSIBILITY_ROLE, - DEFAULT_ACCESSIBLE, - DEFAULT_ANIMATE_ON_MOUNT, - DEFAULT_DYNAMIC_SIZING, - DEFAULT_ENABLE_BLUR_KEYBOARD_ON_GESTURE, - DEFAULT_ENABLE_CONTENT_PANNING_GESTURE, - DEFAULT_ENABLE_HANDLE_PANNING_GESTURE, - DEFAULT_ENABLE_OVER_DRAG, - DEFAULT_ENABLE_PAN_DOWN_TO_CLOSE, - DEFAULT_HANDLE_HEIGHT, - DEFAULT_KEYBOARD_BEHAVIOR, - DEFAULT_KEYBOARD_BLUR_BEHAVIOR, - DEFAULT_KEYBOARD_INDEX, - DEFAULT_KEYBOARD_INPUT_MODE, - DEFAULT_OVER_DRAG_RESISTANCE_FACTOR, - INITIAL_SNAP_POINT, - INITIAL_VALUE, -}; diff --git a/src/components/bottomSheet/index.ts b/src/components/bottomSheet/index.ts deleted file mode 100644 index fce453ad..00000000 --- a/src/components/bottomSheet/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './BottomSheet'; -export type { BottomSheetProps } from './types'; diff --git a/src/components/bottomSheet/styles.ts b/src/components/bottomSheet/styles.ts deleted file mode 100644 index 58458f69..00000000 --- a/src/components/bottomSheet/styles.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { StyleSheet } from 'react-native'; - -export const styles = StyleSheet.create({ - container: { - flexDirection: 'column-reverse', - position: 'absolute', - top: 0, - left: 0, - right: 0, - }, -}); diff --git a/src/components/bottomSheet/types.d.ts b/src/components/bottomSheet/types.d.ts deleted file mode 100644 index 6b23056a..00000000 --- a/src/components/bottomSheet/types.d.ts +++ /dev/null @@ -1,358 +0,0 @@ -import type React from 'react'; -import type { Insets, StyleProp, ViewStyle } from 'react-native'; -import type { PanGesture } from 'react-native-gesture-handler'; -import type { - AnimateStyle, - ReduceMotion, - SharedValue, - WithSpringConfig, - WithTimingConfig, -} from 'react-native-reanimated'; -import type { - ANIMATION_SOURCE, - KEYBOARD_BEHAVIOR, - KEYBOARD_BLUR_BEHAVIOR, - KEYBOARD_INPUT_MODE, - SNAP_POINT_TYPE, -} from '../../constants'; -import type { - ContainerLayoutState, - GestureEventsHandlersHookType, - NullableAccessibilityProps, -} from '../../types'; -import type { BottomSheetBackdropProps } from '../bottomSheetBackdrop'; -import type { BottomSheetBackgroundProps } from '../bottomSheetBackground'; -import type { BottomSheetFooterProps } from '../bottomSheetFooter'; -import type { BottomSheetHandleProps } from '../bottomSheetHandle'; - -export interface BottomSheetProps - extends BottomSheetAnimationConfigs, - Partial, - Omit { - //#region configuration - /** - * Initial snap point index, provide `-1` to initiate bottom sheet in closed state. - * @type number - * @default 0 - */ - index?: number; - /** - * Points for the bottom sheet to snap to. It accepts array of number, string or mix. - * String values should be a percentage. - * - * ⚠️ This prop is required unless you set `enableDynamicSizing` to `true`. - * @example - * snapPoints={[200, 500]} - * snapPoints={[200, '%50']} - * snapPoints={['%100']} - * @type Array - */ - snapPoints?: Array | SharedValue>; - /** - * Defines how violently sheet has to be stopped while over dragging. - * @type number - * @default 2.5 - */ - overDragResistanceFactor?: number; - /** - * Defines whether the bottom sheet is attached to the bottom or no. - * @type boolean - * @default false - */ - detached?: boolean; - /** - * Enable content panning gesture interaction. - * @type boolean - * @default true - */ - enableContentPanningGesture?: boolean; - /** - * Enable handle panning gesture interaction. - * @type boolean - * @default true - */ - enableHandlePanningGesture?: boolean; - /** - * Enable over drag for the sheet. - * @type boolean - * @default true - */ - enableOverDrag?: boolean; - /** - * Enable pan down gesture to close the sheet. - * @type boolean - * @default false - */ - enablePanDownToClose?: boolean; - /** - * Enable dynamic sizing for content view and scrollable content size. - * @type boolean - * @default true - */ - enableDynamicSizing?: boolean; - /** - * To start the sheet closed and snap to initial index when it's mounted. - * @type boolean - * @default true - */ - animateOnMount?: boolean; - /** - * To override the user reduce motion setting. - * - `ReduceMotion.System`: if the `Reduce motion` accessibility setting is enabled on the device, disable the animation. - * - `ReduceMotion.Always`: disable the animation, even if `Reduce motion` accessibility setting is not enabled. - * - `ReduceMotion.Never`: enable the animation, even if `Reduce motion` accessibility setting is enabled. - * @type ReduceMotion - * @see https://docs.swmansion.com/react-native-reanimated/docs/guides/accessibility - * @default ReduceMotion.System - */ - overrideReduceMotion?: ReduceMotion; - //#endregion - - //#region layout - /** - * Container height helps to calculate the internal sheet layouts, - * if `containerHeight` not provided, the library internally will calculate it, - * however this will cause an extra re-rendering. - * @type number | SharedValue; - * @deprecated please use `containerLayoutState` instead. - */ - containerHeight?: number | SharedValue; - /** - * Container offset helps to accurately detect container offsets. - * @type SharedValue; - * @deprecated please use `containerLayoutState` instead. - */ - containerOffset?: SharedValue>; - /** - * Container layout state, this is used to calculate the container height and offsets. - * If not provided, the library will use the default container layout state. - * @type SharedValue - * @default undefined - */ - containerLayoutState?: SharedValue; - /** - * Top inset value helps to calculate percentage snap points values, - * usually comes from `@react-navigation/stack` hook `useHeaderHeight` or - * from `react-native-safe-area-context` hook `useSafeArea`. - * @type number - * @default 0 - */ - topInset?: number; - /** - * Bottom inset value helps to calculate percentage snap points values, - * usually comes from `react-native-safe-area-context` hook `useSafeArea`. - * @type number - * @default 0 - */ - bottomInset?: number; - /** - * Max dynamic content size height to limit the bottom sheet height - * from exceeding a provided size. - * @type number - * @default container height - */ - maxDynamicContentSize?: number; - //#endregion - - //#region keyboard - /** - * Defines the keyboard appearance behavior. - * @enum - * - `interactive`: offset the sheet by the size of the keyboard. - * - `extend`: extend the sheet to its maximum snap point. - * - `fillParent`: extend the sheet to fill parent. - * @type `interactive` | `extend` | `fillParent` - * @default interactive - */ - keyboardBehavior?: keyof typeof KEYBOARD_BEHAVIOR; - /** - * Defines the keyboard blur behavior. - * - `none`: do nothing. - * - `restore`: restore sheet position. - */ - keyboardBlurBehavior?: keyof typeof KEYBOARD_BLUR_BEHAVIOR; - /** - * Enable blurring the keyboard when user start to drag the bottom sheet. - * @default false - */ - enableBlurKeyboardOnGesture?: boolean; - /** - * Defines keyboard input mode for Android only. - * @link {https://developer.android.com/guide/topics/manifest/activity-element#wsoft} - * @type `adjustPan` | `adjustResize` - * @default `adjustPan` - */ - android_keyboardInputMode?: keyof typeof KEYBOARD_INPUT_MODE; - - //#endregion - - //#region styles - /** - * View style to be applied to the container. - * @type ViewStyle - * @default undefined - */ - containerStyle?: StyleProp; - /** - * View style to be applied to the sheet container component, - * it also could be an Animated Style. - * @type AnimateStyle - * @default undefined - */ - style?: StyleProp< - AnimateStyle< - Omit< - ViewStyle, - | 'flexDirection' - | 'position' - | 'top' - | 'left' - | 'bottom' - | 'right' - | 'opacity' - | 'transform' - > - > - >; - /** - * View style to be applied to the background component. - * @type ViewStyle - * @default undefined - */ - backgroundStyle?: StyleProp< - Omit - >; - /** - * View style to be applied to the handle component. - * @type ViewStyle - * @default undefined - */ - handleStyle?: StyleProp; - /** - * View style to be applied to the handle indicator component. - * @type ViewStyle - * @default undefined - */ - handleIndicatorStyle?: StyleProp; - //#endregion - - /** - * Custom hook to provide pan gesture events handler, which will allow advance and - * customize handling for pan gesture. - * @warning this is an experimental feature and the hook signature can change without a major version bump. - * @type GestureEventsHandlersHookType - * @default useGestureEventsHandlersDefault - */ - gestureEventsHandlersHook?: GestureEventsHandlersHookType; - - //#region animated nodes - /** - * Animated value to be used as a callback of the position node internally. - * @type SharedValue - */ - animatedPosition?: SharedValue; - /** - * Animated value to be used as a callback for the position index node internally. - * @type SharedValue - */ - animatedIndex?: SharedValue; - //#endregion - - //#region callbacks - /** - * Callback when the sheet position changed to a provided point. - * - * @type (index: number) => void; - */ - onChange?: (index: number, position: number, type: SNAP_POINT_TYPE) => void; - /** - * Callback when the sheet close. - * - * @type () => void; - */ - onClose?: () => void; - /** - * Callback when the sheet about to animate to a new position. - * - * @type (fromIndex: number, toIndex: number, fromPosition: number, toPosition: number) => void; - */ - onAnimate?: ( - fromIndex: number, - toIndex: number, - fromPosition: number, - toPosition: number - ) => void; - //#endregion - - //#region components - /** - * Component to be placed as a sheet handle. - * @see {BottomSheetHandleProps} - * @type React.FC\ - */ - handleComponent?: React.FC | null; - - /** - * Component to be placed as a sheet backdrop. - * @see {BottomSheetBackdropProps} - * @type React.FC\ - * @default undefined - */ - backdropComponent?: React.FC; - /** - * Component to be placed as a background. - * @see {BottomSheetBackgroundProps} - * @type React.FC\ - */ - backgroundComponent?: React.FC | null; - /** - * Component to be placed as a footer. - * @see {BottomSheetFooterProps} - * @type React.FC\ - */ - footerComponent?: React.FC; - /** - * A scrollable node or normal view. - * @type React.ReactNode - */ - children: React.ReactNode; - //#endregion - - //#region private - /** - * An indicator whether if the sheet running in a modal. - * @type boolean - */ - $modal?: boolean; - //#endregion -} - -export interface BottomSheetAnimationConfigs { - /** - * Animation configs, this could be created by: - * - `useBottomSheetSpringConfigs` - * - `useBottomSheetTimingConfigs` - * @type WithSpringConfig | WithTimingConfig - */ - animationConfigs?: WithSpringConfig | WithTimingConfig; -} - -export type AnimateToPositionType = ( - position: number, - source: ANIMATION_SOURCE, - velocity?: number, - configs?: WithTimingConfig | WithSpringConfig -) => void; - -export type BottomSheetGestureProps = { - activeOffsetX: Parameters[0]; - activeOffsetY: Parameters[0]; - - failOffsetY: Parameters[0]; - failOffsetX: Parameters[0]; - - simultaneousHandlers: Parameters< - PanGesture['simultaneousWithExternalGesture'] - >[0]; - waitFor: Parameters[0]; -}; diff --git a/src/components/bottomSheetBackdrop/BottomSheetBackdrop.tsx b/src/components/bottomSheetBackdrop/BottomSheetBackdrop.tsx deleted file mode 100644 index a0345b01..00000000 --- a/src/components/bottomSheetBackdrop/BottomSheetBackdrop.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import React, { - memo, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; -import { StyleSheet, type ViewProps } from 'react-native'; -import { Gesture, GestureDetector } from 'react-native-gesture-handler'; -import Animated, { - Extrapolation, - interpolate, - runOnJS, - useAnimatedReaction, - useAnimatedStyle, -} from 'react-native-reanimated'; -import { useBottomSheet } from '../../hooks'; -import { - DEFAULT_ACCESSIBILITY_HINT, - DEFAULT_ACCESSIBILITY_LABEL, - DEFAULT_ACCESSIBILITY_ROLE, - DEFAULT_ACCESSIBLE, - DEFAULT_APPEARS_ON_INDEX, - DEFAULT_DISAPPEARS_ON_INDEX, - DEFAULT_ENABLE_TOUCH_THROUGH, - DEFAULT_OPACITY, - DEFAULT_PRESS_BEHAVIOR, -} from './constants'; -import { styles } from './styles'; -import type { BottomSheetDefaultBackdropProps } from './types'; - -const BottomSheetBackdropComponent = ({ - animatedIndex, - opacity: _providedOpacity, - appearsOnIndex: _providedAppearsOnIndex, - disappearsOnIndex: _providedDisappearsOnIndex, - enableTouchThrough: _providedEnableTouchThrough, - pressBehavior = DEFAULT_PRESS_BEHAVIOR, - onPress, - style, - children, - accessible: _providedAccessible = DEFAULT_ACCESSIBLE, - accessibilityRole: _providedAccessibilityRole = DEFAULT_ACCESSIBILITY_ROLE, - accessibilityLabel: _providedAccessibilityLabel = DEFAULT_ACCESSIBILITY_LABEL, - accessibilityHint: _providedAccessibilityHint = DEFAULT_ACCESSIBILITY_HINT, -}: BottomSheetDefaultBackdropProps) => { - //#region hooks - const { snapToIndex, close } = useBottomSheet(); - const isMounted = useRef(false); - //#endregion - - //#region defaults - const opacity = _providedOpacity ?? DEFAULT_OPACITY; - const appearsOnIndex = _providedAppearsOnIndex ?? DEFAULT_APPEARS_ON_INDEX; - const disappearsOnIndex = - _providedDisappearsOnIndex ?? DEFAULT_DISAPPEARS_ON_INDEX; - const enableTouchThrough = - _providedEnableTouchThrough ?? DEFAULT_ENABLE_TOUCH_THROUGH; - //#endregion - - //#region variables - const [pointerEvents, setPointerEvents] = useState< - ViewProps['pointerEvents'] - >(enableTouchThrough ? 'none' : 'auto'); - //#endregion - - //#region callbacks - const handleOnPress = useCallback(() => { - onPress?.(); - - if (pressBehavior === 'close') { - close(); - } else if (pressBehavior === 'collapse') { - snapToIndex(disappearsOnIndex as number); - } else if (typeof pressBehavior === 'number') { - snapToIndex(pressBehavior); - } - }, [snapToIndex, close, disappearsOnIndex, pressBehavior, onPress]); - const handleContainerTouchability = useCallback( - (shouldDisableTouchability: boolean) => { - isMounted.current && - setPointerEvents(shouldDisableTouchability ? 'none' : 'auto'); - }, - [] - ); - //#endregion - - //#region tap gesture - const tapHandler = useMemo(() => { - const gesture = Gesture.Tap().onEnd(() => { - runOnJS(handleOnPress)(); - }); - return gesture; - }, [handleOnPress]); - //#endregion - - //#region styles - const containerAnimatedStyle = useAnimatedStyle( - () => ({ - opacity: interpolate( - animatedIndex.value, - [-1, disappearsOnIndex, appearsOnIndex], - [0, 0, opacity], - Extrapolation.CLAMP - ), - }), - [animatedIndex, appearsOnIndex, disappearsOnIndex, opacity] - ); - const containerStyle = useMemo( - () => [ - StyleSheet.absoluteFill, - styles.backdrop, - style, - containerAnimatedStyle, - ], - [style, containerAnimatedStyle] - ); - //#endregion - - //#region effects - useAnimatedReaction( - () => animatedIndex.value <= disappearsOnIndex, - (shouldDisableTouchability, previous) => { - if (shouldDisableTouchability === previous) { - return; - } - runOnJS(handleContainerTouchability)(shouldDisableTouchability); - }, - [disappearsOnIndex] - ); - - // addressing updating the state after unmounting. - // [link](https://github.com/gorhom/react-native-bottom-sheet/issues/1376) - useEffect(() => { - isMounted.current = true; - return () => { - isMounted.current = false; - }; - }, []); - //#endregion - - const AnimatedView = ( - - {children} - - ); - - return pressBehavior !== 'none' ? ( - {AnimatedView} - ) : ( - AnimatedView - ); -}; - -export const BottomSheetBackdrop = memo(BottomSheetBackdropComponent); -BottomSheetBackdrop.displayName = 'BottomSheetBackdrop'; diff --git a/src/components/bottomSheetBackdrop/constants.ts b/src/components/bottomSheetBackdrop/constants.ts deleted file mode 100644 index b3be051d..00000000 --- a/src/components/bottomSheetBackdrop/constants.ts +++ /dev/null @@ -1,22 +0,0 @@ -const DEFAULT_OPACITY = 0.5; -const DEFAULT_APPEARS_ON_INDEX = 1; -const DEFAULT_DISAPPEARS_ON_INDEX = 0; -const DEFAULT_ENABLE_TOUCH_THROUGH = false; -const DEFAULT_PRESS_BEHAVIOR = 'close' as const; - -const DEFAULT_ACCESSIBLE = true; -const DEFAULT_ACCESSIBILITY_ROLE = 'button'; -const DEFAULT_ACCESSIBILITY_LABEL = 'Bottom sheet backdrop'; -const DEFAULT_ACCESSIBILITY_HINT = 'Tap to close the bottom sheet'; - -export { - DEFAULT_ACCESSIBILITY_HINT, - DEFAULT_ACCESSIBILITY_LABEL, - DEFAULT_ACCESSIBILITY_ROLE, - DEFAULT_ACCESSIBLE, - DEFAULT_APPEARS_ON_INDEX, - DEFAULT_DISAPPEARS_ON_INDEX, - DEFAULT_ENABLE_TOUCH_THROUGH, - DEFAULT_OPACITY, - DEFAULT_PRESS_BEHAVIOR, -}; diff --git a/src/components/bottomSheetBackdrop/index.ts b/src/components/bottomSheetBackdrop/index.ts deleted file mode 100644 index 3ed54ddf..00000000 --- a/src/components/bottomSheetBackdrop/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { BottomSheetBackdrop } from './BottomSheetBackdrop'; -export type { BottomSheetBackdropProps } from './types'; diff --git a/src/components/bottomSheetBackdrop/styles.ts b/src/components/bottomSheetBackdrop/styles.ts deleted file mode 100644 index 71800674..00000000 --- a/src/components/bottomSheetBackdrop/styles.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { StyleSheet } from 'react-native'; - -export const styles = StyleSheet.create({ - backdrop: { - backgroundColor: 'black', - }, -}); diff --git a/src/components/bottomSheetBackdrop/types.d.ts b/src/components/bottomSheetBackdrop/types.d.ts deleted file mode 100644 index f03d6c3d..00000000 --- a/src/components/bottomSheetBackdrop/types.d.ts +++ /dev/null @@ -1,57 +0,0 @@ -import type { ReactNode } from 'react'; -import type { ViewProps } from 'react-native'; -import type { - BottomSheetVariables, - NullableAccessibilityProps, -} from '../../types'; - -export interface BottomSheetBackdropProps - extends Pick, - BottomSheetVariables {} - -export type BackdropPressBehavior = 'none' | 'close' | 'collapse' | number; - -export interface BottomSheetDefaultBackdropProps - extends BottomSheetBackdropProps, - NullableAccessibilityProps { - /** - * Backdrop opacity. - * @type number - * @default 0.5 - */ - opacity?: number; - /** - * Snap point index when backdrop will appears on. - * @type number - * @default 1 - */ - appearsOnIndex?: number; - /** - * Snap point index when backdrop will disappears on. - * @type number - * @default 0 - */ - disappearsOnIndex?: number; - /** - * Enable touch through backdrop component. - * @type boolean - * @default false - */ - enableTouchThrough?: boolean; - /** - * What should happen when user press backdrop? - * @type BackdropPressBehavior - * @default 'close' - */ - pressBehavior?: BackdropPressBehavior; - - /** - * Function which will be executed on pressing backdrop component - * @type {Function} - */ - onPress?: () => void; - /** - * Child component that will be rendered on backdrop. - */ - children?: ReactNode | ReactNode[]; -} diff --git a/src/components/bottomSheetBackground/BottomSheetBackground.tsx b/src/components/bottomSheetBackground/BottomSheetBackground.tsx deleted file mode 100644 index 11e7306b..00000000 --- a/src/components/bottomSheetBackground/BottomSheetBackground.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React, { memo } from 'react'; -import { View } from 'react-native'; -import { styles } from './styles'; -import type { BottomSheetBackgroundProps } from './types'; - -const BottomSheetBackgroundComponent = ({ - pointerEvents, - style, -}: BottomSheetBackgroundProps) => ( - -); - -export const BottomSheetBackground = memo(BottomSheetBackgroundComponent); -BottomSheetBackground.displayName = 'BottomSheetBackground'; diff --git a/src/components/bottomSheetBackground/BottomSheetBackgroundContainer.tsx b/src/components/bottomSheetBackground/BottomSheetBackgroundContainer.tsx deleted file mode 100644 index 08dd4938..00000000 --- a/src/components/bottomSheetBackground/BottomSheetBackgroundContainer.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React, { memo, useMemo } from 'react'; -import { StyleSheet } from 'react-native'; -import { BottomSheetBackground } from './BottomSheetBackground'; -import type { BottomSheetBackgroundContainerProps } from './types'; - -const BottomSheetBackgroundContainerComponent = ({ - animatedIndex, - animatedPosition, - backgroundComponent: _providedBackgroundComponent, - backgroundStyle: _providedBackgroundStyle, -}: BottomSheetBackgroundContainerProps) => { - //#region style - const backgroundStyle = useMemo( - () => [StyleSheet.absoluteFill, _providedBackgroundStyle], - [_providedBackgroundStyle] - ); - //#endregion - - const BackgroundComponent = - _providedBackgroundComponent ?? BottomSheetBackground; - return ( - - ); -}; - -export const BottomSheetBackgroundContainer = memo( - BottomSheetBackgroundContainerComponent -); -BottomSheetBackgroundContainer.displayName = 'BottomSheetBackgroundContainer'; diff --git a/src/components/bottomSheetBackground/index.ts b/src/components/bottomSheetBackground/index.ts deleted file mode 100644 index b2a054f0..00000000 --- a/src/components/bottomSheetBackground/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { BottomSheetBackgroundContainer } from './BottomSheetBackgroundContainer'; -export type { BottomSheetBackgroundProps } from './types'; diff --git a/src/components/bottomSheetBackground/styles.ts b/src/components/bottomSheetBackground/styles.ts deleted file mode 100644 index b79bb9c9..00000000 --- a/src/components/bottomSheetBackground/styles.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { StyleSheet } from 'react-native'; - -export const styles = StyleSheet.create({ - background: { - backgroundColor: 'white', - borderRadius: 15, - }, -}); diff --git a/src/components/bottomSheetBackground/types.d.ts b/src/components/bottomSheetBackground/types.d.ts deleted file mode 100644 index a4c6cbac..00000000 --- a/src/components/bottomSheetBackground/types.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { ViewProps } from 'react-native'; -import type { BottomSheetVariables } from '../../types'; -import type { BottomSheetProps } from '../bottomSheet'; -export interface BottomSheetBackgroundProps - extends Pick, - BottomSheetVariables {} - -export type BottomSheetBackgroundContainerProps = Pick< - BottomSheetProps, - 'backgroundComponent' | 'backgroundStyle' -> & - BottomSheetBackgroundProps; diff --git a/src/components/bottomSheetDebugView/BottomSheetDebugView.tsx b/src/components/bottomSheetDebugView/BottomSheetDebugView.tsx deleted file mode 100644 index 39237bda..00000000 --- a/src/components/bottomSheetDebugView/BottomSheetDebugView.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import { View } from 'react-native'; -import type { SharedValue } from 'react-native-reanimated'; -import ReText from './ReText'; -import { styles } from './styles'; - -interface BottomSheetDebugViewProps { - values: Record | number>; -} - -const BottomSheetDebugView = ({ values }: BottomSheetDebugViewProps) => { - return ( - - {Object.keys(values).map(key => ( - - ))} - - ); -}; - -export default BottomSheetDebugView; diff --git a/src/components/bottomSheetDebugView/ReText.tsx b/src/components/bottomSheetDebugView/ReText.tsx deleted file mode 100644 index 30915651..00000000 --- a/src/components/bottomSheetDebugView/ReText.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import React from 'react'; -import { type TextProps as RNTextProps, TextInput } from 'react-native'; -import Animated, { - type AnimatedProps, - type SharedValue, - useAnimatedProps, - useDerivedValue, -} from 'react-native-reanimated'; - -interface TextProps { - text: string; - value: SharedValue | number; - style?: AnimatedProps['style']; -} - -const AnimatedTextInput = Animated.createAnimatedComponent(TextInput); - -Animated.addWhitelistedNativeProps({ text: true }); - -const ReText = ({ text, value: _providedValue, style }: TextProps) => { - const providedValue = useDerivedValue(() => { - if (!_providedValue) { - return ''; - } - - let rawValue: number | string | object | boolean = ''; - if (typeof _providedValue === 'number') { - rawValue = _providedValue as number; - } else if (typeof _providedValue.get === 'function') { - rawValue = _providedValue.get(); - } - - if (typeof rawValue === 'object') { - const rawValueObject = Object.entries(rawValue) - .map(item => `${item[0]}: ${item[1]}`) - .reduce((result, current, index) => { - if (index !== 0) { - result = `${result} \n`; - } - - result = `${result}- ${current}`; - return result; - }, ''); - - return `${text}\n${rawValueObject}`; - } - - if (typeof rawValue === 'number') { - rawValue = rawValue.toFixed(2); - } - - return `${text}: ${rawValue}`; - }, [text, _providedValue]); - const animatedProps = useAnimatedProps(() => { - return { - text: providedValue.get(), - }; - }, [providedValue]); - return ( - - ); -}; - -export default ReText; diff --git a/src/components/bottomSheetDebugView/ReText.webx.tsx b/src/components/bottomSheetDebugView/ReText.webx.tsx deleted file mode 100644 index db9b483f..00000000 --- a/src/components/bottomSheetDebugView/ReText.webx.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React, { useRef } from 'react'; -import { type TextProps as RNTextProps, TextInput } from 'react-native'; -import Animated, { - type AnimatedProps, - type SharedValue, - useAnimatedReaction, - useDerivedValue, -} from 'react-native-reanimated'; - -interface TextProps { - text: string; - value: SharedValue | number; - style?: AnimatedProps['style']; -} - -const AnimatedTextInput = Animated.createAnimatedComponent(TextInput); - -const ReText = (props: TextProps) => { - const { text, value: _providedValue, style } = { style: {}, ...props }; - const textRef = useRef(null); - - const providedValue = useDerivedValue(() => { - const value = - typeof _providedValue === 'number' - ? _providedValue - : typeof _providedValue.value === 'number' - ? _providedValue.value.toFixed(2) - : _providedValue.value; - - return `${text}: ${value}`; - }, [_providedValue, text]); - - //region effects - useAnimatedReaction( - () => providedValue.value, - result => { - textRef.current?.setNativeProps({ - text: result, - }); - }, - [] - ); - //endregion - - return ( - - ); -}; - -export default ReText; diff --git a/src/components/bottomSheetDebugView/index.ts b/src/components/bottomSheetDebugView/index.ts deleted file mode 100644 index 276798f5..00000000 --- a/src/components/bottomSheetDebugView/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './BottomSheetDebugView'; diff --git a/src/components/bottomSheetDebugView/styles.ts b/src/components/bottomSheetDebugView/styles.ts deleted file mode 100644 index 6ea3b884..00000000 --- a/src/components/bottomSheetDebugView/styles.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { StyleSheet } from 'react-native'; - -export const styles = StyleSheet.create({ - container: { - position: 'absolute', - right: 4, - top: 0, - paddingHorizontal: 4, - backgroundColor: 'rgba(0, 0,0,0.5)', - }, - text: { - fontSize: 14, - lineHeight: 16, - textAlignVertical: 'center', - padding: 0, - marginVertical: 4, - color: 'white', - }, -}); diff --git a/src/components/bottomSheetDebugView/styles.web.ts b/src/components/bottomSheetDebugView/styles.web.ts deleted file mode 100644 index d77bfdc0..00000000 --- a/src/components/bottomSheetDebugView/styles.web.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { StyleSheet } from 'react-native'; - -export const styles = StyleSheet.create({ - container: { - position: 'absolute', - left: 4, - top: 80, - padding: 2, - width: 400, - backgroundColor: 'rgba(0, 0,0,0.5)', - }, - text: { - fontSize: 14, - lineHeight: 16, - textAlignVertical: 'center', - height: 20, - padding: 0, - color: 'white', - }, -}); diff --git a/src/components/bottomSheetDraggableView/BottomSheetDraggableView.tsx b/src/components/bottomSheetDraggableView/BottomSheetDraggableView.tsx deleted file mode 100644 index 923e4301..00000000 --- a/src/components/bottomSheetDraggableView/BottomSheetDraggableView.tsx +++ /dev/null @@ -1,123 +0,0 @@ -import React, { memo, useMemo } from 'react'; -import { Gesture, GestureDetector } from 'react-native-gesture-handler'; -import Animated from 'react-native-reanimated'; -import { BottomSheetDraggableContext } from '../../contexts/gesture'; -import { - useBottomSheetGestureHandlers, - useBottomSheetInternal, -} from '../../hooks'; -import type { BottomSheetDraggableViewProps } from './types'; - -const BottomSheetDraggableViewComponent = ({ - nativeGestureRef, - refreshControlGestureRef, - style, - children, - ...rest -}: BottomSheetDraggableViewProps) => { - //#region hooks - const { - enableContentPanningGesture, - simultaneousHandlers: _providedSimultaneousHandlers, - waitFor, - activeOffsetX, - activeOffsetY, - failOffsetX, - failOffsetY, - } = useBottomSheetInternal(); - const { contentPanGestureHandler } = useBottomSheetGestureHandlers(); - //#endregion - - //#region variables - const simultaneousHandlers = useMemo(() => { - const refs = []; - - if (nativeGestureRef) { - refs.push(nativeGestureRef); - } - - if (refreshControlGestureRef) { - refs.push(refreshControlGestureRef); - } - - if (_providedSimultaneousHandlers) { - if (Array.isArray(_providedSimultaneousHandlers)) { - refs.push(..._providedSimultaneousHandlers); - } else { - refs.push(_providedSimultaneousHandlers); - } - } - - return refs; - }, [ - _providedSimultaneousHandlers, - nativeGestureRef, - refreshControlGestureRef, - ]); - const draggableGesture = useMemo(() => { - let gesture = Gesture.Pan() - .enabled(enableContentPanningGesture) - .shouldCancelWhenOutside(false) - .runOnJS(false) - .onStart(contentPanGestureHandler.handleOnStart) - .onChange(contentPanGestureHandler.handleOnChange) - .onEnd(contentPanGestureHandler.handleOnEnd) - .onFinalize(contentPanGestureHandler.handleOnFinalize); - - if (waitFor) { - gesture = gesture.requireExternalGestureToFail(waitFor); - } - - if (simultaneousHandlers) { - gesture = gesture.simultaneousWithExternalGesture( - simultaneousHandlers as never - ); - } - - if (activeOffsetX) { - gesture = gesture.activeOffsetX(activeOffsetX); - } - - if (activeOffsetY) { - gesture = gesture.activeOffsetY(activeOffsetY); - } - - if (failOffsetX) { - gesture = gesture.failOffsetX(failOffsetX); - } - - if (failOffsetY) { - gesture = gesture.failOffsetY(failOffsetY); - } - - return gesture; - }, [ - activeOffsetX, - activeOffsetY, - enableContentPanningGesture, - failOffsetX, - failOffsetY, - simultaneousHandlers, - waitFor, - contentPanGestureHandler.handleOnChange, - contentPanGestureHandler.handleOnEnd, - contentPanGestureHandler.handleOnFinalize, - contentPanGestureHandler.handleOnStart, - ]); - //#endregion - - return ( - - - - {children} - - - - ); -}; - -const BottomSheetDraggableView = memo(BottomSheetDraggableViewComponent); -BottomSheetDraggableView.displayName = 'BottomSheetDraggableView'; - -export default BottomSheetDraggableView; diff --git a/src/components/bottomSheetDraggableView/index.ts b/src/components/bottomSheetDraggableView/index.ts deleted file mode 100644 index 3d86cb04..00000000 --- a/src/components/bottomSheetDraggableView/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './BottomSheetDraggableView'; diff --git a/src/components/bottomSheetDraggableView/types.d.ts b/src/components/bottomSheetDraggableView/types.d.ts deleted file mode 100644 index 5ed61d78..00000000 --- a/src/components/bottomSheetDraggableView/types.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { ReactNode } from 'react'; -import type { ViewProps as RNViewProps } from 'react-native'; -import type { GestureRef } from 'react-native-gesture-handler/lib/typescript/handlers/gestures/gesture'; - -export type BottomSheetDraggableViewProps = RNViewProps & { - nativeGestureRef?: Exclude; - refreshControlGestureRef?: Exclude; - children: ReactNode[] | ReactNode; -}; diff --git a/src/components/bottomSheetFooter/BottomSheetFooter.tsx b/src/components/bottomSheetFooter/BottomSheetFooter.tsx deleted file mode 100644 index 639f2af3..00000000 --- a/src/components/bottomSheetFooter/BottomSheetFooter.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import React, { memo, useCallback, useMemo, useRef } from 'react'; -import type { LayoutChangeEvent } from 'react-native'; -import Animated, { useAnimatedStyle } from 'react-native-reanimated'; -import { KEYBOARD_STATUS } from '../../constants'; -import { - type BoundingClientRect, - useBottomSheetInternal, - useBoundingClientRect, -} from '../../hooks'; -import { print } from '../../utilities'; -import { styles } from './styles'; -import type { BottomSheetDefaultFooterProps } from './types'; - -function BottomSheetFooterComponent({ - animatedFooterPosition, - bottomInset = 0, - style, - children, -}: BottomSheetDefaultFooterProps) { - //#region refs - const ref = useRef(null); - //#endregion - - //#region hooks - const { animatedLayoutState, animatedKeyboardState } = - useBottomSheetInternal(); - //#endregion - - //#region styles - const containerAnimatedStyle = useAnimatedStyle(() => { - let footerTranslateY = animatedFooterPosition.get(); - - /** - * Offset the bottom inset only when keyboard is not shown - */ - if (animatedKeyboardState.get().status !== KEYBOARD_STATUS.SHOWN) { - footerTranslateY = footerTranslateY - bottomInset; - } - - return { - transform: [ - { - translateY: Math.max(0, footerTranslateY), - }, - ], - }; - }, [bottomInset, animatedKeyboardState, animatedFooterPosition]); - const containerStyle = useMemo( - () => [styles.container, style, containerAnimatedStyle], - [style, containerAnimatedStyle] - ); - //#endregion - - //#region callbacks - const handleContainerLayout = useCallback( - ({ - nativeEvent: { - layout: { height }, - }, - }: LayoutChangeEvent) => { - animatedLayoutState.modify(state => { - 'worklet'; - state.footerHeight = height; - return state; - }); - - if (__DEV__) { - print({ - component: 'BottomSheetFooter', - method: 'handleContainerLayout', - category: 'layout', - params: { - height, - }, - }); - } - }, - [animatedLayoutState] - ); - const handleBoundingClientRect = useCallback( - ({ height }: BoundingClientRect) => { - animatedLayoutState.modify(state => { - 'worklet'; - state.footerHeight = height; - return state; - }); - - if (__DEV__) { - print({ - component: 'BottomSheetFooter', - method: 'handleBoundingClientRect', - category: 'layout', - params: { - height, - }, - }); - } - }, - [animatedLayoutState] - ); - //#endregion - - //#region effects - useBoundingClientRect(ref, handleBoundingClientRect); - //#endregion - - return children !== null ? ( - - {children} - - ) : null; -} - -export const BottomSheetFooter = memo(BottomSheetFooterComponent); -BottomSheetFooter.displayName = 'BottomSheetFooter'; diff --git a/src/components/bottomSheetFooter/BottomSheetFooterContainer.tsx b/src/components/bottomSheetFooter/BottomSheetFooterContainer.tsx deleted file mode 100644 index 183903f7..00000000 --- a/src/components/bottomSheetFooter/BottomSheetFooterContainer.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React, { memo } from 'react'; -import { useDerivedValue } from 'react-native-reanimated'; -import { INITIAL_LAYOUT_VALUE, KEYBOARD_STATUS } from '../../constants'; -import { useBottomSheetInternal } from '../../hooks'; -import type { BottomSheetFooterContainerProps } from './types'; - -const BottomSheetFooterContainerComponent = ({ - footerComponent: FooterComponent, -}: BottomSheetFooterContainerProps) => { - //#region hooks - const { animatedLayoutState, animatedPosition, animatedKeyboardState } = - useBottomSheetInternal(); - //#endregion - - //#region variables - const animatedFooterPosition = useDerivedValue(() => { - const { handleHeight, footerHeight, containerHeight } = - animatedLayoutState.get(); - if (handleHeight === INITIAL_LAYOUT_VALUE) { - return 0; - } - - const { status: keyboardStatus, heightWithinContainer: keyboardHeight } = - animatedKeyboardState.get(); - const position = animatedPosition.get(); - - let footerTranslateY = Math.max(0, containerHeight - position); - if (keyboardStatus === KEYBOARD_STATUS.SHOWN) { - footerTranslateY = footerTranslateY - keyboardHeight; - } - - footerTranslateY = footerTranslateY - footerHeight - handleHeight; - return footerTranslateY; - }, [animatedKeyboardState, animatedPosition, animatedLayoutState]); - //#endregion - - return ; -}; - -export const BottomSheetFooterContainer = memo( - BottomSheetFooterContainerComponent -); -BottomSheetFooterContainer.displayName = 'BottomSheetFooterContainer'; diff --git a/src/components/bottomSheetFooter/index.ts b/src/components/bottomSheetFooter/index.ts deleted file mode 100644 index 73d4c88c..00000000 --- a/src/components/bottomSheetFooter/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { BottomSheetFooter } from './BottomSheetFooter'; -export { BottomSheetFooterContainer } from './BottomSheetFooterContainer'; -export type { BottomSheetFooterProps } from './types'; diff --git a/src/components/bottomSheetFooter/styles.ts b/src/components/bottomSheetFooter/styles.ts deleted file mode 100644 index 278f702e..00000000 --- a/src/components/bottomSheetFooter/styles.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { StyleSheet } from 'react-native'; - -export const styles = StyleSheet.create({ - container: { - position: 'absolute', - top: 0, - left: 0, - right: 0, - zIndex: 9999, - pointerEvents: 'box-none', - }, -}); diff --git a/src/components/bottomSheetFooter/types.d.ts b/src/components/bottomSheetFooter/types.d.ts deleted file mode 100644 index 268ac075..00000000 --- a/src/components/bottomSheetFooter/types.d.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { ReactNode } from 'react'; -import type { ViewStyle } from 'react-native'; -import type { SharedValue } from 'react-native-reanimated'; -import type { BottomSheetProps } from '../bottomSheet/types'; - -export interface BottomSheetFooterProps { - /** - * Calculated footer animated position. - * - * @type SharedValue - */ - animatedFooterPosition: SharedValue; -} - -export interface BottomSheetDefaultFooterProps extends BottomSheetFooterProps { - /** - * Bottom inset to be added below the footer, usually comes - * from `react-native-safe-area-context` hook `useSafeArea`. - * - * @type number - * @default 0 - */ - bottomInset?: number; - - /** - * Container style. - * - * @type ViewStyle - */ - style?: ViewStyle; - - /** - * Component to be placed in the footer. - * - * @type {ReactNode|ReactNode[]} - */ - children?: ReactNode | ReactNode[]; -} - -export interface BottomSheetFooterContainerProps - extends Required> {} diff --git a/src/components/bottomSheetGestureHandlersProvider/BottomSheetGestureHandlersProvider.tsx b/src/components/bottomSheetGestureHandlersProvider/BottomSheetGestureHandlersProvider.tsx deleted file mode 100644 index d1860b98..00000000 --- a/src/components/bottomSheetGestureHandlersProvider/BottomSheetGestureHandlersProvider.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React, { useMemo } from 'react'; -import { useSharedValue } from 'react-native-reanimated'; -import { GESTURE_SOURCE } from '../../constants'; -import { BottomSheetGestureHandlersContext } from '../../contexts'; -import { - useBottomSheetInternal, - useGestureEventsHandlersDefault, - useGestureHandler, -} from '../../hooks'; -import type { BottomSheetGestureHandlersProviderProps } from './types'; - -const BottomSheetGestureHandlersProvider = ({ - gestureEventsHandlersHook: - useGestureEventsHandlers = useGestureEventsHandlersDefault, - children, -}: BottomSheetGestureHandlersProviderProps) => { - //#region variables - const animatedGestureSource = useSharedValue( - GESTURE_SOURCE.UNDETERMINED - ); - //#endregion - - //#region hooks - const { animatedContentGestureState, animatedHandleGestureState } = - useBottomSheetInternal(); - const { handleOnStart, handleOnChange, handleOnEnd, handleOnFinalize } = - useGestureEventsHandlers(); - //#endregion - - //#region gestures - const contentPanGestureHandler = useGestureHandler( - GESTURE_SOURCE.CONTENT, - animatedContentGestureState, - animatedGestureSource, - handleOnStart, - handleOnChange, - handleOnEnd, - handleOnFinalize - ); - - const handlePanGestureHandler = useGestureHandler( - GESTURE_SOURCE.HANDLE, - animatedHandleGestureState, - animatedGestureSource, - handleOnStart, - handleOnChange, - handleOnEnd, - handleOnFinalize - ); - //#endregion - - //#region context - const contextValue = useMemo( - () => ({ - contentPanGestureHandler, - handlePanGestureHandler, - animatedGestureSource, - }), - [contentPanGestureHandler, handlePanGestureHandler, animatedGestureSource] - ); - //#endregion - return ( - - {children} - - ); -}; - -export default BottomSheetGestureHandlersProvider; diff --git a/src/components/bottomSheetGestureHandlersProvider/index.ts b/src/components/bottomSheetGestureHandlersProvider/index.ts deleted file mode 100644 index ab5338f6..00000000 --- a/src/components/bottomSheetGestureHandlersProvider/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './BottomSheetGestureHandlersProvider'; diff --git a/src/components/bottomSheetGestureHandlersProvider/types.d.ts b/src/components/bottomSheetGestureHandlersProvider/types.d.ts deleted file mode 100644 index 9acdb1f9..00000000 --- a/src/components/bottomSheetGestureHandlersProvider/types.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; -import type { BottomSheetProps } from '../bottomSheet/types'; - -export interface BottomSheetGestureHandlersProviderProps - extends Pick { - children?: React.ReactNode; -} diff --git a/src/components/bottomSheetHandle/BottomSheetHandle.tsx b/src/components/bottomSheetHandle/BottomSheetHandle.tsx deleted file mode 100644 index 0c95d13a..00000000 --- a/src/components/bottomSheetHandle/BottomSheetHandle.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { memo, useMemo } from 'react'; -import { StyleSheet, View } from 'react-native'; -import { - DEFAULT_ACCESSIBILITY_HINT, - DEFAULT_ACCESSIBILITY_LABEL, - DEFAULT_ACCESSIBILITY_ROLE, - DEFAULT_ACCESSIBLE, -} from './constants'; -import { styles } from './styles'; -import type { BottomSheetDefaultHandleProps } from './types'; - -function BottomSheetHandleComponent({ - style, - indicatorStyle: _indicatorStyle, - accessible = DEFAULT_ACCESSIBLE, - accessibilityRole = DEFAULT_ACCESSIBILITY_ROLE, - accessibilityLabel = DEFAULT_ACCESSIBILITY_LABEL, - accessibilityHint = DEFAULT_ACCESSIBILITY_HINT, - children, -}: BottomSheetDefaultHandleProps) { - //#region styles - const containerStyle = useMemo( - () => [styles.container, StyleSheet.flatten(style)], - [style] - ); - const indicatorStyle = useMemo( - () => [styles.indicator, StyleSheet.flatten(_indicatorStyle)], - [_indicatorStyle] - ); - //#endregion - - // render - return ( - - - {children} - - ); -} - -const BottomSheetHandle = memo(BottomSheetHandleComponent); -BottomSheetHandle.displayName = 'BottomSheetHandle'; - -export default BottomSheetHandle; diff --git a/src/components/bottomSheetHandle/BottomSheetHandleContainer.tsx b/src/components/bottomSheetHandle/BottomSheetHandleContainer.tsx deleted file mode 100644 index 02238437..00000000 --- a/src/components/bottomSheetHandle/BottomSheetHandleContainer.tsx +++ /dev/null @@ -1,187 +0,0 @@ -import React, { memo, useCallback, useMemo, useRef } from 'react'; -import type { LayoutChangeEvent, View } from 'react-native'; -import { Gesture, GestureDetector } from 'react-native-gesture-handler'; -import Animated from 'react-native-reanimated'; -import { - type BoundingClientRect, - useBottomSheetGestureHandlers, - useBottomSheetInternal, - useBoundingClientRect, -} from '../../hooks'; -import { print } from '../../utilities'; -import { DEFAULT_ENABLE_HANDLE_PANNING_GESTURE } from '../bottomSheet/constants'; -import type { BottomSheetHandleContainerProps } from './types'; - -function BottomSheetHandleContainerComponent({ - animatedIndex, - animatedPosition, - simultaneousHandlers: _internalSimultaneousHandlers, - enableHandlePanningGesture = DEFAULT_ENABLE_HANDLE_PANNING_GESTURE, - handleComponent: HandleComponent, - handleStyle: _providedHandleStyle, - handleIndicatorStyle: _providedIndicatorStyle, -}: BottomSheetHandleContainerProps) { - //#region refs - const ref = useRef(null); - //#endregion - - //#region hooks - const { - animatedLayoutState, - activeOffsetX, - activeOffsetY, - failOffsetX, - failOffsetY, - waitFor, - simultaneousHandlers: _providedSimultaneousHandlers, - } = useBottomSheetInternal(); - const { handlePanGestureHandler } = useBottomSheetGestureHandlers(); - //#endregion - - //#region variables - const simultaneousHandlers = useMemo(() => { - const refs = []; - - if (_internalSimultaneousHandlers) { - refs.push(_internalSimultaneousHandlers); - } - - if (_providedSimultaneousHandlers) { - if (Array.isArray(_providedSimultaneousHandlers)) { - refs.push(..._providedSimultaneousHandlers); - } else { - refs.push(_providedSimultaneousHandlers); - } - } - - return refs; - }, [_providedSimultaneousHandlers, _internalSimultaneousHandlers]); - const panGesture = useMemo(() => { - let gesture = Gesture.Pan() - .enabled(enableHandlePanningGesture) - .shouldCancelWhenOutside(false) - .runOnJS(false) - .onStart(handlePanGestureHandler.handleOnStart) - .onChange(handlePanGestureHandler.handleOnChange) - .onEnd(handlePanGestureHandler.handleOnEnd) - .onFinalize(handlePanGestureHandler.handleOnFinalize); - - if (waitFor) { - gesture = gesture.requireExternalGestureToFail(waitFor); - } - - if (simultaneousHandlers) { - gesture = gesture.simultaneousWithExternalGesture( - simultaneousHandlers as never - ); - } - - if (activeOffsetX) { - gesture = gesture.activeOffsetX(activeOffsetX); - } - - if (activeOffsetY) { - gesture = gesture.activeOffsetY(activeOffsetY); - } - - if (failOffsetX) { - gesture = gesture.failOffsetX(failOffsetX); - } - - if (failOffsetY) { - gesture = gesture.failOffsetY(failOffsetY); - } - - return gesture; - }, [ - activeOffsetX, - activeOffsetY, - enableHandlePanningGesture, - failOffsetX, - failOffsetY, - simultaneousHandlers, - waitFor, - handlePanGestureHandler.handleOnChange, - handlePanGestureHandler.handleOnEnd, - handlePanGestureHandler.handleOnFinalize, - handlePanGestureHandler.handleOnStart, - ]); - //#endregion - - //#region callbacks - const handleContainerLayout = useCallback( - function handleContainerLayout({ - nativeEvent: { - layout: { height }, - }, - }: LayoutChangeEvent) { - animatedLayoutState.modify(state => { - 'worklet'; - state.handleHeight = height; - return state; - }); - - if (__DEV__) { - print({ - component: 'BottomSheetHandleContainer', - method: 'handleContainerLayout', - category: 'layout', - params: { - height, - }, - }); - } - }, - [animatedLayoutState] - ); - const handleBoundingClientRect = useCallback( - ({ height }: BoundingClientRect) => { - animatedLayoutState.modify(state => { - 'worklet'; - state.handleHeight = height; - return state; - }); - - if (__DEV__) { - print({ - component: 'BottomSheetHandleContainer', - method: 'handleBoundingClientRect', - category: 'layout', - params: { - height, - }, - }); - } - }, - [animatedLayoutState] - ); - //#endregion - - //#region effects - useBoundingClientRect(ref, handleBoundingClientRect); - //#endregion - - //#region renders - return ( - - - - - - ); - //#endregion -} - -const BottomSheetHandleContainer = memo(BottomSheetHandleContainerComponent); -BottomSheetHandleContainer.displayName = 'BottomSheetHandleContainer'; - -export default BottomSheetHandleContainer; diff --git a/src/components/bottomSheetHandle/constants.ts b/src/components/bottomSheetHandle/constants.ts deleted file mode 100644 index 3af6afe3..00000000 --- a/src/components/bottomSheetHandle/constants.ts +++ /dev/null @@ -1,12 +0,0 @@ -const DEFAULT_ACCESSIBLE = true; -const DEFAULT_ACCESSIBILITY_ROLE = 'adjustable'; -const DEFAULT_ACCESSIBILITY_LABEL = 'Bottom sheet handle'; -const DEFAULT_ACCESSIBILITY_HINT = - 'Drag up or down to extend or minimize the bottom sheet'; - -export { - DEFAULT_ACCESSIBILITY_HINT, - DEFAULT_ACCESSIBILITY_LABEL, - DEFAULT_ACCESSIBILITY_ROLE, - DEFAULT_ACCESSIBLE, -}; diff --git a/src/components/bottomSheetHandle/index.ts b/src/components/bottomSheetHandle/index.ts deleted file mode 100644 index 564b5f22..00000000 --- a/src/components/bottomSheetHandle/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { default as BottomSheetHandle } from './BottomSheetHandle'; -export { default as BottomSheetHandleContainer } from './BottomSheetHandleContainer'; -export type { - BottomSheetHandleContainerProps, - BottomSheetHandleProps, -} from './types'; diff --git a/src/components/bottomSheetHandle/styles.ts b/src/components/bottomSheetHandle/styles.ts deleted file mode 100644 index c0c050a5..00000000 --- a/src/components/bottomSheetHandle/styles.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Dimensions, Platform, StyleSheet } from 'react-native'; - -export const styles = StyleSheet.create({ - container: { - padding: 10, - ...Platform.select({ - web: { - cursor: 'pointer' as const, - }, - default: {}, - }), - }, - - indicator: { - alignSelf: 'center', - width: (7.5 * Dimensions.get('window').width) / 100, - height: 4, - borderRadius: 4, - backgroundColor: 'rgba(0, 0, 0, 0.75)', - }, -}); diff --git a/src/components/bottomSheetHandle/types.d.ts b/src/components/bottomSheetHandle/types.d.ts deleted file mode 100644 index 7693e15f..00000000 --- a/src/components/bottomSheetHandle/types.d.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type React from 'react'; -import type { ViewProps } from 'react-native'; -import type { PanGestureHandlerProperties } from 'react-native-gesture-handler'; -import type { useInteractivePanGestureHandlerConfigs } from '../../hooks/useGestureHandler'; -import type { - BottomSheetVariables, - NullableAccessibilityProps, -} from '../../types'; -import type { BottomSheetProps } from '../bottomSheet'; - -export type BottomSheetHandleProps = BottomSheetVariables; -export interface BottomSheetDefaultHandleProps - extends BottomSheetHandleProps, - NullableAccessibilityProps { - /** - * View style to be applied to the handle container. - * @type ViewStyle - * @default undefined - */ - style?: ViewProps['style']; - /** - * View style to be applied to the handle indicator. - * @type ViewStyle - * @default undefined - */ - indicatorStyle?: ViewProps['style']; - /** - * Content to be added below the indicator. - * @type React.ReactNode | React.ReactNode[]; - * @default undefined - */ - children?: React.ReactNode | React.ReactNode[]; -} - -export type BottomSheetHandleContainerProps = Pick< - PanGestureHandlerProperties, - 'simultaneousHandlers' -> & { - handleComponent: React.FC; -} & Pick< - BottomSheetProps, - 'enableHandlePanningGesture' | 'handleIndicatorStyle' | 'handleStyle' - > & - Pick< - useInteractivePanGestureHandlerConfigs, - | 'enableOverDrag' - | 'enablePanDownToClose' - | 'overDragResistanceFactor' - | 'keyboardBehavior' - > & - BottomSheetHandleProps; diff --git a/src/components/bottomSheetHostingContainer/BottomSheetHostingContainer.tsx b/src/components/bottomSheetHostingContainer/BottomSheetHostingContainer.tsx deleted file mode 100644 index 879061de..00000000 --- a/src/components/bottomSheetHostingContainer/BottomSheetHostingContainer.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import React, { memo, useCallback, useMemo, useRef } from 'react'; -import { - Dimensions, - type LayoutChangeEvent, - StatusBar, - type StyleProp, - StyleSheet, - View, - type ViewStyle, -} from 'react-native'; -import { print } from '../../utilities'; -import { styles } from './styles'; -import type { BottomSheetHostingContainerProps } from './types'; - -function BottomSheetHostingContainerComponent({ - containerLayoutState, - layoutState, - topInset = 0, - bottomInset = 0, - shouldCalculateHeight = true, - detached, - style, - children, -}: BottomSheetHostingContainerProps) { - //#region refs - const containerRef = useRef(null); - //#endregion - - //#region styles - const containerStyle = useMemo>( - () => [ - style, - StyleSheet.absoluteFill, - styles.container, - { - top: topInset, - bottom: bottomInset, - overflow: detached ? 'visible' : 'hidden', - }, - ], - [style, detached, topInset, bottomInset] - ); - //#endregion - - //#region callbacks - const handleLayoutEvent = useCallback( - function handleLayoutEvent({ - nativeEvent: { - layout: { height }, - }, - }: LayoutChangeEvent) { - if (containerLayoutState) { - containerLayoutState.modify(state => { - 'worklet'; - state.height = height; - return state; - }); - } - - if (layoutState) { - layoutState.modify(state => { - 'worklet'; - state.rawContainerHeight = height; - return state; - }); - } - - const WINDOW_HEIGHT = Dimensions.get('window').height; - containerRef.current?.measure( - (_x, _y, _width, _height, _pageX, pageY) => { - const offset = { - bottom: Math.max( - 0, - WINDOW_HEIGHT - - ((pageY ?? 0) + height + (StatusBar.currentHeight ?? 0)) - ), - top: pageY ?? 0, - left: 0, - right: 0, - }; - - if (containerLayoutState) { - containerLayoutState.modify(state => { - 'worklet'; - state.offset = offset; - return state; - }); - } - - if (layoutState) { - layoutState.modify(state => { - 'worklet'; - state.containerOffset = offset; - return state; - }); - } - } - ); - - if (__DEV__) { - print({ - component: 'BottomSheetHostingContainer', - method: 'handleLayoutEvent', - category: 'layout', - params: { - height, - }, - }); - } - }, - [layoutState, containerLayoutState] - ); - //#endregion - - //#region render - return ( - - {children} - - ); - //#endregion -} - -export const BottomSheetHostingContainer = memo( - BottomSheetHostingContainerComponent -); -BottomSheetHostingContainer.displayName = 'BottomSheetHostingContainer'; diff --git a/src/components/bottomSheetHostingContainer/index.ts b/src/components/bottomSheetHostingContainer/index.ts deleted file mode 100644 index 3aa30185..00000000 --- a/src/components/bottomSheetHostingContainer/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { BottomSheetHostingContainer } from './BottomSheetHostingContainer'; -export type { BottomSheetHostingContainerProps } from './types'; diff --git a/src/components/bottomSheetHostingContainer/styles.ts b/src/components/bottomSheetHostingContainer/styles.ts deleted file mode 100644 index c9d917ba..00000000 --- a/src/components/bottomSheetHostingContainer/styles.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { StyleSheet } from 'react-native'; - -export const styles = StyleSheet.create({ - container: { pointerEvents: 'box-none' }, -}); diff --git a/src/components/bottomSheetHostingContainer/styles.web.ts b/src/components/bottomSheetHostingContainer/styles.web.ts deleted file mode 100644 index 42836a23..00000000 --- a/src/components/bottomSheetHostingContainer/styles.web.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { StyleSheet } from 'react-native'; - -export const styles = StyleSheet.create({ - container: { - position: 'absolute', - left: 0, - right: 0, - bottom: 0, - top: 0, - }, -}); diff --git a/src/components/bottomSheetHostingContainer/types.d.ts b/src/components/bottomSheetHostingContainer/types.d.ts deleted file mode 100644 index ed43773f..00000000 --- a/src/components/bottomSheetHostingContainer/types.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { ReactNode } from 'react'; -import type { StyleProp, ViewStyle } from 'react-native'; -import type { SharedValue } from 'react-native-reanimated'; -import type { ContainerLayoutState, LayoutState } from '../../types'; -import type { BottomSheetProps } from '../bottomSheet/types'; - -export interface BottomSheetHostingContainerProps - extends Partial< - Pick - > { - containerLayoutState?: SharedValue; - layoutState?: SharedValue; - - shouldCalculateHeight?: boolean; - style?: StyleProp; - children?: ReactNode; -} diff --git a/src/components/bottomSheetModal/BottomSheetModal.tsx b/src/components/bottomSheetModal/BottomSheetModal.tsx deleted file mode 100644 index 2954b3f4..00000000 --- a/src/components/bottomSheetModal/BottomSheetModal.tsx +++ /dev/null @@ -1,580 +0,0 @@ -/** biome-ignore-all lint/correctness/useHookAtTopLevel: random error needs extra time to debug */ -import { Portal, usePortal } from '@gorhom/portal'; -import React, { - forwardRef, - memo, - type RefObject, - useCallback, - useImperativeHandle, - useMemo, - useRef, - useState, -} from 'react'; -import type { SNAP_POINT_TYPE } from '../../constants'; -import { useBottomSheetModalInternal } from '../../hooks'; -import type { BottomSheetMethods, BottomSheetModalMethods } from '../../types'; -import { print } from '../../utilities'; -import { id } from '../../utilities/id'; -import BottomSheet from '../bottomSheet'; -import { - DEFAULT_ENABLE_DISMISS_ON_CLOSE, - DEFAULT_STACK_BEHAVIOR, - MODAL_STATUS, -} from './constants'; -import type { - BottomSheetModalPrivateMethods, - BottomSheetModalProps, - BottomSheetModalState, -} from './types'; - -const INITIAL_STATE: BottomSheetModalState = { - mount: false, - data: undefined, -}; - -type BottomSheetModal = BottomSheetModalMethods; - -function BottomSheetModalComponent( - props: BottomSheetModalProps, - ref: React.ForwardedRef> -) { - const { - // modal props - name, - stackBehavior = DEFAULT_STACK_BEHAVIOR, - enableDismissOnClose = DEFAULT_ENABLE_DISMISS_ON_CLOSE, - onDismiss: _providedOnDismiss, - onAnimate: _providedOnAnimate, - - // bottom sheet props - index = 0, - snapPoints, - enablePanDownToClose = true, - animateOnMount = true, - containerComponent: ContainerComponent = React.Fragment, - - // callbacks - onChange: _providedOnChange, - - // components - children: Content, - ...bottomSheetProps - } = props; - - //#region state - const [{ mount, data }, setState] = - useState>(INITIAL_STATE); - const mountRef = useRef(mount); - mountRef.current = mount; - //#endregion - - //#region hooks - const { - hostName, - containerLayoutState, - mountSheet, - unmountSheet, - willUnmountSheet, - } = useBottomSheetModalInternal(); - const { removePortal: unmountPortal } = usePortal(hostName); - //#endregion - - //#region refs - const bottomSheetRef = useRef(null); - const statusRef = useRef(MODAL_STATUS.INITIAL); - const currentIndexRef = useRef(!animateOnMount ? index : -1); - const nextIndexRef = useRef(null); - const restoreIndexRef = useRef(-1); - //#endregion - - //#region variables - const key = useMemo(() => name || `bottom-sheet-modal-${id()}`, [name]); - //#endregion - - //#region private methods - const resetVariables = useCallback(function resetVariables() { - if (__DEV__) { - print({ - component: 'BottomSheetModal', - method: resetVariables.name, - }); - } - currentIndexRef.current = -1; - restoreIndexRef.current = -1; - statusRef.current = MODAL_STATUS.INITIAL; - }, []); - const unmount = useCallback( - function unmount() { - if (__DEV__) { - print({ - component: 'BottomSheetModal', - method: unmount.name, - }); - } - const hadReactMount = mountRef.current; - - // reset variables - resetVariables(); - - // unmount sheet and portal - unmountSheet(key); - unmountPortal(key); - - // unmount the node, if sheet is still mounted in React state - if (hadReactMount) { - setState(INITIAL_STATE); - } - - // fire `onDismiss` callback - if (_providedOnDismiss) { - _providedOnDismiss(); - } - }, - [key, resetVariables, unmountSheet, unmountPortal, _providedOnDismiss] - ); - //#endregion - - //#region bottom sheet methods - const handleSnapToIndex = useCallback( - (...args) => { - if ( - statusRef.current === MODAL_STATUS.MINIMIZED || - statusRef.current === MODAL_STATUS.MINIMIZING - ) { - return; - } - bottomSheetRef.current?.snapToIndex(...args); - }, - [] - ); - const handleSnapToPosition = useCallback< - BottomSheetMethods['snapToPosition'] - >((...args) => { - if ( - [ - MODAL_STATUS.MINIMIZED, - MODAL_STATUS.MINIMIZING, - MODAL_STATUS.DISMISSED, - MODAL_STATUS.DISMISSING, - ].includes(statusRef.current) - ) { - return; - } - bottomSheetRef.current?.snapToPosition(...args); - }, []); - const handleExpand: BottomSheetMethods['expand'] = useCallback((...args) => { - if ( - [ - MODAL_STATUS.MINIMIZED, - MODAL_STATUS.MINIMIZING, - MODAL_STATUS.DISMISSED, - MODAL_STATUS.DISMISSING, - ].includes(statusRef.current) - ) { - return; - } - bottomSheetRef.current?.expand(...args); - }, []); - const handleCollapse: BottomSheetMethods['collapse'] = useCallback( - (...args) => { - if ( - [ - MODAL_STATUS.MINIMIZED, - MODAL_STATUS.MINIMIZING, - MODAL_STATUS.DISMISSED, - MODAL_STATUS.DISMISSING, - ].includes(statusRef.current) - ) { - return; - } - bottomSheetRef.current?.collapse(...args); - }, - [] - ); - const handleClose: BottomSheetMethods['close'] = useCallback((...args) => { - if ( - [ - MODAL_STATUS.MINIMIZED, - MODAL_STATUS.MINIMIZING, - MODAL_STATUS.DISMISSED, - MODAL_STATUS.DISMISSING, - MODAL_STATUS.CLOSED, - ].includes(statusRef.current) - ) { - return; - } - bottomSheetRef.current?.close(...args); - }, []); - const handleForceClose: BottomSheetMethods['forceClose'] = useCallback( - (...args) => { - if ( - [ - MODAL_STATUS.MINIMIZED, - MODAL_STATUS.MINIMIZING, - MODAL_STATUS.DISMISSED, - MODAL_STATUS.DISMISSING, - MODAL_STATUS.CLOSED, - ].includes(statusRef.current) - ) { - return; - } - bottomSheetRef.current?.forceClose(...args); - }, - [] - ); - //#endregion - - //#region bottom sheet modal methods - // biome-ignore lint/correctness/useExhaustiveDependencies(ref): ref is a stable object - const handlePresent = useCallback( - function handlePresent(_data?: T) { - if (__DEV__) { - print({ - component: 'BottomSheetModal', - method: handlePresent.name, - params: { - currentIndexRef: currentIndexRef.current, - nextIndexRef: nextIndexRef.current, - status: statusRef.current, - }, - }); - } - - requestAnimationFrame(() => { - if (mount && bottomSheetRef.current) { - statusRef.current = MODAL_STATUS.ANIMATING; - bottomSheetRef.current.snapToIndex(index); - } - - setState({ - mount: true, - data: _data, - }); - - mountSheet( - key, - ref as unknown as RefObject, - stackBehavior - ); - }); - }, - [index, key, stackBehavior, mount, mountSheet] - ); - const handleDismiss = useCallback( - function handleDismiss(animationConfigs) { - if (__DEV__) { - print({ - component: 'BottomSheetModal', - method: handleDismiss.name, - params: { - status: statusRef.current, - }, - }); - } - - /** - * if the modal position is already in a closed position, - * then we unmount the node and early exit. - */ - if ( - [MODAL_STATUS.CLOSED, MODAL_STATUS.MINIMIZED].includes( - statusRef.current - ) || - (statusRef.current === MODAL_STATUS.DISMISSING && - currentIndexRef.current === -1) - ) { - statusRef.current = MODAL_STATUS.DISMISSED; - unmount(); - return; - } - - statusRef.current = MODAL_STATUS.DISMISSING; - willUnmountSheet(key); - bottomSheetRef.current?.forceClose(animationConfigs); - }, - [willUnmountSheet, unmount, key] - ); - const handleMinimize = useCallback( - function handleMinimize() { - if (__DEV__) { - print({ - component: 'BottomSheetModal', - method: handleMinimize.name, - params: { - index, - currentIndexRef: currentIndexRef.current, - status: statusRef.current, - }, - }); - } - - /** - * if the modal is minimized or animating to a minimized position, - * then we early exit the method. - */ - if ( - statusRef.current === MODAL_STATUS.MINIMIZED || - statusRef.current === MODAL_STATUS.MINIMIZING - ) { - return; - } - - /** - * if modal got minimized before it finish its mounting - * animation, we set the `restoreIndexRef` to the - * provided index. - */ - if (currentIndexRef.current === -1) { - restoreIndexRef.current = index; - } else { - restoreIndexRef.current = currentIndexRef.current; - } - - statusRef.current = MODAL_STATUS.MINIMIZING; - bottomSheetRef.current?.close(); - }, - [index] - ); - const handleRestore = useCallback(function handleRestore() { - if (__DEV__) { - print({ - component: 'BottomSheetModal', - method: handleRestore.name, - params: { - status: statusRef.current, - }, - }); - } - - /** - * we only restore if the modal is minimized or going to be. - */ - const minimizedOrGoingToBe = [ - MODAL_STATUS.MINIMIZING, - MODAL_STATUS.MINIMIZED, - ].includes(statusRef.current); - if (!minimizedOrGoingToBe) { - return; - } - bottomSheetRef.current?.snapToIndex(restoreIndexRef.current); - }, []); - //#endregion - - //#region callbacks - const handlePortalOnUnmount = useCallback( - function handlePortalOnUnmount() { - if (__DEV__) { - print({ - component: 'BottomSheetModal', - method: 'handlePortalOnUnmount', - params: { - status: statusRef.current, - }, - }); - } - - if (statusRef.current === MODAL_STATUS.INITIAL) { - return; - } - - /** - * if modal is already in minimized/closed position, then - * unmount its node and early exit the method. - */ - if ( - statusRef.current === MODAL_STATUS.MINIMIZED || - statusRef.current === MODAL_STATUS.DISMISSED || - currentIndexRef.current === -1 - ) { - unmount(); - return; - } - - statusRef.current = MODAL_STATUS.DISMISSING; - willUnmountSheet(key); - bottomSheetRef.current?.close(); - }, - [key, unmount, willUnmountSheet] - ); - const handlePortalRender = useCallback(function handlePortalRender( - render: () => void - ) { - if (__DEV__) { - print({ - component: 'BottomSheetModal', - method: 'handlePortalRender', - params: { - status: statusRef.current, - }, - }); - } - if ([MODAL_STATUS.DISMISSING].includes(statusRef.current)) { - return; - } - render(); - }, []); - const handleBottomSheetOnChange = useCallback( - function handleBottomSheetOnChange( - _index: number, - _position: number, - _type: SNAP_POINT_TYPE - ) { - if (__DEV__) { - print({ - component: 'BottomSheetModal', - method: handleBottomSheetOnChange.name, - category: 'callback', - params: { - status: statusRef.current, - }, - }); - } - currentIndexRef.current = _index; - nextIndexRef.current = null; - - statusRef.current = - _index === -1 ? MODAL_STATUS.MINIMIZED : MODAL_STATUS.PRESENTED; - - if (_providedOnChange) { - _providedOnChange(_index, _position, _type); - } - }, - [_providedOnChange] - ); - const handleBottomSheetOnAnimate = useCallback( - ( - fromIndex: number, - toIndex: number, - fromPosition: number, - toPosition: number - ) => { - if (__DEV__) { - print({ - component: 'BottomSheetModal', - method: 'handleBottomSheetOnAnimate', - category: 'callback', - params: { - status: statusRef.current, - }, - }); - } - - nextIndexRef.current = toIndex; - - /** - * we do not want to override the pre-set status for minimizing or dismissing, - * as they need to be set manually. - */ - const currentStatusIsDismissingOrMinimizing = [ - MODAL_STATUS.DISMISSING, - MODAL_STATUS.MINIMIZING, - ].includes(statusRef.current); - - if (!(currentStatusIsDismissingOrMinimizing && toIndex === -1)) { - statusRef.current = MODAL_STATUS.ANIMATING; - } - - if (_providedOnAnimate) { - _providedOnAnimate(fromIndex, toIndex, fromPosition, toPosition); - } - }, - [_providedOnAnimate] - ); - const handleBottomSheetOnClose = useCallback( - function handleBottomSheetOnClose() { - if (__DEV__) { - print({ - component: 'BottomSheetModal', - method: 'handleBottomSheetOnClose', - category: 'callback', - params: { - status: statusRef.current, - }, - }); - } - - if (statusRef.current === MODAL_STATUS.DISMISSING) { - statusRef.current = MODAL_STATUS.DISMISSED; - } else if (statusRef.current === MODAL_STATUS.MINIMIZING) { - statusRef.current = MODAL_STATUS.MINIMIZED; - } else { - statusRef.current = enableDismissOnClose - ? MODAL_STATUS.DISMISSED - : MODAL_STATUS.CLOSED; - } - - if (statusRef.current !== MODAL_STATUS.DISMISSED) { - return; - } - - unmount(); - }, - [enableDismissOnClose, unmount] - ); - //#endregion - - //#region expose methods - useImperativeHandle(ref, () => ({ - // sheet - snapToIndex: handleSnapToIndex, - snapToPosition: handleSnapToPosition, - expand: handleExpand, - collapse: handleCollapse, - close: handleClose, - forceClose: handleForceClose, - // modal methods - dismiss: handleDismiss, - present: handlePresent, - // internal - minimize: handleMinimize, - restore: handleRestore, - status: statusRef, - })); - //#endregion - - // render - return mount ? ( - - - - {typeof Content === 'function' ? Content({ data }) : Content} - - - - ) : null; -} - -const BottomSheetModal = memo(forwardRef(BottomSheetModalComponent)) as < - T = never, ->( - props: BottomSheetModalProps & { - ref?: React.ForwardedRef>; - } -) => ReturnType; -( - BottomSheetModal as React.MemoExoticComponent< - typeof BottomSheetModalComponent - > -).displayName = 'BottomSheetModal'; - -export default BottomSheetModal; diff --git a/src/components/bottomSheetModal/constants.ts b/src/components/bottomSheetModal/constants.ts deleted file mode 100644 index 03c1ca5c..00000000 --- a/src/components/bottomSheetModal/constants.ts +++ /dev/null @@ -1,19 +0,0 @@ -const DEFAULT_STACK_BEHAVIOR = 'switch'; -const DEFAULT_ENABLE_DISMISS_ON_CLOSE = true; - -enum MODAL_STATUS { - INITIAL, - PRESENTED, - CLOSED, - MINIMIZED, - MINIMIZING, - ANIMATING, - DISMISSING, - DISMISSED, -} - -export { - DEFAULT_ENABLE_DISMISS_ON_CLOSE, - DEFAULT_STACK_BEHAVIOR, - MODAL_STATUS, -}; diff --git a/src/components/bottomSheetModal/index.ts b/src/components/bottomSheetModal/index.ts deleted file mode 100644 index 841304a3..00000000 --- a/src/components/bottomSheetModal/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { default } from './BottomSheetModal'; -export type { - BottomSheetModalPrivateMethods, - BottomSheetModalProps, - BottomSheetModalStackBehavior, -} from './types'; diff --git a/src/components/bottomSheetModal/types.d.ts b/src/components/bottomSheetModal/types.d.ts deleted file mode 100644 index 3cd6199b..00000000 --- a/src/components/bottomSheetModal/types.d.ts +++ /dev/null @@ -1,68 +0,0 @@ -import type React from 'react'; -import type { MODAL_STACK_BEHAVIOR } from '../../constants'; -import type { BottomSheetProps } from '../bottomSheet'; -import type { MODAL_STATUS } from './constants'; - -export interface BottomSheetModalPrivateMethods { - dismiss: (force?: boolean) => void; - minimize: () => void; - restore: () => void; - status: React.MutableRefObject; -} - -export type BottomSheetModalStackBehavior = keyof typeof MODAL_STACK_BEHAVIOR; - -// biome-ignore lint/suspicious/noExplicitAny: Using 'any' allows users to define their own strict types for 'data' property. -export interface BottomSheetModalProps - extends Omit { - /** - * Modal name to help identify the modal for later on. - * @type string - * @default generated unique key. - */ - name?: string; - - /** - * Defines the stack behavior when modal mount. - * - `push` it will mount the modal on top of the current one. - * - `switch` it will minimize the current modal then mount the new one. - * - `replace` it will dismiss the current modal then mount the new one. - * @type `push` | `switch` | `replace` - * @default switch - */ - stackBehavior?: BottomSheetModalStackBehavior; - - /** - * Enable dismiss the modal when it is closed. - * @type boolean - * @default true - */ - enableDismissOnClose?: boolean; - - /** - * Add a custom container like FullWindowOverlay - * allow to fix issue like https://github.com/gorhom/react-native-bottom-sheet/issues/832 - * @type React.ComponentType - * @default undefined - */ - containerComponent?: React.ComponentType; - - // callbacks - /** - * Callback when the modal dismissed. - * @type () => void; - */ - onDismiss?: () => void; - - /** - * A scrollable node or normal view. - * @type React.ReactNode[] | React.ReactNode | (({ data: any }?) => React.ReactElement) - */ - children: React.FC<{ data?: T }> | React.ReactNode[] | React.ReactNode; -} - -// biome-ignore lint/suspicious/noExplicitAny: Using 'any' allows users to define their own strict types for 'data' property. -export interface BottomSheetModalState { - mount: boolean; - data: T | undefined; -} diff --git a/src/components/bottomSheetModalProvider/BottomSheetModalProvider.tsx b/src/components/bottomSheetModalProvider/BottomSheetModalProvider.tsx deleted file mode 100644 index 8782dee6..00000000 --- a/src/components/bottomSheetModalProvider/BottomSheetModalProvider.tsx +++ /dev/null @@ -1,215 +0,0 @@ -import { PortalProvider } from '@gorhom/portal'; -import React, { useCallback, useMemo, useRef } from 'react'; -import { useSharedValue } from 'react-native-reanimated'; -import { - INITIAL_CONTAINER_LAYOUT, - MODAL_STACK_BEHAVIOR, -} from '../../constants'; -import { - BottomSheetModalInternalProvider, - BottomSheetModalProvider, -} from '../../contexts'; -import type { ContainerLayoutState } from '../../types'; -import { id } from '../../utilities/id'; -import { BottomSheetHostingContainer } from '../bottomSheetHostingContainer'; -import type { - BottomSheetModalPrivateMethods, - BottomSheetModalStackBehavior, -} from '../bottomSheetModal'; -import { MODAL_STATUS } from '../bottomSheetModal/constants'; -import type { - BottomSheetModalProviderProps, - BottomSheetModalRef, -} from './types'; - -const BottomSheetModalProviderWrapper = ({ - children, -}: BottomSheetModalProviderProps) => { - //#region layout variables - const animatedContainerLayoutState = useSharedValue( - INITIAL_CONTAINER_LAYOUT - ); - //#endregion - - //#region variables - const hostName = useMemo(() => `bottom-sheet-portal-${id()}`, []); - const sheetsQueueRef = useRef([]); - //#endregion - - //#region private methods - const handleMountSheet = useCallback( - ( - key: string, - ref: React.RefObject, - stackBehavior: BottomSheetModalStackBehavior - ) => { - const _sheetsQueue = sheetsQueueRef.current.slice(); - const sheetIndex = _sheetsQueue.findIndex(item => item.key === key); - const sheetOnTop = sheetIndex === _sheetsQueue.length - 1; - - /** - * Exit the method, if sheet is already presented - * and at the top. - */ - if (sheetIndex !== -1 && sheetOnTop) { - return; - } - - /** - * Minimize the current sheet if: - * - it exists. - * - it is not unmounting. - * - stack behavior is 'replace'. - */ - - /** - * Handle switch or replace stack behaviors, if: - * - a modal is currently presented. - * - it is not unmounting - */ - const currentMountedSheet = _sheetsQueue[_sheetsQueue.length - 1]; - const currentMountedSheetStatus = - currentMountedSheet?.ref.current?.status.current; - const currentMountedSheetWillUnmount = - currentMountedSheetStatus !== undefined && - currentMountedSheetStatus === MODAL_STATUS.DISMISSING; - - if (currentMountedSheet && !currentMountedSheetWillUnmount) { - if (stackBehavior === MODAL_STACK_BEHAVIOR.replace) { - currentMountedSheet.ref?.current?.dismiss(); - } else if (stackBehavior === MODAL_STACK_BEHAVIOR.switch) { - currentMountedSheet.ref?.current?.minimize(); - } - } - - /** - * Restore and remove incoming sheet from the queue, if it was registered. - */ - if (sheetIndex !== -1) { - _sheetsQueue.splice(sheetIndex, 1); - ref?.current?.restore(); - } - - _sheetsQueue.push({ - key, - ref, - }); - sheetsQueueRef.current = _sheetsQueue; - }, - [] - ); - const handleUnmountSheet = useCallback((key: string) => { - const _sheetsQueue = sheetsQueueRef.current.slice(); - const sheetIndex = _sheetsQueue.findIndex(item => item.key === key); - const sheetOnTop = sheetIndex === _sheetsQueue.length - 1; - - /** - * Here we remove the unmounted sheet and update - * the sheets queue. - */ - if (sheetIndex !== -1) { - _sheetsQueue.splice(sheetIndex, 1); - sheetsQueueRef.current = _sheetsQueue; - } - - /** - * Here we try to restore previous sheet position if unmounted - * sheet was on top. This is needed when user dismiss - * the modal by panning down. - */ - const hasMinimizedSheet = sheetsQueueRef.current.length > 0; - const minimizedSheet = - sheetsQueueRef.current[sheetsQueueRef.current.length - 1]; - const minimizedSheetStatus = minimizedSheet?.ref.current?.status.current; - const minimizedSheetWillUnmount = - minimizedSheetStatus !== undefined && - minimizedSheetStatus === MODAL_STATUS.DISMISSING; - - if ( - sheetOnTop && - hasMinimizedSheet && - minimizedSheet && - !minimizedSheetWillUnmount - ) { - sheetsQueueRef.current[ - sheetsQueueRef.current.length - 1 - ].ref?.current?.restore(); - } - }, []); - const handleWillUnmountSheet = useCallback((key: string) => { - const _sheetsQueue = sheetsQueueRef.current.slice(); - const sheetIndex = _sheetsQueue.findIndex(item => item.key === key); - const sheetOnTop = sheetIndex === _sheetsQueue.length - 1; - - /** - * Here we try to restore previous sheet position, - * This is needed when user dismiss the modal by fire the dismiss action. - */ - const hasMinimizedSheet = _sheetsQueue.length > 1; - if (sheetOnTop && hasMinimizedSheet) { - _sheetsQueue[_sheetsQueue.length - 2].ref?.current?.restore(); - } - - sheetsQueueRef.current = _sheetsQueue; - }, []); - //#endregion - - //#region public methods - const handleDismiss = useCallback((key?: string) => { - const sheetToBeDismissed = key - ? sheetsQueueRef.current.find(item => item.key === key) - : sheetsQueueRef.current[sheetsQueueRef.current.length - 1]; - if (sheetToBeDismissed) { - sheetToBeDismissed.ref?.current?.dismiss(); - return true; - } - return false; - }, []); - const handleDismissAll = useCallback(() => { - sheetsQueueRef.current.forEach(item => { - item.ref?.current?.dismiss(); - }); - }, []); - //#endregion - - //#region context variables - const externalContextVariables = useMemo( - () => ({ - dismiss: handleDismiss, - dismissAll: handleDismissAll, - }), - [handleDismiss, handleDismissAll] - ); - const internalContextVariables = useMemo( - () => ({ - hostName, - containerLayoutState: animatedContainerLayoutState, - mountSheet: handleMountSheet, - unmountSheet: handleUnmountSheet, - willUnmountSheet: handleWillUnmountSheet, - }), - [ - hostName, - animatedContainerLayoutState, - handleMountSheet, - handleUnmountSheet, - handleWillUnmountSheet, - ] - ); - //#endregion - - //#region renders - return ( - - - - {children} - - - ); - //#endregion -}; - -export default BottomSheetModalProviderWrapper; diff --git a/src/components/bottomSheetModalProvider/index.ts b/src/components/bottomSheetModalProvider/index.ts deleted file mode 100644 index 31ec2b8e..00000000 --- a/src/components/bottomSheetModalProvider/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './BottomSheetModalProvider'; diff --git a/src/components/bottomSheetModalProvider/types.d.ts b/src/components/bottomSheetModalProvider/types.d.ts deleted file mode 100644 index a7df5ed1..00000000 --- a/src/components/bottomSheetModalProvider/types.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { ReactNode, RefObject } from 'react'; -import type { BottomSheetModalPrivateMethods } from '../bottomSheetModal'; - -export interface BottomSheetModalRef { - key: string; - ref: RefObject; -} - -export interface BottomSheetModalProviderProps { - children?: ReactNode; -} diff --git a/src/components/bottomSheetRefreshControl/BottomSheetRefreshControl.android.tsx b/src/components/bottomSheetRefreshControl/BottomSheetRefreshControl.android.tsx deleted file mode 100644 index 36a1a60b..00000000 --- a/src/components/bottomSheetRefreshControl/BottomSheetRefreshControl.android.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React, { memo, useContext, useMemo } from 'react'; -import { RefreshControl, type RefreshControlProps } from 'react-native'; -import { - Gesture, - GestureDetector, - type SimultaneousGesture, -} from 'react-native-gesture-handler'; -import Animated, { useAnimatedProps } from 'react-native-reanimated'; -import { SCROLLABLE_STATUS } from '../../constants'; -import { BottomSheetDraggableContext } from '../../contexts/gesture'; -import { useBottomSheetInternal } from '../../hooks'; - -const AnimatedRefreshControl = Animated.createAnimatedComponent(RefreshControl); - -interface BottomSheetRefreshControlProps extends RefreshControlProps { - scrollableGesture: SimultaneousGesture; -} - -function BottomSheetRefreshControlComponent({ - onRefresh, - scrollableGesture, - ...rest -}: BottomSheetRefreshControlProps) { - //#region hooks - const draggableGesture = useContext(BottomSheetDraggableContext); - const { - animatedScrollableStatus: animatedScrollableState, - enableContentPanningGesture, - } = useBottomSheetInternal(); - //#endregion - - if (!draggableGesture && enableContentPanningGesture) { - throw "'BottomSheetRefreshControl' cannot be used out of the BottomSheet!"; - } - - //#region variables - const animatedProps = useAnimatedProps( - () => ({ - enabled: animatedScrollableState.value === SCROLLABLE_STATUS.UNLOCKED, - }), - [animatedScrollableState.value] - ); - - const gesture = useMemo( - () => - draggableGesture - ? Gesture.Native() - .simultaneousWithExternalGesture( - ...draggableGesture.toGestureArray(), - ...scrollableGesture.toGestureArray() - ) - .shouldCancelWhenOutside(true) - : undefined, - [draggableGesture, scrollableGesture] - ); - - //#endregion - - // render - if (gesture) { - return ( - - - - ); - } - return ( - - ); -} - -const BottomSheetRefreshControl = memo(BottomSheetRefreshControlComponent); -BottomSheetRefreshControl.displayName = 'BottomSheetRefreshControl'; - -export default BottomSheetRefreshControl; diff --git a/src/components/bottomSheetRefreshControl/BottomSheetRefreshControl.tsx b/src/components/bottomSheetRefreshControl/BottomSheetRefreshControl.tsx deleted file mode 100644 index 461f67a0..00000000 --- a/src/components/bottomSheetRefreshControl/BottomSheetRefreshControl.tsx +++ /dev/null @@ -1 +0,0 @@ -export default () => null; diff --git a/src/components/bottomSheetRefreshControl/index.ts b/src/components/bottomSheetRefreshControl/index.ts deleted file mode 100644 index b716fa28..00000000 --- a/src/components/bottomSheetRefreshControl/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type React from 'react'; -import type { RefreshControlProps } from 'react-native'; -import type { - NativeViewGestureHandlerProps, - SimultaneousGesture, -} from 'react-native-gesture-handler'; -import BottomSheetRefreshControl from './BottomSheetRefreshControl'; - -export default BottomSheetRefreshControl as never as React.MemoExoticComponent< - React.ForwardRefExoticComponent< - RefreshControlProps & { - scrollableGesture: SimultaneousGesture; - children: React.ReactNode | React.ReactNode[]; - } & React.RefAttributes< - React.ComponentType< - NativeViewGestureHandlerProps & React.RefAttributes - > - > - > ->; diff --git a/src/components/bottomSheetScrollable/BottomSheetDraggableScrollable.tsx b/src/components/bottomSheetScrollable/BottomSheetDraggableScrollable.tsx deleted file mode 100644 index 92d035a9..00000000 --- a/src/components/bottomSheetScrollable/BottomSheetDraggableScrollable.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; -import { - GestureDetector, - type SimultaneousGesture, -} from 'react-native-gesture-handler'; - -interface BottomSheetDraggableScrollableProps { - scrollableGesture?: SimultaneousGesture; - children: React.ReactNode; -} - -export function BottomSheetDraggableScrollable({ - scrollableGesture, - children, -}: BottomSheetDraggableScrollableProps) { - if (scrollableGesture) { - return ( - {children} - ); - } - - return children; -} diff --git a/src/components/bottomSheetScrollable/BottomSheetFlashList.tsx b/src/components/bottomSheetScrollable/BottomSheetFlashList.tsx deleted file mode 100644 index 14238fc1..00000000 --- a/src/components/bottomSheetScrollable/BottomSheetFlashList.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React, { forwardRef, memo, type Ref, useMemo } from 'react'; -import type { FlatListProps, ScrollViewProps } from 'react-native'; -import type { AnimatedProps } from 'react-native-reanimated'; -import BottomSheetScrollView from './BottomSheetScrollView'; -import type { - BottomSheetScrollableProps, - BottomSheetScrollViewMethods, -} from './types'; - -/** - * Minimal subset of FlashListProps needed for BottomSheetFlashList. - * Defined locally to avoid requiring @shopify/flash-list as a dependency, - * since the runtime import is optional (try/catch require). - */ -interface FlashListProps extends FlatListProps { - estimatedItemSize?: number; -} - -let FlashList: { - FlashList: React.FC; -}; -// since FlashList is not a dependency for the library -// we try to import it using metro optional import -try { - FlashList = require('@shopify/flash-list') as never; -} catch (_) {} - -export type BottomSheetFlashListProps = Omit< - AnimatedProps>, - 'decelerationRate' | 'onScroll' | 'scrollEventThrottle' -> & - BottomSheetScrollableProps & { - ref?: Ref; - }; - -const BottomSheetFlashListComponent = forwardRef< - React.FC, - // biome-ignore lint/suspicious/noExplicitAny: to be addressed - BottomSheetFlashListProps ->((props, ref) => { - //#region props - const { - focusHook, - scrollEventsHandlersHook, - enableFooterMarginAdjustment, - ...rest - // biome-ignore lint: to be addressed! - }: any = props; - //#endregion - - useMemo(() => { - if (!FlashList) { - throw 'You need to install FlashList first, `yarn install @shopify/flash-list`'; - } - - console.warn( - 'BottomSheetFlashList is deprecated, please use useBottomSheetScrollableCreator instead.' - ); - }, []); - - //#region render - const renderScrollComponent = useMemo( - () => - forwardRef( - // @ts-expect-error - ({ data, ...props }, ref) => { - return ( - // @ts-expect-error - - ); - } - ), - [focusHook, scrollEventsHandlersHook, enableFooterMarginAdjustment] - ); - return ( - - ); - //#endregion -}); - -export const BottomSheetFlashList = memo(BottomSheetFlashListComponent); - -export default BottomSheetFlashList as ( - props: BottomSheetFlashListProps -) => ReturnType; diff --git a/src/components/bottomSheetScrollable/BottomSheetFlashList.web.tsx b/src/components/bottomSheetScrollable/BottomSheetFlashList.web.tsx deleted file mode 100644 index 461f67a0..00000000 --- a/src/components/bottomSheetScrollable/BottomSheetFlashList.web.tsx +++ /dev/null @@ -1 +0,0 @@ -export default () => null; diff --git a/src/components/bottomSheetScrollable/BottomSheetFlatList.tsx b/src/components/bottomSheetScrollable/BottomSheetFlatList.tsx deleted file mode 100644 index 9bc7c5f6..00000000 --- a/src/components/bottomSheetScrollable/BottomSheetFlatList.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { type ComponentProps, memo } from 'react'; -import { FlatList as RNFlatList } from 'react-native'; -import Animated from 'react-native-reanimated'; -import { SCROLLABLE_TYPE } from '../../constants'; -import { createBottomSheetScrollableComponent } from './createBottomSheetScrollableComponent'; -import type { - BottomSheetFlatListMethods, - BottomSheetFlatListProps, -} from './types'; - -const AnimatedFlatList = - Animated.createAnimatedComponent>( - RNFlatList - ); - -const BottomSheetFlatListComponent = createBottomSheetScrollableComponent< - BottomSheetFlatListMethods, - BottomSheetFlatListProps ->(SCROLLABLE_TYPE.FLATLIST, AnimatedFlatList); - -const BottomSheetFlatList = memo(BottomSheetFlatListComponent); -BottomSheetFlatList.displayName = 'BottomSheetFlatList'; - -export default BottomSheetFlatList as ( - props: BottomSheetFlatListProps -) => ReturnType; diff --git a/src/components/bottomSheetScrollable/BottomSheetScrollView.tsx b/src/components/bottomSheetScrollable/BottomSheetScrollView.tsx deleted file mode 100644 index 68be6ef5..00000000 --- a/src/components/bottomSheetScrollable/BottomSheetScrollView.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { memo } from 'react'; -import { - ScrollView as RNScrollView, - type ScrollViewProps as RNScrollViewProps, -} from 'react-native'; -import Animated from 'react-native-reanimated'; -import { SCROLLABLE_TYPE } from '../../constants'; -import { createBottomSheetScrollableComponent } from './createBottomSheetScrollableComponent'; -import type { - BottomSheetScrollViewMethods, - BottomSheetScrollViewProps, -} from './types'; - -const AnimatedScrollView = - Animated.createAnimatedComponent(RNScrollView); - -const BottomSheetScrollViewComponent = createBottomSheetScrollableComponent< - BottomSheetScrollViewMethods, - BottomSheetScrollViewProps ->(SCROLLABLE_TYPE.SCROLLVIEW, AnimatedScrollView); - -const BottomSheetScrollView = memo(BottomSheetScrollViewComponent); -BottomSheetScrollView.displayName = 'BottomSheetScrollView'; - -export default BottomSheetScrollView as ( - props: BottomSheetScrollViewProps -) => ReturnType; diff --git a/src/components/bottomSheetScrollable/BottomSheetSectionList.tsx b/src/components/bottomSheetScrollable/BottomSheetSectionList.tsx deleted file mode 100644 index 968be38c..00000000 --- a/src/components/bottomSheetScrollable/BottomSheetSectionList.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { type ComponentProps, memo } from 'react'; -import { - type DefaultSectionT, - SectionList as RNSectionList, -} from 'react-native'; -import Animated from 'react-native-reanimated'; -import { SCROLLABLE_TYPE } from '../../constants'; -import { createBottomSheetScrollableComponent } from './createBottomSheetScrollableComponent'; -import type { - BottomSheetSectionListMethods, - BottomSheetSectionListProps, -} from './types'; - -const AnimatedSectionList = - Animated.createAnimatedComponent>( - RNSectionList - ); - -const BottomSheetSectionListComponent = createBottomSheetScrollableComponent< - BottomSheetSectionListMethods, - BottomSheetSectionListProps ->(SCROLLABLE_TYPE.SECTIONLIST, AnimatedSectionList); - -const BottomSheetSectionList = memo(BottomSheetSectionListComponent); -BottomSheetSectionList.displayName = 'BottomSheetSectionList'; - -export default BottomSheetSectionList as ( - props: BottomSheetSectionListProps -) => ReturnType; diff --git a/src/components/bottomSheetScrollable/BottomSheetVirtualizedList.tsx b/src/components/bottomSheetScrollable/BottomSheetVirtualizedList.tsx deleted file mode 100644 index db636bb5..00000000 --- a/src/components/bottomSheetScrollable/BottomSheetVirtualizedList.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { type ComponentProps, memo } from 'react'; -import { VirtualizedList as RNVirtualizedList } from 'react-native'; -import Animated from 'react-native-reanimated'; -import { SCROLLABLE_TYPE } from '../../constants'; -import { createBottomSheetScrollableComponent } from './createBottomSheetScrollableComponent'; -import type { - BottomSheetVirtualizedListMethods, - BottomSheetVirtualizedListProps, -} from './types'; - -const AnimatedVirtualizedList = - Animated.createAnimatedComponent>( - RNVirtualizedList - ); - -const BottomSheetVirtualizedListComponent = - createBottomSheetScrollableComponent< - BottomSheetVirtualizedListMethods, - BottomSheetVirtualizedListProps - >(SCROLLABLE_TYPE.VIRTUALIZEDLIST, AnimatedVirtualizedList); - -const BottomSheetVirtualizedList = memo(BottomSheetVirtualizedListComponent); -BottomSheetVirtualizedList.displayName = 'BottomSheetVirtualizedList'; - -export default BottomSheetVirtualizedList as ( - props: BottomSheetVirtualizedListProps -) => ReturnType; diff --git a/src/components/bottomSheetScrollable/ScrollableContainer.android.tsx b/src/components/bottomSheetScrollable/ScrollableContainer.android.tsx deleted file mode 100644 index 1f944e06..00000000 --- a/src/components/bottomSheetScrollable/ScrollableContainer.android.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React, { forwardRef } from 'react'; -import type { SimultaneousGesture } from 'react-native-gesture-handler'; -import BottomSheetRefreshControl from '../bottomSheetRefreshControl'; -import { BottomSheetDraggableScrollable } from './BottomSheetDraggableScrollable'; -import { styles } from './styles'; - -interface ScrollableContainerProps { - nativeGesture: SimultaneousGesture; - // biome-ignore lint: to be addressed - refreshControl: any; - // biome-ignore lint: to be addressed - progressViewOffset: any; - // biome-ignore lint: to be addressed - refreshing: any; - // biome-ignore lint: to be addressed - onRefresh: any; - // biome-ignore lint: to be addressed - ScrollableComponent: any; -} - -// biome-ignore lint: to be addressed -export const ScrollableContainer = forwardRef( - function ScrollableContainer( - { - nativeGesture, - refreshControl: _refreshControl, - refreshing, - progressViewOffset, - onRefresh, - ScrollableComponent, - ...rest - }, - ref - ) { - const Scrollable = ( - - - - ); - - return onRefresh ? ( - - {Scrollable} - - ) : ( - Scrollable - ); - } -); diff --git a/src/components/bottomSheetScrollable/ScrollableContainer.tsx b/src/components/bottomSheetScrollable/ScrollableContainer.tsx deleted file mode 100644 index ab3bc59b..00000000 --- a/src/components/bottomSheetScrollable/ScrollableContainer.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React, { type FC, forwardRef } from 'react'; -import type { SimultaneousGesture } from 'react-native-gesture-handler'; -import { BottomSheetDraggableScrollable } from './BottomSheetDraggableScrollable'; - -interface ScrollableContainerProps { - nativeGesture?: SimultaneousGesture; - // biome-ignore lint/suspicious/noExplicitAny: 🤷‍♂️ - ScrollableComponent: FC; -} - -export const ScrollableContainer = forwardRef( - function ScrollableContainer( - { nativeGesture, ScrollableComponent, ...rest }, - ref - ) { - return ( - - - - ); - } -); diff --git a/src/components/bottomSheetScrollable/ScrollableContainer.web.tsx b/src/components/bottomSheetScrollable/ScrollableContainer.web.tsx deleted file mode 100644 index bb76c4ea..00000000 --- a/src/components/bottomSheetScrollable/ScrollableContainer.web.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React, { - type ComponentProps, - forwardRef, - useCallback, - useRef, -} from 'react'; -import type { LayoutChangeEvent, ViewProps } from 'react-native'; -import type { SimultaneousGesture } from 'react-native-gesture-handler'; -import Animated from 'react-native-reanimated'; -import { INITIAL_LAYOUT_VALUE } from '../../constants'; -import { useBottomSheetInternal } from '../../hooks'; -import { BottomSheetDraggableScrollable } from './BottomSheetDraggableScrollable'; - -interface ScrollableContainerProps { - nativeGesture: SimultaneousGesture; - setContentSize: (contentHeight: number) => void; - // biome-ignore lint/suspicious/noExplicitAny: 🤷‍♂️ - ScrollableComponent: any; - onLayout: ViewProps['onLayout']; -} - -/** - * Detect if the current browser is Safari or not. - */ -const isWebkit = () => { - // @ts-expect-error - return navigator.userAgent.indexOf('Safari') > -1; -}; - -export const ScrollableContainer = forwardRef< - never, - ScrollableContainerProps & { animatedProps: never } ->(function ScrollableContainer( - { - nativeGesture, - ScrollableComponent, - animatedProps, - setContentSize, - onLayout, - ...rest - }, - ref -) { - //#region refs - const isInitialContentHeightCaptured = useRef(false); - //#endregion - - //#region hooks - const { animatedLayoutState } = useBottomSheetInternal(); - //#endregion - - //#region callbacks - const renderScrollComponent = useCallback( - (props: ComponentProps) => ( - - ), - [animatedProps] - ); - - /** - * A workaround a bug in React Native Web [#1502](https://github.com/necolas/react-native-web/issues/1502), - * where the `onContentSizeChange` won't be call on initial render. - */ - const handleOnLayout = useCallback( - (event: LayoutChangeEvent) => { - if (onLayout) { - onLayout(event); - } - - if (!isInitialContentHeightCaptured.current) { - isInitialContentHeightCaptured.current = true; - if (!isWebkit()) { - return; - } - - /** - * early exit if the content height been calculated. - */ - if (animatedLayoutState.get().contentHeight !== INITIAL_LAYOUT_VALUE) { - return; - } - // @ts-expect-error - window.requestAnimationFrame(() => { - // @ts-expect-error - setContentSize(event.nativeEvent.target.clientHeight); - }); - } - }, - [onLayout, setContentSize, animatedLayoutState] - ); - //#endregion - return ( - - - - ); -}); diff --git a/src/components/bottomSheetScrollable/createBottomSheetScrollableComponent.tsx b/src/components/bottomSheetScrollable/createBottomSheetScrollableComponent.tsx deleted file mode 100644 index 6a57f511..00000000 --- a/src/components/bottomSheetScrollable/createBottomSheetScrollableComponent.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import React, { - forwardRef, - useContext, - useImperativeHandle, - useMemo, -} from 'react'; -import { Gesture } from 'react-native-gesture-handler'; -import { useAnimatedProps } from 'react-native-reanimated'; -import { - SCROLLABLE_DECELERATION_RATE_MAPPER, - SCROLLABLE_STATUS, - type SCROLLABLE_TYPE, -} from '../../constants'; -import { BottomSheetDraggableContext } from '../../contexts/gesture'; -import { - useBottomSheetContentContainerStyle, - useBottomSheetInternal, - useScrollableSetter, - useScrollHandler, - useStableCallback, -} from '../../hooks'; -import { ScrollableContainer } from './ScrollableContainer'; -import { useBottomSheetContentSizeSetter } from './useBottomSheetContentSizeSetter'; - -export function createBottomSheetScrollableComponent( - type: SCROLLABLE_TYPE, - // biome-ignore lint: to be addressed! - ScrollableComponent: any -) { - return forwardRef((props, ref) => { - //#region props - const { - // hooks - focusHook, - scrollEventsHandlersHook, - // props - enableFooterMarginAdjustment = false, - overScrollMode = 'never', - keyboardDismissMode = 'interactive', - showsVerticalScrollIndicator = true, - contentContainerStyle: _providedContentContainerStyle, - refreshing, - onRefresh, - progressViewOffset, - refreshControl, - // events - onScroll, - onScrollBeginDrag, - onScrollEndDrag, - onContentSizeChange, - ...rest - // biome-ignore lint: to be addressed! - }: any = props; - //#endregion - - //#region hooks - const draggableGesture = useContext(BottomSheetDraggableContext); - const { scrollableRef, scrollableContentOffsetY, scrollHandler } = - useScrollHandler( - scrollEventsHandlersHook, - onScroll, - onScrollBeginDrag, - onScrollEndDrag - ); - const { - animatedScrollableStatus: animatedScrollableState, - enableContentPanningGesture, - } = useBottomSheetInternal(); - const { setContentSize } = useBottomSheetContentSizeSetter(); - //#endregion - - if (!draggableGesture && enableContentPanningGesture) { - throw "'Scrollable' cannot be used out of the BottomSheet!"; - } - - //#region variables - const scrollableAnimatedProps = useAnimatedProps( - () => ({ - decelerationRate: - SCROLLABLE_DECELERATION_RATE_MAPPER[animatedScrollableState.value], - showsVerticalScrollIndicator: showsVerticalScrollIndicator - ? animatedScrollableState.value === SCROLLABLE_STATUS.UNLOCKED - : showsVerticalScrollIndicator, - }), - [animatedScrollableState, showsVerticalScrollIndicator] - ); - - const scrollableGesture = useMemo( - () => - draggableGesture - ? Gesture.Native() - // @ts-expect-error - .simultaneousWithExternalGesture(draggableGesture) - .shouldCancelWhenOutside(false) - : undefined, - [draggableGesture] - ); - //#endregion - - //#region callbacks - const handleContentSizeChange = useStableCallback( - (contentWidth: number, contentHeight: number) => { - setContentSize(contentHeight); - if (onContentSizeChange) { - onContentSizeChange(contentWidth, contentHeight); - } - } - ); - //#endregion - - //#region styles - const contentContainerStyle = useBottomSheetContentContainerStyle( - enableFooterMarginAdjustment, - _providedContentContainerStyle - ); - //#endregion - - //#region effects - // @ts-expect-error - useImperativeHandle(ref, () => scrollableRef.current); - useScrollableSetter( - scrollableRef, - type, - scrollableContentOffsetY, - onRefresh !== undefined, - focusHook - ); - //#endregion - - //#region render - return ( - - ); - //#endregion - }); -} diff --git a/src/components/bottomSheetScrollable/index.ts b/src/components/bottomSheetScrollable/index.ts deleted file mode 100644 index 7a15afa7..00000000 --- a/src/components/bottomSheetScrollable/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -export { default as BottomSheetFlashList } from './BottomSheetFlashList'; -export { default as BottomSheetFlatList } from './BottomSheetFlatList'; -export { default as BottomSheetScrollView } from './BottomSheetScrollView'; -export { default as BottomSheetSectionList } from './BottomSheetSectionList'; -export { default as BottomSheetVirtualizedList } from './BottomSheetVirtualizedList'; -export { createBottomSheetScrollableComponent } from './createBottomSheetScrollableComponent'; - -export type { - BottomSheetFlatListMethods, - BottomSheetScrollableProps, - BottomSheetScrollViewMethods, - BottomSheetSectionListMethods, - BottomSheetVirtualizedListMethods, -} from './types'; diff --git a/src/components/bottomSheetScrollable/styles.ts b/src/components/bottomSheetScrollable/styles.ts deleted file mode 100644 index ba0ed48e..00000000 --- a/src/components/bottomSheetScrollable/styles.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { StyleSheet } from 'react-native'; - -export const styles = StyleSheet.create({ - container: { - flex: 1, - overflow: 'visible', - }, -}); diff --git a/src/components/bottomSheetScrollable/types.d.ts b/src/components/bottomSheetScrollable/types.d.ts deleted file mode 100644 index 69515601..00000000 --- a/src/components/bottomSheetScrollable/types.d.ts +++ /dev/null @@ -1,280 +0,0 @@ -import type { - DependencyList, - EffectCallback, - ReactNode, - Ref, - RefObject, -} from 'react'; -import type { - FlatListProps, - NodeHandle, - ScrollResponderMixin, - ScrollViewComponent, - ScrollViewProps, - SectionListProps, - SectionListScrollParams, - View, - VirtualizedListProps, -} from 'react-native'; -import type { AnimatedProps } from 'react-native-reanimated'; -import type { ScrollEventsHandlersHookType } from '../../types'; - -export interface BottomSheetScrollableProps { - /** - * Adjust the scrollable bottom margin to avoid the animated footer. - * - * @type boolean - * @default false - */ - enableFooterMarginAdjustment?: boolean; - - /** - * This needed when bottom sheet used with multiple scrollables to allow bottom sheet - * detect the current scrollable ref, especially when used with `React Navigation`. - * You will need to provide `useFocusEffect` from `@react-navigation/native`. - * - * @type (effect: EffectCallback, deps?: DependencyList) => void - * @default useEffect - */ - focusHook?: (effect: EffectCallback, deps?: DependencyList) => void; - - /** - * Custom hook to provide scroll events handler, which will allow advance and - * customize handling for scrollables. - * - * @warning this is an experimental feature and the hook signature can change without a major version bump. - * @type ScrollEventsHandlersHookType - * @default useScrollEventsHandlersDefault - */ - scrollEventsHandlersHook?: ScrollEventsHandlersHookType; -} - -export type ScrollableProps = - | ScrollViewProps - | FlatListProps - | SectionListProps; - -//#region FlatList -export type BottomSheetFlatListProps = Omit< - AnimatedProps>, - 'decelerationRate' | 'onScroll' | 'scrollEventThrottle' -> & - BottomSheetScrollableProps & { - ref?: Ref; - }; - -export interface BottomSheetFlatListMethods { - /** - * Scrolls to the end of the content. May be janky without `getItemLayout` prop. - */ - scrollToEnd: (params?: { animated?: boolean | null }) => void; - - /** - * Scrolls to the item at the specified index such that it is positioned in the viewable area - * such that viewPosition 0 places it at the top, 1 at the bottom, and 0.5 centered in the middle. - * Cannot scroll to locations outside the render window without specifying the getItemLayout prop. - */ - scrollToIndex: (params: { - animated?: boolean | null; - index: number; - viewOffset?: number; - viewPosition?: number; - }) => void; - - /** - * Requires linear scan through data - use `scrollToIndex` instead if possible. - * May be janky without `getItemLayout` prop. - */ - scrollToItem: (params: { - animated?: boolean | null; - // biome-ignore lint: to be addressed! - item: any; - viewPosition?: number; - }) => void; - - /** - * Scroll to a specific content pixel offset, like a normal `ScrollView`. - */ - scrollToOffset: (params: { - animated?: boolean | null; - offset: number; - }) => void; - - /** - * Tells the list an interaction has occured, which should trigger viewability calculations, - * e.g. if waitForInteractions is true and the user has not scrolled. This is typically called - * by taps on items or by navigation actions. - */ - recordInteraction: () => void; - - /** - * Displays the scroll indicators momentarily. - */ - flashScrollIndicators: () => void; - - /** - * Provides a handle to the underlying scroll responder. - */ - getScrollResponder: () => ScrollResponderMixin | null | undefined; - - /** - * Provides a reference to the underlying host component - */ - getNativeScrollRef: () => - | RefObject - | RefObject - | null - | undefined; - - // biome-ignore lint: to be addressed! - getScrollableNode: () => any; - - // biome-ignore lint: to be addressed! - setNativeProps: (props: { [key: string]: any }) => void; -} -//#endregion - -//#region ScrollView -export type BottomSheetScrollViewProps = Omit< - AnimatedProps, - 'decelerationRate' | 'scrollEventThrottle' -> & - BottomSheetScrollableProps & { - ref?: Ref; - children: ReactNode | ReactNode[]; - }; - -export interface BottomSheetScrollViewMethods { - /** - * Scrolls to a given x, y offset, either immediately or with a smooth animation. - * Syntax: - * - * scrollTo(options: {x: number = 0; y: number = 0; animated: boolean = true}) - * - * Note: The weird argument signature is due to the fact that, for historical reasons, - * the function also accepts separate arguments as an alternative to the options object. - * This is deprecated due to ambiguity (y before x), and SHOULD NOT BE USED. - */ - scrollTo( - y?: number | { x?: number; y?: number; animated?: boolean }, - x?: number, - animated?: boolean - ): void; - - /** - * A helper function that scrolls to the end of the scrollview; - * If this is a vertical ScrollView, it scrolls to the bottom. - * If this is a horizontal ScrollView scrolls to the right. - * - * The options object has an animated prop, that enables the scrolling animation or not. - * The animated prop defaults to true - */ - scrollToEnd(options?: { animated: boolean }): void; - - /** - * Returns a reference to the underlying scroll responder, which supports - * operations like `scrollTo`. All ScrollView-like components should - * implement this method so that they can be composed while providing access - * to the underlying scroll responder's methods. - */ - getScrollResponder(): ScrollResponderMixin; - - // biome-ignore lint: to be addressed! - getScrollableNode(): any; - - // biome-ignore lint: to be addressed! - getInnerViewNode(): any; - - /** - * @deprecated Use scrollTo instead - */ - scrollWithoutAnimationTo?: (y: number, x: number) => void; - - /** - * This function sends props straight to native. They will not participate in - * future diff process - this means that if you do not include them in the - * next render, they will remain active (see [Direct - * Manipulation](https://reactnative.dev/docs/direct-manipulation)). - */ - setNativeProps(nativeProps: object): void; -} -//#endregion - -//#region SectionList -export type BottomSheetSectionListProps = Omit< - AnimatedProps>, - 'decelerationRate' | 'scrollEventThrottle' -> & - BottomSheetScrollableProps & { - ref?: Ref; - }; - -export interface BottomSheetSectionListMethods { - /** - * Scrolls to the item at the specified sectionIndex and itemIndex (within the section) - * positioned in the viewable area such that viewPosition 0 places it at the top - * (and may be covered by a sticky header), 1 at the bottom, and 0.5 centered in the middle. - */ - scrollToLocation(params: SectionListScrollParams): void; - - /** - * Tells the list an interaction has occurred, which should trigger viewability calculations, e.g. - * if `waitForInteractions` is true and the user has not scrolled. This is typically called by - * taps on items or by navigation actions. - */ - recordInteraction(): void; - - /** - * Displays the scroll indicators momentarily. - * - * @platform ios - */ - flashScrollIndicators(): void; - - /** - * Provides a handle to the underlying scroll responder. - */ - getScrollResponder(): ScrollResponderMixin | undefined; - - /** - * Provides a handle to the underlying scroll node. - */ - getScrollableNode(): NodeHandle | undefined; -} -//#endregion - -//#region -export type BottomSheetVirtualizedListProps = Omit< - AnimatedProps>, - 'decelerationRate' | 'scrollEventThrottle' -> & - BottomSheetScrollableProps & { - ref?: Ref; - }; - -export interface BottomSheetVirtualizedListMethods { - scrollToEnd: (params?: { animated?: boolean }) => void; - scrollToIndex: (params: { - animated?: boolean; - index: number; - viewOffset?: number; - viewPosition?: number; - }) => void; - scrollToItem: (params: { - animated?: boolean; - // biome-ignore lint: to be addressed! - item: any; - viewPosition?: number; - }) => void; - - /** - * Scroll to a specific content pixel offset in the list. - * Param `offset` expects the offset to scroll to. In case of horizontal is true, the - * offset is the x-value, in any other case the offset is the y-value. - * Param `animated` (true by default) defines whether the list should do an animation while scrolling. - */ - scrollToOffset: (params: { animated?: boolean; offset: number }) => void; - - recordInteraction: () => void; -} -//#endregion diff --git a/src/components/bottomSheetScrollable/useBottomSheetContentSizeSetter.ts b/src/components/bottomSheetScrollable/useBottomSheetContentSizeSetter.ts deleted file mode 100644 index 28c79875..00000000 --- a/src/components/bottomSheetScrollable/useBottomSheetContentSizeSetter.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { useCallback } from 'react'; -import { useBottomSheetInternal } from '../../hooks'; - -/** - * A hook to set the content size properly into the bottom sheet, - * internals. - */ -export function useBottomSheetContentSizeSetter() { - //#region hooks - const { enableDynamicSizing, animatedLayoutState } = useBottomSheetInternal(); - //#endregion - - //#region methods - const setContentSize = useCallback( - (contentHeight: number) => { - if (!enableDynamicSizing) { - return; - } - animatedLayoutState.modify(state => { - 'worklet'; - state.contentHeight = contentHeight; - return state; - }); - }, - [enableDynamicSizing, animatedLayoutState] - ); - //#endregion - - return { - setContentSize, - }; -} diff --git a/src/components/bottomSheetTextInput/BottomSheetTextInput.tsx b/src/components/bottomSheetTextInput/BottomSheetTextInput.tsx deleted file mode 100644 index 861d276e..00000000 --- a/src/components/bottomSheetTextInput/BottomSheetTextInput.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import React, { - forwardRef, - memo, - useCallback, - useEffect, - useImperativeHandle, - useRef, -} from 'react'; -import type { - NativeSyntheticEvent, - TextInputFocusEventData, -} from 'react-native'; -import { TextInput as RNTextInput } from 'react-native'; -import { TextInput } from 'react-native-gesture-handler'; -import { useBottomSheetInternal } from '../../hooks'; -import { findNodeHandle } from '../../utilities'; -import type { BottomSheetTextInputProps } from './types'; - -const BottomSheetTextInputComponent = forwardRef< - TextInput | undefined, - BottomSheetTextInputProps ->(({ onFocus, onBlur, ...rest }, providedRef) => { - //#region refs - const ref = useRef(null); - //#endregion - - //#region hooks - const { animatedKeyboardState, textInputNodesRef } = useBottomSheetInternal(); - //#endregion - - //#region callbacks - const handleOnFocus = useCallback( - (args: NativeSyntheticEvent) => { - animatedKeyboardState.set(state => ({ - ...state, - target: args.nativeEvent.target, - })); - if (onFocus) { - onFocus(args); - } - }, - [onFocus, animatedKeyboardState] - ); - const handleOnBlur = useCallback( - (args: NativeSyntheticEvent) => { - const keyboardState = animatedKeyboardState.get(); - const currentFocusedInput = findNodeHandle( - RNTextInput.State.currentlyFocusedInput() - ); - - /** - * we need to make sure that we only remove the target - * if the target belong to the current component and - * if the currently focused input is not in the targets set. - */ - const shouldRemoveCurrentTarget = - keyboardState.target === args.nativeEvent.target; - const shouldIgnoreBlurEvent = - currentFocusedInput && - textInputNodesRef.current.has(currentFocusedInput); - - if (shouldRemoveCurrentTarget && !shouldIgnoreBlurEvent) { - animatedKeyboardState.set(state => ({ - ...state, - target: undefined, - })); - } - - if (onBlur) { - onBlur(args); - } - }, - [onBlur, animatedKeyboardState, textInputNodesRef] - ); - //#endregion - - //#region effects - useEffect(() => { - const componentNode = findNodeHandle(ref.current); - if (!componentNode) { - return; - } - - if (!textInputNodesRef.current.has(componentNode)) { - textInputNodesRef.current.add(componentNode); - } - - return () => { - const componentNode = findNodeHandle(ref.current); - if (!componentNode) { - return; - } - - const keyboardState = animatedKeyboardState.get(); - /** - * remove the keyboard state target if it belong - * to the current component. - */ - if (keyboardState.target === componentNode) { - animatedKeyboardState.set(state => ({ - ...state, - target: undefined, - })); - } - - if (textInputNodesRef.current.has(componentNode)) { - textInputNodesRef.current.delete(componentNode); - } - }; - }, [textInputNodesRef, animatedKeyboardState]); - useImperativeHandle(providedRef, () => ref.current ?? undefined, []); - //#endregion - - return ( - - ); -}); - -const BottomSheetTextInput = memo(BottomSheetTextInputComponent); -BottomSheetTextInput.displayName = 'BottomSheetTextInput'; - -export default BottomSheetTextInput; diff --git a/src/components/bottomSheetTextInput/index.ts b/src/components/bottomSheetTextInput/index.ts deleted file mode 100644 index fc7fc37e..00000000 --- a/src/components/bottomSheetTextInput/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default } from './BottomSheetTextInput'; -export type { BottomSheetTextInputProps } from './types'; diff --git a/src/components/bottomSheetTextInput/types.ts b/src/components/bottomSheetTextInput/types.ts deleted file mode 100644 index a64f90b8..00000000 --- a/src/components/bottomSheetTextInput/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { TextInputProps } from 'react-native'; - -export interface BottomSheetTextInputProps extends TextInputProps {} diff --git a/src/components/bottomSheetView/BottomSheetView.tsx b/src/components/bottomSheetView/BottomSheetView.tsx deleted file mode 100644 index e5ad5582..00000000 --- a/src/components/bottomSheetView/BottomSheetView.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import React, { memo, useCallback, useEffect, useMemo } from 'react'; -import { type LayoutChangeEvent, View } from 'react-native'; -import { SCROLLABLE_TYPE } from '../../constants'; -import { - useBottomSheetContentContainerStyle, - useBottomSheetInternal, -} from '../../hooks'; -import { print } from '../../utilities'; -import { styles } from './styles'; -import type { BottomSheetViewProps } from './types'; - -function BottomSheetViewComponent({ - focusHook: useFocusHook = useEffect, - enableFooterMarginAdjustment = false, - onLayout, - style: _providedStyle, - children, - ...rest -}: BottomSheetViewProps) { - //#region hooks - const { animatedScrollableState, enableDynamicSizing, animatedLayoutState } = - useBottomSheetInternal(); - //#endregion - - //#region styles - const containerStyle = useBottomSheetContentContainerStyle( - enableFooterMarginAdjustment, - _providedStyle - ); - const style = useMemo( - () => [containerStyle, styles.container], - [containerStyle] - ); - //#endregion - - //#region callbacks - const handleSettingScrollable = useCallback(() => { - animatedScrollableState.set(state => ({ - ...state, - contentOffsetY: 0, - type: SCROLLABLE_TYPE.VIEW, - })); - }, [animatedScrollableState]); - const handleLayout = useCallback( - (event: LayoutChangeEvent) => { - if (enableDynamicSizing) { - const { - nativeEvent: { - layout: { height }, - }, - } = event; - animatedLayoutState.modify(state => { - 'worklet'; - state.contentHeight = height; - return state; - }); - } - - if (onLayout) { - onLayout(event); - } - - if (__DEV__) { - print({ - component: 'BottomSheetView', - method: 'handleLayout', - category: 'layout', - params: { - height: event.nativeEvent.layout.height, - }, - }); - } - }, - [onLayout, animatedLayoutState, enableDynamicSizing] - ); - //#endregion - - //#region effects - useFocusHook(handleSettingScrollable); - //#endregion - - //render - return ( - - {children} - - ); -} - -const BottomSheetView = memo(BottomSheetViewComponent); -BottomSheetView.displayName = 'BottomSheetView'; - -export default BottomSheetView; diff --git a/src/components/bottomSheetView/index.ts b/src/components/bottomSheetView/index.ts deleted file mode 100644 index 0cbb27a0..00000000 --- a/src/components/bottomSheetView/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './BottomSheetView'; diff --git a/src/components/bottomSheetView/styles.ts b/src/components/bottomSheetView/styles.ts deleted file mode 100644 index 9333d966..00000000 --- a/src/components/bottomSheetView/styles.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { StyleSheet } from 'react-native'; - -export const styles = StyleSheet.create({ - container: { - position: 'absolute', - left: 0, - top: 0, - right: 0, - }, -}); diff --git a/src/components/bottomSheetView/types.d.ts b/src/components/bottomSheetView/types.d.ts deleted file mode 100644 index e2aa233c..00000000 --- a/src/components/bottomSheetView/types.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { DependencyList, EffectCallback, ReactNode } from 'react'; -import type { ViewProps as RNViewProps } from 'react-native'; - -export interface BottomSheetViewProps extends RNViewProps { - /** - * Adjust the scrollable bottom margin to avoid the animated footer. - * - * @type boolean - * @default false - */ - enableFooterMarginAdjustment?: boolean; - - /** - * This needed when bottom sheet used with multiple scrollables to allow bottom sheet - * detect the current scrollable ref, especially when used with `React Navigation`. - * You will need to provide `useFocusEffect` from `@react-navigation/native`. - * - * @type (effect: EffectCallback, deps?: DependencyList) => void - * @default useEffect - */ - focusHook?: (effect: EffectCallback, deps?: DependencyList) => void; - - children: ReactNode[] | ReactNode; -} diff --git a/src/components/touchables/Touchables.ios.tsx b/src/components/touchables/Touchables.ios.tsx deleted file mode 100644 index e3894aed..00000000 --- a/src/components/touchables/Touchables.ios.tsx +++ /dev/null @@ -1,5 +0,0 @@ -export { - TouchableHighlight, - TouchableOpacity, - TouchableWithoutFeedback, -} from 'react-native'; diff --git a/src/components/touchables/Touchables.tsx b/src/components/touchables/Touchables.tsx deleted file mode 100644 index 578de961..00000000 --- a/src/components/touchables/Touchables.tsx +++ /dev/null @@ -1,5 +0,0 @@ -export { - TouchableHighlight, - TouchableOpacity, - TouchableWithoutFeedback, -} from 'react-native-gesture-handler'; diff --git a/src/components/touchables/index.ts b/src/components/touchables/index.ts deleted file mode 100644 index 321bf220..00000000 --- a/src/components/touchables/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { - TouchableHighlight as RNTouchableHighlight, - TouchableOpacity as RNTouchableOpacity, - TouchableWithoutFeedback as RNTouchableWithoutFeedback, -} from 'react-native'; - -import { - TouchableHighlight, - TouchableOpacity, - TouchableWithoutFeedback, -} from './Touchables'; - -export default { - TouchableOpacity: TouchableOpacity as never as typeof RNTouchableOpacity, - TouchableHighlight: - TouchableHighlight as never as typeof RNTouchableHighlight, - TouchableWithoutFeedback: - TouchableWithoutFeedback as never as typeof RNTouchableWithoutFeedback, -}; diff --git a/src/constants.ts b/src/constants.ts deleted file mode 100644 index 8d9c3a95..00000000 --- a/src/constants.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { Platform } from 'react-native'; -import { Easing } from 'react-native-reanimated'; -import type { SpringConfig, TimingConfig } from './types'; - -enum GESTURE_SOURCE { - UNDETERMINED = 0, - SCROLLABLE = 1, - HANDLE = 2, - CONTENT = 3, -} - -enum SHEET_STATE { - CLOSED = 0, - OPENED = 1, - EXTENDED = 2, - OVER_EXTENDED = 3, - FILL_PARENT = 4, -} - -enum SCROLLABLE_STATUS { - LOCKED = 0, - UNLOCKED = 1, - UNDETERMINED = 2, -} - -enum SCROLLABLE_TYPE { - UNDETERMINED = 0, - VIEW = 1, - FLATLIST = 2, - SCROLLVIEW = 3, - SECTIONLIST = 4, - VIRTUALIZEDLIST = 5, -} - -enum ANIMATION_STATUS { - UNDETERMINED = 0, - RUNNING = 1, - STOPPED = 2, - INTERRUPTED = 3, -} - -enum ANIMATION_SOURCE { - NONE = 0, - MOUNT = 1, - GESTURE = 2, - USER = 3, - CONTAINER_RESIZE = 4, - SNAP_POINT_CHANGE = 5, - KEYBOARD = 6, -} - -enum ANIMATION_METHOD { - TIMING = 0, - SPRING = 1, -} - -enum KEYBOARD_STATUS { - UNDETERMINED = 0, - SHOWN = 1, - HIDDEN = 2, -} - -enum SNAP_POINT_TYPE { - PROVIDED = 0, - DYNAMIC = 1, -} - -const ANIMATION_EASING = Easing.out(Easing.exp); -const ANIMATION_DURATION = 250; - -const ANIMATION_CONFIGS = Platform.select({ - android: { - duration: ANIMATION_DURATION, - easing: ANIMATION_EASING, - }, - default: { - damping: 500, - stiffness: 1000, - mass: 3, - overshootClamping: true, - restDisplacementThreshold: 10, - restSpeedThreshold: 10, - }, -}); - -const SCROLLABLE_DECELERATION_RATE_MAPPER = { - [SCROLLABLE_STATUS.UNDETERMINED]: 0, - [SCROLLABLE_STATUS.LOCKED]: 0, - [SCROLLABLE_STATUS.UNLOCKED]: Platform.select({ - ios: 0.998, - android: 0.985, - default: 1, - }), -}; - -const MODAL_STACK_BEHAVIOR = { - replace: 'replace', - push: 'push', - switch: 'switch', -}; - -const KEYBOARD_BEHAVIOR = { - interactive: 'interactive', - extend: 'extend', - fillParent: 'fillParent', -} as const; - -const KEYBOARD_BLUR_BEHAVIOR = { - none: 'none', - restore: 'restore', -} as const; - -const KEYBOARD_INPUT_MODE = { - adjustPan: 'adjustPan', - adjustResize: 'adjustResize', -} as const; - -const KEYBOARD_DISMISS_THRESHOLD = 12.5; - -const INITIAL_LAYOUT_VALUE = -999; -const INITIAL_CONTAINER_LAYOUT = { - height: INITIAL_LAYOUT_VALUE, - offset: { - top: 0, - bottom: 0, - right: 0, - left: 0, - }, -}; - -export { - ANIMATION_CONFIGS, - ANIMATION_DURATION, - ANIMATION_EASING, - ANIMATION_METHOD, - ANIMATION_SOURCE, - ANIMATION_STATUS, - GESTURE_SOURCE, - INITIAL_CONTAINER_LAYOUT, - INITIAL_LAYOUT_VALUE, - KEYBOARD_BEHAVIOR, - KEYBOARD_BLUR_BEHAVIOR, - KEYBOARD_DISMISS_THRESHOLD, - KEYBOARD_INPUT_MODE, - KEYBOARD_STATUS, - MODAL_STACK_BEHAVIOR, - SCROLLABLE_DECELERATION_RATE_MAPPER, - SCROLLABLE_STATUS, - SCROLLABLE_TYPE, - SHEET_STATE, - SNAP_POINT_TYPE, -}; diff --git a/src/contexts/external.ts b/src/contexts/external.ts deleted file mode 100644 index 2e1b8495..00000000 --- a/src/contexts/external.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { createContext } from 'react'; -import type { BottomSheetMethods, BottomSheetVariables } from '../types'; - -export const BottomSheetContext = createContext< - (BottomSheetMethods & BottomSheetVariables) | null ->(null); - -export const BottomSheetProvider = BottomSheetContext.Provider; diff --git a/src/contexts/gesture.ts b/src/contexts/gesture.ts deleted file mode 100644 index a6b2d217..00000000 --- a/src/contexts/gesture.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { createContext } from 'react'; -import type { Gesture } from 'react-native-gesture-handler/lib/typescript/handlers/gestures/gesture'; -import type { GestureHandlersHookType } from '../types'; - -export interface BottomSheetGestureHandlersContextType { - contentPanGestureHandler: ReturnType; - handlePanGestureHandler: ReturnType; -} - -export const BottomSheetGestureHandlersContext = - createContext(null); - -export const BottomSheetDraggableContext = createContext(null); diff --git a/src/contexts/index.ts b/src/contexts/index.ts deleted file mode 100644 index 688a0173..00000000 --- a/src/contexts/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -export { BottomSheetContext, BottomSheetProvider } from './external'; -export { BottomSheetGestureHandlersContext } from './gesture'; -export { - BottomSheetInternalContext, - BottomSheetInternalProvider, -} from './internal'; -export { - BottomSheetModalContext, - BottomSheetModalProvider, -} from './modal/external'; -export type { BottomSheetModalInternalContextType } from './modal/internal'; -export { - BottomSheetModalInternalContext, - BottomSheetModalInternalProvider, -} from './modal/internal'; diff --git a/src/contexts/internal.ts b/src/contexts/internal.ts deleted file mode 100644 index b1140b2c..00000000 --- a/src/contexts/internal.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { createContext, type RefObject } from 'react'; -import type { State } from 'react-native-gesture-handler'; -import type { SharedValue } from 'react-native-reanimated'; -import type { - AnimateToPositionType, - BottomSheetGestureProps, - BottomSheetProps, -} from '../components/bottomSheet/types'; -import type { SCROLLABLE_STATUS, SHEET_STATE } from '../constants'; -import type { - AnimationState, - DetentsState, - KeyboardState, - LayoutState, - Scrollable, - ScrollableRef, - ScrollableState, -} from '../types'; - -export interface BottomSheetInternalContextType - extends Partial, - Required< - Pick< - BottomSheetProps, - | 'enableContentPanningGesture' - | 'enableOverDrag' - | 'enablePanDownToClose' - | 'enableDynamicSizing' - | 'enableBlurKeyboardOnGesture' - | 'overDragResistanceFactor' - > - > { - // animated states - animatedDetentsState: SharedValue; - animatedAnimationState: SharedValue; - animatedSheetState: SharedValue; - animatedKeyboardState: SharedValue; - animatedContentGestureState: SharedValue; - animatedHandleGestureState: SharedValue; - animatedLayoutState: SharedValue; - - // scrollable - animatedScrollableState: SharedValue; - animatedScrollableStatus: SharedValue; - - // animated values - animatedPosition: SharedValue; - animatedIndex: SharedValue; - animatedSheetHeight: SharedValue; - isInTemporaryPosition: SharedValue; - - // methods - stopAnimation: () => void; - animateToPosition: AnimateToPositionType; - setScrollableRef: (ref: ScrollableRef) => void; - removeScrollableRef: (ref: RefObject) => void; - - // refs - textInputNodesRef: React.MutableRefObject>; -} - -export const BottomSheetInternalContext = - createContext(null); - -export const BottomSheetInternalProvider = BottomSheetInternalContext.Provider; diff --git a/src/contexts/modal/external.ts b/src/contexts/modal/external.ts deleted file mode 100644 index c2b63eef..00000000 --- a/src/contexts/modal/external.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { createContext } from 'react'; - -export interface BottomSheetModalContextType { - dismiss: (key?: string) => boolean; - dismissAll: () => void; -} - -export const BottomSheetModalContext = - createContext(null); - -export const BottomSheetModalProvider = BottomSheetModalContext.Provider; diff --git a/src/contexts/modal/internal.ts b/src/contexts/modal/internal.ts deleted file mode 100644 index d652440d..00000000 --- a/src/contexts/modal/internal.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { createContext, type RefObject } from 'react'; -import type { SharedValue } from 'react-native-reanimated'; -import type { - BottomSheetModalPrivateMethods, - BottomSheetModalStackBehavior, -} from '../../components/bottomSheetModal'; -import type { ContainerLayoutState } from '../../types'; - -export interface BottomSheetModalInternalContextType { - hostName: string; - containerLayoutState: SharedValue; - mountSheet: ( - key: string, - ref: RefObject, - stackBehavior: BottomSheetModalStackBehavior - ) => void; - unmountSheet: (key: string) => void; - willUnmountSheet: (key: string) => void; -} - -export const BottomSheetModalInternalContext = - createContext(null); - -export const BottomSheetModalInternalProvider = - BottomSheetModalInternalContext.Provider; diff --git a/src/hooks/index.ts b/src/hooks/index.ts deleted file mode 100644 index b2ec6053..00000000 --- a/src/hooks/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -export { useBottomSheet } from './useBottomSheet'; -export { useBottomSheetInternal } from './useBottomSheetInternal'; - -// modal -export { useBottomSheetModal } from './useBottomSheetModal'; -export { useBottomSheetModalInternal } from './useBottomSheetModalInternal'; - -// scrollable -export { useScrollable } from './useScrollable'; -export { useScrollableSetter } from './useScrollableSetter'; -export { useScrollHandler } from './useScrollHandler'; - -// gestures -export { useGestureHandler } from './useGestureHandler'; -export { useGestureEventsHandlersDefault } from './useGestureEventsHandlersDefault'; -export { useBottomSheetGestureHandlers } from './useBottomSheetGestureHandlers'; - -// utilities -export { useAnimatedLayout } from './useAnimatedLayout'; -export { useAnimatedKeyboard } from './useAnimatedKeyboard'; -export { useStableCallback } from './useStableCallback'; -export { usePropsValidator } from './usePropsValidator'; -export { useAnimatedDetents } from './useAnimatedDetents'; -export { useReactiveSharedValue } from './useReactiveSharedValue'; -export { - useBoundingClientRect, - type BoundingClientRect, -} from './useBoundingClientRect'; -export { useBottomSheetContentContainerStyle } from './useBottomSheetContentContainerStyle'; diff --git a/src/hooks/useAnimatedDetents.ts b/src/hooks/useAnimatedDetents.ts deleted file mode 100644 index cc34d3b0..00000000 --- a/src/hooks/useAnimatedDetents.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { type SharedValue, useDerivedValue } from 'react-native-reanimated'; -import type { BottomSheetProps } from '../components/bottomSheet'; -import { INITIAL_LAYOUT_VALUE } from '../constants'; -import type { DetentsState, LayoutState } from '../types'; -import { normalizeSnapPoint } from '../utilities'; - -/** - * A custom hook that computes and returns the animated detent positions for a bottom sheet component. - * - * This hook normalizes the provided snap points (detents), optionally adds a dynamic detent based on content size, - * and calculates key positions such as the highest detent and the closed position. It supports both static and dynamic - * sizing, and adapts to modal and detached sheet modes. - * - * @param detents - The snap points for the bottom sheet, which can be an array or an object with a `value` property. - * @param layoutState - A shared animated value containing the current layout state (container, handle, and content heights). - * @param enableDynamicSizing - Whether dynamic sizing based on content height is enabled. - * @param maxDynamicContentSize - The maximum allowed content size for dynamic sizing. - * @param detached - Whether the bottom sheet is in detached mode. - * @param $modal - Whether the bottom sheet is presented as a modal. - * @param bottomInset - The bottom inset to apply when the sheet is modal or detached (default is 0). - */ -export const useAnimatedDetents = ( - detents: BottomSheetProps['snapPoints'], - layoutState: SharedValue, - enableDynamicSizing: BottomSheetProps['enableDynamicSizing'], - maxDynamicContentSize: BottomSheetProps['maxDynamicContentSize'], - detached: BottomSheetProps['detached'], - $modal: BottomSheetProps['$modal'], - bottomInset: BottomSheetProps['bottomInset'] = 0 -) => { - const state = useDerivedValue(() => { - const { containerHeight, handleHeight, contentHeight } = layoutState.get(); - - // early exit, if container layout is not ready - if (containerHeight === INITIAL_LAYOUT_VALUE) { - return {}; - } - - // extract detents from provided props - const _detents = detents - ? 'value' in detents - ? detents.value - : detents - : []; - - // normalized all provided detents, converting percentage - // values into absolute values. - let _normalizedDetents = _detents.map(snapPoint => - normalizeSnapPoint(snapPoint, containerHeight) - ) as number[]; - - let highestDetentPosition = - _normalizedDetents[_normalizedDetents.length - 1]; - let closedDetentPosition = containerHeight; - if ($modal || detached) { - closedDetentPosition = containerHeight + bottomInset; - } - - if (!enableDynamicSizing) { - return { - detents: _normalizedDetents, - highestDetentPosition, - closedDetentPosition, - }; - } - - // early exit, if dynamic sizing is enabled and - // content height is not calculated yet. - if (contentHeight === INITIAL_LAYOUT_VALUE) { - return {}; - } - - // early exit, if handle height is not calculated yet. - if (handleHeight === INITIAL_LAYOUT_VALUE) { - return {}; - } - - // calculate a new detents based on content height. - const dynamicSnapPoint = - containerHeight - - Math.min( - contentHeight + handleHeight, - maxDynamicContentSize !== undefined - ? maxDynamicContentSize - : containerHeight - ); - - // push dynamic detent into the normalized detents, - // only if it does not exists in the provided list already. - if (!_normalizedDetents.includes(dynamicSnapPoint)) { - _normalizedDetents.push(dynamicSnapPoint); - } - - // sort all detents. - _normalizedDetents = _normalizedDetents.sort((a, b) => b - a); - - // update the highest detent position. - highestDetentPosition = _normalizedDetents[_normalizedDetents.length - 1]; - - // locate the dynamic detent index. - const dynamicDetentIndex = _normalizedDetents.indexOf(dynamicSnapPoint); - - return { - detents: _normalizedDetents, - dynamicDetentIndex, - highestDetentPosition, - closedDetentPosition, - }; - }, [ - detents, - layoutState, - enableDynamicSizing, - maxDynamicContentSize, - detached, - $modal, - bottomInset, - ]); - return state; -}; diff --git a/src/hooks/useAnimatedKeyboard.ts b/src/hooks/useAnimatedKeyboard.ts deleted file mode 100644 index d7051bfb..00000000 --- a/src/hooks/useAnimatedKeyboard.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { useCallback, useEffect, useRef } from 'react'; -import { - Dimensions, - Keyboard, - type KeyboardEvent, - type KeyboardEventEasing, - type KeyboardEventName, - Platform, -} from 'react-native'; -import { - runOnUI, - useAnimatedReaction, - useSharedValue, -} from 'react-native-reanimated'; -import { KEYBOARD_STATUS } from '../constants'; -import type { KeyboardState } from '../types'; - -const KEYBOARD_EVENT_MAPPER = { - KEYBOARD_SHOW: Platform.select({ - ios: 'keyboardWillShow', - android: 'keyboardDidShow', - default: '', - }) as KeyboardEventName, - KEYBOARD_HIDE: Platform.select({ - ios: 'keyboardWillHide', - android: 'keyboardDidHide', - default: '', - }) as KeyboardEventName, -}; - -const INITIAL_STATE: KeyboardState = { - status: KEYBOARD_STATUS.UNDETERMINED, - height: 0, - heightWithinContainer: 0, - easing: 'keyboard', - duration: 500, -}; - -export const useAnimatedKeyboard = () => { - //#region variables - const textInputNodesRef = useRef(new Set()); - const state = useSharedValue(INITIAL_STATE); - const temporaryCachedState = useSharedValue | null>(null); - //#endregion - - //#region worklets - const handleKeyboardEvent = useCallback( - ( - status: KEYBOARD_STATUS, - height: number, - duration: number, - easing: KeyboardEventEasing, - bottomOffset?: number - ) => { - 'worklet'; - const currentState = state.get(); - - /** - * if the keyboard event was fired before the `onFocus` on TextInput, - * then we cache the event, and wait till the `target` is been set - * to be updated then fire this function again. - */ - if (status === KEYBOARD_STATUS.SHOWN && !currentState.target) { - temporaryCachedState.set({ - status, - height, - duration, - easing, - }); - return; - } - - /** - * clear temporary cached state. - */ - temporaryCachedState.set(null); - - /** - * if keyboard status is hidden, then we keep old height. - */ - let adjustedHeight = - status === KEYBOARD_STATUS.SHOWN ? height : currentState.height; - - /** - * if keyboard had an bottom offset -android bottom bar-, then - * we add that offset to the keyboard height. - */ - if (bottomOffset) { - adjustedHeight = adjustedHeight + bottomOffset; - } - - state.set(state => ({ - status, - easing, - duration, - height: adjustedHeight, - target: state.target, - heightWithinContainer: state.heightWithinContainer, - })); - }, - [state, temporaryCachedState] - ); - //#endregion - - //#region effects - useEffect(() => { - const SCREEN_HEIGHT = Dimensions.get('screen').height; - const handleOnKeyboardShow = (event: KeyboardEvent) => { - runOnUI(handleKeyboardEvent)( - KEYBOARD_STATUS.SHOWN, - event.endCoordinates.height, - event.duration, - event.easing, - SCREEN_HEIGHT - - event.endCoordinates.height - - event.endCoordinates.screenY - ); - }; - const handleOnKeyboardHide = (event: KeyboardEvent) => { - runOnUI(handleKeyboardEvent)( - KEYBOARD_STATUS.HIDDEN, - event.endCoordinates.height, - event.duration, - event.easing - ); - }; - - const showSubscription = Keyboard.addListener( - KEYBOARD_EVENT_MAPPER.KEYBOARD_SHOW, - handleOnKeyboardShow - ); - - const hideSubscription = Keyboard.addListener( - KEYBOARD_EVENT_MAPPER.KEYBOARD_HIDE, - handleOnKeyboardHide - ); - - return () => { - showSubscription.remove(); - hideSubscription.remove(); - }; - }, [handleKeyboardEvent]); - - /** - * This reaction is needed to handle the issue with multiline text input. - * - * @link https://github.com/gorhom/react-native-bottom-sheet/issues/411 - */ - useAnimatedReaction( - () => state.value.target, - (result, previous) => { - if (!result || result === previous) { - return; - } - - const cachedState = temporaryCachedState.get(); - if (!cachedState) { - return; - } - - handleKeyboardEvent( - cachedState.status, - cachedState.height, - cachedState.duration, - cachedState.easing - ); - }, - [temporaryCachedState, handleKeyboardEvent] - ); - //#endregion - - return { state, textInputNodesRef }; -}; diff --git a/src/hooks/useAnimatedLayout.ts b/src/hooks/useAnimatedLayout.ts deleted file mode 100644 index f3c3eaaf..00000000 --- a/src/hooks/useAnimatedLayout.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { useMemo, useState } from 'react'; -import { - makeMutable, - type SharedValue, - useAnimatedReaction, -} from 'react-native-reanimated'; -import { INITIAL_CONTAINER_LAYOUT, INITIAL_LAYOUT_VALUE } from '../constants'; -import type { ContainerLayoutState, LayoutState } from '../types'; - -const INITIAL_STATE: LayoutState = { - rawContainerHeight: INITIAL_LAYOUT_VALUE, - containerHeight: INITIAL_LAYOUT_VALUE, - containerOffset: INITIAL_CONTAINER_LAYOUT.offset, - handleHeight: INITIAL_LAYOUT_VALUE, - footerHeight: INITIAL_LAYOUT_VALUE, - contentHeight: INITIAL_LAYOUT_VALUE, -}; - -/** - * A custom hook that manages and animates the layout state of a container, - * typically used in bottom sheet components. It calculates the effective - * container height by considering top and bottom insets, and updates the - * animated state in response to layout changes. The hook supports both modal - * and non-modal modes, and ensures the container's animated layout state - * remains in sync with the actual layout measurements. - * - * @param containerLayoutState - A shared value representing the current container layout state. - * @param topInset - The top inset value to be subtracted from the container height. - * @param bottomInset - The bottom inset value to be subtracted from the container height. - * @param modal - Optional flag indicating if the layout is in modal mode. - * @param shouldOverrideHandleHeight - Optional flag to override the handle height in the layout state, only when handle is set to null. - * @returns An object containing the animated layout state. - */ -export function useAnimatedLayout( - containerLayoutState: SharedValue | undefined, - topInset: number, - bottomInset: number, - modal?: boolean, - shouldOverrideHandleHeight?: boolean -) { - //#region variables - const verticalInset = useMemo( - () => topInset + bottomInset, - [topInset, bottomInset] - ); - const initialState = useMemo(() => { - const _state = { ...INITIAL_STATE }; - - if (containerLayoutState) { - const containerLayout = containerLayoutState.get(); - _state.containerHeight = modal - ? containerLayout.height - verticalInset - : containerLayout.height; - _state.containerOffset = containerLayout.offset; - } - - if (shouldOverrideHandleHeight) { - _state.handleHeight = 0; - } - - return _state; - }, [containerLayoutState, modal, shouldOverrideHandleHeight, verticalInset]); - //#endregion - - //#region state - const [state] = useState(() => makeMutable(initialState)); - //#endregion - - //#region effects - useAnimatedReaction( - () => state.value.rawContainerHeight, - (result, previous) => { - if (result === previous) { - return; - } - if (result === INITIAL_LAYOUT_VALUE) { - return; - } - - state.modify(_state => { - 'worklet'; - _state.containerHeight = modal ? result - verticalInset : result; - return _state; - }); - }, - [state, verticalInset, modal] - ); - useAnimatedReaction( - () => containerLayoutState?.get().height, - (result, previous) => { - if (!result || result === previous) { - return; - } - if (result === INITIAL_LAYOUT_VALUE) { - return; - } - - state.modify(_state => { - 'worklet'; - _state.containerHeight = modal ? result - verticalInset : result; - return _state; - }); - }, - [state, verticalInset, modal] - ); - //#endregion - - return state; -} diff --git a/src/hooks/useBottomSheet.ts b/src/hooks/useBottomSheet.ts deleted file mode 100644 index e4fcfdaa..00000000 --- a/src/hooks/useBottomSheet.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { useContext } from 'react'; -import { BottomSheetContext } from '../contexts/external'; - -export const useBottomSheet = () => { - const context = useContext(BottomSheetContext); - - if (context === null) { - throw "'useBottomSheet' cannot be used out of the BottomSheet!"; - } - - return context; -}; diff --git a/src/hooks/useBottomSheetContentContainerStyle.ts b/src/hooks/useBottomSheetContentContainerStyle.ts deleted file mode 100644 index 6c0bffe1..00000000 --- a/src/hooks/useBottomSheetContentContainerStyle.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { useMemo, useState } from 'react'; -import { - Platform, - StyleSheet, - type ViewProps, - type ViewStyle, -} from 'react-native'; -import { runOnJS, useAnimatedReaction } from 'react-native-reanimated'; -import { useBottomSheetInternal } from './useBottomSheetInternal'; - -export function useBottomSheetContentContainerStyle( - enableFooterMarginAdjustment: boolean, - _style?: ViewProps['style'] -) { - const [footerHeight, setFooterHeight] = useState(0); - //#region hooks - const { animatedLayoutState } = useBottomSheetInternal(); - //#endregion - - //#region styles - const flattenStyle = useMemo(() => { - return !_style - ? {} - : Array.isArray(_style) - ? // @ts-ignore - (StyleSheet.compose(..._style) as ViewStyle) - : (_style as ViewStyle); - }, [_style]); - const style = useMemo(() => { - if (!enableFooterMarginAdjustment) { - return flattenStyle; - } - - let currentBottomPadding = 0; - if (flattenStyle && typeof flattenStyle === 'object') { - const { paddingBottom, padding, paddingVertical } = flattenStyle; - if (paddingBottom !== undefined && typeof paddingBottom === 'number') { - currentBottomPadding = paddingBottom; - } else if ( - paddingVertical !== undefined && - typeof paddingVertical === 'number' - ) { - currentBottomPadding = paddingVertical; - } else if (padding !== undefined && typeof padding === 'number') { - currentBottomPadding = padding; - } - } - - return [ - flattenStyle, - { - paddingBottom: currentBottomPadding + footerHeight, - overflow: 'visible', - }, - ]; - }, [footerHeight, enableFooterMarginAdjustment, flattenStyle]); - //#endregion - - //#region effects - useAnimatedReaction( - () => animatedLayoutState.get().footerHeight, - (result, previousFooterHeight) => { - if (!enableFooterMarginAdjustment) { - return; - } - runOnJS(setFooterHeight)(result); - - if (Platform.OS === 'web') { - /** - * a reaction that will append the footer height to the content - * height if margin adjustment is true. - * - * This is needed due to the web layout the footer after the content. - */ - if (result && !previousFooterHeight) { - animatedLayoutState.modify(state => { - 'worklet'; - state.contentHeight = state.contentHeight + result; - return state; - }); - } - } - }, - [animatedLayoutState, enableFooterMarginAdjustment] - ); - //#endregion - return style; -} diff --git a/src/hooks/useBottomSheetGestureHandlers.ts b/src/hooks/useBottomSheetGestureHandlers.ts deleted file mode 100644 index 95beb2ce..00000000 --- a/src/hooks/useBottomSheetGestureHandlers.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { useContext } from 'react'; -import { BottomSheetGestureHandlersContext } from '../contexts/gesture'; - -export const useBottomSheetGestureHandlers = () => { - const context = useContext(BottomSheetGestureHandlersContext); - - if (context === null) { - throw "'useBottomSheetGestureHandlers' cannot be used out of the BottomSheet!"; - } - - return context; -}; diff --git a/src/hooks/useBottomSheetInternal.ts b/src/hooks/useBottomSheetInternal.ts deleted file mode 100644 index 72382e96..00000000 --- a/src/hooks/useBottomSheetInternal.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { useContext } from 'react'; -import { - BottomSheetInternalContext, - type BottomSheetInternalContextType, -} from '../contexts/internal'; - -export function useBottomSheetInternal( - unsafe?: false -): BottomSheetInternalContextType; - -export function useBottomSheetInternal( - unsafe: true -): BottomSheetInternalContextType | null; - -export function useBottomSheetInternal( - unsafe?: boolean -): BottomSheetInternalContextType | null { - const context = useContext(BottomSheetInternalContext); - - if (unsafe !== true && context === null) { - throw "'useBottomSheetInternal' cannot be used out of the BottomSheet!"; - } - - return context; -} diff --git a/src/hooks/useBottomSheetModal.ts b/src/hooks/useBottomSheetModal.ts deleted file mode 100644 index 59467802..00000000 --- a/src/hooks/useBottomSheetModal.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { useContext } from 'react'; -import { BottomSheetModalContext } from '../contexts'; - -export const useBottomSheetModal = () => { - const context = useContext(BottomSheetModalContext); - - if (context === null) { - throw "'BottomSheetModalContext' cannot be null!"; - } - - return context; -}; diff --git a/src/hooks/useBottomSheetModalInternal.ts b/src/hooks/useBottomSheetModalInternal.ts deleted file mode 100644 index b3c250e4..00000000 --- a/src/hooks/useBottomSheetModalInternal.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { useContext } from 'react'; -import { - BottomSheetModalInternalContext, - type BottomSheetModalInternalContextType, -} from '../contexts'; - -export function useBottomSheetModalInternal( - unsafe?: false -): BottomSheetModalInternalContextType; - -export function useBottomSheetModalInternal( - unsafe: true -): BottomSheetModalInternalContextType | null; - -export function useBottomSheetModalInternal( - unsafe?: boolean -): BottomSheetModalInternalContextType | null { - const context = useContext(BottomSheetModalInternalContext); - - if (unsafe !== true && context === null) { - throw "'BottomSheetModalInternalContext' cannot be null!"; - } - - return context; -} diff --git a/src/hooks/useBottomSheetScrollableCreator.tsx b/src/hooks/useBottomSheetScrollableCreator.tsx deleted file mode 100644 index b3fa0856..00000000 --- a/src/hooks/useBottomSheetScrollableCreator.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { type ReactElement, useCallback } from 'react'; -import { - type BottomSheetScrollableProps, - BottomSheetScrollView, -} from '../components/bottomSheetScrollable'; - -type BottomSheetScrollableCreatorConfigs = {} & BottomSheetScrollableProps; - -/** - * A custom hook that creates a scrollable component for third-party libraries - * like `LegendList` or `FlashList` to integrate the interaction and scrolling - * behaviors with th BottomSheet component. - * - * @param configs - Configuration options for the scrollable creator. - * @param configs.focusHook - This needed when bottom sheet used with multiple scrollables to allow bottom sheet - * detect the current scrollable ref, especially when used with `React Navigation`. - * You will need to provide `useFocusEffect` from `@react-navigation/native`. - * @param configs.scrollEventsHandlersHook - Custom hook to provide scroll events handler, which will allow advance and - * customize handling for scrollables. - * @param configs.enableFooterMarginAdjustment - Adjust the scrollable bottom margin to avoid the animated footer. - * - * @example - * ```tsx - * const BottomSheetLegendListScrollable = useBottomSheetScrollableCreator(); - * - * // Usage in JSX - * - * ``` - */ -// biome-ignore lint/suspicious/noExplicitAny: out of my control -export function useBottomSheetScrollableCreator({ - focusHook, - scrollEventsHandlersHook, - enableFooterMarginAdjustment, -}: BottomSheetScrollableCreatorConfigs = {}): ( - props: T, - ref?: never -) => ReactElement { - return useCallback( - function useBottomSheetScrollableCreator( - // @ts-expect-error - { data: _, ...props }: T, - ref?: never - ): ReactElement { - return ( - // @ts-expect-error - - ); - }, - [focusHook, scrollEventsHandlersHook, enableFooterMarginAdjustment] - ); -} diff --git a/src/hooks/useBottomSheetSpringConfigs.ts b/src/hooks/useBottomSheetSpringConfigs.ts deleted file mode 100644 index f379aa66..00000000 --- a/src/hooks/useBottomSheetSpringConfigs.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { WithSpringConfig } from 'react-native-reanimated'; - -/** - * Generate spring animation configs. - * @param configs overridable configs. - */ -export const useBottomSheetSpringConfigs = ( - configs: Omit -) => { - return configs; -}; diff --git a/src/hooks/useBottomSheetTimingConfigs.ts b/src/hooks/useBottomSheetTimingConfigs.ts deleted file mode 100644 index ff45b241..00000000 --- a/src/hooks/useBottomSheetTimingConfigs.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { useMemo } from 'react'; -import type { EasingFunction } from 'react-native'; -import type { - EasingFunctionFactory, - ReduceMotion, -} from 'react-native-reanimated'; -import { ANIMATION_DURATION, ANIMATION_EASING } from '../constants'; - -/** - * this is needed to avoid TS4023 - * https://github.com/microsoft/TypeScript/issues/5711 - */ -interface TimingConfig { - duration?: number; - easing?: EasingFunction | EasingFunctionFactory; - reduceMotion?: ReduceMotion; -} - -/** - * Generate timing animation configs. - * @default - * - easing: Easing.out(Easing.exp) - * - duration: 250 - * @param configs overridable configs. - */ -export const useBottomSheetTimingConfigs = (configs: TimingConfig) => { - return useMemo(() => { - const _configs: TimingConfig = { - easing: configs.easing || ANIMATION_EASING, - duration: configs.duration || ANIMATION_DURATION, - reduceMotion: configs.reduceMotion, - }; - - return _configs; - }, [configs.duration, configs.easing, configs.reduceMotion]); -}; diff --git a/src/hooks/useBoundingClientRect.ts b/src/hooks/useBoundingClientRect.ts deleted file mode 100644 index ab0c3b07..00000000 --- a/src/hooks/useBoundingClientRect.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { type RefObject, useLayoutEffect } from 'react'; -import type { View } from 'react-native'; -import { isFabricInstalled } from '../utilities/isFabricInstalled'; - -export type BoundingClientRect = { - x: number; - y: number; - width: number; - height: number; - left: number; - right: number; - top: number; - bottom: number; -}; - -/** - * A custom hook that retrieves the bounding client rectangle of a given `ref` element - * and invokes a handler function with the layout information. - * - * This hook is designed to work with React Native's Fabric architecture and provides - * support for both `unstable_getBoundingClientRect` and `getBoundingClientRect` methods. - * - * @param ref - A `RefObject` pointing to a `View` or `null`. The bounding client rectangle - * will be retrieved from this reference. - * @param handler - A callback function that will be invoked with the layout information - * of the referenced element. - * - * @remarks - * - The hook uses `useLayoutEffect` to ensure the layout information is retrieved - * after the DOM updates. - * - The `isFabricInstalled` function is used to determine if the Fabric architecture - * is available. - * - The `unstable_getBoundingClientRect` method is used if available, falling back - * to `getBoundingClientRect` otherwise. - * - * @example - * ```tsx - * const ref = useRef(null); - * useBoundingClientRect(ref, (layout) => { - * console.log('Bounding client rect:', layout); - * }); - * ``` - */ -export function useBoundingClientRect( - ref: RefObject, - handler: (layout: BoundingClientRect) => void -) { - if (!isFabricInstalled()) { - return; - } - - // biome-ignore lint/correctness/useHookAtTopLevel: `isFabricInstalled` is a constant that will not change during the runtime - useLayoutEffect(() => { - if (!ref || !ref.current) { - return; - } - - if ( - // @ts-expect-error 👉 https://github.com/facebook/react/commit/53b1f69ba - ref.current.unstable_getBoundingClientRect !== null && - // @ts-expect-error 👉 https://github.com/facebook/react/commit/53b1f69ba - typeof ref.current.unstable_getBoundingClientRect === 'function' - ) { - // @ts-expect-error https://github.com/facebook/react/commit/53b1f69ba - const layout = ref.current.unstable_getBoundingClientRect(); - handler(layout); - return; - } - - if ( - // @ts-expect-error once it `unstable_getBoundingClientRect` gets stable 🤞. - ref.current.getBoundingClientRect !== null && - // @ts-expect-error once it `unstable_getBoundingClientRect` gets stable 🤞. - typeof ref.current.getBoundingClientRect === 'function' - ) { - // @ts-expect-error once it `unstable_getBoundingClientRect` gets stable. - const layout = ref.current.getBoundingClientRect(); - handler(layout); - } - }); -} diff --git a/src/hooks/useGestureEventsHandlersDefault.tsx b/src/hooks/useGestureEventsHandlersDefault.tsx deleted file mode 100644 index 5f023069..00000000 --- a/src/hooks/useGestureEventsHandlersDefault.tsx +++ /dev/null @@ -1,444 +0,0 @@ -import { useCallback } from 'react'; -import { Dimensions, Keyboard, Platform } from 'react-native'; -import { runOnJS, useSharedValue } from 'react-native-reanimated'; -import { - ANIMATION_SOURCE, - GESTURE_SOURCE, - KEYBOARD_STATUS, - SCROLLABLE_TYPE, -} from '../constants'; -import type { - GestureEventHandlerCallbackType, - GestureEventsHandlersHookType, -} from '../types'; -import { clamp } from '../utilities/clamp'; -import { snapPoint } from '../utilities/snapPoint'; -import { useBottomSheetInternal } from './useBottomSheetInternal'; - -type GestureEventContextType = { - initialPosition: number; - initialKeyboardStatus: KEYBOARD_STATUS; - isScrollablePositionLocked: boolean; -}; - -const INITIAL_CONTEXT: GestureEventContextType = { - initialPosition: 0, - initialKeyboardStatus: KEYBOARD_STATUS.UNDETERMINED, - isScrollablePositionLocked: false, -}; - -const dismissKeyboard = Keyboard.dismiss; - -// biome-ignore lint: to be addressed! -const resetContext = (context: any) => { - 'worklet'; - Object.keys(context).forEach(key => { - context[key] = undefined; - }); -}; - -export const useGestureEventsHandlersDefault: GestureEventsHandlersHookType = - () => { - //#region variables - const { - animatedPosition, - animatedDetentsState, - animatedKeyboardState, - animatedScrollableState, - animatedLayoutState, - enableOverDrag, - enablePanDownToClose, - overDragResistanceFactor, - isInTemporaryPosition, - enableBlurKeyboardOnGesture, - animateToPosition, - stopAnimation, - } = useBottomSheetInternal(); - - const context = useSharedValue({ - ...INITIAL_CONTEXT, - }); - //#endregion - - //#region gesture methods - const handleOnStart: GestureEventHandlerCallbackType = useCallback( - function handleOnStart(__, _) { - 'worklet'; - // cancel current animation - stopAnimation(); - - let initialKeyboardStatus = animatedKeyboardState.get().status; - // blur the keyboard when user start dragging the bottom sheet - if ( - enableBlurKeyboardOnGesture && - initialKeyboardStatus === KEYBOARD_STATUS.SHOWN - ) { - initialKeyboardStatus = KEYBOARD_STATUS.HIDDEN; - runOnJS(dismissKeyboard)(); - } - - // store current animated position - context.value = { - ...context.value, - initialPosition: animatedPosition.value, - initialKeyboardStatus, - }; - - /** - * if the scrollable content is scrolled, then - * we lock the position. - */ - if (animatedScrollableState.get().contentOffsetY > 0) { - context.value = { - ...context.value, - isScrollablePositionLocked: true, - }; - } - }, - [ - stopAnimation, - context, - enableBlurKeyboardOnGesture, - animatedPosition, - animatedKeyboardState, - animatedScrollableState, - ] - ); - const handleOnChange: GestureEventHandlerCallbackType = useCallback( - function handleOnChange(source, { translationY }) { - 'worklet'; - const { highestDetentPosition, detents } = animatedDetentsState.get(); - if ( - highestDetentPosition === undefined || - detents === undefined || - detents.length === 0 - ) { - return; - } - - let highestSnapPoint = highestDetentPosition; - - /** - * if keyboard is shown, then we set the highest point to the current - * position which includes the keyboard height. - */ - if ( - isInTemporaryPosition.value && - context.value.initialKeyboardStatus === KEYBOARD_STATUS.SHOWN - ) { - highestSnapPoint = context.value.initialPosition; - } - - /** - * if current position is out of provided `snapPoints` and smaller then - * highest snap pont, then we set the highest point to the current position. - */ - if ( - isInTemporaryPosition.value && - context.value.initialPosition < highestSnapPoint - ) { - highestSnapPoint = context.value.initialPosition; - } - - const { containerHeight } = animatedLayoutState.get(); - const lowestSnapPoint = enablePanDownToClose - ? containerHeight - : detents[0]; - - /** - * if scrollable is refreshable and sheet position at the highest - * point, then do not interact with current gesture. - */ - if ( - source === GESTURE_SOURCE.CONTENT && - animatedScrollableState.get().refreshable && - animatedPosition.value === highestSnapPoint - ) { - return; - } - - /** - * a negative scrollable content offset to be subtracted from accumulated - * current position and gesture translation Y to allow user to drag the sheet, - * when scrollable position at the top. - * a negative scrollable content offset when the scrollable is not locked. - */ - const negativeScrollableContentOffset = - (context.value.initialPosition === highestSnapPoint && - source === GESTURE_SOURCE.CONTENT) || - !context.value.isScrollablePositionLocked - ? animatedScrollableState.get().contentOffsetY * -1 - : 0; - - /** - * an accumulated value of starting position with gesture translation y. - */ - const draggedPosition = context.value.initialPosition + translationY; - - /** - * an accumulated value of dragged position and negative scrollable content offset, - * this will insure locking sheet position when user is scrolling the scrollable until, - * they reach to the top of the scrollable. - */ - const accumulatedDraggedPosition = - draggedPosition + negativeScrollableContentOffset; - - /** - * a clamped value of the accumulated dragged position, to insure keeping the dragged - * position between the highest and lowest snap points. - */ - const clampedPosition = clamp( - accumulatedDraggedPosition, - highestSnapPoint, - lowestSnapPoint - ); - - /** - * if scrollable position is locked and the animated position - * reaches the highest point, then we unlock the scrollable position. - */ - if ( - context.value.isScrollablePositionLocked && - source === GESTURE_SOURCE.CONTENT && - animatedPosition.value === highestSnapPoint - ) { - context.value = { - ...context.value, - isScrollablePositionLocked: false, - }; - } - - /** - * over-drag implementation. - */ - if (enableOverDrag) { - if ( - (source === GESTURE_SOURCE.HANDLE || - animatedScrollableState.get().type === SCROLLABLE_TYPE.VIEW) && - draggedPosition < highestSnapPoint - ) { - const resistedPosition = - highestSnapPoint - - Math.sqrt(1 + (highestSnapPoint - draggedPosition)) * - overDragResistanceFactor; - animatedPosition.value = resistedPosition; - return; - } - - if ( - source === GESTURE_SOURCE.HANDLE && - draggedPosition > lowestSnapPoint - ) { - const resistedPosition = - lowestSnapPoint + - Math.sqrt(1 + (draggedPosition - lowestSnapPoint)) * - overDragResistanceFactor; - animatedPosition.value = resistedPosition; - return; - } - - if ( - source === GESTURE_SOURCE.CONTENT && - draggedPosition + negativeScrollableContentOffset > lowestSnapPoint - ) { - const resistedPosition = - lowestSnapPoint + - Math.sqrt( - 1 + - (draggedPosition + - negativeScrollableContentOffset - - lowestSnapPoint) - ) * - overDragResistanceFactor; - animatedPosition.value = resistedPosition; - return; - } - } - - animatedPosition.value = clampedPosition; - }, - [ - enableOverDrag, - enablePanDownToClose, - overDragResistanceFactor, - isInTemporaryPosition, - animatedScrollableState, - animatedDetentsState, - animatedLayoutState, - animatedPosition, - context, - ] - ); - const handleOnEnd: GestureEventHandlerCallbackType = useCallback( - function handleOnEnd(source, { translationY, absoluteY, velocityY }) { - 'worklet'; - const { highestDetentPosition, detents, closedDetentPosition } = - animatedDetentsState.get(); - if ( - highestDetentPosition === undefined || - detents === undefined || - detents.length === 0 || - closedDetentPosition === undefined - ) { - return; - } - - const highestSnapPoint = highestDetentPosition; - const isSheetAtHighestSnapPoint = - animatedPosition.value === highestSnapPoint; - const { - refreshable: scrollableIsRefreshable, - contentOffsetY: scrollableContentOffsetY, - type: scrollableType, - } = animatedScrollableState.get(); - - /** - * if scrollable is refreshable and sheet position at the highest - * point, then do not interact with current gesture. - */ - if ( - source === GESTURE_SOURCE.CONTENT && - scrollableIsRefreshable && - isSheetAtHighestSnapPoint - ) { - return; - } - - /** - * if the sheet is in a temporary position and the gesture ended above - * the current position, then we snap back to the temporary position. - */ - if ( - isInTemporaryPosition.value && - context.value.initialPosition >= animatedPosition.value - ) { - if (context.value.initialPosition > animatedPosition.value) { - animateToPosition( - context.value.initialPosition, - ANIMATION_SOURCE.GESTURE, - velocityY / 2 - ); - } - return; - } - - /** - * close keyboard if current position is below the recorded - * start position and keyboard still shown. - */ - const isScrollable = - scrollableType !== SCROLLABLE_TYPE.UNDETERMINED && - scrollableType !== SCROLLABLE_TYPE.VIEW; - - /** - * if keyboard is shown and the sheet is dragged down, - * then we dismiss the keyboard. - */ - if ( - context.value.initialKeyboardStatus === KEYBOARD_STATUS.SHOWN && - animatedPosition.value > context.value.initialPosition - ) { - /** - * if the platform is ios, current content is scrollable and - * the end touch point is below the keyboard position then - * we exit the method. - * - * because the the keyboard dismiss is interactive in iOS. - */ - const WINDOW_HEIGHT = Dimensions.get('window').height; - if ( - !( - Platform.OS === 'ios' && - isScrollable && - absoluteY > - WINDOW_HEIGHT - - animatedKeyboardState.get().heightWithinContainer - ) - ) { - runOnJS(dismissKeyboard)(); - } - } - - /** - * reset isInTemporaryPosition value - */ - if (isInTemporaryPosition.value) { - isInTemporaryPosition.value = false; - } - - /** - * clone snap points array, and insert the container height - * if pan down to close is enabled. - */ - const snapPoints = detents.slice(); - if (enablePanDownToClose) { - snapPoints.unshift(closedDetentPosition); - } - - const wasGestureHandledByScrollView = - source === GESTURE_SOURCE.CONTENT && scrollableContentOffsetY > 0; - - let rawDestinationPosition = - translationY + context.value.initialPosition; - - if (wasGestureHandledByScrollView) { - rawDestinationPosition -= scrollableContentOffsetY; - } - - /** - * calculate the destination point, using redash. - */ - const destinationPoint = snapPoint( - rawDestinationPosition, - velocityY, - snapPoints - ); - - /** - * if destination point is the same as the current position, - * then no need to perform animation. - */ - if (destinationPoint === animatedPosition.value) { - return; - } - - /** - * prevents snapping from top to middle / bottom with repeated interrupted scrolls - */ - if (wasGestureHandledByScrollView && isSheetAtHighestSnapPoint) { - return; - } - - animateToPosition( - destinationPoint, - ANIMATION_SOURCE.GESTURE, - velocityY / 2 - ); - }, - [ - enablePanDownToClose, - isInTemporaryPosition, - animatedScrollableState, - animatedDetentsState, - animatedKeyboardState, - animatedPosition, - animateToPosition, - context, - ] - ); - const handleOnFinalize: GestureEventHandlerCallbackType = useCallback( - function handleOnFinalize() { - 'worklet'; - resetContext(context); - }, - [context] - ); - //#endregion - - return { - handleOnStart, - handleOnChange, - handleOnEnd, - handleOnFinalize, - }; - }; diff --git a/src/hooks/useGestureEventsHandlersDefault.web.tsx b/src/hooks/useGestureEventsHandlersDefault.web.tsx deleted file mode 100644 index 2701a8c0..00000000 --- a/src/hooks/useGestureEventsHandlersDefault.web.tsx +++ /dev/null @@ -1,418 +0,0 @@ -import { useCallback } from 'react'; -import { Dimensions, Keyboard, Platform } from 'react-native'; -import { runOnJS, useSharedValue } from 'react-native-reanimated'; -import { - ANIMATION_SOURCE, - GESTURE_SOURCE, - KEYBOARD_STATUS, - SCROLLABLE_TYPE, -} from '../constants'; -import type { GestureEventHandlerCallbackType } from '../types'; -import { clamp } from '../utilities/clamp'; -import { snapPoint } from '../utilities/snapPoint'; -import { useBottomSheetInternal } from './useBottomSheetInternal'; - -type GestureEventContextType = { - initialPosition: number; - initialKeyboardStatus: KEYBOARD_STATUS; - initialTranslationY: number; - isScrollablePositionLocked: boolean; -}; - -const INITIAL_CONTEXT: GestureEventContextType = { - initialPosition: 0, - initialTranslationY: 0, - initialKeyboardStatus: KEYBOARD_STATUS.UNDETERMINED, - isScrollablePositionLocked: false, -}; - -const dismissKeyboardOnJs = runOnJS(Keyboard.dismiss); - -// biome-ignore lint: to be addressed! -const resetContext = (context: any) => { - 'worklet'; - Object.keys(context).forEach(key => { - context[key] = undefined; - }); -}; - -export const useGestureEventsHandlersDefault = () => { - //#region variables - const { - animatedPosition, - animatedDetentsState, - animatedKeyboardState, - animatedLayoutState, - animatedScrollableState, - enableOverDrag, - enablePanDownToClose, - overDragResistanceFactor, - isInTemporaryPosition, - animateToPosition, - stopAnimation, - } = useBottomSheetInternal(); - - const context = useSharedValue({ - ...INITIAL_CONTEXT, - }); - //#endregion - - //#region gesture methods - const handleOnStart: GestureEventHandlerCallbackType = useCallback( - function handleOnStart(__, { translationY }) { - 'worklet'; - // cancel current animation - stopAnimation(); - - // store current animated position - context.value = { - ...context.value, - initialPosition: animatedPosition.value, - initialKeyboardStatus: animatedKeyboardState.get().status, - initialTranslationY: translationY, - }; - - /** - * if the scrollable content is scrolled, then - * we lock the position. - */ - if (animatedScrollableState.get().contentOffsetY > 0) { - context.value.isScrollablePositionLocked = true; - } - }, - [ - stopAnimation, - context, - animatedPosition, - animatedKeyboardState, - animatedScrollableState, - ] - ); - const handleOnChange: GestureEventHandlerCallbackType = useCallback( - function handleOnChange(source, { translationY }) { - 'worklet'; - const { highestDetentPosition, detents } = animatedDetentsState.get(); - if (highestDetentPosition === undefined || detents === undefined) { - return; - } - - let highestSnapPoint = highestDetentPosition; - translationY = translationY - context.value.initialTranslationY; - /** - * if keyboard is shown, then we set the highest point to the current - * position which includes the keyboard height. - */ - if ( - isInTemporaryPosition.value && - context.value.initialKeyboardStatus === KEYBOARD_STATUS.SHOWN - ) { - highestSnapPoint = context.value.initialPosition; - } - - /** - * if current position is out of provided `snapPoints` and smaller then - * highest snap pont, then we set the highest point to the current position. - */ - if ( - isInTemporaryPosition.value && - context.value.initialPosition < highestSnapPoint - ) { - highestSnapPoint = context.value.initialPosition; - } - - const { containerHeight } = animatedLayoutState.get(); - const lowestSnapPoint = enablePanDownToClose - ? containerHeight - : detents[0]; - - const { - refreshable: scrollableIsRefreshable, - contentOffsetY: scrollableContentOffsetY, - type: scrollableType, - } = animatedScrollableState.get(); - - /** - * if scrollable is refreshable and sheet position at the highest - * point, then do not interact with current gesture. - */ - if ( - source === GESTURE_SOURCE.CONTENT && - scrollableIsRefreshable && - animatedPosition.value === highestSnapPoint - ) { - return; - } - - /** - * a negative scrollable content offset to be subtracted from accumulated - * current position and gesture translation Y to allow user to drag the sheet, - * when scrollable position at the top. - * a negative scrollable content offset when the scrollable is not locked. - */ - const negativeScrollableContentOffset = - (context.value.initialPosition === highestSnapPoint && - source === GESTURE_SOURCE.CONTENT) || - !context.value.isScrollablePositionLocked - ? scrollableContentOffsetY * -1 - : 0; - - /** - * an accumulated value of starting position with gesture translation y. - */ - const draggedPosition = context.value.initialPosition + translationY; - - /** - * an accumulated value of dragged position and negative scrollable content offset, - * this will insure locking sheet position when user is scrolling the scrollable until, - * they reach to the top of the scrollable. - */ - const accumulatedDraggedPosition = - draggedPosition + negativeScrollableContentOffset; - - /** - * a clamped value of the accumulated dragged position, to insure keeping the dragged - * position between the highest and lowest snap points. - */ - const clampedPosition = clamp( - accumulatedDraggedPosition, - highestSnapPoint, - lowestSnapPoint - ); - - /** - * if scrollable position is locked and the animated position - * reaches the highest point, then we unlock the scrollable position. - */ - if ( - context.value.isScrollablePositionLocked && - source === GESTURE_SOURCE.CONTENT && - animatedPosition.value === highestSnapPoint - ) { - context.value.isScrollablePositionLocked = false; - } - - /** - * over-drag implementation. - */ - if (enableOverDrag) { - if ( - (source === GESTURE_SOURCE.HANDLE || - scrollableType === SCROLLABLE_TYPE.VIEW) && - draggedPosition < highestSnapPoint - ) { - const resistedPosition = - highestSnapPoint - - Math.sqrt(1 + (highestSnapPoint - draggedPosition)) * - overDragResistanceFactor; - animatedPosition.value = resistedPosition; - return; - } - - if ( - source === GESTURE_SOURCE.HANDLE && - draggedPosition > lowestSnapPoint - ) { - const resistedPosition = - lowestSnapPoint + - Math.sqrt(1 + (draggedPosition - lowestSnapPoint)) * - overDragResistanceFactor; - animatedPosition.value = resistedPosition; - return; - } - - if ( - source === GESTURE_SOURCE.CONTENT && - draggedPosition + negativeScrollableContentOffset > lowestSnapPoint - ) { - const resistedPosition = - lowestSnapPoint + - Math.sqrt( - 1 + - (draggedPosition + - negativeScrollableContentOffset - - lowestSnapPoint) - ) * - overDragResistanceFactor; - animatedPosition.value = resistedPosition; - return; - } - } - - animatedPosition.value = clampedPosition; - }, - [ - context, - enableOverDrag, - enablePanDownToClose, - overDragResistanceFactor, - isInTemporaryPosition, - animatedDetentsState, - animatedLayoutState, - animatedPosition, - animatedScrollableState, - ] - ); - const handleOnEnd: GestureEventHandlerCallbackType = useCallback( - function handleOnEnd(source, { translationY, absoluteY, velocityY }) { - 'worklet'; - const { highestDetentPosition, detents, closedDetentPosition } = - animatedDetentsState.get(); - if ( - highestDetentPosition === undefined || - detents === undefined || - closedDetentPosition === undefined - ) { - return; - } - - const highestSnapPoint = highestDetentPosition; - const isSheetAtHighestSnapPoint = - animatedPosition.value === highestSnapPoint; - - const { - refreshable: scrollableIsRefreshable, - contentOffsetY: scrollableContentOffsetY, - type: scrollableType, - } = animatedScrollableState.get(); - - /** - * if scrollable is refreshable and sheet position at the highest - * point, then do not interact with current gesture. - */ - if ( - source === GESTURE_SOURCE.CONTENT && - scrollableIsRefreshable && - isSheetAtHighestSnapPoint - ) { - return; - } - - /** - * if the sheet is in a temporary position and the gesture ended above - * the current position, then we snap back to the temporary position. - */ - if ( - isInTemporaryPosition.value && - context.value.initialPosition >= animatedPosition.value - ) { - if (context.value.initialPosition > animatedPosition.value) { - animateToPosition( - context.value.initialPosition, - ANIMATION_SOURCE.GESTURE, - velocityY / 2 - ); - } - return; - } - - /** - * close keyboard if current position is below the recorded - * start position and keyboard still shown. - */ - const isScrollable = - scrollableType !== SCROLLABLE_TYPE.UNDETERMINED && - scrollableType !== SCROLLABLE_TYPE.VIEW; - - /** - * if keyboard is shown and the sheet is dragged down, - * then we dismiss the keyboard. - */ - if ( - context.value.initialKeyboardStatus === KEYBOARD_STATUS.SHOWN && - animatedPosition.value > context.value.initialPosition - ) { - /** - * if the platform is ios, current content is scrollable and - * the end touch point is below the keyboard position then - * we exit the method. - * - * because the the keyboard dismiss is interactive in iOS. - */ - const WINDOW_HEIGHT = Dimensions.get('window').height; - if ( - !( - Platform.OS === 'ios' && - isScrollable && - absoluteY > - WINDOW_HEIGHT - animatedKeyboardState.get().heightWithinContainer - ) - ) { - dismissKeyboardOnJs(); - } - } - - /** - * reset isInTemporaryPosition value - */ - if (isInTemporaryPosition.value) { - isInTemporaryPosition.value = false; - } - - /** - * clone snap points array, and insert the container height - * if pan down to close is enabled. - */ - const snapPoints = detents.slice(); - if (enablePanDownToClose) { - snapPoints.unshift(closedDetentPosition); - } - - /** - * calculate the destination point, using redash. - */ - const destinationPoint = snapPoint( - translationY + context.value.initialPosition, - velocityY, - snapPoints - ); - - /** - * if destination point is the same as the current position, - * then no need to perform animation. - */ - if (destinationPoint === animatedPosition.value) { - return; - } - - const wasGestureHandledByScrollView = - source === GESTURE_SOURCE.CONTENT && scrollableContentOffsetY > 0; - /** - * prevents snapping from top to middle / bottom with repeated interrupted scrolls - */ - if (wasGestureHandledByScrollView && isSheetAtHighestSnapPoint) { - return; - } - - animateToPosition( - destinationPoint, - ANIMATION_SOURCE.GESTURE, - velocityY / 2 - ); - }, - [ - enablePanDownToClose, - isInTemporaryPosition, - animatedScrollableState, - animatedDetentsState, - animatedKeyboardState, - animatedPosition, - animateToPosition, - context, - ] - ); - const handleOnFinalize: GestureEventHandlerCallbackType = useCallback( - function handleOnFinalize() { - 'worklet'; - resetContext(context); - }, - [context] - ); - //#endregion - - return { - handleOnStart, - handleOnChange, - handleOnEnd, - handleOnFinalize, - }; -}; diff --git a/src/hooks/useGestureHandler.ts b/src/hooks/useGestureHandler.ts deleted file mode 100644 index 6f35e1c7..00000000 --- a/src/hooks/useGestureHandler.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { useCallback } from 'react'; -import { - type GestureStateChangeEvent, - type GestureUpdateEvent, - type PanGestureChangeEventPayload, - type PanGestureHandlerEventPayload, - State, -} from 'react-native-gesture-handler'; -import type { SharedValue } from 'react-native-reanimated'; -import { GESTURE_SOURCE } from '../constants'; -import type { - GestureEventHandlerCallbackType, - GestureHandlersHookType, -} from '../types'; - -export const useGestureHandler: GestureHandlersHookType = ( - source: GESTURE_SOURCE, - state: SharedValue, - gestureSource: SharedValue, - onStart: GestureEventHandlerCallbackType, - onChange: GestureEventHandlerCallbackType, - onEnd: GestureEventHandlerCallbackType, - onFinalize: GestureEventHandlerCallbackType -) => { - const handleOnStart = useCallback( - (event: GestureStateChangeEvent) => { - 'worklet'; - state.value = State.BEGAN; - gestureSource.value = source; - - onStart(source, event); - return; - }, - [state, gestureSource, source, onStart] - ); - - const handleOnChange = useCallback( - ( - event: GestureUpdateEvent< - PanGestureHandlerEventPayload & PanGestureChangeEventPayload - > - ) => { - 'worklet'; - if (gestureSource.value !== source) { - return; - } - - state.value = event.state; - onChange(source, event); - }, - [state, gestureSource, source, onChange] - ); - - const handleOnEnd = useCallback( - (event: GestureStateChangeEvent) => { - 'worklet'; - if (gestureSource.value !== source) { - return; - } - - state.value = event.state; - gestureSource.value = GESTURE_SOURCE.UNDETERMINED; - - onEnd(source, event); - }, - [state, gestureSource, source, onEnd] - ); - - const handleOnFinalize = useCallback( - (event: GestureStateChangeEvent) => { - 'worklet'; - if (gestureSource.value !== source) { - return; - } - - state.value = event.state; - gestureSource.value = GESTURE_SOURCE.UNDETERMINED; - - onFinalize(source, event); - }, - [state, gestureSource, source, onFinalize] - ); - - return { - handleOnStart, - handleOnChange, - handleOnEnd, - handleOnFinalize, - }; -}; diff --git a/src/hooks/usePropsValidator.ts b/src/hooks/usePropsValidator.ts deleted file mode 100644 index 9a2100b7..00000000 --- a/src/hooks/usePropsValidator.ts +++ /dev/null @@ -1,108 +0,0 @@ -import invariant from 'invariant'; -import { useMemo } from 'react'; -import type { BottomSheetProps } from '../components/bottomSheet'; -import { INITIAL_SNAP_POINT } from '../components/bottomSheet/constants'; - -/** - * @todo - * replace this with `prop-types`. - */ - -export const usePropsValidator = ({ - index, - snapPoints, - enableDynamicSizing, - topInset, - bottomInset, - containerHeight, - containerOffset, -}: Pick< - BottomSheetProps, - | 'index' - | 'snapPoints' - | 'enableDynamicSizing' - | 'topInset' - | 'bottomInset' - | 'containerHeight' - | 'containerOffset' ->) => { - useMemo(() => { - //#region snap points - const _snapPoints = snapPoints - ? 'get' in snapPoints - ? snapPoints.get() - : snapPoints - : []; - invariant( - _snapPoints || enableDynamicSizing, - `'snapPoints' was not provided! please provide at least one snap point.` - ); - - _snapPoints.forEach(snapPoint => { - const _snapPoint = - typeof snapPoint === 'number' - ? snapPoint - : Number.parseInt(snapPoint.replace('%', ''), 10); - - invariant( - _snapPoint > 0 || _snapPoint === INITIAL_SNAP_POINT, - `Snap point '${snapPoint}' is invalid. if you want to allow user to close the sheet, Please use 'enablePanDownToClose' prop.` - ); - }); - - invariant( - 'value' in _snapPoints || _snapPoints.length > 0 || enableDynamicSizing, - `'snapPoints' was provided with no points! please provide at least one snap point.` - ); - //#endregion - - //#region index - invariant( - typeof index === 'number' || typeof index === 'undefined', - `'index' was provided but with wrong type ! expected type is a number.` - ); - - invariant( - enableDynamicSizing || - (typeof index === 'number' - ? index >= -1 && index <= _snapPoints.length - 1 - : true), - `'index' was provided but out of the provided snap points range! expected value to be between -1, ${ - _snapPoints.length - 1 - }` - ); - //#endregion - - //#region insets - invariant( - typeof topInset === 'number' || typeof topInset === 'undefined', - `'topInset' was provided but with wrong type ! expected type is a number.` - ); - invariant( - typeof bottomInset === 'number' || typeof bottomInset === 'undefined', - `'bottomInset' was provided but with wrong type ! expected type is a number.` - ); - //#endregion - - //#region container height and offset - invariant( - containerHeight === undefined, - `'containerHeight' is deprecated, please use 'containerLayoutState'.` - ); - - invariant( - containerOffset === undefined, - `'containerHeight' is deprecated, please use 'containerLayoutState'.` - ); - - // animations - }, [ - index, - snapPoints, - topInset, - bottomInset, - enableDynamicSizing, - containerHeight, - containerOffset, - ]); -}; diff --git a/src/hooks/useReactiveSharedValue.ts b/src/hooks/useReactiveSharedValue.ts deleted file mode 100644 index 905fd066..00000000 --- a/src/hooks/useReactiveSharedValue.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { useEffect, useRef } from 'react'; -import type { SharedValue } from 'react-native-reanimated'; -import { cancelAnimation, makeMutable } from 'react-native-reanimated'; -import type { Primitive } from '../types'; - -export const useReactiveSharedValue = ( - value: T -): T extends Primitive ? SharedValue : T => { - const initialValueRef = useRef(null); - const valueRef = useRef>(null); - - if (value && typeof value === 'object' && 'value' in value) { - /** - * if provided value is a shared value, - * then we do not initialize another one. - */ - } else if (valueRef.current === null) { - // @ts-expect-error - initialValueRef.current = value; - /** - * if value is an object, then we need to - * pass a clone. - */ - if (typeof value === 'object') { - // @ts-expect-error - valueRef.current = makeMutable({ ...value }); - } else { - // @ts-expect-error - valueRef.current = makeMutable(value); - } - } else if (initialValueRef.current !== value) { - valueRef.current.value = value as T; - } - - useEffect(() => { - return () => { - if (valueRef.current) { - cancelAnimation(valueRef.current); - } - }; - }, []); - - // @ts-expect-error - return valueRef.current ?? value; -}; diff --git a/src/hooks/useScrollEventsHandlersDefault.ts b/src/hooks/useScrollEventsHandlersDefault.ts deleted file mode 100644 index 7b84480e..00000000 --- a/src/hooks/useScrollEventsHandlersDefault.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { useCallback } from 'react'; -import { State } from 'react-native-gesture-handler'; -import { scrollTo } from 'react-native-reanimated'; -import { ANIMATION_STATUS, SCROLLABLE_STATUS, SHEET_STATE } from '../constants'; -import type { - ScrollEventHandlerCallbackType, - ScrollEventsHandlersHookType, -} from '../types'; -import { useBottomSheetInternal } from './useBottomSheetInternal'; - -export type ScrollEventContextType = { - initialContentOffsetY: number; - shouldLockInitialPosition: boolean; -}; - -export const useScrollEventsHandlersDefault: ScrollEventsHandlersHookType = ( - scrollableRef, - scrollableContentOffsetY -) => { - // hooks - const { - animatedSheetState, - animatedScrollableState, - animatedScrollableStatus, - animatedAnimationState, - animatedHandleGestureState, - } = useBottomSheetInternal(); - - //#region callbacks - const handleOnScroll: ScrollEventHandlerCallbackType = - useCallback( - ({ contentOffset: { y } }, context) => { - 'worklet'; - /** - * if sheet position is extended or fill parent, then we reset - * `shouldLockInitialPosition` value to false. - */ - if ( - animatedSheetState.value === SHEET_STATE.EXTENDED || - animatedSheetState.value === SHEET_STATE.FILL_PARENT - ) { - context.shouldLockInitialPosition = false; - } - - /** - * if handle gesture state is active, then we capture the offset y position - * and lock the scrollable with it. - */ - if (animatedHandleGestureState.value === State.ACTIVE) { - context.shouldLockInitialPosition = true; - context.initialContentOffsetY = y; - } - - if (animatedScrollableStatus.value === SCROLLABLE_STATUS.LOCKED) { - const lockPosition = context.shouldLockInitialPosition - ? (context.initialContentOffsetY ?? 0) - : 0; - // @ts-expect-error - scrollTo(scrollableRef, 0, lockPosition, false); - scrollableContentOffsetY.value = lockPosition; - return; - } - }, - [ - scrollableRef, - scrollableContentOffsetY, - animatedScrollableStatus, - animatedSheetState, - animatedHandleGestureState, - ] - ); - const handleOnBeginDrag: ScrollEventHandlerCallbackType = - useCallback( - ({ contentOffset: { y } }, context) => { - 'worklet'; - scrollableContentOffsetY.value = y; - context.initialContentOffsetY = y; - animatedScrollableState.set(state => ({ - ...state, - contentOffsetY: y, - })); - - /** - * if sheet position not extended or fill parent and the scrollable position - * not at the top, then we should lock the initial scrollable position. - */ - if ( - animatedSheetState.value !== SHEET_STATE.EXTENDED && - animatedSheetState.value !== SHEET_STATE.FILL_PARENT && - y > 0 - ) { - context.shouldLockInitialPosition = true; - } else { - context.shouldLockInitialPosition = false; - } - }, - [scrollableContentOffsetY, animatedSheetState, animatedScrollableState] - ); - const handleOnEndDrag: ScrollEventHandlerCallbackType = - useCallback( - ({ contentOffset: { y } }, context) => { - 'worklet'; - if (animatedScrollableStatus.value === SCROLLABLE_STATUS.LOCKED) { - const lockPosition = context.shouldLockInitialPosition - ? (context.initialContentOffsetY ?? 0) - : 0; - // @ts-expect-error - scrollTo(scrollableRef, 0, lockPosition, false); - scrollableContentOffsetY.value = lockPosition; - return; - } - - if (animatedAnimationState.get().status !== ANIMATION_STATUS.RUNNING) { - scrollableContentOffsetY.value = y; - animatedScrollableState.set(state => ({ - ...state, - contentOffsetY: y, - })); - } - }, - [ - scrollableRef, - scrollableContentOffsetY, - animatedAnimationState, - animatedScrollableStatus, - animatedScrollableState, - ] - ); - const handleOnMomentumEnd: ScrollEventHandlerCallbackType = - useCallback( - ({ contentOffset: { y } }, context) => { - 'worklet'; - if (animatedScrollableStatus.value === SCROLLABLE_STATUS.LOCKED) { - const lockPosition = context.shouldLockInitialPosition - ? (context.initialContentOffsetY ?? 0) - : 0; - // @ts-expect-error - scrollTo(scrollableRef, 0, lockPosition, false); - scrollableContentOffsetY.value = 0; - return; - } - - if (animatedAnimationState.get().status !== ANIMATION_STATUS.RUNNING) { - scrollableContentOffsetY.value = y; - animatedScrollableState.set(state => ({ - ...state, - contentOffsetY: y, - })); - } - }, - [ - scrollableContentOffsetY, - scrollableRef, - animatedAnimationState, - animatedScrollableStatus, - animatedScrollableState, - ] - ); - //#endregion - - return { - handleOnScroll, - handleOnBeginDrag, - handleOnEndDrag, - handleOnMomentumEnd, - }; -}; diff --git a/src/hooks/useScrollHandler.ts b/src/hooks/useScrollHandler.ts deleted file mode 100644 index a7910834..00000000 --- a/src/hooks/useScrollHandler.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { - runOnJS, - useAnimatedRef, - useAnimatedScrollHandler, - useSharedValue, -} from 'react-native-reanimated'; -import type { Scrollable, ScrollableEvent } from '../types'; -import { workletNoop as noop } from '../utilities'; -import { useScrollEventsHandlersDefault } from './useScrollEventsHandlersDefault'; - -export const useScrollHandler = ( - useScrollEventsHandlers = useScrollEventsHandlersDefault, - onScroll?: ScrollableEvent, - onScrollBeginDrag?: ScrollableEvent, - onScrollEndDrag?: ScrollableEvent -) => { - // refs - const scrollableRef = useAnimatedRef(); - - // variables - const scrollableContentOffsetY = useSharedValue(0); - - // hooks - const { - handleOnScroll = noop, - handleOnBeginDrag = noop, - handleOnEndDrag = noop, - handleOnMomentumEnd = noop, - handleOnMomentumBegin = noop, - } = useScrollEventsHandlers(scrollableRef, scrollableContentOffsetY); - - // callbacks - const scrollHandler = useAnimatedScrollHandler( - { - onScroll: (event, context) => { - handleOnScroll(event, context); - - if (onScroll) { - runOnJS(onScroll)({ nativeEvent: event }); - } - }, - onBeginDrag: (event, context) => { - handleOnBeginDrag(event, context); - - if (onScrollBeginDrag) { - runOnJS(onScrollBeginDrag)({ nativeEvent: event }); - } - }, - onEndDrag: (event, context) => { - handleOnEndDrag(event, context); - - if (onScrollEndDrag) { - runOnJS(onScrollEndDrag)({ nativeEvent: event }); - } - }, - onMomentumBegin: handleOnMomentumBegin, - onMomentumEnd: handleOnMomentumEnd, - }, - [ - handleOnScroll, - handleOnBeginDrag, - handleOnEndDrag, - handleOnMomentumBegin, - handleOnMomentumEnd, - onScroll, - onScrollBeginDrag, - onScrollEndDrag, - ] - ); - - return { scrollHandler, scrollableRef, scrollableContentOffsetY }; -}; diff --git a/src/hooks/useScrollHandler.web.ts b/src/hooks/useScrollHandler.web.ts deleted file mode 100644 index 9076cd41..00000000 --- a/src/hooks/useScrollHandler.web.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { type TouchEvent, useEffect, useRef } from 'react'; -import { useSharedValue } from 'react-native-reanimated'; -import { ANIMATION_STATUS, SCROLLABLE_STATUS } from '../constants'; -import type { Scrollable, ScrollableEvent } from '../types'; -import { findNodeHandle } from '../utilities/findNodeHandle.web'; -import { useBottomSheetInternal } from './useBottomSheetInternal'; - -export type ScrollEventContextType = { - initialContentOffsetY: number; - shouldLockInitialPosition: boolean; -}; - -export const useScrollHandler = (_: never, onScroll?: ScrollableEvent) => { - //#region refs - const scrollableRef = useRef(null); - //#endregion - - //#region variables - const scrollableContentOffsetY = useSharedValue(0); - //#endregion - - //#region hooks - const { - animatedScrollableState, - animatedScrollableStatus, - animatedAnimationState, - } = useBottomSheetInternal(); - //#endregion - - //#region effects - useEffect(() => { - // biome-ignore lint: to be addressed! - const element = findNodeHandle(scrollableRef.current) as any; - let scrollOffset = 0; - let supportsPassive = false; - let maybePrevent = false; - let lastTouchY = 0; - - let initialContentOffsetY = 0; - const shouldLockInitialPosition = false; - - function handleOnTouchStart(event: TouchEvent) { - if (event.touches.length !== 1) { - return; - } - - initialContentOffsetY = element.scrollTop; - lastTouchY = event.touches[0].clientY; - maybePrevent = scrollOffset <= 0; - } - - function handleOnTouchMove(event: TouchEvent) { - if ( - animatedScrollableStatus.value === SCROLLABLE_STATUS.LOCKED && - event.cancelable - ) { - return event.preventDefault(); - } - - if (maybePrevent) { - maybePrevent = false; - - const touchY = event.touches[0].clientY; - const touchYDelta = touchY - lastTouchY; - - if (touchYDelta > 0 && event.cancelable) { - return event.preventDefault(); - } - } - - return true; - } - - function handleOnTouchEnd() { - if (animatedScrollableStatus.value === SCROLLABLE_STATUS.LOCKED) { - const lockPosition = shouldLockInitialPosition - ? (initialContentOffsetY ?? 0) - : 0; - element.scroll({ - top: 0, - left: 0, - behavior: 'instant', - }); - scrollableContentOffsetY.value = lockPosition; - return; - } - } - - function handleOnScroll(event: TouchEvent) { - scrollOffset = element.scrollTop; - - if (animatedAnimationState.get().status !== ANIMATION_STATUS.RUNNING) { - const contentOffsetY = Math.max(0, scrollOffset); - scrollableContentOffsetY.value = contentOffsetY; - animatedScrollableState.set(state => ({ - ...state, - contentOffsetY, - })); - } - - if (scrollOffset <= 0 && event.cancelable) { - event.preventDefault(); - event.stopPropagation(); - return false; - } - return true; - } - - try { - // @ts-expect-error - window.addEventListener('test', null, { - // @ts-expect-error - // biome-ignore lint: to be addressed - get passive() { - supportsPassive = true; - }, - }); - } catch (_e) {} - - element.addEventListener( - 'touchstart', - handleOnTouchStart, - supportsPassive - ? { - passive: true, - } - : false - ); - - element.addEventListener( - 'touchmove', - handleOnTouchMove, - supportsPassive - ? { - passive: false, - } - : false - ); - - element.addEventListener( - 'touchend', - handleOnTouchEnd, - supportsPassive - ? { - passive: false, - } - : false - ); - - element.addEventListener( - 'scroll', - handleOnScroll, - supportsPassive - ? { - passive: false, - } - : false - ); - - return () => { - // @ts-expect-error - window.removeEventListener('test', null); - element.removeEventListener('touchstart', handleOnTouchStart); - element.removeEventListener('touchmove', handleOnTouchMove); - element.removeEventListener('touchend', handleOnTouchEnd); - element.removeEventListener('scroll', handleOnScroll); - }; - }, [ - animatedAnimationState, - animatedScrollableState, - animatedScrollableStatus, - scrollableContentOffsetY, - ]); - //#endregion - - return { - scrollHandler: onScroll, - scrollableRef, - scrollableContentOffsetY, - }; -}; diff --git a/src/hooks/useScrollable.ts b/src/hooks/useScrollable.ts deleted file mode 100644 index 9c19051d..00000000 --- a/src/hooks/useScrollable.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { type RefObject, useCallback, useRef } from 'react'; -import type { NodeHandle } from 'react-native'; -import { - type SharedValue, - useDerivedValue, - useSharedValue, -} from 'react-native-reanimated'; -import { - ANIMATION_STATUS, - KEYBOARD_STATUS, - SCROLLABLE_STATUS, - SCROLLABLE_TYPE, - SHEET_STATE, -} from '../constants'; -import type { - AnimationState, - KeyboardState, - Scrollable, - ScrollableRef, - ScrollableState, -} from '../types'; -import { findNodeHandle } from '../utilities'; - -export const useScrollable = ( - enableContentPanningGesture: boolean, - animatedSheetState: SharedValue, - animatedKeyboardState: SharedValue, - animatedAnimationState: SharedValue -) => { - //#region refs - const scrollableRef = useRef(null); - const previousScrollableRef = useRef(null); - //#endregion - - //#region variables - const state = useSharedValue({ - type: SCROLLABLE_TYPE.UNDETERMINED, - contentOffsetY: 0, - refreshable: false, - }); - const status = useDerivedValue(() => { - /** - * if user had disabled content panning gesture, then we unlock - * the scrollable state. - */ - if (!enableContentPanningGesture) { - return SCROLLABLE_STATUS.UNLOCKED; - } - - /** - * if sheet state is fill parent, then unlock scrolling - */ - if (animatedSheetState.value === SHEET_STATE.FILL_PARENT) { - return SCROLLABLE_STATUS.UNLOCKED; - } - - /** - * if sheet state is extended, then unlock scrolling - */ - if (animatedSheetState.value === SHEET_STATE.EXTENDED) { - return SCROLLABLE_STATUS.UNLOCKED; - } - - /** - * if keyboard is shown and sheet is animating - * then we do not lock the scrolling to not lose - * current scrollable scroll position. - */ - if ( - animatedKeyboardState.get().status === KEYBOARD_STATUS.SHOWN && - animatedAnimationState.get().status === ANIMATION_STATUS.RUNNING - ) { - return SCROLLABLE_STATUS.UNLOCKED; - } - - return SCROLLABLE_STATUS.LOCKED; - }, [ - enableContentPanningGesture, - animatedSheetState, - animatedKeyboardState, - animatedAnimationState, - state, - ]); - //#endregion - - //#region callbacks - const setScrollableRef = useCallback((ref: ScrollableRef) => { - // get current node handle id - const currentRefId = scrollableRef.current?.id ?? null; - - if (currentRefId !== ref.id) { - if (scrollableRef.current) { - // @ts-expect-error - previousScrollableRef.current = scrollableRef.current; - } - // @ts-expect-error - scrollableRef.current = ref; - } - }, []); - - const removeScrollableRef = useCallback((ref: RefObject) => { - // find node handle id - let id: NodeHandle | null; - try { - id = findNodeHandle(ref.current); - } catch { - return; - } - - // get current node handle id - const currentRefId = scrollableRef.current?.id ?? null; - - /** - * @DEV - * when the incoming node is actually the current node, we reset - * the current scrollable ref to the previous one. - */ - if (id === currentRefId) { - // @ts-expect-error - scrollableRef.current = previousScrollableRef.current; - } - }, []); - //#endregion - - return { - state, - status, - setScrollableRef, - removeScrollableRef, - }; -}; diff --git a/src/hooks/useScrollableSetter.ts b/src/hooks/useScrollableSetter.ts deleted file mode 100644 index ee2208ee..00000000 --- a/src/hooks/useScrollableSetter.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type React from 'react'; -import { useCallback, useEffect } from 'react'; -import type { SharedValue } from 'react-native-reanimated'; -import type { SCROLLABLE_TYPE } from '../constants'; -import type { Scrollable } from '../types'; -import { findNodeHandle } from '../utilities'; -import { useBottomSheetInternal } from './useBottomSheetInternal'; - -export const useScrollableSetter = ( - ref: React.RefObject, - type: SCROLLABLE_TYPE, - contentOffsetY: SharedValue, - refreshable: boolean, - useFocusHook = useEffect -) => { - // hooks - const { animatedScrollableState, setScrollableRef, removeScrollableRef } = - useBottomSheetInternal(); - - // callbacks - const handleSettingScrollable = useCallback(() => { - // set current content offset - animatedScrollableState.set(state => ({ - ...state, - contentOffsetY: contentOffsetY.value, - type, - refreshable, - })); - - // set current scrollable ref - const id = findNodeHandle(ref.current); - if (id) { - setScrollableRef({ - id: id, - node: ref, - }); - } else { - console.warn(`Couldn't find the scrollable node handle id!`); - } - - return () => { - removeScrollableRef(ref); - }; - }, [ - ref, - type, - refreshable, - contentOffsetY, - animatedScrollableState, - setScrollableRef, - removeScrollableRef, - ]); - - // effects - useFocusHook(handleSettingScrollable); -}; diff --git a/src/hooks/useStableCallback.ts b/src/hooks/useStableCallback.ts deleted file mode 100644 index 9202b4bf..00000000 --- a/src/hooks/useStableCallback.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { useCallback, useEffect, useLayoutEffect, useRef } from 'react'; - -type Callback = (...args: T) => R; - -/** - * Provide a stable version of useCallback. - */ -export function useStableCallback( - callback: Callback -) { - const callbackRef = useRef>(); - - useLayoutEffect(() => { - callbackRef.current = callback; - }); - - useEffect(() => { - return () => { - callbackRef.current = undefined; - }; - }, []); - - return useCallback>((...args) => { - return callbackRef.current?.(...args); - }, []); -} diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 7aaea57d..00000000 --- a/src/index.ts +++ /dev/null @@ -1,79 +0,0 @@ -// bottom sheet -export { default } from './components/bottomSheet'; - -// bottom sheet modal -export { default as BottomSheetModal } from './components/bottomSheetModal'; -export { default as BottomSheetModalProvider } from './components/bottomSheetModalProvider'; - -//#region hooks -export { useBottomSheet } from './hooks/useBottomSheet'; -export { useBottomSheetModal } from './hooks/useBottomSheetModal'; -export { useBottomSheetSpringConfigs } from './hooks/useBottomSheetSpringConfigs'; -export { useBottomSheetTimingConfigs } from './hooks/useBottomSheetTimingConfigs'; -export { useBottomSheetInternal } from './hooks/useBottomSheetInternal'; -export { useBottomSheetModalInternal } from './hooks/useBottomSheetModalInternal'; -export { useScrollEventsHandlersDefault } from './hooks/useScrollEventsHandlersDefault'; -export { useGestureEventsHandlersDefault } from './hooks/useGestureEventsHandlersDefault'; -export { useBottomSheetGestureHandlers } from './hooks/useBottomSheetGestureHandlers'; -export { useScrollHandler } from './hooks/useScrollHandler'; -export { useScrollableSetter } from './hooks/useScrollableSetter'; -export { useBottomSheetScrollableCreator } from './hooks/useBottomSheetScrollableCreator'; -//#endregion - -//#region components -export { - BottomSheetScrollView, - BottomSheetSectionList, - BottomSheetFlatList, - BottomSheetVirtualizedList, - BottomSheetFlashList, -} from './components/bottomSheetScrollable'; -export { BottomSheetHandle } from './components/bottomSheetHandle'; -export { default as BottomSheetDraggableView } from './components/bottomSheetDraggableView'; -export { default as BottomSheetView } from './components/bottomSheetView'; -export { default as BottomSheetTextInput } from './components/bottomSheetTextInput'; -export { BottomSheetBackdrop } from './components/bottomSheetBackdrop'; -export { - BottomSheetFooter, - BottomSheetFooterContainer, -} from './components/bottomSheetFooter'; - -// touchables -import BottomSheetTouchable from './components/touchables'; -export const { - TouchableHighlight, - TouchableOpacity, - TouchableWithoutFeedback, -} = BottomSheetTouchable; -// utils -export { createBottomSheetScrollableComponent } from './components/bottomSheetScrollable'; -//#endregion - -//#region types -export type { BottomSheetProps } from './components/bottomSheet'; -export type { BottomSheetModalProps } from './components/bottomSheetModal'; -export type { BottomSheetHandleProps } from './components/bottomSheetHandle'; -export type { BottomSheetBackgroundProps } from './components/bottomSheetBackground'; -export type { BottomSheetBackdropProps } from './components/bottomSheetBackdrop'; -export type { BottomSheetFooterProps } from './components/bottomSheetFooter'; - -export type { - BottomSheetFlatListMethods, - BottomSheetScrollViewMethods, - BottomSheetSectionListMethods, - BottomSheetVirtualizedListMethods, - BottomSheetScrollableProps, -} from './components/bottomSheetScrollable'; - -export type { - ScrollEventsHandlersHookType, - GestureEventsHandlersHookType, - ScrollEventHandlerCallbackType, - GestureEventHandlerCallbackType, -} from './types'; -//#endregion - -//#region utilities -export * from './constants'; -export { enableLogging } from './utilities/logger'; -//#endregion diff --git a/src/types.d.ts b/src/types.d.ts deleted file mode 100644 index 9e9f71f6..00000000 --- a/src/types.d.ts +++ /dev/null @@ -1,335 +0,0 @@ -import type React from 'react'; -import type { - AccessibilityProps, - FlatList, - Insets, - KeyboardEventEasing, - NativeScrollEvent, - NativeSyntheticEvent, - ScrollView, - SectionList, -} from 'react-native'; -import type { - GestureEventPayload, - GestureStateChangeEvent, - GestureUpdateEvent, - PanGestureChangeEventPayload, - PanGestureHandlerEventPayload, - State, -} from 'react-native-gesture-handler'; -import type { - EasingFunction, - EasingFunctionFactory, - ReduceMotion, - SharedValue, - WithSpringConfig, - WithTimingConfig, -} from 'react-native-reanimated'; -import type { - ANIMATION_SOURCE, - ANIMATION_STATUS, - GESTURE_SOURCE, - KEYBOARD_STATUS, - SCROLLABLE_TYPE, -} from './constants'; - -//#region Methods -export interface BottomSheetMethods { - /** - * Snap to one of the provided points from `snapPoints`. - * @param index snap point index. - * @param animationConfigs snap animation configs. - * - * @see {WithSpringConfig} - * @see {WithTimingConfig} - */ - snapToIndex: ( - index: number, - animationConfigs?: WithSpringConfig | WithTimingConfig - ) => void; - /** - * Snap to a position out of provided `snapPoints`. - * @param position position in pixel or percentage. - * @param animationConfigs snap animation configs. - * - * @see {WithSpringConfig} - * @see {WithTimingConfig} - */ - snapToPosition: ( - position: number | string, - animationConfigs?: WithSpringConfig | WithTimingConfig - ) => void; - /** - * Snap to the maximum provided point from `snapPoints`. - * @param animationConfigs snap animation configs. - * - * @see {WithSpringConfig} - * @see {WithTimingConfig} - */ - expand: (animationConfigs?: WithSpringConfig | WithTimingConfig) => void; - /** - * Snap to the minimum provided point from `snapPoints`. - * @param animationConfigs snap animation configs. - * - * @see {WithSpringConfig} - * @see {WithTimingConfig} - */ - collapse: (animationConfigs?: WithSpringConfig | WithTimingConfig) => void; - /** - * Close the bottom sheet. - * @param animationConfigs snap animation configs. - * - * @see {WithSpringConfig} - * @see {WithTimingConfig} - */ - close: (animationConfigs?: WithSpringConfig | WithTimingConfig) => void; - /** - * Force close the bottom sheet, this prevent any interruptions till the sheet is closed. - * @param animationConfigs snap animation configs. - * - * @see {WithSpringConfig} - * @see {WithTimingConfig} - */ - forceClose: (animationConfigs?: WithSpringConfig | WithTimingConfig) => void; -} - -// biome-ignore lint/suspicious/noExplicitAny: Using 'any' allows users to define their own strict types for 'data' property. -export interface BottomSheetModalMethods extends BottomSheetMethods { - /** - * Mount and present the bottom sheet modal to the initial snap point. - * @param data to be passed to the modal. - */ - present: (data?: T) => void; - /** - * Close and unmount the bottom sheet modal. - * @param animationConfigs snap animation configs. - * - * @see {WithSpringConfig} - * @see {WithTimingConfig} - */ - dismiss: (animationConfigs?: WithSpringConfig | WithTimingConfig) => void; -} -//#endregion - -export interface BottomSheetVariables { - /** - * Current sheet position index. - * @type SharedValue - */ - animatedIndex: SharedValue; - /** - * Current sheet position. - * @type SharedValue - */ - animatedPosition: SharedValue; -} - -//#region scrollable -export type ScrollableState = { - type: SCROLLABLE_TYPE; - contentOffsetY: number; - refreshable: boolean; -}; -export type Scrollable = FlatList | ScrollView | SectionList; -export type ScrollableRef = { - id: number; - node: React.RefObject; -}; -export type ScrollableEvent = ( - event: Pick, 'nativeEvent'> -) => void; -//#endregion - -//#region utils -export interface TimingConfig { - duration?: number; - reduceMotion?: ReduceMotion; - easing?: EasingFunction | EasingFunctionFactory; -} - -export type SpringConfig = { - stiffness?: number; - overshootClamping?: boolean; - restDisplacementThreshold?: number; - restSpeedThreshold?: number; - velocity?: number; - reduceMotion?: ReduceMotion; -} & ( - | { - mass?: number; - damping?: number; - duration?: never; - dampingRatio?: never; - clamp?: never; - } - | { - mass?: never; - damping?: never; - duration?: number; - dampingRatio?: number; - clamp?: { min?: number; max?: number }; - } -); - -export type Primitive = string | number | boolean; -//#endregion - -//#region hooks -export type GestureEventPayloadType = GestureEventPayload & - PanGestureHandlerEventPayload; - -export type GestureEventContextType = { - didStart?: boolean; -}; - -export type GestureEventHandlerCallbackType = ( - source: GESTURE_SOURCE, - payload: GestureEventPayloadType -) => void; - -export type GestureEventsHandlersHookType = () => { - handleOnStart: GestureEventHandlerCallbackType; - handleOnChange: GestureEventHandlerCallbackType; - handleOnEnd: GestureEventHandlerCallbackType; - handleOnFinalize: GestureEventHandlerCallbackType; -}; - -export type GestureHandlersHookType = ( - source: GESTURE_SOURCE, - state: SharedValue, - gestureSource: SharedValue, - onStart: GestureEventHandlerCallbackType, - onChange: GestureEventHandlerCallbackType, - onEnd: GestureEventHandlerCallbackType, - onFinalize: GestureEventHandlerCallbackType -) => { - handleOnStart: ( - event: GestureStateChangeEvent - ) => void; - handleOnChange: ( - event: GestureUpdateEvent< - PanGestureHandlerEventPayload & PanGestureChangeEventPayload - > - ) => void; - handleOnEnd: ( - event: GestureStateChangeEvent - ) => void; - handleOnFinalize: ( - event: GestureStateChangeEvent - ) => void; -}; - -type ScrollEventHandlerCallbackType = ( - payload: NativeScrollEvent, - context: C -) => void; - -export type ScrollEventsHandlersHookType = ( - ref: React.RefObject, - contentOffsetY: SharedValue -) => { - handleOnScroll?: ScrollEventHandlerCallbackType; - handleOnBeginDrag?: ScrollEventHandlerCallbackType; - handleOnEndDrag?: ScrollEventHandlerCallbackType; - handleOnMomentumBegin?: ScrollEventHandlerCallbackType; - handleOnMomentumEnd?: ScrollEventHandlerCallbackType; -}; -//#endregion - -//#region accessibility -export interface NullableAccessibilityProps extends AccessibilityProps { - accessible?: AccessibilityProps['accessible'] | null; - accessibilityLabel?: AccessibilityProps['accessibilityLabel'] | null; - accessibilityHint?: AccessibilityProps['accessibilityHint'] | null; - accessibilityRole?: AccessibilityProps['accessibilityRole'] | null; -} -//#endregion - -//#region states -export type KeyboardState = { - target?: number; - status: KEYBOARD_STATUS; - height: number; - heightWithinContainer: number; - easing: KeyboardEventEasing; - duration: number; -}; - -/** - * Represents the state of an animation, including its current status and the source that triggered it. - */ -export type AnimationState = { - /** - * The current status of the animation, this can be one of the values defined in the `ANIMATION_STATUS` enum, such as 'idle', 'running', 'completed', etc. - */ - status: ANIMATION_STATUS; - /** - * The source of the animation which indicates where the animation was initiated from, such as user interaction, system event, or programmatic trigger. - * It is represented by the `ANIMATION_SOURCE` enum, which includes values like 'user', 'system', etc. - */ - source: ANIMATION_SOURCE; - /** - * The index of the next snap point that the animation is targeting. - */ - nextIndex?: number; - /** - * The next position in pixels that the animation is targeting. - */ - nextPosition?: number; - /** - * Indicates whether the animation is forced closing to prevent any interruptions. - */ - isForcedClosing?: boolean; -}; - -/** - * Represents the layout state of the bottom sheet container. - */ -export type ContainerLayoutState = { - /** - * The height of the container in pixels. - */ - height: number; - /** - * The required insets applied to the container, such as padding or safe area. - */ - offset: Required; -}; - -/** - * Represents the layout state of the bottom sheet components. - */ -export type LayoutState = { - /** - * The original height of the container before any adjustments. - */ - rawContainerHeight: number; - /** - * The adjusted height of the container after applying insets or other modifications. - */ - containerHeight: number; - /** - * The required insets applied to the container, such as padding or safe area. - */ - containerOffset: Required; - /** - * The height of the handle element used to drag the bottom sheet. - */ - handleHeight: number; - /** - * The height of the footer section within the bottom sheet. - */ - footerHeight: number; - /** - * The total height of the content inside the bottom sheet. - */ - contentHeight: number; -}; - -export type DetentsState = { - detents?: number[]; - dynamicDetentIndex?: number; - highestDetentPosition?: number; - closedDetentPosition?: number; -}; -//#endregion diff --git a/src/utilities/animate.ts b/src/utilities/animate.ts deleted file mode 100644 index b1f6fb43..00000000 --- a/src/utilities/animate.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - type AnimationCallback, - type ReduceMotion, - type WithSpringConfig, - type WithTimingConfig, - withSpring, - withTiming, -} from 'react-native-reanimated'; -import { ANIMATION_CONFIGS, ANIMATION_METHOD } from '../constants'; - -interface AnimateParams { - point: number; - velocity?: number; - configs?: WithSpringConfig | WithTimingConfig; - overrideReduceMotion?: ReduceMotion; - onComplete?: AnimationCallback; -} - -export const animate = ({ - point, - configs, - velocity = 0, - overrideReduceMotion, - onComplete, -}: AnimateParams) => { - 'worklet'; - - if (!configs) { - configs = ANIMATION_CONFIGS; - } - - // Users might have an accessibility setting to reduce motion turned on. - // This prevents the animation from running when presenting the sheet, which results in - // the bottom sheet not even appearing so we need to override it to ensure the animation runs. - // configs.reduceMotion = ReduceMotion.Never; - - if (overrideReduceMotion) { - configs.reduceMotion = overrideReduceMotion; - } - - // detect animation type - const type = - 'duration' in configs || 'easing' in configs - ? ANIMATION_METHOD.TIMING - : ANIMATION_METHOD.SPRING; - - if (type === ANIMATION_METHOD.TIMING) { - return withTiming(point, configs as WithTimingConfig, onComplete); - } - - return withSpring( - point, - Object.assign({ velocity }, configs) as WithSpringConfig, - onComplete - ); -}; diff --git a/src/utilities/clamp.ts b/src/utilities/clamp.ts deleted file mode 100644 index 6e005950..00000000 --- a/src/utilities/clamp.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const clamp = ( - value: number, - lowerBound: number, - upperBound: number -) => { - 'worklet'; - return Math.min(Math.max(lowerBound, value), upperBound); -}; diff --git a/src/utilities/easingExp.ts b/src/utilities/easingExp.ts deleted file mode 100644 index 0fa42d0c..00000000 --- a/src/utilities/easingExp.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * A modified version of the default AnimatedEasing.exp, - * to insure its value never goes below `0`. - * @see https://github.com/software-mansion/react-native-reanimated/issues/1610 - * @param t number - */ -export const exp = (t: number) => { - 'worklet'; - return Math.min(Math.max(0, Math.pow(2, 10 * (t - 1))), 1); -}; diff --git a/src/utilities/findNodeHandle.ts b/src/utilities/findNodeHandle.ts deleted file mode 100644 index ad39910f..00000000 --- a/src/utilities/findNodeHandle.ts +++ /dev/null @@ -1 +0,0 @@ -export { findNodeHandle } from 'react-native'; diff --git a/src/utilities/findNodeHandle.web.ts b/src/utilities/findNodeHandle.web.ts deleted file mode 100644 index b601e93c..00000000 --- a/src/utilities/findNodeHandle.web.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { - findNodeHandle as _findNodeHandle, - type NodeHandle, -} from 'react-native'; - -/** - * Type bridge for accessing undocumented React Native scroll component internals. - * These properties exist at runtime but aren't exposed in RN's public type definitions. - * - * @see https://github.com/facebook/react-native/blob/main/packages/virtualized-lists/Lists/VirtualizedList.js#L1252 - */ -interface ScrollComponentInternals { - /** Available on ScrollView, FlatList, etc. to get the underlying native scroll ref */ - getNativeScrollRef?: () => NodeHandle | null; - getScrollableNode?: () => NodeHandle | null; - /** Internal property on VirtualizedList storing the scroll ref */ - _scrollRef?: NodeHandle | null; -} - -export function findNodeHandle( - componentOrHandle: Parameters['0'] -): NodeHandle | null | typeof componentOrHandle { - // Early return for null/undefined (React 19 fix) - if (componentOrHandle == null) { - return null; - } - - let nodeHandle: NodeHandle | null = null; - - try { - nodeHandle = _findNodeHandle(componentOrHandle); - if (nodeHandle) { - return nodeHandle; - } - } catch {} - - // Type bridge: componentOrHandle may have scroll internals at runtime - const scrollable = componentOrHandle as unknown as ScrollComponentInternals; - - try { - if (typeof scrollable.getNativeScrollRef === 'function') { - nodeHandle = scrollable.getNativeScrollRef(); - if (nodeHandle) { - return nodeHandle; - } - } - } catch {} - - try { - if (typeof scrollable.getScrollableNode === 'function') { - nodeHandle = scrollable.getScrollableNode(); - if (nodeHandle) { - return nodeHandle; - } - } - } catch {} - - if (scrollable._scrollRef != null) { - return scrollable._scrollRef; - } - - console.warn('could not find scrollable ref!'); - return componentOrHandle; -} diff --git a/src/utilities/getKeyboardAnimationConfigs.ts b/src/utilities/getKeyboardAnimationConfigs.ts deleted file mode 100644 index 13103474..00000000 --- a/src/utilities/getKeyboardAnimationConfigs.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { KeyboardEventEasing } from 'react-native'; -import { Easing } from 'react-native-reanimated'; - -export const getKeyboardAnimationConfigs = ( - easing: KeyboardEventEasing, - duration: number -) => { - 'worklet'; - switch (easing) { - case 'easeIn': - return { - easing: Easing.in(Easing.ease), - duration, - }; - - case 'easeOut': - return { - easing: Easing.out(Easing.ease), - duration, - }; - - case 'easeInEaseOut': - return { - easing: Easing.inOut(Easing.ease), - duration, - }; - - case 'linear': - return { - easing: Easing.linear, - duration, - }; - - case 'keyboard': - return { - damping: 500, - stiffness: 1000, - mass: 3, - overshootClamping: true, - restDisplacementThreshold: 10, - restSpeedThreshold: 10, - }; - } -}; diff --git a/src/utilities/getRefNativeTag.web.ts b/src/utilities/getRefNativeTag.web.ts deleted file mode 100644 index 82ae671d..00000000 --- a/src/utilities/getRefNativeTag.web.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { RefObject } from 'react'; -import { findNodeHandle } from 'react-native'; - -export function getRefNativeTag(ref: RefObject) { - return findNodeHandle(ref?.current) || null; -} diff --git a/src/utilities/id.ts b/src/utilities/id.ts deleted file mode 100644 index 2f106671..00000000 --- a/src/utilities/id.ts +++ /dev/null @@ -1,6 +0,0 @@ -let current = 0; - -export const id = () => { - current = (current + 1) % Number.MAX_SAFE_INTEGER; - return current; -}; diff --git a/src/utilities/index.ts b/src/utilities/index.ts deleted file mode 100644 index 1dc0c050..00000000 --- a/src/utilities/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export { normalizeSnapPoint } from './normalizeSnapPoint'; -export { animate } from './animate'; -export { getKeyboardAnimationConfigs } from './getKeyboardAnimationConfigs'; -export { print } from './logger'; -export { noop, workletNoop } from './noop'; -export { isFabricInstalled } from './isFabricInstalled'; -export { findNodeHandle } from './findNodeHandle'; diff --git a/src/utilities/isFabricInstalled.ts b/src/utilities/isFabricInstalled.ts deleted file mode 100644 index 78b69429..00000000 --- a/src/utilities/isFabricInstalled.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Checks if the Fabric renderer is installed in the current environment. - * - * @returns {boolean} `true` if Fabric is installed, otherwise `false`. - */ -export function isFabricInstalled() { - // @ts-expect-error - return global?.nativeFabricUIManager != null; -} diff --git a/src/utilities/logger.ts b/src/utilities/logger.ts deleted file mode 100644 index f5d8f655..00000000 --- a/src/utilities/logger.ts +++ /dev/null @@ -1,51 +0,0 @@ -interface PrintOptions { - component?: string; - category?: 'layout' | 'effect' | 'callback'; - method?: string; - params?: Record | string | number | boolean; -} - -type Print = (options: PrintOptions) => void; - -let _isLoggingEnabled = false; -let _excludeCategories: PrintOptions['category'][] | undefined; - -const enableLogging = (excludeCategories?: PrintOptions['category'][]) => { - if (!__DEV__) { - console.warn('[BottomSheet] could not enable logging on production!'); - return; - } - - _isLoggingEnabled = true; - _excludeCategories = excludeCategories; -}; - -let print: Print = () => {}; - -if (__DEV__) { - print = ({ component, method, params, category }) => { - if (!_isLoggingEnabled) { - return; - } - - if (category && _excludeCategories?.includes(category)) { - return; - } - - let message = ''; - - if (typeof params === 'object') { - message = Object.keys(params) - .map(key => `${key}:${params[key]}`) - .join(' '); - } else { - message = `${params ?? ''}`; - } - // biome-ignore lint/suspicious/noConsole: used for debugging - console.log(`[${[component, method].filter(Boolean).join('::')}]`, message); - }; -} - -Object.freeze(print); - -export { enableLogging, print }; diff --git a/src/utilities/noop.ts b/src/utilities/noop.ts deleted file mode 100644 index 9a9f6981..00000000 --- a/src/utilities/noop.ts +++ /dev/null @@ -1,7 +0,0 @@ -const workletNoop = () => { - 'worklet'; -}; - -const noop = () => {}; - -export { noop, workletNoop }; diff --git a/src/utilities/normalizeSnapPoint.ts b/src/utilities/normalizeSnapPoint.ts deleted file mode 100644 index 46745dbf..00000000 --- a/src/utilities/normalizeSnapPoint.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Converts a snap point to fixed numbers. - */ -export const normalizeSnapPoint = ( - snapPoint: number | string, - containerHeight: number -) => { - 'worklet'; - let normalizedSnapPoint = snapPoint; - - // percentage snap point - if (typeof normalizedSnapPoint === 'string') { - normalizedSnapPoint = - (Number(normalizedSnapPoint.split('%')[0]) * containerHeight) / 100; - } - return Math.max(0, containerHeight - normalizedSnapPoint); -}; diff --git a/src/utilities/snapPoint.ts b/src/utilities/snapPoint.ts deleted file mode 100644 index 1b99e165..00000000 --- a/src/utilities/snapPoint.ts +++ /dev/null @@ -1,11 +0,0 @@ -export const snapPoint = ( - value: number, - velocity: number, - points: ReadonlyArray -): number => { - 'worklet'; - const point = value + 0.2 * velocity; - const deltas = points.map(p => Math.abs(point - p)); - const minDelta = Math.min.apply(null, deltas); - return points.filter(p => Math.abs(point - p) === minDelta)[0]; -}; diff --git a/src/utilities/validateSnapPoint.ts b/src/utilities/validateSnapPoint.ts deleted file mode 100644 index 8b6d7534..00000000 --- a/src/utilities/validateSnapPoint.ts +++ /dev/null @@ -1,20 +0,0 @@ -import invariant from 'invariant'; - -export const validateSnapPoint = (snapPoint: number | string) => { - invariant( - typeof snapPoint === 'number' || typeof snapPoint === 'string', - `'${snapPoint}' is not a valid snap point! expected types are string or number.` - ); - - invariant( - typeof snapPoint === 'number' || - (typeof snapPoint === 'string' && snapPoint.includes('%')), - `'${snapPoint}' is not a valid percentage snap point! expected percentage snap point must include '%'. e.g. '50%'` - ); - - invariant( - typeof snapPoint === 'number' || - (typeof snapPoint === 'string' && Number(snapPoint.split('%')[0])), - `'${snapPoint}' is not a valid percentage snap point! expected percentage snap point must be only numbers and '%'. e.g. '50%'` - ); -};