@ng-native/router 0.3.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 +24 -0
- package/dist/native-back.js +29 -0
- 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 +50 -4
- package/dist/native-stack-outlet.js +200 -12
- 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-tabs-outlet.d.ts +13 -0
- package/dist/native-tabs-outlet.js +52 -3
- 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/package.json +3 -3
|
@@ -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
|
|
@@ -42,6 +42,10 @@ export declare class NativeStackOutlet implements RouterOutletContract {
|
|
|
42
42
|
/** For a refused back to reach the screen as native's own report would. */
|
|
43
43
|
private readonly engine;
|
|
44
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;
|
|
45
49
|
private readonly entries;
|
|
46
50
|
/**
|
|
47
51
|
* The screens a replace or a reset supersedes, kept until the navigation that supersedes them
|
|
@@ -52,8 +56,33 @@ export declare class NativeStackOutlet implements RouterOutletContract {
|
|
|
52
56
|
flex: number;
|
|
53
57
|
};
|
|
54
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;
|
|
55
76
|
/** The screens currently in the stack, oldest first. Exposed for tests. */
|
|
56
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;
|
|
57
86
|
get isActivated(): boolean;
|
|
58
87
|
get component(): Object | null;
|
|
59
88
|
get activatedRoute(): ActivatedRoute | null;
|
|
@@ -71,6 +100,12 @@ export declare class NativeStackOutlet implements RouterOutletContract {
|
|
|
71
100
|
* app's `ErrorHandler` before the router fails the navigation with it.
|
|
72
101
|
*/
|
|
73
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
|
+
*/
|
|
74
109
|
deactivate(): void;
|
|
75
110
|
/**
|
|
76
111
|
* Hand the live `ComponentRef` back to the router without destroying it. The screen stays
|
|
@@ -167,6 +202,17 @@ export declare class NativeStackOutlet implements RouterOutletContract {
|
|
|
167
202
|
* way, through the engine, as if native had sent it.
|
|
168
203
|
*/
|
|
169
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;
|
|
170
216
|
private goBack;
|
|
171
217
|
/** Whether the top screen is part of what the router shows now. Assumed without a router. */
|
|
172
218
|
private isShowing;
|
|
@@ -194,8 +240,8 @@ export declare class NativeStackOutlet implements RouterOutletContract {
|
|
|
194
240
|
private historyUrl;
|
|
195
241
|
private removeScreen;
|
|
196
242
|
/**
|
|
197
|
-
* What a page is created with: its route, the outlet contexts its own outlets
|
|
198
|
-
* 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
|
|
199
245
|
* screen of another, that screen's too.
|
|
200
246
|
*/
|
|
201
247
|
private outletInjector;
|
|
@@ -16,7 +16,7 @@
|
|
|
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 { ApplicationRef, Component, DestroyRef, ElementRef, ErrorHandler, HostAttributeToken, Injector, Renderer2, computed, createComponent, inject, signal, } from '@angular/core';
|
|
19
|
+
import { ApplicationRef, Component, DestroyRef, ElementRef, ErrorHandler, HostAttributeToken, InjectionToken, Injector, Renderer2, afterEveryRender, computed, createComponent, inject, signal, } from '@angular/core';
|
|
20
20
|
import { ActivatedRoute, ChildrenOutletContexts, NavigationCancel, NavigationEnd, NavigationError, PRIMARY_OUTLET, Router, createUrlTreeFromSnapshot, } from '@angular/router';
|
|
21
21
|
import { Location, PlatformLocation } from '@angular/common';
|
|
22
22
|
import { SCREEN_IN_FRONT } from '@ng-native/device';
|
|
@@ -24,6 +24,7 @@ import { bindRouteInputs } from "./bind-route-inputs.js";
|
|
|
24
24
|
import { NativeBack, isShowing } from "./native-back.js";
|
|
25
25
|
import { intentOf } from "./native-navigation.js";
|
|
26
26
|
import { NativePlatformLocation } from "./native-platform-location.js";
|
|
27
|
+
import { PRESENTED, withoutPresented } from "./presented-route.js";
|
|
27
28
|
import { markScreenRoute } from "./tab-routes.js";
|
|
28
29
|
import { ownHost } from "./own-host.js";
|
|
29
30
|
import { HostEngine } from '@ng-native/fabric';
|
|
@@ -42,6 +43,14 @@ const presented = new WeakMap();
|
|
|
42
43
|
const FILL = { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0 };
|
|
43
44
|
/** A swipe-back area with no limit on any edge. */
|
|
44
45
|
const UNLIMITED_SWIPE = { start: -1, end: -1, top: -1, bottom: -1 };
|
|
46
|
+
/**
|
|
47
|
+
* The screen a page lives in, for a stack inside that page. A presented screen with a header is
|
|
48
|
+
* such a stack, and the page that knows whether to refuse a dismissal is a screen inside it, where
|
|
49
|
+
* the screen a swipe down dismisses is this one.
|
|
50
|
+
*/
|
|
51
|
+
const HOLDING_SCREEN = new InjectionToken('ng-native.router.holding-screen');
|
|
52
|
+
/** Whether a screen refuses a native dismissal, as its page bound it. */
|
|
53
|
+
const refuses = (screen) => screen?.props?.['preventNativeDismiss'] === true;
|
|
45
54
|
export class NativeStackOutlet {
|
|
46
55
|
/**
|
|
47
56
|
* Named outlets work exactly as on the web: `<native-stack-outlet name="list" />` is filled by
|
|
@@ -65,6 +74,10 @@ export class NativeStackOutlet {
|
|
|
65
74
|
/** For a refused back to reach the screen as native's own report would. */
|
|
66
75
|
engine = inject(HostEngine, { optional: true });
|
|
67
76
|
errors = inject(ErrorHandler);
|
|
77
|
+
/** The screen this stack sits in, when it is a stack inside a page of another. */
|
|
78
|
+
holder = inject(HOLDING_SCREEN, { optional: true });
|
|
79
|
+
/** What the holder's own `preventNativeDismiss` was, while this stack's top screen refuses. */
|
|
80
|
+
held = null;
|
|
68
81
|
entries = [];
|
|
69
82
|
/**
|
|
70
83
|
* The screens a replace or a reset supersedes, kept until the navigation that supersedes them
|
|
@@ -102,7 +115,11 @@ export class NativeStackOutlet {
|
|
|
102
115
|
else if (event instanceof NavigationError)
|
|
103
116
|
this.rollBack(event.id);
|
|
104
117
|
});
|
|
118
|
+
const unhold = this.holder ? this.holdRefusals(this.holder) : undefined;
|
|
119
|
+
const unpresent = this.takePresentedPages(back);
|
|
105
120
|
inject(DestroyRef).onDestroy(() => {
|
|
121
|
+
unhold?.();
|
|
122
|
+
unpresent?.();
|
|
106
123
|
unsubscribeBack();
|
|
107
124
|
unsubscribeStack();
|
|
108
125
|
navigations?.unsubscribe();
|
|
@@ -114,10 +131,125 @@ export class NativeStackOutlet {
|
|
|
114
131
|
this.parentContexts.onChildOutletDestroyed(this.name);
|
|
115
132
|
});
|
|
116
133
|
}
|
|
134
|
+
/** The page presented over everything, while there is one. See `presented-route.ts`. */
|
|
135
|
+
presentedPage = null;
|
|
136
|
+
/**
|
|
137
|
+
* The app's own stack, the one in the root component, is also the root's presented outlet: a
|
|
138
|
+
* page of a tab that is not in front is shown here, as a screen over the bar and everything in
|
|
139
|
+
* it. One page at a time, which the router sees to, and never kept: it is dismissed.
|
|
140
|
+
*/
|
|
141
|
+
takePresentedPages(back) {
|
|
142
|
+
const router = this.router;
|
|
143
|
+
const root = inject(ActivatedRoute, { optional: true });
|
|
144
|
+
if (!router?.routerState || this.name !== PRIMARY_OUTLET || root !== router.routerState.root) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
const page = () => this.presentedPage;
|
|
148
|
+
const outlet = {
|
|
149
|
+
supportsBindingToComponentInputs: true,
|
|
150
|
+
get isActivated() {
|
|
151
|
+
return page() !== null;
|
|
152
|
+
},
|
|
153
|
+
get component() {
|
|
154
|
+
return page()?.ref.instance ?? null;
|
|
155
|
+
},
|
|
156
|
+
get activatedRoute() {
|
|
157
|
+
return page()?.route ?? null;
|
|
158
|
+
},
|
|
159
|
+
get activatedRouteData() {
|
|
160
|
+
return page()?.route.snapshot.data ?? {};
|
|
161
|
+
},
|
|
162
|
+
activateWith: (route, environmentInjector) => this.presentPage(route, environmentInjector, back),
|
|
163
|
+
deactivate: () => this.dropPresentedPage(),
|
|
164
|
+
detach: () => {
|
|
165
|
+
throw new Error('[angular-native] a presented page is dismissed, not detached');
|
|
166
|
+
},
|
|
167
|
+
attach: () => {
|
|
168
|
+
throw new Error('[angular-native] a presented page is not kept to attach again');
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
this.parentContexts.onChildOutletCreated(PRESENTED, outlet);
|
|
172
|
+
back.presents = true;
|
|
173
|
+
return () => {
|
|
174
|
+
back.presents = false;
|
|
175
|
+
this.dropPresentedPage();
|
|
176
|
+
this.parentContexts.onChildOutletDestroyed(PRESENTED);
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
presentPage(route, environmentInjector, back) {
|
|
180
|
+
const component = (route.component ?? route.snapshot.component);
|
|
181
|
+
if (!component)
|
|
182
|
+
throw new Error('[angular-native] route has no component to present');
|
|
183
|
+
const presentation = this.claimPresentation(this.currentIntent()) ?? {
|
|
184
|
+
stackPresentation: 'modal',
|
|
185
|
+
};
|
|
186
|
+
const screen = this.createScreen(presentation, () => this.dismissPresentedPage());
|
|
187
|
+
const inFront = signal(true, /* @ts-ignore */
|
|
188
|
+
...(ngDevMode ? [{ debugName: "inFront" }] : /* istanbul ignore next */ []));
|
|
189
|
+
const { ref, unbind } = this.createPage(component, route, environmentInjector, screen, inFront, PRESENTED);
|
|
190
|
+
this.fillScreen(screen);
|
|
191
|
+
// Subscribed now, so it is the newest answer and the first asked: a back dismisses the page
|
|
192
|
+
// before it pops anything in the tab under it.
|
|
193
|
+
const stopBack = back.handle(() => this.dismissPresentedPage());
|
|
194
|
+
this.presentedPage = {
|
|
195
|
+
route,
|
|
196
|
+
ref,
|
|
197
|
+
screen,
|
|
198
|
+
detached: false,
|
|
199
|
+
unbind,
|
|
200
|
+
presentation,
|
|
201
|
+
inFront,
|
|
202
|
+
stopBack,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/** The router left the presented page: take it off the stack. */
|
|
206
|
+
dropPresentedPage() {
|
|
207
|
+
const page = this.presentedPage;
|
|
208
|
+
if (!page)
|
|
209
|
+
return;
|
|
210
|
+
this.presentedPage = null;
|
|
211
|
+
page.stopBack();
|
|
212
|
+
this.drop(page);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Take the router off the presented page, for a back or for a sheet the user swiped away: one
|
|
216
|
+
* entry back where that is the url without the page, and otherwise a navigation to that url in
|
|
217
|
+
* place of this entry, since something under the page was navigated while it was up.
|
|
218
|
+
*/
|
|
219
|
+
dismissPresentedPage() {
|
|
220
|
+
if (!this.presentedPage || !this.router)
|
|
221
|
+
return false;
|
|
222
|
+
const without = withoutPresented(this.router, this.router.url);
|
|
223
|
+
if (this.historyUrl(-1) === without)
|
|
224
|
+
this.location.historyGo(-1);
|
|
225
|
+
else
|
|
226
|
+
void this.router.navigateByUrl(without, { replaceUrl: true });
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
117
229
|
/** The screens currently in the stack, oldest first. Exposed for tests. */
|
|
118
230
|
get depth() {
|
|
119
231
|
return this.entries.length;
|
|
120
232
|
}
|
|
233
|
+
ngOnInit() {
|
|
234
|
+
this.activateCurrentRoute();
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* The route the router is already on, for an outlet created after the navigation that reached
|
|
238
|
+
* it: one held behind a condition until a session or a database is ready. The router activates
|
|
239
|
+
* an outlet as a navigation ends, and that one ended with no outlet to activate, so the outlet
|
|
240
|
+
* takes the route from its context as it arrives, as Angular's own `RouterOutlet` does.
|
|
241
|
+
*/
|
|
242
|
+
activateCurrentRoute() {
|
|
243
|
+
if (this.isActivated)
|
|
244
|
+
return;
|
|
245
|
+
const context = this.parentContexts.getContext(this.name);
|
|
246
|
+
if (!context?.route)
|
|
247
|
+
return;
|
|
248
|
+
if (context.attachRef)
|
|
249
|
+
this.attach(context.attachRef, context.route);
|
|
250
|
+
else
|
|
251
|
+
this.activateWith(context.route, context.injector);
|
|
252
|
+
}
|
|
121
253
|
// --- RouterOutletContract ----------------------------------------------
|
|
122
254
|
get isActivated() {
|
|
123
255
|
return this.top !== undefined && !this.top.detached;
|
|
@@ -188,13 +320,13 @@ export class NativeStackOutlet {
|
|
|
188
320
|
* screen it was pushed from given back its change detection, and the error handed to the
|
|
189
321
|
* app's `ErrorHandler` before the router fails the navigation with it.
|
|
190
322
|
*/
|
|
191
|
-
createPage(component, route, environmentInjector, screen, inFront) {
|
|
323
|
+
createPage(component, route, environmentInjector, screen, inFront, outlet = this.name) {
|
|
192
324
|
let ref;
|
|
193
325
|
let unbind;
|
|
194
326
|
try {
|
|
195
327
|
ref = createComponent(component, {
|
|
196
328
|
environmentInjector,
|
|
197
|
-
elementInjector: this.outletInjector(route, inFront),
|
|
329
|
+
elementInjector: this.outletInjector(route, inFront, screen, outlet),
|
|
198
330
|
hostElement: screen,
|
|
199
331
|
});
|
|
200
332
|
this.applicationRef.attachView(ref.hostView);
|
|
@@ -219,11 +351,16 @@ export class NativeStackOutlet {
|
|
|
219
351
|
throw error;
|
|
220
352
|
}
|
|
221
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* Take the screen in front off the stack, when there is one: nothing once the router has
|
|
356
|
+
* detached it, as Angular's own outlet does nothing then. A route with no component of its own,
|
|
357
|
+
* a `loadChildren` wrapper or a group, has the router detach the page under it and then
|
|
358
|
+
* deactivate this same outlet, and that page is a screen pushed from, still to come back to.
|
|
359
|
+
*/
|
|
222
360
|
deactivate() {
|
|
223
|
-
|
|
224
|
-
if (!entry)
|
|
361
|
+
if (!this.isActivated)
|
|
225
362
|
return;
|
|
226
|
-
this.drop(
|
|
363
|
+
this.drop(this.entries.pop());
|
|
227
364
|
}
|
|
228
365
|
/**
|
|
229
366
|
* Hand the live `ComponentRef` back to the router without destroying it. The screen stays
|
|
@@ -414,7 +551,7 @@ export class NativeStackOutlet {
|
|
|
414
551
|
this.renderer.setStyle(screen, key, value);
|
|
415
552
|
}
|
|
416
553
|
}
|
|
417
|
-
createScreen(presentation) {
|
|
554
|
+
createScreen(presentation, dismissed = (count) => this.onNativeDismiss(count)) {
|
|
418
555
|
const screen = ownHost(this.renderer.createElement('screen'));
|
|
419
556
|
this.fillScreen(screen);
|
|
420
557
|
// Set once and never lowered: see the note at the top of this file.
|
|
@@ -422,7 +559,7 @@ export class NativeStackOutlet {
|
|
|
422
559
|
// A swipe-back or an Android back button pops the screen natively and only then tells JS.
|
|
423
560
|
// Without this the native stack and the router disagree from that moment on: the screen is
|
|
424
561
|
// gone but the URL still points at it, so the next navigation starts from the wrong place.
|
|
425
|
-
this.renderer.listen(screen, 'dismissed', (event) =>
|
|
562
|
+
this.renderer.listen(screen, 'dismissed', (event) => dismissed(event?.nativeEvent?.dismissCount ?? 1));
|
|
426
563
|
// Every key is a prop `RNSScreen` already declares, spelled the way the codegen spec spells
|
|
427
564
|
// it, so this forwards rather than translates. Set before the screen joins the stack: the
|
|
428
565
|
// native side reads the presentation as it mounts.
|
|
@@ -478,6 +615,50 @@ export class NativeStackOutlet {
|
|
|
478
615
|
native?.dispatchEvent?.(screen, 'topNativeDismissCancelled', { dismissCount: 1 });
|
|
479
616
|
return true;
|
|
480
617
|
}
|
|
618
|
+
/**
|
|
619
|
+
* Passes the top screen's refusal to the screen this stack sits in, and that screen's report of
|
|
620
|
+
* an attempt back to the top screen: the swipe that dismisses a presented stack is on the
|
|
621
|
+
* screen holding it, which the page inside cannot bind.
|
|
622
|
+
*
|
|
623
|
+
* Read after every render, since the refusal is a host binding of whatever page is on top.
|
|
624
|
+
*/
|
|
625
|
+
holdRefusals(holder) {
|
|
626
|
+
const render = afterEveryRender(() => this.holdRefusal(holder));
|
|
627
|
+
const unlisten = this.renderer.listen(holder.screen, 'nativeDismissCancelled', (event) => {
|
|
628
|
+
// The stack can have lost its last screen with the refusal not yet released.
|
|
629
|
+
const top = this.top;
|
|
630
|
+
if (!this.held || !top)
|
|
631
|
+
return;
|
|
632
|
+
const native = this.engine;
|
|
633
|
+
const report = event?.nativeEvent ?? { dismissCount: 1 };
|
|
634
|
+
native?.dispatchEvent?.(top.screen, 'topNativeDismissCancelled', report);
|
|
635
|
+
});
|
|
636
|
+
return () => {
|
|
637
|
+
render.destroy();
|
|
638
|
+
unlisten();
|
|
639
|
+
this.release(holder);
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
holdRefusal(holder) {
|
|
643
|
+
// A stack in a tab behind is inside the same screen, and has no say in it.
|
|
644
|
+
if (!refuses(this.top?.screen) || !this.isShowing())
|
|
645
|
+
return this.release(holder);
|
|
646
|
+
if (refuses(holder.screen))
|
|
647
|
+
return;
|
|
648
|
+
// ponytail: the holder's own binding is read when the top screen starts refusing. One that
|
|
649
|
+
// turns true while it does is put back as it was then; bind the refusal in one of the two.
|
|
650
|
+
this.held = { own: holder.screen.props?.['preventNativeDismiss'] };
|
|
651
|
+
this.renderer.setProperty(holder.screen, 'preventNativeDismiss', true);
|
|
652
|
+
holder.refusalChanged();
|
|
653
|
+
}
|
|
654
|
+
/** The holder's own answer again, once the top screen no longer refuses. */
|
|
655
|
+
release(holder) {
|
|
656
|
+
if (!this.held)
|
|
657
|
+
return;
|
|
658
|
+
this.renderer.setProperty(holder.screen, 'preventNativeDismiss', this.held.own ?? false);
|
|
659
|
+
this.held = null;
|
|
660
|
+
holder.refusalChanged();
|
|
661
|
+
}
|
|
481
662
|
goBack() {
|
|
482
663
|
if (this.entries.length < 2 || !this.isShowing())
|
|
483
664
|
return false;
|
|
@@ -561,20 +742,27 @@ export class NativeStackOutlet {
|
|
|
561
742
|
this.renderer.removeChild(this.host.nativeElement, screen);
|
|
562
743
|
}
|
|
563
744
|
/**
|
|
564
|
-
* What a page is created with: its route, the outlet contexts its own outlets
|
|
565
|
-
* whether its screen is in front - which is this screen's own say and, for a stack inside a
|
|
745
|
+
* What a page is created with: its route, its screen, the outlet contexts its own outlets
|
|
746
|
+
* register in, and whether its screen is in front - which is this screen's own say and, for a stack inside a
|
|
566
747
|
* screen of another, that screen's too.
|
|
567
748
|
*/
|
|
568
|
-
outletInjector(route, inFront) {
|
|
749
|
+
outletInjector(route, inFront, screen, outlet = this.name) {
|
|
569
750
|
const outer = this.injector.get(SCREEN_IN_FRONT);
|
|
570
751
|
return Injector.create({
|
|
571
752
|
parent: this.injector,
|
|
572
753
|
providers: [
|
|
573
754
|
{ provide: SCREEN_IN_FRONT, useValue: computed(() => outer() && inFront()) },
|
|
755
|
+
{
|
|
756
|
+
provide: HOLDING_SCREEN,
|
|
757
|
+
useValue: {
|
|
758
|
+
screen,
|
|
759
|
+
refusalChanged: () => this.holder && this.holdRefusal(this.holder),
|
|
760
|
+
},
|
|
761
|
+
},
|
|
574
762
|
{ provide: ActivatedRoute, useValue: route },
|
|
575
763
|
{
|
|
576
764
|
provide: ChildrenOutletContexts,
|
|
577
|
-
useValue: this.parentContexts.getOrCreateContext(
|
|
765
|
+
useValue: this.parentContexts.getOrCreateContext(outlet).children,
|
|
578
766
|
},
|
|
579
767
|
],
|
|
580
768
|
});
|