diff --git a/native/navigation/overlay-context.js b/native/navigation/overlay-context.js --- a/native/navigation/overlay-context.js +++ b/native/navigation/overlay-context.js @@ -3,18 +3,21 @@ import * as React from 'react'; import Animated from 'react-native-reanimated'; -type ScrollBlockingModalStatus = 'open' | 'closed' | 'closing'; +export type VisibleOverlay = { + +routeKey: string, + +routeName: string, + +position: Animated.Value, + +presentedFrom: ?string, +}; + +export type ScrollBlockingModalStatus = 'open' | 'closed' | 'closing'; + export type OverlayContextType = { // position and isDismissing are local to the current route +position: Animated.Node, +isDismissing: boolean, // The rest are global to the entire OverlayNavigator - +visibleOverlays: $ReadOnlyArray<{ - +routeKey: string, - +routeName: string, - +position: Animated.Value, - +presentedFrom: ?string, - }>, + +visibleOverlays: $ReadOnlyArray, +scrollBlockingModalStatus: ScrollBlockingModalStatus, +setScrollBlockingModalStatus: ScrollBlockingModalStatus => void, +resetScrollBlockingModalStatus: () => void, diff --git a/native/navigation/overlay-navigator.react.js b/native/navigation/overlay-navigator.react.js --- a/native/navigation/overlay-navigator.react.js +++ b/native/navigation/overlay-navigator.react.js @@ -10,6 +10,8 @@ StackNavigationHelpers, ScreenListeners, StackRouterOptions, + Descriptor, + Route, } from '@react-navigation/core'; import { useNavigationBuilder, @@ -24,7 +26,11 @@ import { values } from 'lib/utils/objects.js'; -import { OverlayContext } from './overlay-context.js'; +import { + OverlayContext, + type VisibleOverlay, + type ScrollBlockingModalStatus, +} from './overlay-context.js'; import OverlayRouter from './overlay-router.js'; import type { OverlayRouterExtraNavigationHelpers, @@ -52,6 +58,34 @@ const { Value, timing, cond, call, lessOrEq, block } = Animated; /* eslint-enable import/no-named-as-default-member */ +type Scene = { + +route: Route<>, + +descriptor: Descriptor, {}>, + +context: { + +position: Value, + +isDismissing: boolean, + }, + +ordering: { + +routeIndex: number, + }, +}; + +type SceneData = $ReadOnly<{ + ...Scene, + +context: $ReadOnly<{ + ...$PropertyType, + +visibleOverlays: $ReadOnlyArray, + +scrollBlockingModalStatus: ScrollBlockingModalStatus, + +setScrollBlockingModalStatus: ScrollBlockingModalStatus => void, + +resetScrollBlockingModalStatus: () => void, + }>, + +ordering: $ReadOnly<{ + ...$PropertyType, + +creationTime: number, + }>, + +listeners: $ReadOnlyArray, +}>; + type Props = $Exact< NavigatorPropsBase< {}, @@ -77,7 +111,7 @@ }); const curIndex = state.index; - const positionRefs = React.useRef({}); + const positionRefs = React.useRef<{ [string]: Animated.Value }>({}); const positions = positionRefs.current; const firstRenderRef = React.useRef(true); @@ -117,18 +151,19 @@ [positions, routes, curIndex], ); - const prevScenesRef = React.useRef(); + const prevScenesRef = React.useRef>(); const prevScenes = prevScenesRef.current; - const visibleOverlayEntryForNewScene = scene => { + const visibleOverlayEntryForNewScene = (scene: Scene) => { const { route } = scene; if (route.name === TabNavigatorRouteName) { // We don't consider the TabNavigator at the bottom to be an overlay return undefined; } - const presentedFrom = route.params - ? route.params.presentedFrom - : undefined; + const presentedFrom = + typeof route.params?.presentedFrom === 'string' + ? route.params.presentedFrom + : undefined; return { routeKey: route.key, routeName: route.name, @@ -137,7 +172,7 @@ }; }; - const visibleOverlaysRef = React.useRef(); + const visibleOverlaysRef = React.useRef>(); if (!visibleOverlaysRef.current) { visibleOverlaysRef.current = scenes .map(visibleOverlayEntryForNewScene) @@ -150,7 +185,9 @@ // each screen. Note that we also include the setter in OverlayContext. We // do this so that screens can freeze ScrollViews as quickly as possible to // avoid drags after onLongPress is triggered - const getScrollBlockingModalStatus = data => { + const getScrollBlockingModalStatus = ( + data: $ReadOnlyArray, + ) => { let status = 'closed'; for (const scene of data) { if (!scrollBlockingModals.includes(scene.route.name)) { @@ -173,7 +210,7 @@ ); }, []); - const sceneDataForNewScene = scene => ({ + const sceneDataForNewScene = (scene: Scene) => ({ ...scene, context: { ...scene.context, @@ -207,7 +244,7 @@ // We need state to continue rendering screens while they are dismissing const [sceneData, setSceneData] = React.useState(() => { - const newSceneData = {}; + const newSceneData: { [string]: SceneData } = {}; for (const scene of scenes) { const { key } = scene.route; newSceneData[key] = sceneDataForNewScene(scene); @@ -235,7 +272,7 @@ const updatedSceneData = { ...sceneData }; let sceneDataChanged = false; if (prevScenes && scenes !== prevScenes) { - const currentKeys = new Set(); + const currentKeys = new Set(); for (const scene of scenes) { const { key } = scene.route; currentKeys.add(key); @@ -325,14 +362,14 @@ 'visibleOverlaysRef should be set', ); const newVisibleOverlays = curVisibleOverlays.filter( - overlay => overlay.routeKey !== key, + (overlay: VisibleOverlay) => overlay.routeKey !== key, ); if (newVisibleOverlays.length === curVisibleOverlays.length) { return; } visibleOverlaysRef.current = newVisibleOverlays; setSceneData(curSceneData => { - const newSceneData = {}; + const newSceneData: { [string]: SceneData } = {}; for (const sceneKey in curSceneData) { if (sceneKey === key) { continue;