@ng-native/router 0.0.1 → 0.1.1
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/LICENSE +21 -0
- package/README.md +74 -1
- package/dist/bind-route-inputs.d.ts +20 -0
- package/dist/bind-route-inputs.js +52 -0
- package/dist/bind-route-inputs.js.map +1 -0
- package/dist/full-window-overlay.d.ts +16 -0
- package/dist/full-window-overlay.js +52 -0
- package/dist/full-window-overlay.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/native-back.d.ts +38 -0
- package/dist/native-back.js +88 -0
- package/dist/native-back.js.map +1 -0
- package/dist/native-bar-defaults.d.ts +50 -0
- package/dist/native-bar-defaults.js +17 -0
- package/dist/native-bar-defaults.js.map +1 -0
- package/dist/native-header-item.d.ts +12 -0
- package/dist/native-header-item.js +40 -0
- package/dist/native-header-item.js.map +1 -0
- package/dist/native-header-palette.d.ts +45 -0
- package/dist/native-header-palette.js +42 -0
- package/dist/native-header-palette.js.map +1 -0
- package/dist/native-header.d.ts +103 -0
- package/dist/native-header.js +204 -0
- package/dist/native-header.js.map +1 -0
- package/dist/native-links.d.ts +30 -0
- package/dist/native-links.js +40 -0
- package/dist/native-links.js.map +1 -0
- package/dist/native-navigation.d.ts +84 -0
- package/dist/native-navigation.js +153 -0
- package/dist/native-navigation.js.map +1 -0
- package/dist/native-platform-location.d.ts +62 -0
- package/dist/native-platform-location.js +110 -0
- package/dist/native-platform-location.js.map +1 -0
- package/dist/native-router-link.d.ts +14 -0
- package/dist/native-router-link.js +51 -0
- package/dist/native-router-link.js.map +1 -0
- package/dist/native-search-bar.d.ts +44 -0
- package/dist/native-search-bar.js +132 -0
- package/dist/native-search-bar.js.map +1 -0
- package/dist/native-stack-outlet.d.ts +195 -0
- package/dist/native-stack-outlet.js +564 -0
- package/dist/native-stack-outlet.js.map +1 -0
- package/dist/native-stack-reuse-strategy.d.ts +57 -0
- package/dist/native-stack-reuse-strategy.js +126 -0
- package/dist/native-stack-reuse-strategy.js.map +1 -0
- package/dist/native-tab.d.ts +147 -0
- package/dist/native-tab.js +209 -0
- package/dist/native-tab.js.map +1 -0
- package/dist/native-tabs-outlet.d.ts +138 -0
- package/dist/native-tabs-outlet.js +408 -0
- package/dist/native-tabs-outlet.js.map +1 -0
- package/dist/own-host.d.ts +6 -0
- package/dist/own-host.js +28 -0
- package/dist/own-host.js.map +1 -0
- package/dist/provide-native-router.d.ts +41 -0
- package/dist/provide-native-router.js +151 -0
- package/dist/provide-native-router.js.map +1 -0
- package/dist/screen-presentation.d.ts +69 -0
- package/dist/screen-presentation.js +10 -0
- package/dist/screen-presentation.js.map +1 -0
- package/dist/screens.d.ts +37 -0
- package/dist/screens.js +108 -0
- package/dist/screens.js.map +1 -0
- package/dist/tab-routes.d.ts +16 -0
- package/dist/tab-routes.js +24 -0
- package/dist/tab-routes.js.map +1 -0
- package/dist/tab-safe-area-view.d.ts +15 -0
- package/dist/tab-safe-area-view.js +63 -0
- package/dist/tab-safe-area-view.js.map +1 -0
- package/dist/transforms.d.ts +2 -0
- package/dist/transforms.js +13 -0
- package/dist/transforms.js.map +1 -0
- package/package.json +59 -3
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Angular's Router, on native navigation: `provideRouter` and the native pieces under it, in one
|
|
3
|
+
* call.
|
|
4
|
+
*
|
|
5
|
+
* `provideNativeRouter(routes, withComponentInputBinding())` takes the same routes and the same
|
|
6
|
+
* router features as `provideRouter` - it calls it - and adds what native needs on top: a
|
|
7
|
+
* `PlatformLocation` over an in-memory history, a `RouteReuseStrategy` that detaches a pushed-away
|
|
8
|
+
* screen instead of destroying it, and `NativeNavigation`. Those come after `provideRouter`'s own
|
|
9
|
+
* providers, so they win over the web defaults without the app having to order anything.
|
|
10
|
+
*
|
|
11
|
+
* Deep links and Android's back button come from `@ng-native/device` and need no wiring: off a
|
|
12
|
+
* device both report nothing, and navigation works either way. The native options - `withLinkParent`,
|
|
13
|
+
* `withHeaderDefaults` and `withTabDefaults` - are passed like any other feature.
|
|
14
|
+
*/
|
|
15
|
+
import { PlatformLocation } from '@angular/common';
|
|
16
|
+
import { NavigationCancel, NavigationEnd, NavigationError, RouteReuseStrategy, Router, provideRouter, } from '@angular/router';
|
|
17
|
+
import { computed, inject, makeEnvironmentProviders, provideEnvironmentInitializer, } from '@angular/core';
|
|
18
|
+
import { ColorScheme, DeepLinks } from '@ng-native/device';
|
|
19
|
+
import { NATIVE_HEADER_DEFAULTS, NATIVE_TAB_DEFAULTS, } from "./native-bar-defaults.js";
|
|
20
|
+
import { followLink, linkAncestry } from "./native-links.js";
|
|
21
|
+
import { NativeNavigation } from "./native-navigation.js";
|
|
22
|
+
import { NativePlatformLocation } from "./native-platform-location.js";
|
|
23
|
+
import { NativeStackReuseStrategy } from "./native-stack-reuse-strategy.js";
|
|
24
|
+
import { registerScreenComponents } from "./screens.js";
|
|
25
|
+
/**
|
|
26
|
+
* The page a deep link opens on top of, or null to open it alone. Without it a link to
|
|
27
|
+
* `/settings/notifications` opens that page with no Back. See native-links.ts.
|
|
28
|
+
*/
|
|
29
|
+
export function withLinkParent(parentOf) {
|
|
30
|
+
return { ɵnativeRouter: { linkParent: parentOf } };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* How every `<native-header>` looks when it does not say: its colours, blur, shadow, fonts and
|
|
34
|
+
* light or dark style. A header's own binding always wins.
|
|
35
|
+
*
|
|
36
|
+
* ```ts
|
|
37
|
+
* provideNativeRouter(
|
|
38
|
+
* routes,
|
|
39
|
+
* withHeaderDefaults((scheme) => ({
|
|
40
|
+
* backgroundColor: scheme === 'dark' ? '#101014' : '#f4f4f7',
|
|
41
|
+
* titleColor: scheme === 'dark' ? '#ffffff' : '#101014',
|
|
42
|
+
* color: '#3b6ef5',
|
|
43
|
+
* })),
|
|
44
|
+
* );
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* Colours it leaves out still come from `NATIVE_HEADER_PALETTE`.
|
|
48
|
+
*/
|
|
49
|
+
export function withHeaderDefaults(defaults) {
|
|
50
|
+
return { ɵnativeRouter: { providers: [bySchemeProvider(NATIVE_HEADER_DEFAULTS, defaults)] } };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* How every `<native-tabs-outlet>` and `<native-tab>` looks when it does not say: the tint, the
|
|
54
|
+
* background behind the tabs, the light or dark style, and each tab's bar appearance. An outlet's
|
|
55
|
+
* or a tab's own binding always wins.
|
|
56
|
+
*/
|
|
57
|
+
export function withTabDefaults(defaults) {
|
|
58
|
+
return { ɵnativeRouter: { providers: [bySchemeProvider(NATIVE_TAB_DEFAULTS, defaults)] } };
|
|
59
|
+
}
|
|
60
|
+
/** One signal for the whole app, rather than every bar calling the function again. */
|
|
61
|
+
function bySchemeProvider(token, defaults) {
|
|
62
|
+
return {
|
|
63
|
+
provide: token,
|
|
64
|
+
useFactory: () => {
|
|
65
|
+
if (typeof defaults !== 'function')
|
|
66
|
+
return computed(() => defaults);
|
|
67
|
+
const scheme = inject(ColorScheme);
|
|
68
|
+
return computed(() => defaults(scheme.current()));
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function isNative(feature) {
|
|
73
|
+
return 'ɵnativeRouter' in feature;
|
|
74
|
+
}
|
|
75
|
+
export function provideNativeRouter(routes, ...features) {
|
|
76
|
+
const angular = features.filter((feature) => !isNative(feature));
|
|
77
|
+
const native = features.filter(isNative).map((feature) => feature.ɵnativeRouter);
|
|
78
|
+
const parentOf = native.find((feature) => feature.linkParent)?.linkParent;
|
|
79
|
+
return makeEnvironmentProviders([
|
|
80
|
+
provideRouter(routes, ...angular),
|
|
81
|
+
...nativeProviders(parentOf),
|
|
82
|
+
...native.flatMap((feature) => feature.providers ?? []),
|
|
83
|
+
]);
|
|
84
|
+
}
|
|
85
|
+
function nativeProviders(parentOf) {
|
|
86
|
+
// The engine needs the element-to-view-name mapping before the first navigation renders.
|
|
87
|
+
registerScreenComponents();
|
|
88
|
+
const shared = [
|
|
89
|
+
// Without this the router never detaches, so a pushed-away screen is destroyed and rebuilt.
|
|
90
|
+
{ provide: RouteReuseStrategy, useClass: NativeStackReuseStrategy },
|
|
91
|
+
NativeNavigation,
|
|
92
|
+
];
|
|
93
|
+
if (!parentOf) {
|
|
94
|
+
return [
|
|
95
|
+
{
|
|
96
|
+
provide: PlatformLocation,
|
|
97
|
+
useFactory: () => new NativePlatformLocation(inject(DeepLinks)),
|
|
98
|
+
},
|
|
99
|
+
...shared,
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
return [
|
|
103
|
+
{
|
|
104
|
+
provide: PlatformLocation,
|
|
105
|
+
// The app launches on the page the link belongs under; the link follows once it is shown.
|
|
106
|
+
// Links while running go through the router below rather than straight into history.
|
|
107
|
+
useFactory: () => {
|
|
108
|
+
const initial = inject(DeepLinks).initialUrl();
|
|
109
|
+
return new NativePlatformLocation({
|
|
110
|
+
initialUrl: () => (initial && linkAncestry(initial, parentOf)[0]) || initial,
|
|
111
|
+
subscribe: () => () => { },
|
|
112
|
+
});
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
provideEnvironmentInitializer(() => {
|
|
116
|
+
const router = inject(Router);
|
|
117
|
+
const links = inject(DeepLinks);
|
|
118
|
+
const initial = links.initialUrl();
|
|
119
|
+
if (initial && parentOf(initial)) {
|
|
120
|
+
const first = router.events.subscribe((event) => {
|
|
121
|
+
if (!(event instanceof NavigationEnd))
|
|
122
|
+
return;
|
|
123
|
+
first.unsubscribe();
|
|
124
|
+
void followLink(router, initial, parentOf);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
// A launch link arrives the way any other does, once `getInitialURL()` settles, which is
|
|
128
|
+
// usually while the router's first navigation is still going: following it then cancels that
|
|
129
|
+
// navigation, and the app opens with no root screen under the link. So links wait for it.
|
|
130
|
+
const settled = new Promise((resolve) => {
|
|
131
|
+
if (router.navigated)
|
|
132
|
+
return resolve();
|
|
133
|
+
const first = router.events.subscribe((event) => {
|
|
134
|
+
if (!isEnd(event))
|
|
135
|
+
return;
|
|
136
|
+
first.unsubscribe();
|
|
137
|
+
resolve();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
links.subscribe((url) => void settled.then(() => followLink(router, url, parentOf)));
|
|
141
|
+
}),
|
|
142
|
+
...shared,
|
|
143
|
+
];
|
|
144
|
+
}
|
|
145
|
+
/** A navigation's last event, whichever way it went. */
|
|
146
|
+
function isEnd(event) {
|
|
147
|
+
return (event instanceof NavigationEnd ||
|
|
148
|
+
event instanceof NavigationCancel ||
|
|
149
|
+
event instanceof NavigationError);
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=provide-native-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provide-native-router.js","sourceRoot":"","sources":["../src/provide-native-router.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Angular's Router, on native navigation: `provideRouter` and the native pieces under it, in one\n * call.\n *\n * `provideNativeRouter(routes, withComponentInputBinding())` takes the same routes and the same\n * router features as `provideRouter` - it calls it - and adds what native needs on top: a\n * `PlatformLocation` over an in-memory history, a `RouteReuseStrategy` that detaches a pushed-away\n * screen instead of destroying it, and `NativeNavigation`. Those come after `provideRouter`'s own\n * providers, so they win over the web defaults without the app having to order anything.\n *\n * Deep links and Android's back button come from `@ng-native/device` and need no wiring: off a\n * device both report nothing, and navigation works either way. The native options - `withLinkParent`,\n * `withHeaderDefaults` and `withTabDefaults` - are passed like any other feature.\n */\nimport { PlatformLocation } from '@angular/common';\nimport {\n NavigationCancel,\n NavigationEnd,\n NavigationError,\n RouteReuseStrategy,\n Router,\n provideRouter,\n type RouterFeatures,\n type Routes,\n} from '@angular/router';\nimport {\n computed,\n inject,\n makeEnvironmentProviders,\n provideEnvironmentInitializer,\n type EnvironmentProviders,\n type InjectionToken,\n type Provider,\n type Signal,\n} from '@angular/core';\nimport { ColorScheme, DeepLinks, type Scheme } from '@ng-native/device';\nimport {\n NATIVE_HEADER_DEFAULTS,\n NATIVE_TAB_DEFAULTS,\n type HeaderDefaults,\n type SchemeDefaults,\n type TabDefaults,\n} from './native-bar-defaults.ts';\nimport { followLink, linkAncestry, type LinkParent } from './native-links.ts';\nimport { NativeNavigation } from './native-navigation.ts';\nimport { NativePlatformLocation } from './native-platform-location.ts';\nimport { NativeStackReuseStrategy } from './native-stack-reuse-strategy.ts';\nimport { registerScreenComponents } from './screens.ts';\n\n/** A native-only router option, passed to `provideNativeRouter` beside Angular's own features. */\nexport interface NativeRouterFeature {\n readonly ɵnativeRouter: { readonly linkParent?: LinkParent; readonly providers?: Provider[] };\n}\n\n/**\n * The page a deep link opens on top of, or null to open it alone. Without it a link to\n * `/settings/notifications` opens that page with no Back. See native-links.ts.\n */\nexport function withLinkParent(parentOf: LinkParent): NativeRouterFeature {\n return { ɵnativeRouter: { linkParent: parentOf } };\n}\n\n/**\n * How every `<native-header>` looks when it does not say: its colours, blur, shadow, fonts and\n * light or dark style. A header's own binding always wins.\n *\n * ```ts\n * provideNativeRouter(\n * routes,\n * withHeaderDefaults((scheme) => ({\n * backgroundColor: scheme === 'dark' ? '#101014' : '#f4f4f7',\n * titleColor: scheme === 'dark' ? '#ffffff' : '#101014',\n * color: '#3b6ef5',\n * })),\n * );\n * ```\n *\n * Colours it leaves out still come from `NATIVE_HEADER_PALETTE`.\n */\nexport function withHeaderDefaults(defaults: SchemeDefaults<HeaderDefaults>): NativeRouterFeature {\n return { ɵnativeRouter: { providers: [bySchemeProvider(NATIVE_HEADER_DEFAULTS, defaults)] } };\n}\n\n/**\n * How every `<native-tabs-outlet>` and `<native-tab>` looks when it does not say: the tint, the\n * background behind the tabs, the light or dark style, and each tab's bar appearance. An outlet's\n * or a tab's own binding always wins.\n */\nexport function withTabDefaults(defaults: SchemeDefaults<TabDefaults>): NativeRouterFeature {\n return { ɵnativeRouter: { providers: [bySchemeProvider(NATIVE_TAB_DEFAULTS, defaults)] } };\n}\n\n/** One signal for the whole app, rather than every bar calling the function again. */\nfunction bySchemeProvider<T>(\n token: InjectionToken<Signal<T>>,\n defaults: SchemeDefaults<T>,\n): Provider {\n return {\n provide: token,\n useFactory: () => {\n if (typeof defaults !== 'function') return computed(() => defaults);\n const scheme = inject(ColorScheme);\n return computed(() => (defaults as (scheme: Scheme) => T)(scheme.current()));\n },\n };\n}\n\nfunction isNative(feature: RouterFeatures | NativeRouterFeature): feature is NativeRouterFeature {\n return 'ɵnativeRouter' in feature;\n}\n\nexport function provideNativeRouter(\n routes: Routes,\n ...features: (RouterFeatures | NativeRouterFeature)[]\n): EnvironmentProviders {\n const angular = features.filter((feature): feature is RouterFeatures => !isNative(feature));\n const native = features.filter(isNative).map((feature) => feature.ɵnativeRouter);\n const parentOf = native.find((feature) => feature.linkParent)?.linkParent;\n return makeEnvironmentProviders([\n provideRouter(routes, ...angular),\n ...nativeProviders(parentOf),\n ...native.flatMap((feature) => feature.providers ?? []),\n ]);\n}\n\nfunction nativeProviders(parentOf: LinkParent | undefined): (Provider | EnvironmentProviders)[] {\n // The engine needs the element-to-view-name mapping before the first navigation renders.\n registerScreenComponents();\n\n const shared = [\n // Without this the router never detaches, so a pushed-away screen is destroyed and rebuilt.\n { provide: RouteReuseStrategy, useClass: NativeStackReuseStrategy },\n NativeNavigation,\n ];\n if (!parentOf) {\n return [\n {\n provide: PlatformLocation,\n useFactory: () => new NativePlatformLocation(inject(DeepLinks)),\n },\n ...shared,\n ];\n }\n\n return [\n {\n provide: PlatformLocation,\n // The app launches on the page the link belongs under; the link follows once it is shown.\n // Links while running go through the router below rather than straight into history.\n useFactory: () => {\n const initial = inject(DeepLinks).initialUrl();\n return new NativePlatformLocation({\n initialUrl: () => (initial && linkAncestry(initial, parentOf)[0]) || initial,\n subscribe: () => () => {},\n });\n },\n },\n provideEnvironmentInitializer(() => {\n const router = inject(Router);\n const links = inject(DeepLinks);\n const initial = links.initialUrl();\n if (initial && parentOf(initial)) {\n const first = router.events.subscribe((event) => {\n if (!(event instanceof NavigationEnd)) return;\n first.unsubscribe();\n void followLink(router, initial, parentOf);\n });\n }\n // A launch link arrives the way any other does, once `getInitialURL()` settles, which is\n // usually while the router's first navigation is still going: following it then cancels that\n // navigation, and the app opens with no root screen under the link. So links wait for it.\n const settled = new Promise<void>((resolve) => {\n if (router.navigated) return resolve();\n const first = router.events.subscribe((event) => {\n if (!isEnd(event)) return;\n first.unsubscribe();\n resolve();\n });\n });\n links.subscribe((url) => void settled.then(() => followLink(router, url, parentOf)));\n }),\n ...shared,\n ];\n}\n\n/** A navigation's last event, whichever way it went. */\nfunction isEnd(event: unknown): boolean {\n return (\n event instanceof NavigationEnd ||\n event instanceof NavigationCancel ||\n event instanceof NavigationError\n );\n}\n"]}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a screen arrives, which is the part of mobile navigation a URL cannot express.
|
|
3
|
+
*
|
|
4
|
+
* Every field is a prop on `RNSScreen`, spelled exactly as the codegen spec spells it, so the
|
|
5
|
+
* outlet forwards the object key by key rather than translating it. Anything here rides in
|
|
6
|
+
* history state (ADR 0003), so going back to a screen restores the presentation it was opened
|
|
7
|
+
* with.
|
|
8
|
+
*/
|
|
9
|
+
/** How the screen is mounted on the stack. */
|
|
10
|
+
export type StackPresentation = 'push' | 'modal' | 'transparentModal' | 'fullScreenModal' | 'formSheet' | 'pageSheet' | 'containedModal' | 'containedTransparentModal';
|
|
11
|
+
/** The transition it animates in with. `default` is the platform's own. */
|
|
12
|
+
export type StackAnimation = 'default' | 'flip' | 'simple_push' | 'none' | 'fade' | 'slide_from_right' | 'slide_from_left' | 'slide_from_bottom' | 'fade_from_bottom' | 'ios_from_right' | 'ios_from_left';
|
|
13
|
+
/** Which way the dismiss gesture runs. Vertical is what a sheet wants. */
|
|
14
|
+
export type SwipeDirection = 'vertical' | 'horizontal';
|
|
15
|
+
/** Whether replacing a screen animates as a push or a pop. */
|
|
16
|
+
export type ReplaceAnimation = 'pop' | 'push';
|
|
17
|
+
/** How far from the edge a dismiss gesture is recognised, in points. */
|
|
18
|
+
export interface GestureResponseDistance {
|
|
19
|
+
readonly start?: number;
|
|
20
|
+
readonly end?: number;
|
|
21
|
+
readonly top?: number;
|
|
22
|
+
readonly bottom?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface ScreenPresentation {
|
|
25
|
+
readonly stackPresentation?: StackPresentation;
|
|
26
|
+
readonly stackAnimation?: StackAnimation;
|
|
27
|
+
/** Milliseconds. Only read when `stackAnimation` is not `default`. */
|
|
28
|
+
readonly transitionDuration?: number;
|
|
29
|
+
readonly replaceAnimation?: ReplaceAnimation;
|
|
30
|
+
/** Swipe back, or drag a sheet down. On by default. */
|
|
31
|
+
readonly gestureEnabled?: boolean;
|
|
32
|
+
/** iOS: allow the swipe to start anywhere on the screen, not just its edge. */
|
|
33
|
+
readonly fullScreenSwipeEnabled?: boolean;
|
|
34
|
+
readonly fullScreenSwipeShadowEnabled?: boolean;
|
|
35
|
+
readonly swipeDirection?: SwipeDirection;
|
|
36
|
+
readonly gestureResponseDistance?: GestureResponseDistance;
|
|
37
|
+
/** Run the screen's own animation while swiping rather than the platform's. */
|
|
38
|
+
readonly customAnimationOnSwipe?: boolean;
|
|
39
|
+
/** Dismiss the keyboard when the gesture starts. */
|
|
40
|
+
readonly hideKeyboardOnSwipe?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Refuse a native dismissal outright. The screen still reports the attempt, which is how a
|
|
43
|
+
* "discard your changes?" prompt is built.
|
|
44
|
+
*/
|
|
45
|
+
readonly preventNativeDismiss?: boolean;
|
|
46
|
+
/** Heights the sheet snaps to, as fractions of the screen. */
|
|
47
|
+
readonly sheetAllowedDetents?: number[];
|
|
48
|
+
/** Index of the largest detent that leaves the screen behind undimmed; -1 dims at every one. */
|
|
49
|
+
readonly sheetLargestUndimmedDetent?: number;
|
|
50
|
+
readonly sheetGrabberVisible?: boolean;
|
|
51
|
+
/** Corner radius in points; -1 is the system default. */
|
|
52
|
+
readonly sheetCornerRadius?: number;
|
|
53
|
+
readonly sheetExpandsWhenScrolledToEdge?: boolean;
|
|
54
|
+
/** Which detent it opens at. */
|
|
55
|
+
readonly sheetInitialDetent?: number;
|
|
56
|
+
/** Android elevation. */
|
|
57
|
+
readonly sheetElevation?: number;
|
|
58
|
+
readonly sheetShouldOverflowTopInset?: boolean;
|
|
59
|
+
readonly statusBarHidden?: boolean;
|
|
60
|
+
readonly statusBarStyle?: string;
|
|
61
|
+
readonly statusBarColor?: string | number;
|
|
62
|
+
readonly statusBarAnimation?: string;
|
|
63
|
+
readonly statusBarTranslucent?: boolean;
|
|
64
|
+
readonly navigationBarColor?: string | number;
|
|
65
|
+
readonly navigationBarHidden?: boolean;
|
|
66
|
+
readonly navigationBarTranslucent?: boolean;
|
|
67
|
+
readonly homeIndicatorHidden?: boolean;
|
|
68
|
+
readonly screenOrientation?: string;
|
|
69
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a screen arrives, which is the part of mobile navigation a URL cannot express.
|
|
3
|
+
*
|
|
4
|
+
* Every field is a prop on `RNSScreen`, spelled exactly as the codegen spec spells it, so the
|
|
5
|
+
* outlet forwards the object key by key rather than translating it. Anything here rides in
|
|
6
|
+
* history state (ADR 0003), so going back to a screen restores the presentation it was opened
|
|
7
|
+
* with.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=screen-presentation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screen-presentation.js","sourceRoot":"","sources":["../src/screen-presentation.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG","sourcesContent":["/**\n * How a screen arrives, which is the part of mobile navigation a URL cannot express.\n *\n * Every field is a prop on `RNSScreen`, spelled exactly as the codegen spec spells it, so the\n * outlet forwards the object key by key rather than translating it. Anything here rides in\n * history state (ADR 0003), so going back to a screen restores the presentation it was opened\n * with.\n */\n\n/** How the screen is mounted on the stack. */\nexport type StackPresentation =\n | 'push'\n | 'modal'\n | 'transparentModal'\n | 'fullScreenModal'\n | 'formSheet'\n | 'pageSheet'\n | 'containedModal'\n | 'containedTransparentModal';\n\n/** The transition it animates in with. `default` is the platform's own. */\nexport type StackAnimation =\n | 'default'\n | 'flip'\n | 'simple_push'\n | 'none'\n | 'fade'\n | 'slide_from_right'\n | 'slide_from_left'\n | 'slide_from_bottom'\n | 'fade_from_bottom'\n | 'ios_from_right'\n | 'ios_from_left';\n\n/** Which way the dismiss gesture runs. Vertical is what a sheet wants. */\nexport type SwipeDirection = 'vertical' | 'horizontal';\n\n/** Whether replacing a screen animates as a push or a pop. */\nexport type ReplaceAnimation = 'pop' | 'push';\n\n/** How far from the edge a dismiss gesture is recognised, in points. */\nexport interface GestureResponseDistance {\n readonly start?: number;\n readonly end?: number;\n readonly top?: number;\n readonly bottom?: number;\n}\n\nexport interface ScreenPresentation {\n readonly stackPresentation?: StackPresentation;\n readonly stackAnimation?: StackAnimation;\n /** Milliseconds. Only read when `stackAnimation` is not `default`. */\n readonly transitionDuration?: number;\n readonly replaceAnimation?: ReplaceAnimation;\n\n // --- the dismiss gesture -------------------------------------------------------------\n\n /** Swipe back, or drag a sheet down. On by default. */\n readonly gestureEnabled?: boolean;\n /** iOS: allow the swipe to start anywhere on the screen, not just its edge. */\n readonly fullScreenSwipeEnabled?: boolean;\n readonly fullScreenSwipeShadowEnabled?: boolean;\n readonly swipeDirection?: SwipeDirection;\n readonly gestureResponseDistance?: GestureResponseDistance;\n /** Run the screen's own animation while swiping rather than the platform's. */\n readonly customAnimationOnSwipe?: boolean;\n /** Dismiss the keyboard when the gesture starts. */\n readonly hideKeyboardOnSwipe?: boolean;\n /**\n * Refuse a native dismissal outright. The screen still reports the attempt, which is how a\n * \"discard your changes?\" prompt is built.\n */\n readonly preventNativeDismiss?: boolean;\n\n // --- sheets (formSheet and pageSheet) --------------------------------------------------\n\n /** Heights the sheet snaps to, as fractions of the screen. */\n readonly sheetAllowedDetents?: number[];\n /** Index of the largest detent that leaves the screen behind undimmed; -1 dims at every one. */\n readonly sheetLargestUndimmedDetent?: number;\n readonly sheetGrabberVisible?: boolean;\n /** Corner radius in points; -1 is the system default. */\n readonly sheetCornerRadius?: number;\n readonly sheetExpandsWhenScrolledToEdge?: boolean;\n /** Which detent it opens at. */\n readonly sheetInitialDetent?: number;\n /** Android elevation. */\n readonly sheetElevation?: number;\n readonly sheetShouldOverflowTopInset?: boolean;\n\n // --- system chrome ---------------------------------------------------------------------\n\n readonly statusBarHidden?: boolean;\n readonly statusBarStyle?: string;\n readonly statusBarColor?: string | number;\n readonly statusBarAnimation?: string;\n readonly statusBarTranslucent?: boolean;\n readonly navigationBarColor?: string | number;\n readonly navigationBarHidden?: boolean;\n readonly navigationBarTranslucent?: boolean;\n readonly homeIndicatorHidden?: boolean;\n readonly screenOrientation?: string;\n}\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Element name -> codegen'd Fabric component name, and the base style that component's React
|
|
3
|
+
* wrapper would have applied in JS. Element names are lowercase, because the compiler silently
|
|
4
|
+
* empties a template containing a capitalized one.
|
|
5
|
+
*
|
|
6
|
+
* The header's styles are `ScreenStackHeaderConfig.tsx`'s own, and the absolute positioning is
|
|
7
|
+
* load-bearing: without it the config view takes part in the screen's flex layout and every page
|
|
8
|
+
* renders a header's height too far down. `alignItems` is iOS-only there; it is harmless on
|
|
9
|
+
* Android, where the native side lays the bar out itself.
|
|
10
|
+
*/
|
|
11
|
+
export declare const SCREEN_VIEW_NAMES: Record<string, [viewName: string, defaults?: object]>;
|
|
12
|
+
/**
|
|
13
|
+
* How visible a screen is. The native stack keeps every screen mounted and reads this rather
|
|
14
|
+
* than adding and removing views, which is what preserves a pushed-away screen's native state.
|
|
15
|
+
*
|
|
16
|
+
* `Screen.tsx` throws if this ever *decreases* while in a native stack, so a screen goes
|
|
17
|
+
* 0 -> 2 on push and is removed outright on pop rather than walked back down.
|
|
18
|
+
*/
|
|
19
|
+
export declare const ActivityState: {
|
|
20
|
+
/** Off screen, and a candidate for freezing. */
|
|
21
|
+
readonly Inactive: 0;
|
|
22
|
+
/** Mid transition: on screen but not the target. */
|
|
23
|
+
readonly Transitioning: 1;
|
|
24
|
+
/** The screen the user is on. */
|
|
25
|
+
readonly Active: 2;
|
|
26
|
+
};
|
|
27
|
+
export type ActivityStateValue = (typeof ActivityState)[keyof typeof ActivityState];
|
|
28
|
+
/**
|
|
29
|
+
* Call once at startup, before the first navigation.
|
|
30
|
+
*
|
|
31
|
+
* The app must also have `react-native-screens` installed so the native side registers these
|
|
32
|
+
* components; this only teaches the engine the element-to-view-name mapping. A name the native
|
|
33
|
+
* side does not know renders as `UnimplementedNativeView`, not an error. None of its JavaScript
|
|
34
|
+
* is imported - Expo Go happens to bundle the native half, which is why the canary worked before
|
|
35
|
+
* it declared the dependency at all, and why an app that forgets it fails only once it ships.
|
|
36
|
+
*/
|
|
37
|
+
export declare function registerScreenComponents(): void;
|
package/dist/screens.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* react-native-screens, driven directly.
|
|
3
|
+
*
|
|
4
|
+
* The native half of navigation already exists as codegen'd Fabric components. We register their
|
|
5
|
+
* names and drive them with `createNode`; their React components are unreachable (hooks) and
|
|
6
|
+
* `react-navigation` is not a dependency.
|
|
7
|
+
*
|
|
8
|
+
* No decorators here on purpose: tests import these at runtime, and Node's type stripping
|
|
9
|
+
* refuses decorators.
|
|
10
|
+
*/
|
|
11
|
+
import { registerHoist, registerViewName } from '@ng-native/fabric';
|
|
12
|
+
/**
|
|
13
|
+
* Element name -> codegen'd Fabric component name, and the base style that component's React
|
|
14
|
+
* wrapper would have applied in JS. Element names are lowercase, because the compiler silently
|
|
15
|
+
* empties a template containing a capitalized one.
|
|
16
|
+
*
|
|
17
|
+
* The header's styles are `ScreenStackHeaderConfig.tsx`'s own, and the absolute positioning is
|
|
18
|
+
* load-bearing: without it the config view takes part in the screen's flex layout and every page
|
|
19
|
+
* renders a header's height too far down. `alignItems` is iOS-only there; it is harmless on
|
|
20
|
+
* Android, where the native side lays the bar out itself.
|
|
21
|
+
*/
|
|
22
|
+
export const SCREEN_VIEW_NAMES = {
|
|
23
|
+
// NativeStackOutlet's own host element is the stack.
|
|
24
|
+
'native-stack-outlet': ['RNSScreenStack'],
|
|
25
|
+
// Created by the outlet, one per activated route; nobody writes this in a template.
|
|
26
|
+
screen: ['RNSScreen'],
|
|
27
|
+
'native-header': [
|
|
28
|
+
'RNSScreenStackHeaderConfig',
|
|
29
|
+
{
|
|
30
|
+
position: 'absolute',
|
|
31
|
+
width: '100%',
|
|
32
|
+
flexDirection: 'row',
|
|
33
|
+
justifyContent: 'space-between',
|
|
34
|
+
alignItems: 'center',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
// Placed in a `<native-header-item type="searchBar">`, as react-native-screens places its own.
|
|
38
|
+
'native-search-bar': ['RNSSearchBar'],
|
|
39
|
+
'native-header-item': [
|
|
40
|
+
'RNSScreenStackHeaderSubview',
|
|
41
|
+
{ flexDirection: 'row', alignItems: 'center', justifyContent: 'center' },
|
|
42
|
+
],
|
|
43
|
+
// Insets by what the screen it is in says is covered, the tab bar included. See
|
|
44
|
+
// `tab-safe-area-view.ts`. The same name on both platforms.
|
|
45
|
+
'tab-safe-area-view': ['RNSSafeAreaView'],
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* The tabs half, which react-native-screens names per platform rather than sharing one component:
|
|
49
|
+
* `RNSTabsHostIOS` wraps a `UITabBarController`, `RNSTabsHostAndroid` a bottom navigation bar,
|
|
50
|
+
* and their props differ enough that the codegen specs are separate files.
|
|
51
|
+
*
|
|
52
|
+
* The fills are `TabsHost.ios.tsx`'s and `TabsScreen.ios.tsx`'s own. A tab screen is positioned
|
|
53
|
+
* absolutely because it is one of several occupying the same space, only one of which is visible.
|
|
54
|
+
*/
|
|
55
|
+
const TAB_VIEW_NAMES = {
|
|
56
|
+
'native-tabs-outlet': [
|
|
57
|
+
'RNSTabsHostIOS',
|
|
58
|
+
'RNSTabsHostAndroid',
|
|
59
|
+
{ flex: 1, width: '100%', height: '100%' },
|
|
60
|
+
],
|
|
61
|
+
'native-tab': [
|
|
62
|
+
'RNSTabsScreenIOS',
|
|
63
|
+
'RNSTabsScreenAndroid',
|
|
64
|
+
{ position: 'absolute', flex: 1, width: '100%', height: '100%' },
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* How visible a screen is. The native stack keeps every screen mounted and reads this rather
|
|
69
|
+
* than adding and removing views, which is what preserves a pushed-away screen's native state.
|
|
70
|
+
*
|
|
71
|
+
* `Screen.tsx` throws if this ever *decreases* while in a native stack, so a screen goes
|
|
72
|
+
* 0 -> 2 on push and is removed outright on pop rather than walked back down.
|
|
73
|
+
*/
|
|
74
|
+
export const ActivityState = {
|
|
75
|
+
/** Off screen, and a candidate for freezing. */
|
|
76
|
+
Inactive: 0,
|
|
77
|
+
/** Mid transition: on screen but not the target. */
|
|
78
|
+
Transitioning: 1,
|
|
79
|
+
/** The screen the user is on. */
|
|
80
|
+
Active: 2,
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Call once at startup, before the first navigation.
|
|
84
|
+
*
|
|
85
|
+
* The app must also have `react-native-screens` installed so the native side registers these
|
|
86
|
+
* components; this only teaches the engine the element-to-view-name mapping. A name the native
|
|
87
|
+
* side does not know renders as `UnimplementedNativeView`, not an error. None of its JavaScript
|
|
88
|
+
* is imported - Expo Go happens to bundle the native half, which is why the canary worked before
|
|
89
|
+
* it declared the dependency at all, and why an app that forgets it fails only once it ships.
|
|
90
|
+
*/
|
|
91
|
+
export function registerScreenComponents() {
|
|
92
|
+
for (const [element, [viewName, defaults]] of Object.entries(SCREEN_VIEW_NAMES)) {
|
|
93
|
+
registerViewName(element, viewName, defaults);
|
|
94
|
+
}
|
|
95
|
+
// By platform, chosen when the view is looked up: this runs as the app's config is imported,
|
|
96
|
+
// before its `main.ts` has reported the platform.
|
|
97
|
+
// A window of its own on iOS; see `full-window-overlay.ts`. Its fill is the component's.
|
|
98
|
+
registerViewName('full-window-overlay', { ios: 'RNSFullWindowOverlay', android: 'RCTView' });
|
|
99
|
+
for (const [element, [ios, android, defaults]] of Object.entries(TAB_VIEW_NAMES)) {
|
|
100
|
+
registerViewName(element, { ios, android }, defaults);
|
|
101
|
+
}
|
|
102
|
+
// `findHeaderConfig` in `RNSScreen.mm` looks at the screen's direct children and no deeper, so
|
|
103
|
+
// a header a page wrote inside a `safe-area-view` or a component of its own commits beside it.
|
|
104
|
+
// Nothing in `RNSScreen.mm` updates the bar when its config is unmounted, so a header an `@if`
|
|
105
|
+
// takes away leaves a config behind that hides the bar, and one that comes back adopts it.
|
|
106
|
+
registerHoist('native-header', 'screen', { standIn: { hidden: true } });
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=screens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screens.js","sourceRoot":"","sources":["../src/screens.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAEpE;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAA0D;IACtF,qDAAqD;IACrD,qBAAqB,EAAE,CAAC,gBAAgB,CAAC;IACzC,oFAAoF;IACpF,MAAM,EAAE,CAAC,WAAW,CAAC;IACrB,eAAe,EAAE;QACf,4BAA4B;QAC5B;YACE,QAAQ,EAAE,UAAU;YACpB,KAAK,EAAE,MAAM;YACb,aAAa,EAAE,KAAK;YACpB,cAAc,EAAE,eAAe;YAC/B,UAAU,EAAE,QAAQ;SACrB;KACF;IACD,+FAA+F;IAC/F,mBAAmB,EAAE,CAAC,cAAc,CAAC;IACrC,oBAAoB,EAAE;QACpB,6BAA6B;QAC7B,EAAE,aAAa,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,cAAc,EAAE,QAAQ,EAAE;KACzE;IACD,gFAAgF;IAChF,4DAA4D;IAC5D,oBAAoB,EAAE,CAAC,iBAAiB,CAAC;CAC1C,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,cAAc,GAAsE;IACxF,oBAAoB,EAAE;QACpB,gBAAgB;QAChB,oBAAoB;QACpB,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE;KAC3C;IACD,YAAY,EAAE;QACZ,kBAAkB;QAClB,sBAAsB;QACtB,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE;KACjE;CACF,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,gDAAgD;IAChD,QAAQ,EAAE,CAAC;IACX,oDAAoD;IACpD,aAAa,EAAE,CAAC;IAChB,iCAAiC;IACjC,MAAM,EAAE,CAAC;CACD,CAAC;AAIX;;;;;;;;GAQG;AACH,MAAM,UAAU,wBAAwB;IACtC,KAAK,MAAM,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAE,CAAC;QAChF,gBAAgB,CAAC,OAAO,EAAE,QAAQ,EAAE,QAA+C,CAAC,CAAC;IACvF,CAAC;IACD,6FAA6F;IAC7F,kDAAkD;IAClD,yFAAyF;IACzF,gBAAgB,CAAC,qBAAqB,EAAE,EAAE,GAAG,EAAE,sBAAsB,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;IAC7F,KAAK,MAAM,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,cAAc,CAAC,EAAE,CAAC;QACjF,gBAAgB,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,QAAmC,CAAC,CAAC;IACnF,CAAC;IACD,+FAA+F;IAC/F,+FAA+F;IAC/F,+FAA+F;IAC/F,2FAA2F;IAC3F,aAAa,CAAC,eAAe,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;AAC1E,CAAC","sourcesContent":["/**\n * react-native-screens, driven directly.\n *\n * The native half of navigation already exists as codegen'd Fabric components. We register their\n * names and drive them with `createNode`; their React components are unreachable (hooks) and\n * `react-navigation` is not a dependency.\n *\n * No decorators here on purpose: tests import these at runtime, and Node's type stripping\n * refuses decorators.\n */\nimport { registerHoist, registerViewName } from '@ng-native/fabric';\n\n/**\n * Element name -> codegen'd Fabric component name, and the base style that component's React\n * wrapper would have applied in JS. Element names are lowercase, because the compiler silently\n * empties a template containing a capitalized one.\n *\n * The header's styles are `ScreenStackHeaderConfig.tsx`'s own, and the absolute positioning is\n * load-bearing: without it the config view takes part in the screen's flex layout and every page\n * renders a header's height too far down. `alignItems` is iOS-only there; it is harmless on\n * Android, where the native side lays the bar out itself.\n */\nexport const SCREEN_VIEW_NAMES: Record<string, [viewName: string, defaults?: object]> = {\n // NativeStackOutlet's own host element is the stack.\n 'native-stack-outlet': ['RNSScreenStack'],\n // Created by the outlet, one per activated route; nobody writes this in a template.\n screen: ['RNSScreen'],\n 'native-header': [\n 'RNSScreenStackHeaderConfig',\n {\n position: 'absolute',\n width: '100%',\n flexDirection: 'row',\n justifyContent: 'space-between',\n alignItems: 'center',\n },\n ],\n // Placed in a `<native-header-item type=\"searchBar\">`, as react-native-screens places its own.\n 'native-search-bar': ['RNSSearchBar'],\n 'native-header-item': [\n 'RNSScreenStackHeaderSubview',\n { flexDirection: 'row', alignItems: 'center', justifyContent: 'center' },\n ],\n // Insets by what the screen it is in says is covered, the tab bar included. See\n // `tab-safe-area-view.ts`. The same name on both platforms.\n 'tab-safe-area-view': ['RNSSafeAreaView'],\n};\n\n/**\n * The tabs half, which react-native-screens names per platform rather than sharing one component:\n * `RNSTabsHostIOS` wraps a `UITabBarController`, `RNSTabsHostAndroid` a bottom navigation bar,\n * and their props differ enough that the codegen specs are separate files.\n *\n * The fills are `TabsHost.ios.tsx`'s and `TabsScreen.ios.tsx`'s own. A tab screen is positioned\n * absolutely because it is one of several occupying the same space, only one of which is visible.\n */\nconst TAB_VIEW_NAMES: Record<string, [ios: string, android: string, defaults?: object]> = {\n 'native-tabs-outlet': [\n 'RNSTabsHostIOS',\n 'RNSTabsHostAndroid',\n { flex: 1, width: '100%', height: '100%' },\n ],\n 'native-tab': [\n 'RNSTabsScreenIOS',\n 'RNSTabsScreenAndroid',\n { position: 'absolute', flex: 1, width: '100%', height: '100%' },\n ],\n};\n\n/**\n * How visible a screen is. The native stack keeps every screen mounted and reads this rather\n * than adding and removing views, which is what preserves a pushed-away screen's native state.\n *\n * `Screen.tsx` throws if this ever *decreases* while in a native stack, so a screen goes\n * 0 -> 2 on push and is removed outright on pop rather than walked back down.\n */\nexport const ActivityState = {\n /** Off screen, and a candidate for freezing. */\n Inactive: 0,\n /** Mid transition: on screen but not the target. */\n Transitioning: 1,\n /** The screen the user is on. */\n Active: 2,\n} as const;\n\nexport type ActivityStateValue = (typeof ActivityState)[keyof typeof ActivityState];\n\n/**\n * Call once at startup, before the first navigation.\n *\n * The app must also have `react-native-screens` installed so the native side registers these\n * components; this only teaches the engine the element-to-view-name mapping. A name the native\n * side does not know renders as `UnimplementedNativeView`, not an error. None of its JavaScript\n * is imported - Expo Go happens to bundle the native half, which is why the canary worked before\n * it declared the dependency at all, and why an app that forgets it fails only once it ships.\n */\nexport function registerScreenComponents(): void {\n for (const [element, [viewName, defaults]] of Object.entries(SCREEN_VIEW_NAMES)) {\n registerViewName(element, viewName, defaults as Record<string, unknown> | undefined);\n }\n // By platform, chosen when the view is looked up: this runs as the app's config is imported,\n // before its `main.ts` has reported the platform.\n // A window of its own on iOS; see `full-window-overlay.ts`. Its fill is the component's.\n registerViewName('full-window-overlay', { ios: 'RNSFullWindowOverlay', android: 'RCTView' });\n for (const [element, [ios, android, defaults]] of Object.entries(TAB_VIEW_NAMES)) {\n registerViewName(element, { ios, android }, defaults as Record<string, unknown>);\n }\n // `findHeaderConfig` in `RNSScreen.mm` looks at the screen's direct children and no deeper, so\n // a header a page wrote inside a `safe-area-view` or a component of its own commits beside it.\n // Nothing in `RNSScreen.mm` updates the bar when its config is unmounted, so a header an `@if`\n // takes away leaves a config behind that hides the bar, and one that comes back adopts it.\n registerHoist('native-header', 'screen', { standIn: { hidden: true } });\n}\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which route configs are tabs.
|
|
3
|
+
*
|
|
4
|
+
* The reuse strategy has to know, because a tab is detached whole rather than by its leaf, and it
|
|
5
|
+
* has nothing but an `ActivatedRouteSnapshot` to go on. Tabs are declared in a template now, so
|
|
6
|
+
* there is nothing on the route config to read: the outlet marks the config it matched each
|
|
7
|
+
* `<native-tab>` to, and the strategy asks here.
|
|
8
|
+
*
|
|
9
|
+
* A `WeakSet` rather than a flag written onto the config, so nothing an app can read changes, and
|
|
10
|
+
* so a lazily loaded config that goes away takes its entry with it.
|
|
11
|
+
*/
|
|
12
|
+
import type { Route } from '@angular/router';
|
|
13
|
+
export declare function markTabRoute(route: Route): void;
|
|
14
|
+
export declare function isTabRoute(route: Route | null | undefined): boolean;
|
|
15
|
+
export declare function markScreenRoute(route: Route): void;
|
|
16
|
+
export declare function isScreenRoute(route: Route | null | undefined): boolean;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const TAB_ROUTES = new WeakSet();
|
|
2
|
+
export function markTabRoute(route) {
|
|
3
|
+
TAB_ROUTES.add(route);
|
|
4
|
+
}
|
|
5
|
+
export function isTabRoute(route) {
|
|
6
|
+
return route != null && TAB_ROUTES.has(route);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Route configs a native stack has put on screen.
|
|
10
|
+
*
|
|
11
|
+
* A stack's screen is usually a leaf, but not always: an app's root stack whose first screen is
|
|
12
|
+
* the tab bar shows a route with children. When something is presented over it - a sheet opened
|
|
13
|
+
* from inside a tab - that route has to be kept like any other screen below, or the router
|
|
14
|
+
* destroys the whole tab bar and the sheet has nothing to sit on. The stack outlet marks what it
|
|
15
|
+
* activates, so the strategy can tell a screen with children from a parent that is only layout.
|
|
16
|
+
*/
|
|
17
|
+
const SCREEN_ROUTES = new WeakSet();
|
|
18
|
+
export function markScreenRoute(route) {
|
|
19
|
+
SCREEN_ROUTES.add(route);
|
|
20
|
+
}
|
|
21
|
+
export function isScreenRoute(route) {
|
|
22
|
+
return route != null && SCREEN_ROUTES.has(route);
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=tab-routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tab-routes.js","sourceRoot":"","sources":["../src/tab-routes.ts"],"names":[],"mappings":"AAaA,MAAM,UAAU,GAAG,IAAI,OAAO,EAAS,CAAC;AAExC,MAAM,UAAU,YAAY,CAAC,KAAY;IACvC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAA+B;IACxD,OAAO,KAAK,IAAI,IAAI,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAChD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,aAAa,GAAG,IAAI,OAAO,EAAS,CAAC;AAE3C,MAAM,UAAU,eAAe,CAAC,KAAY;IAC1C,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAA+B;IAC3D,OAAO,KAAK,IAAI,IAAI,IAAI,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AACnD,CAAC","sourcesContent":["/**\n * Which route configs are tabs.\n *\n * The reuse strategy has to know, because a tab is detached whole rather than by its leaf, and it\n * has nothing but an `ActivatedRouteSnapshot` to go on. Tabs are declared in a template now, so\n * there is nothing on the route config to read: the outlet marks the config it matched each\n * `<native-tab>` to, and the strategy asks here.\n *\n * A `WeakSet` rather than a flag written onto the config, so nothing an app can read changes, and\n * so a lazily loaded config that goes away takes its entry with it.\n */\nimport type { Route } from '@angular/router';\n\nconst TAB_ROUTES = new WeakSet<Route>();\n\nexport function markTabRoute(route: Route): void {\n TAB_ROUTES.add(route);\n}\n\nexport function isTabRoute(route: Route | null | undefined): boolean {\n return route != null && TAB_ROUTES.has(route);\n}\n\n/**\n * Route configs a native stack has put on screen.\n *\n * A stack's screen is usually a leaf, but not always: an app's root stack whose first screen is\n * the tab bar shows a route with children. When something is presented over it - a sheet opened\n * from inside a tab - that route has to be kept like any other screen below, or the router\n * destroys the whole tab bar and the sheet has nothing to sit on. The stack outlet marks what it\n * activates, so the strategy can tell a screen with children from a parent that is only layout.\n */\nconst SCREEN_ROUTES = new WeakSet<Route>();\n\nexport function markScreenRoute(route: Route): void {\n SCREEN_ROUTES.add(route);\n}\n\nexport function isScreenRoute(route: Route | null | undefined): boolean {\n return route != null && SCREEN_ROUTES.has(route);\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
/** The edges of the screen. */
|
|
3
|
+
export type TabSafeAreaEdge = 'top' | 'right' | 'bottom' | 'left';
|
|
4
|
+
export declare class TabSafeAreaView {
|
|
5
|
+
constructor();
|
|
6
|
+
/** Which edges to inset: usually `['bottom']`. Absent insets all four. */
|
|
7
|
+
readonly edges: import("@angular/core").InputSignal<readonly TabSafeAreaEdge[] | undefined>;
|
|
8
|
+
/**
|
|
9
|
+
* Native takes all four edges every time, as booleans; a partial record leaves the missing
|
|
10
|
+
* ones at whatever they were.
|
|
11
|
+
*/
|
|
12
|
+
protected readonly resolvedEdges: import("@angular/core").Signal<Record<TabSafeAreaEdge, boolean>>;
|
|
13
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<TabSafeAreaView, never>;
|
|
14
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<TabSafeAreaView, "tab-safe-area-view", never, { "edges": { "alias": "edges"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A view that keeps its content clear of the tab bar, as well as the home indicator and the
|
|
3
|
+
* system bars.
|
|
4
|
+
*
|
|
5
|
+
* ```html
|
|
6
|
+
* <view class="panel absolute bottom-0 left-0 right-0">
|
|
7
|
+
* <tab-safe-area-view [edges]="['bottom']">…</tab-safe-area-view>
|
|
8
|
+
* </view>
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* `<safe-area-view>` cannot do this. Its insets come from the nearest `<safe-area-provider>`,
|
|
12
|
+
* which sits at the root of the app, above the tab bar, and so measures only the window's own
|
|
13
|
+
* unsafe areas: inside a tab its bottom inset is the home indicator, and anything pinned to the
|
|
14
|
+
* bottom of a tab ends up under the bar. On iOS 26 the bar floats over the screen's content, and
|
|
15
|
+
* on Android the bottom navigation bar overlays it, so there is no layout that avoids it either.
|
|
16
|
+
*
|
|
17
|
+
* This is react-native-screens' own `RNSSafeAreaView`, which asks the screen it is in instead.
|
|
18
|
+
* On iOS that is the tab screen's `safeAreaInsets`, which UIKit extends by the tab bar - floating
|
|
19
|
+
* or not, and with the gap under a floating one - on top of the home indicator; a stack inside a
|
|
20
|
+
* tab passes the same insets down to its pages. On Android it is the larger of the bottom
|
|
21
|
+
* navigation bar's height and the system bars. Both follow the bar as it changes, rotates, or is
|
|
22
|
+
* hidden.
|
|
23
|
+
*
|
|
24
|
+
* **The insets are applied as margin, not padding.** Whatever is beneath the inset shows through
|
|
25
|
+
* it, so a panel whose background has to reach the bottom of the screen puts that background on
|
|
26
|
+
* a parent and this inside it, as above: the parent grows by the margin and paints behind the
|
|
27
|
+
* bar, and the content ends where the bar begins.
|
|
28
|
+
*
|
|
29
|
+
* It has to be inside a tab, or a page of a stack inside one. On iOS, with no screen above it, it
|
|
30
|
+
* insets by nothing.
|
|
31
|
+
*/
|
|
32
|
+
import { Component, ElementRef, computed, inject, input } from '@angular/core';
|
|
33
|
+
import { ownHost } from "./own-host.js";
|
|
34
|
+
import * as i0 from "@angular/core";
|
|
35
|
+
const ALL = ['top', 'right', 'bottom', 'left'];
|
|
36
|
+
export class TabSafeAreaView {
|
|
37
|
+
constructor() {
|
|
38
|
+
ownHost(inject(ElementRef).nativeElement, this.constructor);
|
|
39
|
+
}
|
|
40
|
+
/** Which edges to inset: usually `['bottom']`. Absent insets all four. */
|
|
41
|
+
edges = input(/* @ts-ignore */
|
|
42
|
+
...(ngDevMode ? [undefined, { debugName: "edges" }] : /* istanbul ignore next */ []));
|
|
43
|
+
/**
|
|
44
|
+
* Native takes all four edges every time, as booleans; a partial record leaves the missing
|
|
45
|
+
* ones at whatever they were.
|
|
46
|
+
*/
|
|
47
|
+
resolvedEdges = computed(() => {
|
|
48
|
+
const edges = this.edges() ?? ALL;
|
|
49
|
+
return Object.fromEntries(ALL.map((edge) => [edge, edges.includes(edge)]));
|
|
50
|
+
}, /* @ts-ignore */
|
|
51
|
+
...(ngDevMode ? [{ debugName: "resolvedEdges" }] : /* istanbul ignore next */ []));
|
|
52
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: TabSafeAreaView, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
53
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.2.0", type: TabSafeAreaView, isStandalone: true, selector: "tab-safe-area-view", inputs: { edges: { classPropertyName: "edges", publicName: "edges", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "edges": "resolvedEdges()" } }, ngImport: i0, template: '<ng-content />', isInline: true });
|
|
54
|
+
}
|
|
55
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: TabSafeAreaView, decorators: [{
|
|
56
|
+
type: Component,
|
|
57
|
+
args: [{
|
|
58
|
+
selector: 'tab-safe-area-view',
|
|
59
|
+
template: '<ng-content />',
|
|
60
|
+
host: { '[edges]': 'resolvedEdges()' },
|
|
61
|
+
}]
|
|
62
|
+
}], ctorParameters: () => [], propDecorators: { edges: [{ type: i0.Input, args: [{ isSignal: true, alias: "edges", required: false }] }] } });
|
|
63
|
+
//# sourceMappingURL=tab-safe-area-view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tab-safe-area-view.js","sourceRoot":"","sources":["../src/tab-safe-area-view.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAE/E,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;;AAKxC,MAAM,GAAG,GAA+B,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AAO3E,MAAM,OAAO,eAAe;IAC1B;QACE,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,aAA2B,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IAC5E,CAAC;IAED,0EAA0E;IACjE,KAAK,GAAG,KAAK;yFAA8B,CAAC;IAErD;;;OAGG;IACgB,aAAa,GAAG,QAAQ,CAAC,GAAG,EAAE;QAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,IAAI,GAAG,CAAC;QAClC,OAAO,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAGxE,CAAC;IACJ,CAAC;sFAAC,CAAC;uGAlBQ,eAAe;2FAAf,eAAe,wQAHhB,gBAAgB;;2FAGf,eAAe;kBAL3B,SAAS;mBAAC;oBACT,QAAQ,EAAE,oBAAoB;oBAC9B,QAAQ,EAAE,gBAAgB;oBAC1B,IAAI,EAAE,EAAE,SAAS,EAAE,iBAAiB,EAAE;iBACvC","sourcesContent":["/**\n * A view that keeps its content clear of the tab bar, as well as the home indicator and the\n * system bars.\n *\n * ```html\n * <view class=\"panel absolute bottom-0 left-0 right-0\">\n * <tab-safe-area-view [edges]=\"['bottom']\">…</tab-safe-area-view>\n * </view>\n * ```\n *\n * `<safe-area-view>` cannot do this. Its insets come from the nearest `<safe-area-provider>`,\n * which sits at the root of the app, above the tab bar, and so measures only the window's own\n * unsafe areas: inside a tab its bottom inset is the home indicator, and anything pinned to the\n * bottom of a tab ends up under the bar. On iOS 26 the bar floats over the screen's content, and\n * on Android the bottom navigation bar overlays it, so there is no layout that avoids it either.\n *\n * This is react-native-screens' own `RNSSafeAreaView`, which asks the screen it is in instead.\n * On iOS that is the tab screen's `safeAreaInsets`, which UIKit extends by the tab bar - floating\n * or not, and with the gap under a floating one - on top of the home indicator; a stack inside a\n * tab passes the same insets down to its pages. On Android it is the larger of the bottom\n * navigation bar's height and the system bars. Both follow the bar as it changes, rotates, or is\n * hidden.\n *\n * **The insets are applied as margin, not padding.** Whatever is beneath the inset shows through\n * it, so a panel whose background has to reach the bottom of the screen puts that background on\n * a parent and this inside it, as above: the parent grows by the margin and paints behind the\n * bar, and the content ends where the bar begins.\n *\n * It has to be inside a tab, or a page of a stack inside one. On iOS, with no screen above it, it\n * insets by nothing.\n */\nimport { Component, ElementRef, computed, inject, input } from '@angular/core';\nimport type { EngineNode } from '@ng-native/fabric';\nimport { ownHost } from './own-host.ts';\n\n/** The edges of the screen. */\nexport type TabSafeAreaEdge = 'top' | 'right' | 'bottom' | 'left';\n\nconst ALL: readonly TabSafeAreaEdge[] = ['top', 'right', 'bottom', 'left'];\n\n@Component({\n selector: 'tab-safe-area-view',\n template: '<ng-content />',\n host: { '[edges]': 'resolvedEdges()' },\n})\nexport class TabSafeAreaView {\n constructor() {\n ownHost(inject(ElementRef).nativeElement as EngineNode, this.constructor);\n }\n\n /** Which edges to inset: usually `['bottom']`. Absent insets all four. */\n readonly edges = input<readonly TabSafeAreaEdge[]>();\n\n /**\n * Native takes all four edges every time, as booleans; a partial record leaves the missing\n * ones at whatever they were.\n */\n protected readonly resolvedEdges = computed(() => {\n const edges = this.edges() ?? ALL;\n return Object.fromEntries(ALL.map((edge) => [edge, edges.includes(edge)])) as Record<\n TabSafeAreaEdge,\n boolean\n >;\n });\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input transforms that keep an unset prop unset, so a header that never mentions `largeTitle`
|
|
3
|
+
* sends nothing rather than `false`. The same pair exists in the components package; see
|
|
4
|
+
* `own-host.ts` for why it is copied rather than imported.
|
|
5
|
+
*/
|
|
6
|
+
import { booleanAttribute, numberAttribute } from '@angular/core';
|
|
7
|
+
export function optionalBoolean(value) {
|
|
8
|
+
return value === undefined ? undefined : booleanAttribute(value);
|
|
9
|
+
}
|
|
10
|
+
export function optionalNumber(value) {
|
|
11
|
+
return value === undefined ? undefined : numberAttribute(value);
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=transforms.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transforms.js","sourceRoot":"","sources":["../src/transforms.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAElE,MAAM,UAAU,eAAe,CAAC,KAAc;IAC5C,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;AAClE,CAAC","sourcesContent":["/**\n * Input transforms that keep an unset prop unset, so a header that never mentions `largeTitle`\n * sends nothing rather than `false`. The same pair exists in the components package; see\n * `own-host.ts` for why it is copied rather than imported.\n */\nimport { booleanAttribute, numberAttribute } from '@angular/core';\n\nexport function optionalBoolean(value: unknown): boolean | undefined {\n return value === undefined ? undefined : booleanAttribute(value);\n}\n\nexport function optionalNumber(value: unknown): number | undefined {\n return value === undefined ? undefined : numberAttribute(value);\n}\n"]}
|