@ng-native/router 0.2.0 → 0.4.0
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/dist/native-back.d.ts +32 -3
- package/dist/native-back.js +33 -4
- package/dist/native-back.js.map +1 -1
- package/dist/native-bar-defaults.d.ts +7 -0
- package/dist/native-bar-defaults.js.map +1 -1
- package/dist/native-header.d.ts +17 -1
- package/dist/native-header.js +30 -6
- package/dist/native-header.js.map +1 -1
- package/dist/native-navigation.d.ts +19 -0
- package/dist/native-navigation.js +81 -10
- package/dist/native-navigation.js.map +1 -1
- package/dist/native-stack-outlet.d.ts +59 -4
- package/dist/native-stack-outlet.js +232 -15
- package/dist/native-stack-outlet.js.map +1 -1
- package/dist/native-stack-reuse-strategy.d.ts +18 -6
- package/dist/native-stack-reuse-strategy.js +71 -19
- package/dist/native-stack-reuse-strategy.js.map +1 -1
- package/dist/native-tab.d.ts +36 -2
- package/dist/native-tab.js +38 -4
- package/dist/native-tab.js.map +1 -1
- package/dist/native-tabs-outlet.d.ts +14 -0
- package/dist/native-tabs-outlet.js +59 -6
- package/dist/native-tabs-outlet.js.map +1 -1
- package/dist/presented-route.d.ts +27 -0
- package/dist/presented-route.js +184 -0
- package/dist/presented-route.js.map +1 -0
- package/dist/provide-native-router.js +7 -3
- package/dist/provide-native-router.js.map +1 -1
- package/package.json +5 -4
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import { inject } from '@angular/core';
|
|
17
17
|
import { NavigationCancel, NavigationEnd, NavigationError, Router, } from '@angular/router';
|
|
18
18
|
import { NativeBack } from "./native-back.js";
|
|
19
|
+
import { leavingPresented, presentedCommands } from "./presented-route.js";
|
|
19
20
|
/**
|
|
20
21
|
* Where the intent rides. `NavigationExtras.state` is the app's own object, so ours sits under
|
|
21
22
|
* one reserved key rather than spread through it.
|
|
@@ -61,10 +62,16 @@ export class NativeNavigation {
|
|
|
61
62
|
*/
|
|
62
63
|
present(commands, options = {}) {
|
|
63
64
|
const { as = 'modal', presentation, ...extras } = options;
|
|
64
|
-
|
|
65
|
+
const intent = {
|
|
65
66
|
stack: 'push',
|
|
66
67
|
presentation: { stackPresentation: as, ...presentation },
|
|
67
|
-
}
|
|
68
|
+
};
|
|
69
|
+
// Asked at once, and only waited for when the page is another tab's: finding its route may
|
|
70
|
+
// load a lazy one.
|
|
71
|
+
const over = this.overTheTabInFront(commands, extras);
|
|
72
|
+
if (!over)
|
|
73
|
+
return this.go(commands, extras, intent);
|
|
74
|
+
return over.then((found) => this.go(found?.commands ?? commands, found?.extras ?? extras, intent));
|
|
68
75
|
}
|
|
69
76
|
/**
|
|
70
77
|
* Replace the whole stack with one screen, the way signing out or finishing onboarding does.
|
|
@@ -79,8 +86,29 @@ export class NativeNavigation {
|
|
|
79
86
|
* another tab is that tab. At the root of the app there is nowhere to go, and this does nothing.
|
|
80
87
|
*/
|
|
81
88
|
back() {
|
|
82
|
-
|
|
89
|
+
// Optional call: a test that stands a plain object in for the router has no such signal.
|
|
90
|
+
if (!this.router.currentNavigation?.()) {
|
|
91
|
+
this.outlets.back();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// Asked while a navigation is still putting its screen up: a page that leaves as it appears,
|
|
95
|
+
// from an effect in its constructor. No stack has that screen to pop yet, so the back waits
|
|
96
|
+
// for the navigation, and goes nowhere if the navigation does not arrive.
|
|
97
|
+
if (this.backWaiting)
|
|
98
|
+
return;
|
|
99
|
+
this.backWaiting = this.router.events.subscribe((event) => {
|
|
100
|
+
const ended = event instanceof NavigationEnd;
|
|
101
|
+
if (!ended && !(event instanceof NavigationCancel) && !(event instanceof NavigationError)) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
this.backWaiting?.unsubscribe();
|
|
105
|
+
this.backWaiting = null;
|
|
106
|
+
if (ended)
|
|
107
|
+
this.outlets.back();
|
|
108
|
+
});
|
|
83
109
|
}
|
|
110
|
+
/** The wait of a back asked for mid-navigation, so two asked for in one are one. */
|
|
111
|
+
backWaiting = null;
|
|
84
112
|
/**
|
|
85
113
|
* Pop straight back to the screen at `commands`, taking every screen above it off the stack at
|
|
86
114
|
* once - `popToViewController`. The screen is the one already there, with its state, not a new
|
|
@@ -105,18 +133,61 @@ export class NativeNavigation {
|
|
|
105
133
|
const full = presentation
|
|
106
134
|
? { ...intent, presentation: { ...intent.presentation, ...presentation } }
|
|
107
135
|
: intent;
|
|
108
|
-
const navigate = () =>
|
|
109
|
-
...
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
136
|
+
const navigate = () => {
|
|
137
|
+
const list = typeof commands === 'string' ? [pathOf(commands)] : [...commands];
|
|
138
|
+
const all = {
|
|
139
|
+
...extras,
|
|
140
|
+
...(typeof commands === 'string' ? this.queryOf(commands, extras) : {}),
|
|
141
|
+
state: { ...state, [NATIVE_INTENT]: full },
|
|
142
|
+
};
|
|
143
|
+
const leaving = leavingPresented(this.router, list, all);
|
|
144
|
+
return leaving ? this.router.navigateByUrl(leaving, all) : this.router.navigate(list, all);
|
|
145
|
+
};
|
|
113
146
|
// A screen stacked on the first one waits for it. Navigating while the router's first
|
|
114
147
|
// navigation is still running - a lazily loaded root waiting on its import - cancels that
|
|
115
148
|
// navigation, and the app opens on this screen with nothing beneath it. A reset replaces the
|
|
116
149
|
// stack anyway, so it goes at once.
|
|
150
|
+
const start = () => this.underTabRoot(commands, extras, full, navigate);
|
|
117
151
|
return intent.stack !== 'reset' && this.firstNavigationRunning()
|
|
118
|
-
? this.firstNavigation().then(
|
|
119
|
-
:
|
|
152
|
+
? this.firstNavigation().then(start)
|
|
153
|
+
: start();
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* A push into a tab nobody has opened goes to the tab first, so its stack has the tab's own
|
|
157
|
+
* first screen under the page, and a back from the page lands there: what a tab bar with a
|
|
158
|
+
* navigation controller per tab does. Without it the page is the stack's only screen, and the
|
|
159
|
+
* same push leaves a different stack behind depending on whether the tab was ever tapped. A
|
|
160
|
+
* presented screen goes where it is asked, and a replace or a reset has nothing to keep.
|
|
161
|
+
*/
|
|
162
|
+
underTabRoot(commands, extras, intent, navigate) {
|
|
163
|
+
if (intent.stack !== 'push' || intent.presentation)
|
|
164
|
+
return navigate();
|
|
165
|
+
const tab = this.outlets.unopenedTabOf(this.pathFor(commands, extras));
|
|
166
|
+
if (!tab)
|
|
167
|
+
return navigate();
|
|
168
|
+
return this.router.navigateByUrl(tab).then((arrived) => (arrived ? navigate() : false));
|
|
169
|
+
}
|
|
170
|
+
/** The path the commands lead to, without its query and fragment. */
|
|
171
|
+
pathFor(commands, extras) {
|
|
172
|
+
const url = typeof commands === 'string'
|
|
173
|
+
? commands
|
|
174
|
+
: this.router.serializeUrl(this.router.createUrlTree([...commands], extras));
|
|
175
|
+
return pathOf(url);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* A page of a tab that is not in front, presented over the one that is: its url would select
|
|
179
|
+
* its own tab and show it there, so it goes to the root's presented outlet instead, where the
|
|
180
|
+
* app's own stack shows it over everything. See `presented-route.ts`. Null for a page of the
|
|
181
|
+
* tab in front, or of no tab, which is presented where its url puts it.
|
|
182
|
+
*/
|
|
183
|
+
overTheTabInFront(commands, extras) {
|
|
184
|
+
if (!this.outlets.presents)
|
|
185
|
+
return null;
|
|
186
|
+
const path = this.pathFor(commands, extras);
|
|
187
|
+
if (!this.outlets.inTabBehind(path))
|
|
188
|
+
return null;
|
|
189
|
+
const query = typeof commands === 'string' ? this.queryOf(commands, extras) : {};
|
|
190
|
+
return presentedCommands(this.router, path).then((presented) => presented && { commands: presented, extras: { ...extras, ...query, relativeTo: null } });
|
|
120
191
|
}
|
|
121
192
|
/**
|
|
122
193
|
* The query and fragment written into a url string, as the extras `navigate` takes them: it
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-navigation.js","sourceRoot":"","sources":["../src/native-navigation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The typed facade over `Router` for the intents a URL cannot express (ADR 0003).\n *\n * Angular's `Router` stays the single source of truth: each of these is a line over\n * `router.navigate`, setting `replaceUrl` and a presentation on `NavigationExtras.state`. Deep\n * links, guards and resolvers keep working because it is still the Router, and because state\n * rides in history a back restores the presentation the screen was opened with.\n *\n * A navigation service that owns the stack was rejected: it either re-implements guards,\n * resolvers and deep linking, or it drifts from the URL, and once the stack is not derived from\n * router state the two can disagree.\n *\n * No decorators, so tests can import it at runtime and so it can be provided with `useClass`,\n * exactly as `NativePlatformLocation` and `NativeStackReuseStrategy` are.\n */\nimport { inject } from '@angular/core';\nimport {\n NavigationCancel,\n NavigationEnd,\n NavigationError,\n Router,\n type NavigationExtras,\n} from '@angular/router';\nimport { NativeBack } from './native-back.ts';\nimport type { ScreenPresentation, StackPresentation } from './screen-presentation.ts';\n\n/** What `Router` takes: a url string, or the array form with segments and params. */\nexport type NavigationCommands = string | readonly unknown[];\n\n/**\n * Where the intent rides. `NavigationExtras.state` is the app's own object, so ours sits under\n * one reserved key rather than spread through it.\n */\nexport const NATIVE_INTENT = 'ɵnativeStack';\n\n/** What a navigation asks the outlet to do with the stack, over and above rendering the route. */\nexport interface NativeIntent {\n /** `replace` drops the screen it supersedes; `reset` empties the stack first. */\n readonly stack?: 'push' | 'replace' | 'reset';\n readonly presentation?: ScreenPresentation;\n}\n\nexport interface NativeNavigationOptions extends NavigationExtras {\n /** How the screen arrives: presentation, animation, gesture and sheet props. */\n presentation?: ScreenPresentation;\n}\n\nexport interface PresentOptions extends NativeNavigationOptions {\n /** Shorthand for `presentation.stackPresentation`. Defaults to `modal`. */\n as?: StackPresentation;\n}\n\n/** A url string up to its query or fragment. */\nconst pathOf = (url: string): string => url.split(/[?#]/, 1)[0]!;\n\n/** Read the intent off a navigation's state, if it carries one. */\nexport function intentOf(state: unknown): NativeIntent | null {\n if (!state || typeof state !== 'object') return null;\n const intent = (state as Record<string, unknown>)[NATIVE_INTENT];\n return intent && typeof intent === 'object' ? (intent as NativeIntent) : null;\n}\n\nexport class NativeNavigation {\n private readonly router = inject(Router);\n private readonly outlets = inject(NativeBack);\n\n /**\n * Push a new screen onto the stack. The plain case, and what `nativeRouterLink` does.\n *\n * From a presented screen the new one is presented the same way, over it, since a pushed\n * screen would slide in under it; `presentation` says otherwise, `stackAnimation` and all.\n */\n push(commands: NavigationCommands, options: NativeNavigationOptions = {}): Promise<boolean> {\n return this.go(commands, options, { stack: 'push' });\n }\n\n /**\n * Swap the current screen for another. The history entry is replaced too, so a back from the\n * new screen goes where a back from the old one would have.\n */\n replace(commands: NavigationCommands, options: NavigationExtras = {}): Promise<boolean> {\n return this.go(commands, { ...options, replaceUrl: true }, { stack: 'replace' });\n }\n\n /**\n * Present a screen over the stack rather than pushing into it: a modal, or a sheet.\n *\n * `present('/filters', { as: 'formSheet', presentation: { sheetAllowedDetents: [0.5, 1] } })`.\n *\n * A presented screen is put on screen outside the stack's navigation controller, so it has no\n * native header and no automatic safe-area insets: it needs its own way out, and its own\n * `safe-area-view`. A sheet can also be dragged away, but `fullScreenModal` cannot, and with\n * `gestureEnabled: false` neither can.\n */\n present(commands: NavigationCommands, options: PresentOptions = {}): Promise<boolean> {\n const { as = 'modal', presentation, ...extras } = options;\n return this.go(commands, extras, {\n stack: 'push',\n presentation: { stackPresentation: as, ...presentation },\n });\n }\n\n /**\n * Replace the whole stack with one screen, the way signing out or finishing onboarding does.\n * Every screen below is destroyed, so there is nothing to go back to.\n */\n reset(commands: NavigationCommands, options: NavigationExtras = {}): Promise<boolean> {\n return this.go(commands, { ...options, replaceUrl: true }, { stack: 'reset' });\n }\n\n /**\n * Go back exactly as the Android back button does: pop the stack in front, or at the root of a\n * tab's stack, go to the first tab. Not one entry back through history, which after a trip to\n * another tab is that tab. At the root of the app there is nowhere to go, and this does nothing.\n */\n back(): void {\n this.outlets.back();\n }\n\n /**\n * Pop straight back to the screen at `commands`, taking every screen above it off the stack at\n * once - `popToViewController`. The screen is the one already there, with its state, not a new\n * one. Resolves false, doing nothing, when no screen below the top of a stack in front is\n * showing that url; a push is the way to a screen that is not there.\n */\n popTo(commands: NavigationCommands): Promise<boolean> {\n const tree =\n typeof commands === 'string'\n ? this.router.parseUrl(commands)\n : this.router.createUrlTree([...commands]);\n return this.outlets.popTo(this.router.serializeUrl(tree));\n }\n\n /**\n * Pop the stack in front to its first screen - `popToRootViewController`. Inside a tab, that is\n * the tab's first screen; the screens below keep their state. Resolves false at the root already.\n */\n popToRoot(): Promise<boolean> {\n return this.outlets.popToRoot();\n }\n\n private go(\n commands: NavigationCommands,\n options: NativeNavigationOptions,\n intent: NativeIntent,\n ): Promise<boolean> {\n const { presentation, state, ...extras } = options;\n const full: NativeIntent = presentation\n ? { ...intent, presentation: { ...intent.presentation, ...presentation } }\n : intent;\n\n const navigate = () =>\n this.router.navigate(typeof commands === 'string' ? [pathOf(commands)] : [...commands], {\n ...extras,\n ...(typeof commands === 'string' ? this.queryOf(commands, extras) : {}),\n state: { ...(state as object), [NATIVE_INTENT]: full },\n });\n // A screen stacked on the first one waits for it. Navigating while the router's first\n // navigation is still running - a lazily loaded root waiting on its import - cancels that\n // navigation, and the app opens on this screen with nothing beneath it. A reset replaces the\n // stack anyway, so it goes at once.\n return intent.stack !== 'reset' && this.firstNavigationRunning()\n ? this.firstNavigation().then(navigate)\n : navigate();\n }\n\n /**\n * The query and fragment written into a url string, as the extras `navigate` takes them: it\n * reads a string command as one path segment, and would encode a `?` into it. Extras given\n * alongside win over the ones in the string.\n */\n private queryOf(url: string, extras: NavigationExtras): NavigationExtras {\n const cut = url.search(/[?#]/);\n if (cut < 0) return {};\n const { queryParams, fragment } = this.router.parseUrl(url.slice(cut));\n return {\n queryParams: { ...queryParams, ...extras.queryParams },\n fragment: extras.fragment ?? fragment ?? undefined,\n };\n }\n\n private firstNavigationRunning(): boolean {\n const router = this.router as Partial<Router>;\n return router.navigated === false && !!router.currentNavigation?.();\n }\n\n private firstNavigation(): Promise<void> {\n return new Promise((resolve) => {\n const events = this.router.events.subscribe((event) => {\n if (\n event instanceof NavigationEnd ||\n event instanceof NavigationCancel ||\n event instanceof NavigationError\n ) {\n events.unsubscribe();\n resolve();\n }\n });\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"native-navigation.js","sourceRoot":"","sources":["../src/native-navigation.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The typed facade over `Router` for the intents a URL cannot express (ADR 0003).\n *\n * Angular's `Router` stays the single source of truth: each of these is a line over\n * `router.navigate`, setting `replaceUrl` and a presentation on `NavigationExtras.state`. Deep\n * links, guards and resolvers keep working because it is still the Router, and because state\n * rides in history a back restores the presentation the screen was opened with.\n *\n * A navigation service that owns the stack was rejected: it either re-implements guards,\n * resolvers and deep linking, or it drifts from the URL, and once the stack is not derived from\n * router state the two can disagree.\n *\n * No decorators, so tests can import it at runtime and so it can be provided with `useClass`,\n * exactly as `NativePlatformLocation` and `NativeStackReuseStrategy` are.\n */\nimport { inject } from '@angular/core';\nimport {\n NavigationCancel,\n NavigationEnd,\n NavigationError,\n Router,\n type NavigationExtras,\n} from '@angular/router';\nimport { NativeBack } from './native-back.ts';\nimport type { ScreenPresentation, StackPresentation } from './screen-presentation.ts';\nimport { leavingPresented, presentedCommands } from './presented-route.ts';\n\n/** What `Router` takes: a url string, or the array form with segments and params. */\nexport type NavigationCommands = string | readonly unknown[];\n\n/**\n * Where the intent rides. `NavigationExtras.state` is the app's own object, so ours sits under\n * one reserved key rather than spread through it.\n */\nexport const NATIVE_INTENT = 'ɵnativeStack';\n\n/** What a navigation asks the outlet to do with the stack, over and above rendering the route. */\nexport interface NativeIntent {\n /** `replace` drops the screen it supersedes; `reset` empties the stack first. */\n readonly stack?: 'push' | 'replace' | 'reset';\n readonly presentation?: ScreenPresentation;\n}\n\nexport interface NativeNavigationOptions extends NavigationExtras {\n /** How the screen arrives: presentation, animation, gesture and sheet props. */\n presentation?: ScreenPresentation;\n}\n\nexport interface PresentOptions extends NativeNavigationOptions {\n /** Shorthand for `presentation.stackPresentation`. Defaults to `modal`. */\n as?: StackPresentation;\n}\n\n/** A url string up to its query or fragment. */\nconst pathOf = (url: string): string => url.split(/[?#]/, 1)[0]!;\n\n/** Read the intent off a navigation's state, if it carries one. */\nexport function intentOf(state: unknown): NativeIntent | null {\n if (!state || typeof state !== 'object') return null;\n const intent = (state as Record<string, unknown>)[NATIVE_INTENT];\n return intent && typeof intent === 'object' ? (intent as NativeIntent) : null;\n}\n\nexport class NativeNavigation {\n private readonly router = inject(Router);\n private readonly outlets = inject(NativeBack);\n\n /**\n * Push a new screen onto the stack. The plain case, and what `nativeRouterLink` does.\n *\n * From a presented screen the new one is presented the same way, over it, since a pushed\n * screen would slide in under it; `presentation` says otherwise, `stackAnimation` and all.\n */\n push(commands: NavigationCommands, options: NativeNavigationOptions = {}): Promise<boolean> {\n return this.go(commands, options, { stack: 'push' });\n }\n\n /**\n * Swap the current screen for another. The history entry is replaced too, so a back from the\n * new screen goes where a back from the old one would have.\n */\n replace(commands: NavigationCommands, options: NavigationExtras = {}): Promise<boolean> {\n return this.go(commands, { ...options, replaceUrl: true }, { stack: 'replace' });\n }\n\n /**\n * Present a screen over the stack rather than pushing into it: a modal, or a sheet.\n *\n * `present('/filters', { as: 'formSheet', presentation: { sheetAllowedDetents: [0.5, 1] } })`.\n *\n * A presented screen is put on screen outside the stack's navigation controller, so it has no\n * native header and no automatic safe-area insets: it needs its own way out, and its own\n * `safe-area-view`. A sheet can also be dragged away, but `fullScreenModal` cannot, and with\n * `gestureEnabled: false` neither can.\n */\n present(commands: NavigationCommands, options: PresentOptions = {}): Promise<boolean> {\n const { as = 'modal', presentation, ...extras } = options;\n const intent: NativeIntent = {\n stack: 'push',\n presentation: { stackPresentation: as, ...presentation },\n };\n // Asked at once, and only waited for when the page is another tab's: finding its route may\n // load a lazy one.\n const over = this.overTheTabInFront(commands, extras);\n if (!over) return this.go(commands, extras, intent);\n return over.then((found) =>\n this.go(found?.commands ?? commands, found?.extras ?? extras, intent),\n );\n }\n\n /**\n * Replace the whole stack with one screen, the way signing out or finishing onboarding does.\n * Every screen below is destroyed, so there is nothing to go back to.\n */\n reset(commands: NavigationCommands, options: NavigationExtras = {}): Promise<boolean> {\n return this.go(commands, { ...options, replaceUrl: true }, { stack: 'reset' });\n }\n\n /**\n * Go back exactly as the Android back button does: pop the stack in front, or at the root of a\n * tab's stack, go to the first tab. Not one entry back through history, which after a trip to\n * another tab is that tab. At the root of the app there is nowhere to go, and this does nothing.\n */\n back(): void {\n // Optional call: a test that stands a plain object in for the router has no such signal.\n if (!this.router.currentNavigation?.()) {\n this.outlets.back();\n return;\n }\n // Asked while a navigation is still putting its screen up: a page that leaves as it appears,\n // from an effect in its constructor. No stack has that screen to pop yet, so the back waits\n // for the navigation, and goes nowhere if the navigation does not arrive.\n if (this.backWaiting) return;\n this.backWaiting = this.router.events.subscribe((event) => {\n const ended = event instanceof NavigationEnd;\n if (!ended && !(event instanceof NavigationCancel) && !(event instanceof NavigationError)) {\n return;\n }\n this.backWaiting?.unsubscribe();\n this.backWaiting = null;\n if (ended) this.outlets.back();\n });\n }\n\n /** The wait of a back asked for mid-navigation, so two asked for in one are one. */\n private backWaiting: { unsubscribe(): void } | null = null;\n\n /**\n * Pop straight back to the screen at `commands`, taking every screen above it off the stack at\n * once - `popToViewController`. The screen is the one already there, with its state, not a new\n * one. Resolves false, doing nothing, when no screen below the top of a stack in front is\n * showing that url; a push is the way to a screen that is not there.\n */\n popTo(commands: NavigationCommands): Promise<boolean> {\n const tree =\n typeof commands === 'string'\n ? this.router.parseUrl(commands)\n : this.router.createUrlTree([...commands]);\n return this.outlets.popTo(this.router.serializeUrl(tree));\n }\n\n /**\n * Pop the stack in front to its first screen - `popToRootViewController`. Inside a tab, that is\n * the tab's first screen; the screens below keep their state. Resolves false at the root already.\n */\n popToRoot(): Promise<boolean> {\n return this.outlets.popToRoot();\n }\n\n private go(\n commands: NavigationCommands,\n options: NativeNavigationOptions,\n intent: NativeIntent,\n ): Promise<boolean> {\n const { presentation, state, ...extras } = options;\n const full: NativeIntent = presentation\n ? { ...intent, presentation: { ...intent.presentation, ...presentation } }\n : intent;\n\n const navigate = () => {\n const list = typeof commands === 'string' ? [pathOf(commands)] : [...commands];\n const all = {\n ...extras,\n ...(typeof commands === 'string' ? this.queryOf(commands, extras) : {}),\n state: { ...(state as object), [NATIVE_INTENT]: full },\n };\n const leaving = leavingPresented(this.router, list, all);\n return leaving ? this.router.navigateByUrl(leaving, all) : this.router.navigate(list, all);\n };\n // A screen stacked on the first one waits for it. Navigating while the router's first\n // navigation is still running - a lazily loaded root waiting on its import - cancels that\n // navigation, and the app opens on this screen with nothing beneath it. A reset replaces the\n // stack anyway, so it goes at once.\n const start = () => this.underTabRoot(commands, extras, full, navigate);\n return intent.stack !== 'reset' && this.firstNavigationRunning()\n ? this.firstNavigation().then(start)\n : start();\n }\n\n /**\n * A push into a tab nobody has opened goes to the tab first, so its stack has the tab's own\n * first screen under the page, and a back from the page lands there: what a tab bar with a\n * navigation controller per tab does. Without it the page is the stack's only screen, and the\n * same push leaves a different stack behind depending on whether the tab was ever tapped. A\n * presented screen goes where it is asked, and a replace or a reset has nothing to keep.\n */\n private underTabRoot(\n commands: NavigationCommands,\n extras: NavigationExtras,\n intent: NativeIntent,\n navigate: () => Promise<boolean>,\n ): Promise<boolean> {\n if (intent.stack !== 'push' || intent.presentation) return navigate();\n const tab = this.outlets.unopenedTabOf(this.pathFor(commands, extras));\n if (!tab) return navigate();\n return this.router.navigateByUrl(tab).then((arrived) => (arrived ? navigate() : false));\n }\n\n /** The path the commands lead to, without its query and fragment. */\n private pathFor(commands: NavigationCommands, extras: NavigationExtras): string {\n const url =\n typeof commands === 'string'\n ? commands\n : this.router.serializeUrl(this.router.createUrlTree([...commands], extras));\n return pathOf(url);\n }\n\n /**\n * A page of a tab that is not in front, presented over the one that is: its url would select\n * its own tab and show it there, so it goes to the root's presented outlet instead, where the\n * app's own stack shows it over everything. See `presented-route.ts`. Null for a page of the\n * tab in front, or of no tab, which is presented where its url puts it.\n */\n private overTheTabInFront(\n commands: NavigationCommands,\n extras: NavigationExtras,\n ): Promise<{ commands: unknown[]; extras: NavigationExtras } | null> | null {\n if (!this.outlets.presents) return null;\n const path = this.pathFor(commands, extras);\n if (!this.outlets.inTabBehind(path)) return null;\n const query = typeof commands === 'string' ? this.queryOf(commands, extras) : {};\n return presentedCommands(this.router, path).then(\n (presented) =>\n presented && { commands: presented, extras: { ...extras, ...query, relativeTo: null } },\n );\n }\n\n /**\n * The query and fragment written into a url string, as the extras `navigate` takes them: it\n * reads a string command as one path segment, and would encode a `?` into it. Extras given\n * alongside win over the ones in the string.\n */\n private queryOf(url: string, extras: NavigationExtras): NavigationExtras {\n const cut = url.search(/[?#]/);\n if (cut < 0) return {};\n const { queryParams, fragment } = this.router.parseUrl(url.slice(cut));\n return {\n queryParams: { ...queryParams, ...extras.queryParams },\n fragment: extras.fragment ?? fragment ?? undefined,\n };\n }\n\n private firstNavigationRunning(): boolean {\n const router = this.router as Partial<Router>;\n return router.navigated === false && !!router.currentNavigation?.();\n }\n\n private firstNavigation(): Promise<void> {\n return new Promise((resolve) => {\n const events = this.router.events.subscribe((event) => {\n if (\n event instanceof NavigationEnd ||\n event instanceof NavigationCancel ||\n event instanceof NavigationError\n ) {\n events.unsubscribe();\n resolve();\n }\n });\n });\n }\n}\n"]}
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
* of its children rather than from a per-screen flag. Toggling that flag is for `ScreenContainer`
|
|
17
17
|
* (tabs), where every screen is mounted at once.
|
|
18
18
|
*/
|
|
19
|
-
import { type ComponentRef, type EnvironmentInjector } from '@angular/core';
|
|
19
|
+
import { type ComponentRef, type EnvironmentInjector, type OnInit } from '@angular/core';
|
|
20
20
|
import { ActivatedRoute, type Data, type RouterOutletContract } from '@angular/router';
|
|
21
21
|
import * as i0 from "@angular/core";
|
|
22
|
-
export declare class NativeStackOutlet implements RouterOutletContract {
|
|
22
|
+
export declare class NativeStackOutlet implements RouterOutletContract, OnInit {
|
|
23
23
|
/**
|
|
24
24
|
* Named outlets work exactly as on the web: `<native-stack-outlet name="list" />` is filled by
|
|
25
25
|
* routes with `outlet: 'list'`. The default is the primary one. Read once, as the router's own
|
|
@@ -39,7 +39,13 @@ export declare class NativeStackOutlet implements RouterOutletContract {
|
|
|
39
39
|
* `RouterOutletContract` directly, which is how its own tests drive it.
|
|
40
40
|
*/
|
|
41
41
|
private readonly router;
|
|
42
|
+
/** For a refused back to reach the screen as native's own report would. */
|
|
43
|
+
private readonly engine;
|
|
42
44
|
private readonly errors;
|
|
45
|
+
/** The screen this stack sits in, when it is a stack inside a page of another. */
|
|
46
|
+
private readonly holder;
|
|
47
|
+
/** What the holder's own `preventNativeDismiss` was, while this stack's top screen refuses. */
|
|
48
|
+
private held;
|
|
43
49
|
private readonly entries;
|
|
44
50
|
/**
|
|
45
51
|
* The screens a replace or a reset supersedes, kept until the navigation that supersedes them
|
|
@@ -50,8 +56,33 @@ export declare class NativeStackOutlet implements RouterOutletContract {
|
|
|
50
56
|
flex: number;
|
|
51
57
|
};
|
|
52
58
|
constructor();
|
|
59
|
+
/** The page presented over everything, while there is one. See `presented-route.ts`. */
|
|
60
|
+
private presentedPage;
|
|
61
|
+
/**
|
|
62
|
+
* The app's own stack, the one in the root component, is also the root's presented outlet: a
|
|
63
|
+
* page of a tab that is not in front is shown here, as a screen over the bar and everything in
|
|
64
|
+
* it. One page at a time, which the router sees to, and never kept: it is dismissed.
|
|
65
|
+
*/
|
|
66
|
+
private takePresentedPages;
|
|
67
|
+
private presentPage;
|
|
68
|
+
/** The router left the presented page: take it off the stack. */
|
|
69
|
+
private dropPresentedPage;
|
|
70
|
+
/**
|
|
71
|
+
* Take the router off the presented page, for a back or for a sheet the user swiped away: one
|
|
72
|
+
* entry back where that is the url without the page, and otherwise a navigation to that url in
|
|
73
|
+
* place of this entry, since something under the page was navigated while it was up.
|
|
74
|
+
*/
|
|
75
|
+
private dismissPresentedPage;
|
|
53
76
|
/** The screens currently in the stack, oldest first. Exposed for tests. */
|
|
54
77
|
get depth(): number;
|
|
78
|
+
ngOnInit(): void;
|
|
79
|
+
/**
|
|
80
|
+
* The route the router is already on, for an outlet created after the navigation that reached
|
|
81
|
+
* it: one held behind a condition until a session or a database is ready. The router activates
|
|
82
|
+
* an outlet as a navigation ends, and that one ended with no outlet to activate, so the outlet
|
|
83
|
+
* takes the route from its context as it arrives, as Angular's own `RouterOutlet` does.
|
|
84
|
+
*/
|
|
85
|
+
private activateCurrentRoute;
|
|
55
86
|
get isActivated(): boolean;
|
|
56
87
|
get component(): Object | null;
|
|
57
88
|
get activatedRoute(): ActivatedRoute | null;
|
|
@@ -69,6 +100,12 @@ export declare class NativeStackOutlet implements RouterOutletContract {
|
|
|
69
100
|
* app's `ErrorHandler` before the router fails the navigation with it.
|
|
70
101
|
*/
|
|
71
102
|
private createPage;
|
|
103
|
+
/**
|
|
104
|
+
* Take the screen in front off the stack, when there is one: nothing once the router has
|
|
105
|
+
* detached it, as Angular's own outlet does nothing then. A route with no component of its own,
|
|
106
|
+
* a `loadChildren` wrapper or a group, has the router detach the page under it and then
|
|
107
|
+
* deactivate this same outlet, and that page is a screen pushed from, still to come back to.
|
|
108
|
+
*/
|
|
72
109
|
deactivate(): void;
|
|
73
110
|
/**
|
|
74
111
|
* Hand the live `ComponentRef` back to the router without destroying it. The screen stays
|
|
@@ -158,6 +195,24 @@ export declare class NativeStackOutlet implements RouterOutletContract {
|
|
|
158
195
|
* a screen pushed over the tab bar, is still subscribed, and the newest one would otherwise
|
|
159
196
|
* answer for the stack in front.
|
|
160
197
|
*/
|
|
198
|
+
/**
|
|
199
|
+
* Android's Back button is the platform's own dismissal there, and react-native-screens leaves
|
|
200
|
+
* it to JS: its Android screen ignores `preventNativeDismiss`. So a top screen that sets it
|
|
201
|
+
* refuses the press here, as iOS refuses a swipe, and hears `nativeDismissCancelled` the same
|
|
202
|
+
* way, through the engine, as if native had sent it.
|
|
203
|
+
*/
|
|
204
|
+
private refusesButton;
|
|
205
|
+
/**
|
|
206
|
+
* Passes the top screen's refusal to the screen this stack sits in, and that screen's report of
|
|
207
|
+
* an attempt back to the top screen: the swipe that dismisses a presented stack is on the
|
|
208
|
+
* screen holding it, which the page inside cannot bind.
|
|
209
|
+
*
|
|
210
|
+
* Read after every render, since the refusal is a host binding of whatever page is on top.
|
|
211
|
+
*/
|
|
212
|
+
private holdRefusals;
|
|
213
|
+
private holdRefusal;
|
|
214
|
+
/** The holder's own answer again, once the top screen no longer refuses. */
|
|
215
|
+
private release;
|
|
161
216
|
private goBack;
|
|
162
217
|
/** Whether the top screen is part of what the router shows now. Assumed without a router. */
|
|
163
218
|
private isShowing;
|
|
@@ -185,8 +240,8 @@ export declare class NativeStackOutlet implements RouterOutletContract {
|
|
|
185
240
|
private historyUrl;
|
|
186
241
|
private removeScreen;
|
|
187
242
|
/**
|
|
188
|
-
* What a page is created with: its route, the outlet contexts its own outlets
|
|
189
|
-
* whether its screen is in front - which is this screen's own say and, for a stack inside a
|
|
243
|
+
* What a page is created with: its route, its screen, the outlet contexts its own outlets
|
|
244
|
+
* register in, and whether its screen is in front - which is this screen's own say and, for a stack inside a
|
|
190
245
|
* screen of another, that screen's too.
|
|
191
246
|
*/
|
|
192
247
|
private outletInjector;
|