diff --git a/src/BottomSheetScaleView.tsx b/src/BottomSheetScaleView.tsx index e02bade..5e157af 100644 --- a/src/BottomSheetScaleView.tsx +++ b/src/BottomSheetScaleView.tsx @@ -1,7 +1,7 @@ import { type PropsWithChildren } from 'react'; import { StyleSheet } from 'react-native'; import Animated from 'react-native-reanimated'; -import { useScaleAnimatedStyle } from './useScaleAnimation'; +import { useBackgroundScaleAnimatedStyle } from './useScaleAnimation'; /** * Wraps your app content with iOS-style scale animation when a bottom sheet @@ -19,7 +19,7 @@ import { useScaleAnimatedStyle } from './useScaleAnimation'; * ``` */ export function BottomSheetScaleView({ children }: PropsWithChildren) { - const animatedStyle = useScaleAnimatedStyle(); + const animatedStyle = useBackgroundScaleAnimatedStyle(); return ( diff --git a/src/QueueItem.tsx b/src/QueueItem.tsx index 6fa44cc..516f4aa 100644 --- a/src/QueueItem.tsx +++ b/src/QueueItem.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import { memo, useEffect } from 'react'; import { StyleSheet, View } from 'react-native'; import Animated from 'react-native-reanimated'; import { useSafeAreaFrame } from 'react-native-safe-area-context'; @@ -15,7 +15,7 @@ import { } from './bottomSheet.store'; import { BottomSheetBackdrop } from './BottomSheetBackdrop'; import { cleanupSheetRef } from './refsMap'; -import { useScaleAnimatedStyle } from './useScaleAnimation'; +import { useSheetScaleAnimatedStyle } from './useScaleAnimation'; interface QueueItemProps { id: string; @@ -23,7 +23,11 @@ interface QueueItemProps { isActive: boolean; } -export function QueueItem({ id, stackIndex, isActive }: QueueItemProps) { +export const QueueItem = memo(function QueueItem({ + id, + stackIndex, + isActive, +}: QueueItemProps) { const content = useSheetContent(id); const usePortal = useSheetUsePortal(id); const keepMounted = useSheetKeepMounted(id); @@ -31,7 +35,7 @@ export function QueueItem({ id, stackIndex, isActive }: QueueItemProps) { const startClosing = useStartClosing(); const { width, height } = useSafeAreaFrame(); - const scaleStyle = useScaleAnimatedStyle({ id }); + const scaleStyle = useSheetScaleAnimatedStyle(id); useEffect(() => { return () => { @@ -76,7 +80,7 @@ export function QueueItem({ id, stackIndex, isActive }: QueueItemProps) { ); -} +}); const styles = StyleSheet.create({ container: {}, diff --git a/src/store/hooks.ts b/src/store/hooks.ts index 2a620c2..34e6308 100644 --- a/src/store/hooks.ts +++ b/src/store/hooks.ts @@ -32,6 +32,31 @@ export const useIsSheetOpen = (id: string) => return status === 'open' || status === 'opening'; }, shallow); +export const useHasScaleBackgroundAbove = (id: string) => + useBottomSheetStore((state) => { + const { stackOrder, sheetsById } = state; + const sheetIndex = stackOrder.indexOf(id); + + if (sheetIndex === -1) { + return false; + } + + // Check if any sheet above this one has scaleBackground + for (let i = sheetIndex + 1; i < stackOrder.length; i++) { + const aboveId = stackOrder[i]!; + const aboveSheet = sheetsById[aboveId]; + if ( + aboveSheet && + aboveSheet.scaleBackground && + aboveSheet.status !== 'closing' && + aboveSheet.status !== 'hidden' + ) { + return true; + } + } + return false; + }, shallow); + // Action hooks export const useOpen = () => useBottomSheetStore((state) => state.open); diff --git a/src/useScaleAnimation.ts b/src/useScaleAnimation.ts index 05adf51..9c7c0bf 100644 --- a/src/useScaleAnimation.ts +++ b/src/useScaleAnimation.ts @@ -38,7 +38,7 @@ const DEFAULT_CONFIG = { } satisfies Required; function useBackgroundScaleDepth(groupId: string): number { - return useBottomSheetStore((state) => { + const depth = useBottomSheetStore((state) => { const { stackOrder, sheetsById } = state; for (let i = 0; i < stackOrder.length; i++) { @@ -55,6 +55,7 @@ function useBackgroundScaleDepth(groupId: string): number { } return 0; }); + return depth; } function useSheetScaleDepth( @@ -63,7 +64,7 @@ function useSheetScaleDepth( ): number { const prevDepthRef = useRef(0); - return useBottomSheetStore((state) => { + const result = useBottomSheetStore((state) => { if (!sheetId) { return 0; } @@ -93,10 +94,11 @@ function useSheetScaleDepth( prevDepthRef.current = depth; return depth; }); + return result; } -export function useScaleAnimatedStyle({ id }: { id?: string } = {}) { - const { groupId, scaleConfig } = useBottomSheetManagerContext(); +function useScaleAnimatedStyleInternal(scaleDepth: number) { + const { scaleConfig } = useBottomSheetManagerContext(); const { scale = DEFAULT_CONFIG.scale, @@ -105,14 +107,6 @@ export function useScaleAnimatedStyle({ id }: { id?: string } = {}) { animation = DEFAULT_CONFIG.animation, } = scaleConfig ?? {}; - const isBackground = id === undefined; - - const backgroundDepth = useBackgroundScaleDepth(groupId); - const sheetDepth = useSheetScaleDepth(groupId, id); - - const scaleDepth = isBackground ? backgroundDepth : sheetDepth; - - // Animate the depth change const progress = useDerivedValue(() => { if (animation.type === 'spring') { return withSpring(scaleDepth, animation.config); @@ -142,3 +136,15 @@ export function useScaleAnimatedStyle({ id }: { id?: string } = {}) { }; }); } + +export function useBackgroundScaleAnimatedStyle() { + const { groupId } = useBottomSheetManagerContext(); + const scaleDepth = useBackgroundScaleDepth(groupId); + return useScaleAnimatedStyleInternal(scaleDepth); +} + +export function useSheetScaleAnimatedStyle(sheetId: string) { + const { groupId } = useBottomSheetManagerContext(); + const scaleDepth = useSheetScaleDepth(groupId, sheetId); + return useScaleAnimatedStyleInternal(scaleDepth); +} diff --git a/src/useSheetRenderData.ts b/src/useSheetRenderData.ts index 2f2e6f4..d244cca 100644 --- a/src/useSheetRenderData.ts +++ b/src/useSheetRenderData.ts @@ -1,4 +1,3 @@ -import { shallow } from 'zustand/shallow'; import { useBottomSheetStore, type BottomSheetState, @@ -11,6 +10,31 @@ export interface SheetRenderItem { isActive: boolean; } +/** + * Deep comparison for SheetRenderItem arrays. + * Returns true if arrays have same items with same values. + */ +function sheetRenderDataEqual( + a: SheetRenderItem[], + b: SheetRenderItem[] +): boolean { + if (a.length !== b.length) return false; + + for (let i = 0; i < a.length; i++) { + const itemA = a[i]!; + const itemB = b[i]!; + if ( + itemA.id !== itemB.id || + itemA.stackIndex !== itemB.stackIndex || + itemA.isActive !== itemB.isActive + ) { + return false; + } + } + + return true; +} + /** * Returns a flat list of sheets to render. * @@ -29,7 +53,7 @@ export function useSheetRenderData(): SheetRenderItem[] { const active = getActiveSheets(state, groupId); return [...hiddenPersistent, ...active]; - }, shallow); + }, sheetRenderDataEqual); } function getHiddenPersistentSheets(