@apexnative/navigation 1.0.0-alpha.4 → 1.0.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/README.md +1 -1
- package/android/CLAUDE.md +23 -14
- package/android/CORE.md +32 -19
- package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +60 -13
- package/android/src/main/java/dev/apexnative/navigation/options/AnimationsOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/BottomTabsOptions.kt +13 -8
- package/android/src/main/java/dev/apexnative/navigation/options/HardwareBackButtonOptions.kt +9 -1
- package/android/src/main/java/dev/apexnative/navigation/options/StackAnimationOptions.kt +40 -2
- package/android/src/main/java/dev/apexnative/navigation/react/CommandAnswer.kt +28 -13
- package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +7 -4
- package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +39 -20
- package/android/src/main/java/dev/apexnative/navigation/react/ReloadCoordinator.kt +7 -6
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenEnvironment.kt +177 -0
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +19 -51
- package/android/src/main/java/dev/apexnative/navigation/react/ScrollEvents.kt +49 -128
- package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +30 -39
- package/android/src/main/java/dev/apexnative/navigation/react/events/EventEmitter.kt +34 -7
- package/android/src/main/java/dev/apexnative/navigation/react/events/NavigationState.kt +154 -0
- package/android/src/main/java/dev/apexnative/navigation/safearea/SafeAreaView.kt +12 -10
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +12 -12
- package/android/src/main/java/dev/apexnative/navigation/utils/ViewExtensions.kt +0 -13
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/ArrangementController.kt +10 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsCollapseBehavior.kt +5 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +22 -21
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowGeometry.kt +22 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/TabsRoom.kt +0 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentViewController.kt +12 -9
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ContentReadiness.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenReadiness.kt +103 -56
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenSafeArea.kt +29 -11
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/externalcomponent/ExternalComponentViewController.kt +2 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +20 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +119 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +7 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetBack.kt +42 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackCheck.kt +50 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackState.kt +45 -14
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +25 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +3 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/PreparedFrame.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +13 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +3 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/ScreenSearch.kt +3 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +2 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarComponents.kt +5 -51
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +4 -9
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ScrollEventListener.kt +6 -9
- package/android/src/main/java/dev/apexnative/navigation/views/animations/AnimationLayer.kt +73 -0
- package/android/src/main/java/dev/apexnative/navigation/views/animations/ValueInterpolation.kt +14 -0
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabsLayout.kt +61 -2
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomBottomTabItemView.kt +0 -5
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomTabRow.kt +72 -10
- package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +9 -2
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +11 -0
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +318 -25
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +18 -20
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +17 -25
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +15 -22
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +12 -9
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/TextChangeAnimator.kt +84 -35
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +49 -40
- package/cli/doctor/checks/splash.js +4 -22
- package/cli/splash/index.js +3 -1
- package/docs-md/architecture/transitions.md +4 -2
- package/docs-md/containers/modals.md +4 -2
- package/docs-md/containers/overlays.md +1 -1
- package/docs-md/containers/side-menu.md +1 -1
- package/docs-md/containers/tabs.md +2 -0
- package/docs-md/flows/bottom-sheets.md +1 -1
- package/docs-md/fundamentals/starting-the-app.md +1 -1
- package/docs-md/guides/cli.md +1 -2
- package/docs-md/help/glossary.md +2 -2
- package/docs-md/help/troubleshooting.md +23 -2
- package/docs-md/index.json +12 -0
- package/docs-md/migration/from-expo-router.md +1 -1
- package/docs-md/migration/from-react-native-navigation.md +1 -1
- package/docs-md/migration/from-react-navigation.md +2 -2
- package/docs-md/screen-ui/back-and-gestures.md +36 -0
- package/docs-md/screen-ui/bar-buttons.md +4 -4
- package/docs-md/screen-ui/search.md +1 -1
- package/docs-md/screen-ui/system-ui.md +16 -11
- package/docs-md/screen-ui/toolbar-and-fab.md +2 -2
- package/docs-md/screen-ui/top-bar.md +8 -4
- package/docs-md/start/installation.md +1 -1
- package/ios/Animations/ANNFrameDriver.mm +2 -0
- package/ios/Animations/Elements/ANNSharedElementFlight.mm +70 -4
- package/ios/Animations/Interpolators/ANNInterpolate.h +2 -30
- package/ios/Animations/Interpolators/ANNTransformParts.h +48 -0
- package/ios/Animations/Interpolators/ANNTransformParts.mm +507 -0
- package/ios/Animations/Interpolators/ANNViewTracks.h +6 -4
- package/ios/Animations/Interpolators/ANNViewTracks.mm +49 -9
- package/ios/CLAUDE.md +17 -15
- package/ios/CORE.md +30 -26
- package/ios/Contract/ANNCommandAnswer.h +9 -2
- package/ios/Contract/ANNCommandAnswer.mm +25 -12
- package/ios/Contract/ANNCommandRunner.h +8 -7
- package/ios/Contract/ANNCommandRunner.mm +21 -27
- package/ios/Contract/ANNLayoutNode.h +3 -3
- package/ios/Contract/ANNLayoutReader.h +3 -3
- package/ios/Core/ANNAppearanceDispatcher.h +5 -4
- package/ios/Core/ANNAppearanceDispatcher.mm +12 -1
- package/ios/Core/ANNJavaScriptCalls.h +11 -12
- package/ios/Core/ANNJavaScriptCalls.mm +7 -16
- package/ios/Core/ANNNavigationState.h +4 -6
- package/ios/Core/ANNNavigationState.mm +0 -4
- package/ios/Core/ANNReloadCoordinator.mm +4 -8
- package/ios/Core/ApexNavigation+Internal.h +1 -1
- package/ios/Layouts/Arrangement/ANNArrangementState.h +2 -17
- package/ios/Layouts/Arrangement/ANNArrangementState.mm +0 -38
- package/ios/Layouts/Arrangement/ANNArrangementViewController.mm +4 -4
- package/ios/Layouts/Root/ANNSplashViewController.h +5 -3
- package/ios/Layouts/Root/ANNSplashViewController.mm +147 -0
- package/ios/Layouts/Screen/ANNComponentViewController.h +2 -2
- package/ios/Layouts/Screen/ANNComponentViewController.mm +9 -22
- package/ios/Layouts/Screen/ANNPreviewInteraction.h +7 -4
- package/ios/Layouts/Screen/ANNPreviewInteraction.mm +72 -10
- package/ios/Layouts/Screen/ANNScreenEnvironment.h +63 -0
- package/ios/Layouts/Screen/ANNScreenEnvironment.mm +181 -0
- package/ios/Layouts/Screen/ANNScreenRegions.h +3 -18
- package/ios/Layouts/Screen/ANNScreenRegions.mm +0 -38
- package/ios/Layouts/Screen/ANNScreenSafeArea.h +4 -9
- package/ios/Layouts/Screen/ANNScreenSafeArea.mm +14 -16
- package/ios/Layouts/Stack/ANNPreviewPush.h +7 -6
- package/ios/Layouts/Stack/ANNPreviewPush.mm +142 -27
- package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.h +12 -1
- package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.mm +23 -0
- package/ios/Layouts/Tabs/ANNBottomTabsController.mm +2 -1
- package/ios/Layouts/Tabs/ANNBottomTabsCustomRow.mm +8 -3
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +4 -0
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +123 -36
- package/ios/Layouts/Tabs/ANNTabsDesign.h +6 -5
- package/ios/Layouts/Tabs/ANNTabsDesign.mm +9 -1
- package/ios/Layouts/Tabs/UITabBar+utils.h +5 -5
- package/ios/Layouts/Tabs/UITabBar+utils.mm +32 -13
- package/ios/Options/ANNBottomTabsOptions.h +2 -0
- package/ios/Options/ANNBottomTabsOptions.mm +1 -0
- package/ios/Options/ANNOptions.h +18 -0
- package/ios/Options/ANNOptions.mm +112 -1
- package/ios/Options/ANNOptionsReader.mm +110 -0
- package/ios/Options/Params/ANNParam.h +15 -0
- package/ios/Options/Params/ANNParam.mm +61 -0
- package/ios/Options/Params/Color.h +3 -0
- package/ios/Options/Params/Color.mm +21 -0
- package/ios/Options/Params/Image.h +4 -0
- package/ios/Options/Params/Image.mm +20 -0
- package/ios/React/ANNReactView.mm +6 -25
- package/ios/React/ANNSafeAreaViewComponentView.mm +26 -15
- package/ios/React/ANNSurfaceHostingView.h +6 -0
- package/ios/React/ANNSurfaceHostingView.mm +116 -0
- package/ios/TopBar/ANNBackButtonPresenter.mm +6 -5
- package/ios/TopBar/ANNBarAppearance.h +2 -1
- package/ios/TopBar/ANNBarAppearance.mm +111 -30
- package/ios/TopBar/ANNBarBackgroundComponent.mm +16 -0
- package/ios/TopBar/ANNBarButtonComponentView.h +4 -4
- package/ios/TopBar/ANNBarButtonComponentView.mm +1 -1
- package/ios/TopBar/ANNBarButtons.h +5 -3
- package/ios/TopBar/ANNBarButtons.mm +186 -17
- package/ios/TopBar/ANNLargeTitlePresenter.mm +24 -4
- package/ios/TopBar/ANNMainList.h +3 -0
- package/ios/TopBar/ANNMainList.mm +4 -0
- package/ios/TopBar/ANNScrollEdgeEffects.h +22 -6
- package/ios/TopBar/ANNScrollEdgeEffects.mm +123 -16
- package/ios/TopBar/ANNSearchPresenter.mm +2 -2
- package/ios/TopBar/ANNSearchResultsViewController.mm +5 -5
- package/ios/TopBar/ANNTopBarPresenter.h +3 -2
- package/ios/TopBar/ANNTopBarPresenter.mm +95 -22
- package/ios/TurboModules/ANNTurboModule.mm +20 -16
- package/lib/module/ARCHITECTURE.md +8 -4
- package/lib/module/api/commands/commands.js +54 -6
- package/lib/module/api/commands/commands.js.map +1 -1
- package/lib/module/api/hooks/useSafeAreaInsets.js +4 -3
- package/lib/module/api/hooks/useSafeAreaInsets.js.map +1 -1
- package/lib/module/api/options/OptionsChain.js +2 -2
- package/lib/module/engine/commands.js +21 -1
- package/lib/module/engine/commands.js.map +1 -1
- package/lib/module/engine/places.js +8 -0
- package/lib/module/engine/places.js.map +1 -1
- package/lib/module/engine/screens.js +29 -4
- package/lib/module/engine/screens.js.map +1 -1
- package/lib/typescript/api/app/createNavigation.d.ts +3 -3
- package/lib/typescript/api/commands/commands.d.ts.map +1 -1
- package/lib/typescript/api/commands/presentation.d.ts +4 -3
- package/lib/typescript/api/commands/presentation.d.ts.map +1 -1
- package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts +4 -3
- package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts.map +1 -1
- package/lib/typescript/api/options/OptionsChain.d.ts +2 -2
- package/lib/typescript/engine/commands.d.ts.map +1 -1
- package/lib/typescript/engine/places.d.ts +2 -0
- package/lib/typescript/engine/places.d.ts.map +1 -1
- package/lib/typescript/engine/screens.d.ts +20 -6
- package/lib/typescript/engine/screens.d.ts.map +1 -1
- package/lib/typescript/interfaces/Options.d.ts +1 -1
- package/lib/typescript/interfaces/Options.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/animations.d.ts +30 -9
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/bottomTabs.d.ts +18 -5
- package/lib/typescript/interfaces/options/bottomTabs.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/buttons.d.ts +8 -8
- package/lib/typescript/interfaces/options/stack.d.ts +9 -0
- package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/system.d.ts +8 -5
- package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/toolbar.d.ts +2 -2
- package/lib/typescript/interfaces/options/topBar.d.ts +32 -29
- package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ARCHITECTURE.md +8 -4
- package/src/api/app/createNavigation.ts +3 -3
- package/src/api/commands/commands.ts +48 -7
- package/src/api/commands/presentation.ts +4 -3
- package/src/api/hooks/useSafeAreaInsets.ts +4 -3
- package/src/api/options/OptionsChain.ts +2 -2
- package/src/engine/commands.ts +30 -6
- package/src/engine/places.ts +9 -0
- package/src/engine/screens.tsx +29 -8
- package/src/interfaces/Options.ts +1 -0
- package/src/interfaces/options/animations.ts +31 -9
- package/src/interfaces/options/bottomTabs.ts +18 -5
- package/src/interfaces/options/buttons.ts +8 -8
- package/src/interfaces/options/stack.ts +9 -0
- package/src/interfaces/options/system.ts +8 -5
- package/src/interfaces/options/toolbar.ts +2 -2
- package/src/interfaces/options/topBar.ts +32 -29
- package/android/src/main/java/dev/apexnative/navigation/react/ArrangementStates.kt +0 -42
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +0 -70
- package/android/src/main/java/dev/apexnative/navigation/react/events/NavigationStateReporter.kt +0 -62
- package/android/src/main/java/dev/apexnative/navigation/react/events/StateHold.kt +0 -62
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowPlacer.kt +0 -223
- package/ios/Layouts/Screen/ANNScreenInsets.h +0 -30
- package/ios/Layouts/Screen/ANNScreenInsets.mm +0 -70
|
@@ -17,34 +17,16 @@ const CIRCLE = 192;
|
|
|
17
17
|
const LOGO_HEIGHT = /<dimen name="apex_navigation_splash_logo_height">([\d.]+)dp<\/dimen>/;
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
* The native splash: made from its settings in package.json on both platforms, its logo whole on Android 12
|
|
21
|
-
* settings
|
|
20
|
+
* The native splash: made from its settings in package.json on both platforms, its logo whole on Android 12+. Without
|
|
21
|
+
* settings there is nothing to check: the navigation keeps a copy of a storyboard and of `UILaunchScreen` alike.
|
|
22
22
|
* @param {string} root
|
|
23
23
|
* @param {Project} project
|
|
24
24
|
* @returns {Check[]}
|
|
25
25
|
*/
|
|
26
26
|
function splashChecks(root, project) {
|
|
27
|
-
const plist = project.infoPlist && fs.readFileSync(project.infoPlist, 'utf8');
|
|
28
27
|
const saved = readConfig(root).splash;
|
|
29
|
-
if (!saved)
|
|
30
|
-
|
|
31
|
-
if (
|
|
32
|
-
plist?.includes('<key>UILaunchScreen</key>') &&
|
|
33
|
-
!plist.includes('<key>UILaunchStoryboardName</key>')
|
|
34
|
-
) {
|
|
35
|
-
return [
|
|
36
|
-
{
|
|
37
|
-
group: 'app',
|
|
38
|
-
title: 'Launch screen',
|
|
39
|
-
status: 'warn',
|
|
40
|
-
detail:
|
|
41
|
-
'UILaunchScreen in Info.plist: an empty window shows between it and the first screen',
|
|
42
|
-
fix: SPLASH,
|
|
43
|
-
},
|
|
44
|
-
];
|
|
45
|
-
}
|
|
46
|
-
return [];
|
|
47
|
-
}
|
|
28
|
+
if (!saved) return [];
|
|
29
|
+
const plist = project.infoPlist && fs.readFileSync(project.infoPlist, 'utf8');
|
|
48
30
|
const resolved = resolveSettings(root, {}, saved);
|
|
49
31
|
if ('errors' in resolved) {
|
|
50
32
|
return [
|
package/cli/splash/index.js
CHANGED
|
@@ -88,7 +88,9 @@ async function splash({
|
|
|
88
88
|
}
|
|
89
89
|
const plist = project.infoPlist && fs.readFileSync(project.infoPlist, 'utf8');
|
|
90
90
|
if (plist?.includes('<key>UILaunchScreen</key>')) {
|
|
91
|
-
command.warn(
|
|
91
|
+
command.warn(
|
|
92
|
+
'Info.plist has UILaunchScreen too: iOS shows it instead of the storyboard; remove it, so that iOS shows the storyboard.'
|
|
93
|
+
);
|
|
92
94
|
}
|
|
93
95
|
return command.apply(
|
|
94
96
|
[
|
|
@@ -82,7 +82,9 @@ navigation.push('Person', {
|
|
|
82
82
|
|
|
83
83
|
The avatar of the row moves and grows into the photo of the profile, and the name into its larger title. The views can differ in size and style. A pair that isn't found is skipped (the transition waits for it first); one that fails on the way is skipped with a warning in the log. Either way its views show with their screens, and the other pairs move. `type` and `duration` next to `shared` change how the rest of the screen comes, `'fade'` by default.
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
A view turned or scaled by its own `transform`, or by a parent's, flies from where it shows and lands as the other view shows, turning the shorter way. The angle is the one React Native draws, from −180° to 180°: `rotate: '270deg'` turns back a quarter, and no pair spins a full turn; a spin is an animation of the view's own.
|
|
86
|
+
|
|
87
|
+
Both platforms. (Android) The next screen waits until it is drawn, so its views are there to move into; a `type` other than a fade gives way to a fade. A skew and a tilt (`skewX`, `rotateX`, …) don't fly: the view flies without them and has them again at the end.
|
|
86
88
|
|
|
87
89
|
### Zoom
|
|
88
90
|
|
|
@@ -121,7 +123,7 @@ function Tile({ id, uri }: { id: string; uri: string }) {
|
|
|
121
123
|
}
|
|
122
124
|
```
|
|
123
125
|
|
|
124
|
-
The preview goes when the component unmounts. On Android a long press on the view does nothing of the navigation's: the view has no preview.
|
|
126
|
+
The previewed screen renders when the press starts, not when the component mounts ([when the screen renders](/api/transitions/usePreview#time)). The preview goes when the component unmounts. On Android a long press on the view does nothing of the navigation's: the view has no preview.
|
|
125
127
|
|
|
126
128
|
## Animations of your own
|
|
127
129
|
|
|
@@ -52,8 +52,8 @@ Compose shows with a close button at the leading edge of its top bar. The settin
|
|
|
52
52
|
|
|
53
53
|
| Setting | What it does |
|
|
54
54
|
| --- | --- |
|
|
55
|
-
| `closeButton` | A close button on the first screen: the system one on iOS, an icon on Android. Needs the stack. |
|
|
56
|
-
| `onDismiss` | Called once the modal is gone, whatever closed it: a swipe, a command, a new root. |
|
|
55
|
+
| `closeButton` | A close button on the first screen: the system one on iOS, an icon on Android. Needs the stack. A new first screen from `setStack` or `replace` in the modal's stack gets it too. |
|
|
56
|
+
| `onDismiss` | Called once the modal is gone, whatever closed it: a swipe, a command, a new root. `setStack` and `replace` in the modal's stack replace its first screen and call it too. |
|
|
57
57
|
| `stack` | `false`: the screen alone, without a stack and its top bar. |
|
|
58
58
|
| `style`, `sheet`, `popover`, `contentSize` | How it looks: see [How a modal looks](#styles). |
|
|
59
59
|
| `transition`, `options` | The animation, and the screen's options for this show. |
|
|
@@ -126,6 +126,8 @@ navigation.setOptions({
|
|
|
126
126
|
|
|
127
127
|
On (Android) a sheet is the Material bottom sheet. It stops at half (`'medium'`) and full (`'large'`) height and at heights of its own; `'content'` stops at half.
|
|
128
128
|
|
|
129
|
+
(Android) 14+ The back gesture over a sheet shows it sliding down before the user lets go: [the preview of a sheet closing](../screen-ui/back-and-gestures#sheet-preview).
|
|
130
|
+
|
|
129
131
|
Sheets in a real app, step by step: [Bottom sheets](../flows/bottom-sheets).
|
|
130
132
|
|
|
131
133
|
## How a modal closes
|
|
@@ -26,7 +26,7 @@ A push or a modal moves the user somewhere. An overlay doesn't move anyone: it p
|
|
|
26
26
|
|
|
27
27
|
An overlay is a screen of `screens` like any other: a component, its props, its options. It shows above the tabs, the bars and the modals; on (iOS) it is in a window of its own. Its background is transparent, so the app shows wherever the overlay draws nothing.
|
|
28
28
|
|
|
29
|
-
An overlay covers the whole window, **the status bar included**: a dialog's dimmed backdrop dims the status bar too, as a system dialog does. Its safe area keeps the
|
|
29
|
+
An overlay covers the whole window, **the status bar included**: a dialog's dimmed backdrop dims the status bar too, as a system dialog does. Its safe area keeps the top and the sides off the bars, so a toast at the top of the overlay sits below the status bar. On (Android) a status bar with a color of its own (`statusBar.backgroundColor`) stays over the backdrop, and `statusBar.drawBehind: false` starts the overlay below the status bar. What overlays are for:
|
|
30
30
|
|
|
31
31
|
## Showing and closing
|
|
32
32
|
|
|
@@ -108,7 +108,7 @@ A tap on Settings closes the menu and shows Settings in the center as it goes. W
|
|
|
108
108
|
| (iOS) | Over the content, which dims: `openMode: 'aboveContent'` of the side, the default. Or under it, with the content moved aside: `'pushContent'`. Then `animation(…)` says how the menu appears: `'parallax'`, `'slide'`, `'slide-and-scale'` or `'door'`. |
|
|
109
109
|
| (Android) | The drawer of the platform: over the content, which dims. |
|
|
110
110
|
|
|
111
|
-
The menu's color is its screen's `layout.backgroundColor`: it fills the whole height of the menu, behind the status bar and the navigation bar, while the menu's content keeps clear of them. A background on the React content stops at the safe area.
|
|
111
|
+
The menu's color is its screen's `layout.backgroundColor`: it fills the whole height of the menu, behind the status bar and the navigation bar, while the menu's content keeps its top and sides clear of them (the bottom goes down to the edge: a list adds `insets.bottom`). A background on the React content stops at the safe area.
|
|
112
112
|
|
|
113
113
|
`.ios()` keeps the settings that only iOS has apart from the others:
|
|
114
114
|
|
|
@@ -92,6 +92,8 @@ Inbox shows, and Chat comes in over it, in the stack of the Inbox tab.
|
|
|
92
92
|
|
|
93
93
|
On (Android) the content changes with Material's fade through, as in the system's Phone app: the tab that goes fades out, then the new one fades in and grows to its size, in 300 ms; the tab bar stays. On (iOS) the new tab shows at once, as the system's tab bar does.
|
|
94
94
|
|
|
95
|
+
To switch at once on Android too, give `bottomTabs.switchAnimation: 'none'`: to every tabs node in the app's default `options`, to these tabs with `Tabs().options({ switchAnimation: 'none' })`, or to a screen. The tab that is selected decides, by the screen on top of it: a tab whose top screen says `'none'` comes in at once, and leaving it fades as usual. A screen's `setOptions` changes it from the next switch.
|
|
96
|
+
|
|
95
97
|
### A press on a tab
|
|
96
98
|
|
|
97
99
|
A second tap on the selected tab is a convention of both platforms: **back to the start of the tab**. The navigation does it without code:
|
|
@@ -152,7 +152,7 @@ navigation.showModal('PhotoActions', {
|
|
|
152
152
|
});
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
-
The content is measured after every render, so the sheet follows it when it grows. It needs (iOS) 16; on (Android) the sheet stops at half the height instead, as the Material sheet has no height of its content. A screen that fills its parent (`flex: 1`) has no height of its own, so give the content of this screen its natural height.
|
|
155
|
+
The content is measured after every render, so the sheet follows it when it grows. It needs (iOS) 16; on (Android) the sheet stops at half the height instead, as the Material sheet has no height of its content. A screen that fills its parent (`flex: 1`) has no height of its own, so give the content of this screen its natural height. On (iOS) UIKit adds the bottom safe area to the height of a `'content'` sheet itself, so don't add `insets.bottom` to its content: the gap would come twice.
|
|
156
156
|
|
|
157
157
|
## 8. Close it with a pick
|
|
158
158
|
|
|
@@ -176,7 +176,7 @@ More: [Errors and loading](../architecture/errors-and-loading).
|
|
|
176
176
|
|
|
177
177
|
Keys of the system; the defaults suit most apps.
|
|
178
178
|
|
|
179
|
-
- `safeArea` — the edges on which every screen keeps its content out of the safe area: `{ edges: ['top', 'bottom'] }`.
|
|
179
|
+
- `safeArea` — the edges on which every screen keeps its content out of the safe area: `{ edges: ['top', 'left', 'right', 'bottom'] }`. Listed edges replace the default, so list every edge the screens keep. Without it the screens keep the top and the sides, and the bottom is free.
|
|
180
180
|
- `window` — (iOS) the color behind the screens, seen around a modal as it comes in: `{ backgroundColor: '#000000' }`.
|
|
181
181
|
- `direction` — `'ltr'`, `'rtl'`, or `'locale'` for the language of the app; `navigation.setDirection` changes it.
|
|
182
182
|
|
package/docs-md/guides/cli.md
CHANGED
|
@@ -311,7 +311,7 @@ Each file and why: [What the command writes](./splash-screen#files).
|
|
|
311
311
|
- **The settings** go into `package.json`, under `apexNavigation.splash`. An option given replaces its saved value; to drop the dark logo or color, remove `darkLogo` or `darkBackground` there and run the command again.
|
|
312
312
|
- **A logo whose corners leave the circle** of Android 12's splash icon, 192 dp, gets a warning: Android cuts them. Make `--logo-width` smaller, or give the logo room around it.
|
|
313
313
|
- **A file it can't change** is listed as `manual`, with the change to make: an app without `Images.xcassets` next to `Info.plist`, a manifest whose `MainActivity` and `<application>` have no theme to go over.
|
|
314
|
-
- **`Info.plist` with `UILaunchScreen`** as well gets a warning:
|
|
314
|
+
- **`Info.plist` with `UILaunchScreen`** as well gets a warning: iOS shows that dictionary instead of the storyboard, so remove it.
|
|
315
315
|
- **Its errors** in the JSON: `splash-settings` for a setting it can't take, `splash-image` for a logo it can't read or that doesn't fit (too tall at its width, a dark logo of other proportions), and `sharp-missing`, as for `icons`.
|
|
316
316
|
|
|
317
317
|
`doctor` checks the splash in the App group:
|
|
@@ -320,7 +320,6 @@ Each file and why: [What the command writes](./splash-screen#files).
|
|
|
320
320
|
| --- | --- |
|
|
321
321
|
| Splash | The files of iOS or Android aren't made from the settings in `package.json`: changed by hand, made on one platform only, or by an older version of the command. Or the settings themselves are wrong, such as a logo that isn't there. |
|
|
322
322
|
| Splash logo | The logo's corners leave Android 12's circle. |
|
|
323
|
-
| Launch screen | Without settings: `Info.plist` has `UILaunchScreen` and no storyboard. iOS shows it at launch, but the navigation can only keep a storyboard on screen, so an empty window shows between it and the first screen. |
|
|
324
323
|
|
|
325
324
|
Each warning names its fix, such as `npx apex-navigation splash`; `--fix` leaves these to you. What the user sees, and a splash of your own after the native one: [Splash screen](./splash-screen).
|
|
326
325
|
|
package/docs-md/help/glossary.md
CHANGED
|
@@ -194,7 +194,7 @@ A description of what the platform shows: a screen name, or a tree of [builders]
|
|
|
194
194
|
|
|
195
195
|
### Liquid Glass
|
|
196
196
|
|
|
197
|
-
(iOS) 26: the see-through material of the bars, the tab bar and the buttons. A background color in the options takes
|
|
197
|
+
(iOS) 26: the see-through material of the bars, the tab bar and the buttons; the app gets this look, "the glass design", unless it is in the compatibility mode (`UIDesignRequiresCompatibility` in Info.plist), which keeps the look of before iOS 26. A background color in the options takes the glass away. More: [The glass design and the compatibility mode](../screen-ui/top-bar#glass-design), [The background and the border](../screen-ui/top-bar#background).
|
|
198
198
|
|
|
199
199
|
### listeners
|
|
200
200
|
|
|
@@ -314,7 +314,7 @@ An entry of `linking.routes`: a path pattern, such as `'user/:userId'`, and what
|
|
|
314
314
|
|
|
315
315
|
### Safe area
|
|
316
316
|
|
|
317
|
-
The part of the screen that nothing covers: not the bars, the status bar, the cutout or the home indicator. The navigation keeps every screen in it by default, measured on the native side. More: [The safe area](../screen-ui/system-ui#safe-area).
|
|
317
|
+
The part of the screen that nothing covers: not the bars, the status bar, the cutout or the home indicator. The navigation keeps every screen's top and sides in it by default, and the bottom is free, measured on the native side. More: [The safe area](../screen-ui/system-ui#safe-area).
|
|
318
318
|
|
|
319
319
|
### Screen
|
|
320
320
|
|
|
@@ -232,9 +232,9 @@ A signed-in user sees Login for a moment, then the app.
|
|
|
232
232
|
|
|
233
233
|
(iOS) Between the launch screen and the first screen, the window is empty for a moment.
|
|
234
234
|
|
|
235
|
-
**Why:** the
|
|
235
|
+
**Why:** the app has no launch screen the navigation can copy: `Info.plist` names no storyboard and has no `UILaunchScreen` dictionary (or `UILaunchScreens` default), and no legacy launch images.
|
|
236
236
|
|
|
237
|
-
**Fix:** run `npx apex-navigation splash`, or name a storyboard in `UILaunchStoryboardName
|
|
237
|
+
**Fix:** run `npx apex-navigation splash`, or name a storyboard in `UILaunchStoryboardName`. More: [Make the native splash](../guides/splash-screen#command).
|
|
238
238
|
|
|
239
239
|
### Android brings the app back without its pushed screens
|
|
240
240
|
|
|
@@ -439,6 +439,27 @@ return (
|
|
|
439
439
|
|
|
440
440
|
More: [Hiding the bar](../screen-ui/top-bar#hide), [The large title](../screen-ui/top-bar#large-title).
|
|
441
441
|
|
|
442
|
+
### The last rows of a list sit under the home indicator or the tab bar
|
|
443
|
+
|
|
444
|
+
The last row of a list can't be scrolled above the home indicator, the navigation bar or the tab bar.
|
|
445
|
+
|
|
446
|
+
**Why:** the bottom edge of a screen is not padded by default, so the content goes down to the edge, under the system and under the tab bar or toolbar.
|
|
447
|
+
|
|
448
|
+
**Fix:** add the bottom inset to the padding of the list; on (iOS) `contentInsetAdjustmentBehavior="automatic"` does it by itself. Or have the screen pad it: list every edge the screen keeps (`safeArea('top', 'left', 'right', 'bottom')`), since listed edges replace the default. An explicit `'top'` also keeps the top under a large title, so a large-title screen usually pads its list with `insets.bottom` instead:
|
|
449
|
+
|
|
450
|
+
```tsx
|
|
451
|
+
const insets = useSafeAreaInsets();
|
|
452
|
+
return (
|
|
453
|
+
<FlatList
|
|
454
|
+
data={items}
|
|
455
|
+
contentContainerStyle={{ paddingBottom: 16 + insets.bottom }}
|
|
456
|
+
renderItem={renderItem}
|
|
457
|
+
/>
|
|
458
|
+
);
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
More: [The safe area](../screen-ui/system-ui#safe-area).
|
|
462
|
+
|
|
442
463
|
### A pick in the overflow menu does nothing
|
|
443
464
|
|
|
444
465
|
The user picks Export in the overflow menu, and nothing happens.
|
package/docs-md/index.json
CHANGED
|
@@ -856,6 +856,10 @@
|
|
|
856
856
|
"heading": "What the top bar is",
|
|
857
857
|
"anchor": "what"
|
|
858
858
|
},
|
|
859
|
+
{
|
|
860
|
+
"heading": "The glass design and the compatibility mode",
|
|
861
|
+
"anchor": "glass-design"
|
|
862
|
+
},
|
|
859
863
|
{
|
|
860
864
|
"heading": "The title",
|
|
861
865
|
"anchor": "title"
|
|
@@ -978,6 +982,10 @@
|
|
|
978
982
|
"heading": "The preview of a pop",
|
|
979
983
|
"anchor": "pop-preview"
|
|
980
984
|
},
|
|
985
|
+
{
|
|
986
|
+
"heading": "The preview of a sheet closing",
|
|
987
|
+
"anchor": "sheet-preview"
|
|
988
|
+
},
|
|
981
989
|
{
|
|
982
990
|
"heading": "Back to the home screen",
|
|
983
991
|
"anchor": "system-animation"
|
|
@@ -4092,6 +4100,10 @@
|
|
|
4092
4100
|
"heading": "The first rows of a list hide under the top bar",
|
|
4093
4101
|
"anchor": "rows-under-bar"
|
|
4094
4102
|
},
|
|
4103
|
+
{
|
|
4104
|
+
"heading": "The last rows of a list sit under the home indicator or the tab bar",
|
|
4105
|
+
"anchor": "rows-under-bottom"
|
|
4106
|
+
},
|
|
4095
4107
|
{
|
|
4096
4108
|
"heading": "A pick in the overflow menu does nothing",
|
|
4097
4109
|
"anchor": "overflow-pick"
|
|
@@ -425,7 +425,7 @@ The route patterns of `linking` are typed too: in `'user/:userId'` the handler g
|
|
|
425
425
|
| `useNavigation` | `useNavigation`: the commands of this screen. |
|
|
426
426
|
| `useIsFocused` | `useIsFocused`. |
|
|
427
427
|
|
|
428
|
-
Every screen keeps clear of the bars by itself, so a `SafeAreaView` around the content can go; `useSafeAreaInsets` of this package gives what still covers the content, 0 on an edge the screen keeps clear, so padding by it never comes twice. More: [Lifecycle and events](../fundamentals/lifecycle-and-events#hooks), [The safe area](../screen-ui/system-ui#safe-area).
|
|
428
|
+
The bottom edge of a screen is not padded by default, as with `react-native-safe-area-context`: a list adds `useSafeAreaInsets().bottom` to its bottom padding, or the screen lists `'bottom'` in `safeArea(…)`. Every screen keeps its top and sides clear of the bars by itself, so a `SafeAreaView` around the content can go; `useSafeAreaInsets` of this package gives what still covers the content, 0 on an edge the screen keeps clear, so padding by it never comes twice. More: [Lifecycle and events](../fundamentals/lifecycle-and-events#hooks), [The safe area](../screen-ui/system-ui#safe-area).
|
|
429
429
|
|
|
430
430
|
## What has no direct equivalent
|
|
431
431
|
|
|
@@ -779,7 +779,7 @@ The whole list of options, with what each does on each platform: [Where each opt
|
|
|
779
779
|
| `react-native-navigation` | What to do here |
|
|
780
780
|
| --- | --- |
|
|
781
781
|
| `Navigation.updateProps` | Props are given once. What changes goes in the screen's state, or the app's context or store. |
|
|
782
|
-
| `Navigation.constants()` | `useSafeAreaInsets()` of a screen: what the bars and the system still cover of its content, measured for that screen; 0 on an edge the screen keeps clear itself. A screen that needs the height of its top bar goes under it (`drawBehind`) and reads the top inset. |
|
|
782
|
+
| `Navigation.constants()` | `useSafeAreaInsets()` of a screen (the bottom is not padded by default: a list adds `insets.bottom` to its bottom padding): what the bars and the system still cover of its content, measured for that screen; 0 on an edge the screen keeps clear itself. A screen that needs the height of its top bar goes under it (`drawBehind`) and reads the top inset. |
|
|
783
783
|
| `componentId` in a class component | Hooks need a function component: wrap the class in one that passes `useNavigation()` down as a prop. |
|
|
784
784
|
| `topTabs` | No container for it. A pager component inside one screen does the same. |
|
|
785
785
|
| `Navigation.handleDeepLink(url)` | Run the command the route would run, such as `navigation.push('Profile', { props })`. |
|
|
@@ -64,7 +64,7 @@ The left side renders the navigation. The right side describes it: `createNaviga
|
|
|
64
64
|
| **State** | A React object: routes, params, indexes. | The platform's. JavaScript hears events and reads a small state: the screen the user sees and the selected tab. |
|
|
65
65
|
| **Params** | `route.params`, changed with `setParams`. | Props of the component, given by the command that shows the screen. |
|
|
66
66
|
| **Options** | Props of a screen, with React elements for the header. | Data the platform reads before the screen shows. A component in a bar is registered in `screens`, as a screen is. |
|
|
67
|
-
| **Safe area** | `SafeAreaView` or a safe area library. | Every screen keeps clear of the bars by itself. See [The safe area](../screen-ui/system-ui#safe-area). |
|
|
67
|
+
| **Safe area** | `SafeAreaView` or a safe area library. | Every screen keeps its top and sides clear of the bars by itself; the bottom is yours (`insets.bottom`). See [The safe area](../screen-ui/system-ui#safe-area). |
|
|
68
68
|
|
|
69
69
|
Because options are data, a title is there from the first frame, and because the state is the platform's, a move renders nothing in React by itself. More on the model: [Introduction](../start/introduction#models).
|
|
70
70
|
|
|
@@ -605,7 +605,7 @@ Once no file imports them, remove `@react-navigation/*`, and `react-native-scree
|
|
|
605
605
|
npx apex-navigation doctor
|
|
606
606
|
```
|
|
607
607
|
|
|
608
|
-
Screens wrapped in `SafeAreaView` can drop it, since every screen keeps clear of the bars by itself; `useSafeAreaInsets` of this package gives what still covers the content of the screen. Code such as `paddingTop: insets.top` keeps working with it: on an edge the screen keeps clear the inset is 0, so nothing is padded twice.
|
|
608
|
+
The bottom edge of a screen is not padded by default, as with `react-native-safe-area-context`: a list adds `useSafeAreaInsets().bottom` to its bottom padding, or the screen lists `'bottom'` in `safeArea(…)`. Screens wrapped in `SafeAreaView` can drop it, since every screen keeps its top and sides clear of the bars by itself; `useSafeAreaInsets` of this package gives what still covers the content of the screen. Code such as `paddingTop: insets.top` keeps working with it: on an edge the screen keeps clear the inset is 0, so nothing is padded twice.
|
|
609
609
|
|
|
610
610
|
## What has no direct equivalent
|
|
611
611
|
|
|
@@ -106,6 +106,31 @@ export const MapScreen = defineScreen({
|
|
|
106
106
|
|
|
107
107
|
The preview shows when back pops the screen with the default transition. It doesn't when back does something else first, or when the app takes it: a menu or a search that closes, a screen that holds back (`hardwareBackButton.popStackOnPress: false`, a [`BackHandler` listener](#back-handler), `usePreventLeave`), or a pop with an animation of its own (`animations.pop`). On Android 13 and older the screen pops when the swipe ends.
|
|
108
108
|
|
|
109
|
+
`animations.pop.preview: false` turns the preview off for a screen: the gesture shows nothing, and the screen pops when the user lets go, as on Android 13. It is read from the screen that goes, so the app's `options` turn it off everywhere:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
defineScreen({
|
|
113
|
+
component: Checkout,
|
|
114
|
+
options: { animations: { pop: { preview: false } } },
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
iOS ignores it: the swipe back there is UIKit's.
|
|
119
|
+
|
|
120
|
+
### The preview of a sheet closing
|
|
121
|
+
|
|
122
|
+
(Android) 14+ The back gesture over a modal sheet that back would close shows the close before the user lets go: the sheet shrinks a little toward the bottom as the swipe goes on, and the dimming behind it follows. Letting go slides the sheet down and closes it exactly as a swipe down does, with the same events and the same answers (`onDismiss`, `modalDismissed`). Swiping back cancels, and the sheet returns.
|
|
123
|
+
|
|
124
|
+
There is no preview, and the sheet closes when the swipe ends (at once for a button press), when:
|
|
125
|
+
|
|
126
|
+
- the sheet has several detents and is above its lowest one: the sheet can be hidden only at the lowest detent, so back closes it right there, as before;
|
|
127
|
+
- its screen holds back: `hardwareBackButton.dismissModalOnPress: false`, a [`BackHandler` listener](#back-handler), `usePreventLeave`;
|
|
128
|
+
- a screen pushed inside the sheet pops first: that pop keeps [its own preview](#pop-preview);
|
|
129
|
+
- the modal can't be swiped down (`swipeToDismiss: false`);
|
|
130
|
+
- the sheet is the only modal over no root.
|
|
131
|
+
|
|
132
|
+
On Android 13 and older the sheet closes when the swipe ends. iOS keeps UIKit's sheet: nothing changes there.
|
|
133
|
+
|
|
109
134
|
### Back to the home screen
|
|
110
135
|
|
|
111
136
|
(Android) On the first screen of the app, with nothing left to go back to, the navigation leaves the back to the system. The system then plays its predictive back animation: as the user swipes, the app shrinks and the home screen shows behind it, and the user can still change their mind halfway. The system decides this as the swipe starts, so the navigation takes the back only while it has something to do with it: a screen to pop, a modal, a menu or a search to close, a tab to go to (`backBehavior`), or a [`BackHandler` listener](#back-handler) of the app.
|
|
@@ -122,6 +147,17 @@ Without it, back on the first screen still leaves the app, at once. See [What se
|
|
|
122
147
|
|
|
123
148
|
With the animation on, the system leaves the app itself: an `invokeDefaultOnBackPressed` of the app's `MainActivity` (such as `moveTaskToBack(true)`) isn't called on the first screen. Without it, and before Android 13, the back goes through the activity as before.
|
|
124
149
|
|
|
150
|
+
`hardwareBackButton.systemAnimation: false` keeps the back in the app on the first screen too, so the system plays no animation there: the gesture shows nothing, and on release the press goes to the [`BackHandler` listeners](#back-handler), then to the activity's own back, `invokeDefaultOnBackPressed` included. It is read from the screen back acts on; for the whole app, set it in the app's `options`:
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
createNavigation({
|
|
154
|
+
// …
|
|
155
|
+
options: { hardwareBackButton: { systemAnimation: false } },
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
iOS ignores it.
|
|
160
|
+
|
|
125
161
|
### Handling back yourself: BackHandler
|
|
126
162
|
|
|
127
163
|
(Android) React Native's `BackHandler` works on every screen, as in any React Native app. A listener runs before the navigation's own back: returning `true` takes the press, `false` passes it on to the next listener, then to the navigation.
|
|
@@ -64,8 +64,8 @@ The look and the state of a button:
|
|
|
64
64
|
| --- | --- |
|
|
65
65
|
| `color` | The color of this button. |
|
|
66
66
|
| `enabled: false` | Dimmed; a press does nothing. |
|
|
67
|
-
| `style: 'prominent'` | (iOS) The main action, such as Done or Send.
|
|
68
|
-
| `group` | (iOS)
|
|
67
|
+
| `style: 'prominent'` | (iOS) The main action, such as Done or Send. With the glass design a filled capsule in the button's `color`; without it, a bold title. See [the glass design](./top-bar#glass-design). |
|
|
68
|
+
| `group` | (iOS) Glass design: buttons with the same group share one glass capsule; each other button has a capsule of its own. |
|
|
69
69
|
| `badge` | (iOS) 26: a number, a short text, or `true` for a dot. |
|
|
70
70
|
| `toggle`, `selected` | (iOS) A button that switches on and off at each tap; the press carries the new state as `selected`. |
|
|
71
71
|
|
|
@@ -248,7 +248,7 @@ s.rightButtons({
|
|
|
248
248
|
});
|
|
249
249
|
```
|
|
250
250
|
|
|
251
|
-
The bar sends no press for it: the component's own `onPress` is the press. On (iOS)
|
|
251
|
+
The bar sends no press for it: the component's own `onPress` is the press. On (iOS) with the glass design it has no glass capsule; `hideSharedBackground: false` puts it on one. On (Android) `component.width` and `height` size it.
|
|
252
252
|
|
|
253
253
|
## Buttons that change
|
|
254
254
|
|
|
@@ -273,7 +273,7 @@ function NewNote() {
|
|
|
273
273
|
|
|
274
274
|
The options are built on each render and applied before the screen paints when they changed, so the bar never lags behind the state. `onPress` sees the state of the same render. A full form in a modal, with Cancel and Save, is walked through in [Forms in a modal](../flows/forms-in-a-modal).
|
|
275
275
|
|
|
276
|
-
On (iOS) `animateRightButtons` and `animateLeftButtons` fade the new buttons in.
|
|
276
|
+
On (iOS) `animateRightButtons` and `animateLeftButtons` fade the new buttons in. With the glass design buttons that share a capsule (several with one `group`) change at once: UIKit has no animation for groups of buttons.
|
|
277
277
|
|
|
278
278
|
## Buttons on iPhone Duo
|
|
279
279
|
|
|
@@ -171,7 +171,7 @@ The component gets the search events itself, with `useSearchBar`, as a screen of
|
|
|
171
171
|
|
|
172
172
|
| Platform | The results screen |
|
|
173
173
|
| --- | --- |
|
|
174
|
-
| (iOS) | Covers the whole screen, under the search field and to the bottom edge, so its background fills it. It keeps its content clear of the bar itself: a list with `contentInsetAdjustmentBehavior="automatic"`, other content with `useSafeAreaInsets`. |
|
|
174
|
+
| (iOS) | Covers the whole screen, under the search field and to the bottom edge, so its background fills it. It keeps its content clear of the bar itself: a list with `contentInsetAdjustmentBehavior="automatic"`, other content with `useSafeAreaInsets`. The results screen has no padding of its own. |
|
|
175
175
|
| (Android) | The search opens over the whole screen, the field at the top and the component under it. |
|
|
176
176
|
|
|
177
177
|
## In the toolbar
|
|
@@ -29,12 +29,12 @@ The status bar sits at the top, with the camera cutout in it; the home indicator
|
|
|
29
29
|
|
|
30
30
|
## The safe area
|
|
31
31
|
|
|
32
|
-
The safe area is where nothing covers the content: not the bars of the navigation, not the status bar, not the cutout, not the home indicator. **The navigation keeps every screen in it by default**, so a screen doesn't need its own padding:
|
|
32
|
+
The safe area is where nothing covers the content: not the bars of the navigation, not the status bar, not the cutout, not the home indicator. **The navigation keeps every screen's top and sides in it by default**, so a screen doesn't need its own padding there. The bottom is not padded: the content goes down to the edge, under the home indicator, the navigation bar and a tab bar or toolbar, and a list adds `useSafeAreaInsets().bottom` to its own padding, as with `react-native-safe-area-context`.
|
|
33
33
|
|
|
34
34
|
| Platform | By default |
|
|
35
35
|
| --- | --- |
|
|
36
|
-
| (iOS) |
|
|
37
|
-
| (Android) |
|
|
36
|
+
| (iOS) | The top and the sides, but not the top under a top bar that shows: a list goes under the bar and keeps its rows clear itself, so the bar's glass shows the content scrolling under it. A screen with a large title leaves the top out too, and on iPhone Duo the side of the [vertical bar](#vertical-bar). The bottom goes under the home indicator and under a tab bar or toolbar. |
|
|
37
|
+
| (Android) | The top and the sides: under the status bar, out of the cutout. The top is free under a top bar that hides on scroll and under a large title. The bottom goes under the navigation bar, when the screen draws under it (not with an opaque bar), and under the tabs only with `bottomTabs.drawBehind` or `hideOnScroll`. |
|
|
38
38
|
|
|
39
39
|
> **Tip**
|
|
40
40
|
>
|
|
@@ -47,6 +47,12 @@ A screen that should go under the system, such as a photo or a map, says which e
|
|
|
47
47
|
```ts
|
|
48
48
|
s.safeArea('bottom'); // only the bottom: the photo goes under the top
|
|
49
49
|
s.safeArea(); // no edge: the content goes everywhere
|
|
50
|
+
s.safeArea('top', 'left', 'right', 'bottom'); // all four: a form above the home indicator
|
|
51
|
+
|
|
52
|
+
// listed edges replace the default: this one loses
|
|
53
|
+
// the sides; under a large title the top stays free,
|
|
54
|
+
// because the list keeps clear of the bar itself
|
|
55
|
+
s.safeArea('top', 'bottom');
|
|
50
56
|
```
|
|
51
57
|
|
|
52
58
|
The edges for every screen of the app go in `createNavigation`, and a screen's own `safeArea` wins over them:
|
|
@@ -54,11 +60,11 @@ The edges for every screen of the app go in `createNavigation`, and a screen's o
|
|
|
54
60
|
```ts
|
|
55
61
|
export const Navigation = createNavigation({
|
|
56
62
|
// …
|
|
57
|
-
safeArea: { edges: ['top', 'bottom'] },
|
|
63
|
+
safeArea: { edges: ['top', 'left', 'right', 'bottom'] },
|
|
58
64
|
});
|
|
59
65
|
```
|
|
60
66
|
|
|
61
|
-
Content that keeps clear of the system itself, such as a list that scrolls under the home indicator, reads what still covers it with `useSafeAreaInsets()`. The insets are **what is left after the screen's own padding**: 0 on an edge the screen keeps, all of what covers it on an edge its content goes under. So the content adds them to its own padding, and no edge is padded twice. Here the screen keeps the top
|
|
67
|
+
Content that keeps clear of the system itself, such as a list that scrolls under the home indicator, reads what still covers it with `useSafeAreaInsets()`. The insets are **what is left after the screen's own padding**: 0 on an edge the screen keeps, all of what covers it on an edge its content goes under. So the content adds them to its own padding, and no edge is padded twice. Here the screen keeps the top and the sides by default, and the list pads its own bottom:
|
|
62
68
|
|
|
63
69
|
```tsx
|
|
64
70
|
import { ScrollView } from 'react-native';
|
|
@@ -71,7 +77,7 @@ function Inbox() {
|
|
|
71
77
|
const insets = useSafeAreaInsets();
|
|
72
78
|
return (
|
|
73
79
|
<ScrollView
|
|
74
|
-
contentContainerStyle={{ paddingBottom: insets.bottom }}
|
|
80
|
+
contentContainerStyle={{ paddingBottom: 16 + insets.bottom }}
|
|
75
81
|
>
|
|
76
82
|
{/* the rows scroll under the home indicator,
|
|
77
83
|
and the last one ends above it */}
|
|
@@ -80,12 +86,11 @@ function Inbox() {
|
|
|
80
86
|
}
|
|
81
87
|
|
|
82
88
|
export const InboxScreen = defineScreen({
|
|
83
|
-
component: Inbox,
|
|
84
|
-
options: (s) => s.safeArea('top'),
|
|
89
|
+
component: Inbox, // the default edges: the top and the sides
|
|
85
90
|
});
|
|
86
91
|
```
|
|
87
92
|
|
|
88
|
-
On a screen that
|
|
93
|
+
On a screen that lists `'bottom'` in its edges, `insets.bottom` is 0, so the same code works there: the content is already above the home indicator. Below a top bar the top inset is 0; on a screen drawn behind the bars it is their height. On (iOS) a scroll view with `contentInsetAdjustmentBehavior="automatic"` keeps clear of the bars by itself: leave the inset out on those edges. More: [The insets and the safe area of the screen](/api/system-ui/useSafeAreaInsets#safe-area).
|
|
89
94
|
|
|
90
95
|
### The vertical bar of iPhone Duo
|
|
91
96
|
|
|
@@ -231,7 +236,7 @@ export const PhotoScreen = defineScreen({
|
|
|
231
236
|
| Platform | The bottom |
|
|
232
237
|
| --- | --- |
|
|
233
238
|
| (iOS) | The home indicator. `layout.autoHideHomeIndicator` fades it out while the screen isn't touched, as in a video. `layout.deferSystemGestures` makes the first swipe on those edges go to the app, for a game or a drawing; the second swipe goes home. |
|
|
234
|
-
| (Android) | The navigation bar: a handle, or three buttons. The screen goes under it, and
|
|
239
|
+
| (Android) | The navigation bar: a handle, or three buttons. The screen goes under it, and by default so does the content; a list adds `insets.bottom`. With an opaque bar or `drawBehind: false` the screen ends above it. `navigationBar` sets its `backgroundColor`, hides it with `visible: false`, or puts the screen above it with `drawBehind: false`. |
|
|
235
240
|
|
|
236
241
|
(Android) The screen goes edge to edge as the platform says: always on Android 16+ for apps targeting SDK 36, on Android 15 for SDK 35, and on every version with `edgeToEdgeEnabled=true` in `android/gradle.properties`, the default of new React Native apps.
|
|
237
242
|
|
|
@@ -274,7 +279,7 @@ export const Navigation = createNavigation({
|
|
|
274
279
|
s.scrollEdgeEffect({ top: { style: 'hard' } });
|
|
275
280
|
```
|
|
276
281
|
|
|
277
|
-
The effect
|
|
282
|
+
The effect applies to the vertical lists and text views of the screen when it shows, so a list that mounts after the screen shows gets it the next time the screen appears. A list nested very deep in the screen's views may not get it. A list inside another vertical list, like a horizontal carousel in a feed, gets none: UIKit draws no edge effect there. Render the list at once, and fill it when the data comes.
|
|
278
283
|
|
|
279
284
|
## Keyboard shortcuts
|
|
280
285
|
|
|
@@ -46,10 +46,10 @@ Filter sits at the leading edge and Compose at the trailing one. The buttons are
|
|
|
46
46
|
| Item | What it does |
|
|
47
47
|
| --- | --- |
|
|
48
48
|
| `{ systemItem: 'flexibleSpace' }` | Takes the free width, and pushes the next buttons to the far side. |
|
|
49
|
-
| `{ systemItem: 'fixedSpace' }` | A gap.
|
|
49
|
+
| `{ systemItem: 'fixedSpace' }` | A gap. With the glass design it also starts a new glass capsule. |
|
|
50
50
|
| `{ systemItem: 'searchField' }` | iOS 26: the search field of the screen, between the buttons. See [Search](./search#toolbar). |
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
With the glass design ([what it is](./top-bar#glass-design)) icons next to each other share one glass capsule; a text button has one of its own, and a `badge` doesn't show in the toolbar. `visible: false` hides the toolbar with its buttons.
|
|
53
53
|
|
|
54
54
|
On iPhone with iOS 26 the floating tab bar takes the same place at the bottom. A screen with a toolbar inside [tabs](../containers/tabs) hides the tab bar with `hideBottomTabs()`.
|
|
55
55
|
|
|
@@ -31,6 +31,10 @@ The top bar belongs to **the stack, not to a screen**: one bar for all the scree
|
|
|
31
31
|
|
|
32
32
|
The platform draws the bar: `UINavigationBar` on (iOS), with Liquid Glass on iOS 26, and the Material 3 top app bar on (Android). Its colors come from the [theme](../architecture/theme-and-context) until a screen sets its own.
|
|
33
33
|
|
|
34
|
+
## The glass design and the compatibility mode
|
|
35
|
+
|
|
36
|
+
On (iOS) the look of iOS 26 (Liquid Glass) is *the glass design*, and this page and [the bar buttons](./bar-buttons) call it that. An app in the compatibility mode (`UIDesignRequiresCompatibility = YES` in Info.plist) doesn't get it: the mode applies on iOS 26, and on iOS 27 only to an app built with an SDK before iOS 27. There the bar looks and behaves as before iOS 26: a title with a subtitle as two lines only when no large title can show (with a large title the subtitle shows nowhere), no capsules (`title.glass`, `hideSharedBackground` and `group` do nothing), `style: 'prominent'` is a bold Done button, the back button shows the title of the screen below, `background.color` gives an opaque bar, and a large title is drawn in the bar. [The bottom tabs](../containers/tabs) follow the same mode.
|
|
37
|
+
|
|
34
38
|
## The title
|
|
35
39
|
|
|
36
40
|
The title is the name of the screen. The subtitle is a second line under it, such as a status or a count. Both can come from the props of the screen:
|
|
@@ -84,20 +88,20 @@ s.title('Details').largeTitle({ displayMode: 'never' });
|
|
|
84
88
|
|
|
85
89
|
## The background and the border
|
|
86
90
|
|
|
87
|
-
|
|
91
|
+
With the glass design the bar is Liquid Glass, and the content shows through it as it scrolls under. **A color takes the glass away**, so set one only when the design needs it:
|
|
88
92
|
|
|
89
93
|
```ts
|
|
90
94
|
s.topBar({ background: { color: '#F3E9D2' }, noBorder: true });
|
|
91
95
|
```
|
|
92
96
|
|
|
93
|
-
`background.color` colors the bar on both platforms. With a large title
|
|
97
|
+
`background.color` colors the bar on both platforms. With a large title and the glass design the color goes behind the title in the list, and fills the bar once the title collapses. Under a bar with a color the list has no edge effect at the top: see [The edge of the scroll](./system-ui#scroll-edge).
|
|
94
98
|
|
|
95
99
|
The other keys of the background are rarer:
|
|
96
100
|
|
|
97
101
|
| Option | What it does |
|
|
98
102
|
| --- | --- |
|
|
99
103
|
| `background.component` | A component of `screens` drawn behind the bar, such as a gradient. It is registered as the [title component](#title-component) is. |
|
|
100
|
-
| `background.translucent` | (iOS) `true`: the content shows through; Liquid Glass
|
|
104
|
+
| `background.translucent` | (iOS) `true`: the content shows through; Liquid Glass with the glass design, where it is the default. `false`: a solid bar, white in the light theme and black in the dark one. |
|
|
101
105
|
| `background.blur` | (iOS) The system's frosted blur, plainer than the glass. A `color` wins over it, and it wins over `translucent`. |
|
|
102
106
|
| `scrollEdgeAppearance` | (iOS) The look of the bar while the content is at its top, with `active: true`. The bar takes its usual look once the content scrolls under it. |
|
|
103
107
|
|
|
@@ -132,7 +136,7 @@ The bar can also hide and show by itself:
|
|
|
132
136
|
| `hideOnKeyboard` | (iOS) Hides while the keyboard shows. |
|
|
133
137
|
| `animate` | `false`: hides and shows at once, without the animation. |
|
|
134
138
|
|
|
135
|
-
With `hideOnScroll` the screen goes under the bar, so its rows show where the bar was. Its list keeps its first rows clear: on (iOS) with `contentInsetAdjustmentBehavior="automatic"`, on (Android) with `paddingTop: useSafeAreaInsets().top`.
|
|
139
|
+
With `hideOnScroll` the screen goes under the bar, so its rows show where the bar was. Its list keeps its first rows clear: on (iOS) with `contentInsetAdjustmentBehavior="automatic"`, on (Android) with `paddingTop: useSafeAreaInsets().top`. The bottom edge is not padded by default, so the list also adds `useSafeAreaInsets().bottom` to its bottom padding (an iOS list with `contentInsetAdjustmentBehavior="automatic"` does it itself).
|
|
136
140
|
|
|
137
141
|
`drawBehind: true` keeps the bar and puts the content under it, as a map or a photo under a glass bar.
|
|
138
142
|
|
|
@@ -215,7 +215,7 @@ export {};
|
|
|
215
215
|
|
|
216
216
|
| Group | What it checks |
|
|
217
217
|
| --- | --- |
|
|
218
|
-
| App | React Native and the New Architecture, the package, the files `setup` sets up, the entry point, React Native loaded twice, the status bar of the template, the generated code of native screens. Typed commands: `navigation-env.d.ts` there, pointing to the navigation, with its `context` line, and read by `tsconfig.json`. The splash screen made from its settings, with its logo inside the circle of Android's splash icon
|
|
218
|
+
| App | React Native and the New Architecture, the package, the files `setup` sets up, the entry point, React Native loaded twice, the status bar of the template, the generated code of native screens. Typed commands: `navigation-env.d.ts` there, pointing to the navigation, with its `context` line, and read by `tsconfig.json`. The splash screen made from its settings, with its logo inside the circle of Android's splash icon. The app icon made from its settings, and an iOS icon set with no image, which the App Store refuses. |
|
|
219
219
|
| Navigation | From the app's code. Screens: no name given twice in `screens` and the groups, and no file of the screens' folder with a `defineScreen` or `nativeScreen` that no file imports, a screen made and never added. Links: each prefix of `linking` declared on both platforms, the declarations no prefix has, a main activity that isn't `singleTask`. Screen names, in a JavaScript app only: a name that `push`, `replace`, `showModal` or `showInColumn` gives and no screen has, with its file and line. |
|
|
220
220
|
| (Android) | JDK 17 in use, the Android SDK, `minSdk`, how edge-to-edge works in the app. |
|
|
221
221
|
| (iOS) | Xcode 26 or newer, CocoaPods, the pods of the navigation and whether they are out of date, the iOS version of the Podfile. |
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#import "ANNFrameDriver.h"
|
|
2
2
|
#import "ANNAssert.h"
|
|
3
3
|
#import "ANNCurve.h"
|
|
4
|
+
#import "ANNTransformParts.h"
|
|
4
5
|
#import <QuartzCore/QuartzCore.h>
|
|
5
6
|
|
|
6
7
|
// The display link holds its target strongly and the run loop holds the link: the driver is reached weakly, so a driver
|
|
@@ -57,6 +58,7 @@
|
|
|
57
58
|
ANNAssertMainQueue();
|
|
58
59
|
#if DEBUG
|
|
59
60
|
ANNCurveSelfCheck();
|
|
61
|
+
ANNTransformPartsSelfCheck();
|
|
60
62
|
#endif
|
|
61
63
|
if (_link) {
|
|
62
64
|
return;
|