@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
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* was: its scroll offset, its text, and its own stack if it has one. That is the reuse strategy
|
|
26
26
|
* detaching the tab's whole subtree rather than only its leaf.
|
|
27
27
|
*/
|
|
28
|
-
import { ApplicationRef,
|
|
28
|
+
import { ApplicationRef, ChangeDetectorRef, Component, DestroyRef, ElementRef, ErrorHandler, Injector, Renderer2, computed, contentChildren, createComponent, inject, input, signal, } from '@angular/core';
|
|
29
29
|
import { SCREEN_IN_FRONT } from '@ng-native/device';
|
|
30
30
|
import { ActivatedRoute, ChildrenOutletContexts, NavigationEnd, PRIMARY_OUTLET, Router, } from '@angular/router';
|
|
31
31
|
import { bindRouteInputs } from "./bind-route-inputs.js";
|
|
@@ -35,6 +35,7 @@ import { optionalBoolean } from "./transforms.js";
|
|
|
35
35
|
import { NativeTab } from "./native-tab.js";
|
|
36
36
|
import { ownHost } from "./own-host.js";
|
|
37
37
|
import { markTabRoute } from "./tab-routes.js";
|
|
38
|
+
import { withoutPresented } from "./presented-route.js";
|
|
38
39
|
import * as i0 from "@angular/core";
|
|
39
40
|
export class NativeTabsOutlet {
|
|
40
41
|
name = PRIMARY_OUTLET;
|
|
@@ -42,6 +43,7 @@ export class NativeTabsOutlet {
|
|
|
42
43
|
parentContexts = inject(ChildrenOutletContexts);
|
|
43
44
|
renderer = inject(Renderer2);
|
|
44
45
|
applicationRef = inject(ApplicationRef);
|
|
46
|
+
changeDetector = inject(ChangeDetectorRef);
|
|
45
47
|
injector = inject(Injector);
|
|
46
48
|
host = inject(ElementRef);
|
|
47
49
|
route = inject(ActivatedRoute);
|
|
@@ -89,14 +91,22 @@ export class NativeTabsOutlet {
|
|
|
89
91
|
// for it; the router's navigation is the only sign. Without this, coming back to the tab
|
|
90
92
|
// returns it to its root and pops the screens the user left it on.
|
|
91
93
|
const following = this.router?.events?.subscribe((event) => {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
+
// Without a page presented over the bar, which is no part of where the tab is.
|
|
95
|
+
if (event instanceof NavigationEnd) {
|
|
96
|
+
this.follow(withoutPresented(this.router, event.urlAfterRedirects));
|
|
97
|
+
}
|
|
94
98
|
});
|
|
95
99
|
// Android's back button, and `NativeNavigation.back()`, once the stack in front, if the tab
|
|
96
100
|
// has one, is at its root. Subscribed before any tab's own stack, so it is asked after them.
|
|
97
|
-
const
|
|
101
|
+
const back = inject(NativeBack);
|
|
102
|
+
const unsubscribeBack = back.handle(() => this.goBack());
|
|
103
|
+
const unsubscribeBar = back.addTabBar({
|
|
104
|
+
unopenedTabOf: (url) => this.unopenedTabOf(url),
|
|
105
|
+
behind: (url) => this.behind(url),
|
|
106
|
+
});
|
|
98
107
|
inject(DestroyRef).onDestroy(() => {
|
|
99
108
|
unsubscribeBack();
|
|
109
|
+
unsubscribeBar();
|
|
100
110
|
following?.unsubscribe();
|
|
101
111
|
for (const entry of this.entries) {
|
|
102
112
|
entry.unbind();
|
|
@@ -109,6 +119,23 @@ export class NativeTabsOutlet {
|
|
|
109
119
|
/** A bar nobody has navigated into yet still has to show a selected item. */
|
|
110
120
|
ngAfterContentInit() {
|
|
111
121
|
this.readTabs();
|
|
122
|
+
this.activateCurrentRoute();
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The route the router is already on, for an outlet created after the navigation that reached
|
|
126
|
+
* it, as `NativeStackOutlet` takes its own: a tab bar held behind a condition until a session
|
|
127
|
+
* is ready. Once the tabs are read, since activating one needs to know which they are.
|
|
128
|
+
*/
|
|
129
|
+
activateCurrentRoute() {
|
|
130
|
+
if (this.isActivated)
|
|
131
|
+
return;
|
|
132
|
+
const context = this.parentContexts.getContext(this.name);
|
|
133
|
+
if (!context?.route)
|
|
134
|
+
return;
|
|
135
|
+
if (context.attachRef)
|
|
136
|
+
this.attach(context.attachRef, context.route);
|
|
137
|
+
else
|
|
138
|
+
this.activateWith(context.route, context.injector);
|
|
112
139
|
}
|
|
113
140
|
/** The tabs in the bar, in template order. Exposed for tests. */
|
|
114
141
|
get tabKeys() {
|
|
@@ -322,6 +349,29 @@ export class NativeTabsOutlet {
|
|
|
322
349
|
if (url === root || url.startsWith(`${root}/`) || url.startsWith(`${root}?`))
|
|
323
350
|
entry.url = url;
|
|
324
351
|
}
|
|
352
|
+
/**
|
|
353
|
+
* The url of the tab `url` is a page inside, when nothing has opened that tab: the tab's own
|
|
354
|
+
* first screen, which a push into the tab from outside it shows first.
|
|
355
|
+
*/
|
|
356
|
+
unopenedTabOf(url) {
|
|
357
|
+
for (const entry of this.entries) {
|
|
358
|
+
if (entry.ref)
|
|
359
|
+
continue;
|
|
360
|
+
const root = this.pathOf(entry);
|
|
361
|
+
if (url.startsWith(`${root}/`))
|
|
362
|
+
return root;
|
|
363
|
+
}
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
/** Whether `url` is a tab other than the one in front, or a page inside one. */
|
|
367
|
+
behind(url) {
|
|
368
|
+
return this.entries.some((entry) => {
|
|
369
|
+
if (entry === this.selected)
|
|
370
|
+
return false;
|
|
371
|
+
const root = this.pathOf(entry);
|
|
372
|
+
return url === root || url.startsWith(`${root}/`);
|
|
373
|
+
});
|
|
374
|
+
}
|
|
325
375
|
request(entry) {
|
|
326
376
|
this.renderer.setProperty(this.host.nativeElement, 'navStateRequest', {
|
|
327
377
|
selectedScreenKey: entry.key,
|
|
@@ -361,6 +411,10 @@ export class NativeTabsOutlet {
|
|
|
361
411
|
if (arrived || !this.selected || this.router?.getCurrentNavigation())
|
|
362
412
|
return;
|
|
363
413
|
this.request(this.selected);
|
|
414
|
+
// A failed navigation ends in a promise callback, which schedules no change detection, and a
|
|
415
|
+
// prop reaches native only with the commit at the end of a pass. Without one the bar stayed
|
|
416
|
+
// on the tab that failed until something else happened to render.
|
|
417
|
+
this.changeDetector.markForCheck();
|
|
364
418
|
}
|
|
365
419
|
/** A tab's own url, from the outlet's route down. */
|
|
366
420
|
pathOf(entry) {
|
|
@@ -385,14 +439,13 @@ export class NativeTabsOutlet {
|
|
|
385
439
|
});
|
|
386
440
|
}
|
|
387
441
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: NativeTabsOutlet, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
388
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.2.0", type: NativeTabsOutlet, isStandalone: true, selector: "native-tabs-outlet", inputs: { barHidden: { classPropertyName: "barHidden", publicName: "barHidden", isSignal: true, isRequired: false, transformFunction: null }, tintColor: { classPropertyName: "tintColor", publicName: "tintColor", isSignal: true, isRequired: false, transformFunction: null }, backgroundColor: { classPropertyName: "backgroundColor", publicName: "backgroundColor", isSignal: true, isRequired: false, transformFunction: null }, colorScheme: { classPropertyName: "colorScheme", publicName: "colorScheme", isSignal: true, isRequired: false, transformFunction: null }, minimizeBehavior: { classPropertyName: "minimizeBehavior", publicName: "minimizeBehavior", isSignal: true, isRequired: false, transformFunction: null }, controllerMode: { classPropertyName: "controllerMode", publicName: "controllerMode", isSignal: true, isRequired: false, transformFunction: null }, respectsKeyboard: { classPropertyName: "respectsKeyboard", publicName: "respectsKeyboard", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style": "fill", "tabBarHidden": "barHidden()", "tabBarTintColor": "tintColor() ?? defaults().tintColor", "nativeContainerBackgroundColor": "backgroundColor() ?? defaults().backgroundColor", "colorScheme": "colorScheme() ?? defaults().colorScheme", "tabBarMinimizeBehavior": "minimizeBehavior()", "tabBarControllerMode": "controllerMode()", "tabBarRespectsIMEInsets": "respectsKeyboard()" } }, queries: [{ propertyName: "declared", predicate: NativeTab, isSignal: true }], ngImport: i0, template: '<ng-content />', isInline: true
|
|
442
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.2.0", type: NativeTabsOutlet, isStandalone: true, selector: "native-tabs-outlet", inputs: { barHidden: { classPropertyName: "barHidden", publicName: "barHidden", isSignal: true, isRequired: false, transformFunction: null }, tintColor: { classPropertyName: "tintColor", publicName: "tintColor", isSignal: true, isRequired: false, transformFunction: null }, backgroundColor: { classPropertyName: "backgroundColor", publicName: "backgroundColor", isSignal: true, isRequired: false, transformFunction: null }, colorScheme: { classPropertyName: "colorScheme", publicName: "colorScheme", isSignal: true, isRequired: false, transformFunction: null }, minimizeBehavior: { classPropertyName: "minimizeBehavior", publicName: "minimizeBehavior", isSignal: true, isRequired: false, transformFunction: null }, controllerMode: { classPropertyName: "controllerMode", publicName: "controllerMode", isSignal: true, isRequired: false, transformFunction: null }, respectsKeyboard: { classPropertyName: "respectsKeyboard", publicName: "respectsKeyboard", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style": "fill", "tabBarHidden": "barHidden()", "tabBarTintColor": "tintColor() ?? defaults().tintColor", "nativeContainerBackgroundColor": "backgroundColor() ?? defaults().backgroundColor", "colorScheme": "colorScheme() ?? defaults().colorScheme", "tabBarMinimizeBehavior": "minimizeBehavior()", "tabBarControllerMode": "controllerMode()", "tabBarRespectsIMEInsets": "respectsKeyboard()" } }, queries: [{ propertyName: "declared", predicate: NativeTab, isSignal: true }], ngImport: i0, template: '<ng-content />', isInline: true });
|
|
389
443
|
}
|
|
390
444
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: NativeTabsOutlet, decorators: [{
|
|
391
445
|
type: Component,
|
|
392
446
|
args: [{
|
|
393
447
|
selector: 'native-tabs-outlet',
|
|
394
448
|
template: '<ng-content />',
|
|
395
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
396
449
|
host: {
|
|
397
450
|
'[style]': 'fill',
|
|
398
451
|
'[tabBarHidden]': 'barHidden()',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-tabs-outlet.js","sourceRoot":"","sources":["../src/native-tabs-outlet.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A `RouterOutlet` that renders a native tab bar: a `UITabBarController` on iOS, a bottom\n * navigation bar on Android.\n *\n * The outlet's host element *is* the tabs host, and each `<native-tab>` in its content is one\n * item and the screen behind it. An activated route's component is created on a child view of\n * the matching tab screen. The screen belongs to the tab declaration and must survive the route\n * component being destroyed; Angular removes a component's host node when it destroys the ref.\n *\n * ```html\n * <native-tabs-outlet>\n * <native-tab path=\"library\" title=\"Library\" sfSymbol=\"books.vertical.fill\" />\n * <native-tab path=\"inbox\" title=\"Inbox\" sfSymbol=\"tray\" [badge]=\"unread()\" />\n * </native-tabs-outlet>\n * ```\n *\n * **Native holds the selection, not us.** The tabs host owns its navigation state and reports\n * every change - a tap, or one we asked for - through `tabSelected`, carrying a `provenance`\n * number that increments per change. We send `navStateRequest` with the provenance of the last\n * state we acknowledged, which is how native tells a stale request from a current one when a tap\n * and a navigation cross in flight. The alternative, holding the selection in JS, means a tap is\n * only reflected after a round trip, and the bar lags the finger. See ADR 0006.\n *\n * **Each tab keeps its own screen mounted**, so switching away and back finds a tab exactly as it\n * was: its scroll offset, its text, and its own stack if it has one. That is the reuse strategy\n * detaching the tab's whole subtree rather than only its leaf.\n */\nimport {\n ApplicationRef,\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n ErrorHandler,\n Injector,\n Renderer2,\n computed,\n contentChildren,\n createComponent,\n inject,\n input,\n signal,\n type AfterContentInit,\n type ComponentRef,\n type EnvironmentInjector,\n type Type,\n type WritableSignal,\n} from '@angular/core';\nimport { SCREEN_IN_FRONT } from '@ng-native/device';\nimport {\n ActivatedRoute,\n ChildrenOutletContexts,\n NavigationEnd,\n PRIMARY_OUTLET,\n Router,\n type Data,\n type RouterOutletContract,\n} from '@angular/router';\nimport { bindRouteInputs } from './bind-route-inputs.ts';\nimport { NATIVE_TAB_DEFAULTS } from './native-bar-defaults.ts';\nimport { NativeBack, isShowing } from './native-back.ts';\nimport { optionalBoolean } from './transforms.ts';\nimport { NativeTab } from './native-tab.ts';\nimport { ownHost } from './own-host.ts';\nimport { markTabRoute } from './tab-routes.ts';\n\ninterface TabEntry {\n readonly tab: NativeTab;\n /** The key native identifies this tab by: its path, unique among the bar's items. */\n readonly key: string;\n /** The url this tab was last on, so returning to it returns where the user left. */\n url: string;\n ref: ComponentRef<unknown> | null;\n route: ActivatedRoute | null;\n detached: boolean;\n /** Stops feeding route params into the component's inputs. */\n unbind: () => void;\n /** Whether this tab is the one showing, for what its screens hold: see `SCREEN_IN_FRONT`. */\n readonly inFront: WritableSignal<boolean>;\n}\n\n/** What `tabSelected` carries. Only two fields matter here; the rest is diagnostics. */\ninterface TabSelection {\n readonly selectedScreenKey: string;\n readonly provenance: number;\n}\n\n@Component({\n selector: 'native-tabs-outlet',\n template: '<ng-content />',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[style]': 'fill',\n '[tabBarHidden]': 'barHidden()',\n '[tabBarTintColor]': 'tintColor() ?? defaults().tintColor',\n '[nativeContainerBackgroundColor]': 'backgroundColor() ?? defaults().backgroundColor',\n '[colorScheme]': 'colorScheme() ?? defaults().colorScheme',\n '[tabBarMinimizeBehavior]': 'minimizeBehavior()',\n '[tabBarControllerMode]': 'controllerMode()',\n '[tabBarRespectsIMEInsets]': 'respectsKeyboard()',\n },\n})\nexport class NativeTabsOutlet implements RouterOutletContract, AfterContentInit {\n name = PRIMARY_OUTLET;\n\n readonly supportsBindingToComponentInputs = true;\n\n private readonly parentContexts = inject(ChildrenOutletContexts);\n private readonly renderer = inject(Renderer2);\n private readonly applicationRef = inject(ApplicationRef);\n private readonly injector = inject(Injector);\n private readonly host = inject(ElementRef);\n private readonly route = inject(ActivatedRoute);\n /** Optional so the outlet can be driven through `RouterOutletContract` directly, as tests do. */\n private readonly router = inject(Router, { optional: true });\n private readonly errors = inject(ErrorHandler);\n /** What activation has reported already, so the failed navigation it causes is not reported again. */\n private readonly reported = new WeakSet<object>();\n /** The app's `withTabDefaults`, for whatever the outlet does not bind itself. */\n protected readonly defaults = inject(NATIVE_TAB_DEFAULTS);\n\n private readonly declared = contentChildren(NativeTab);\n private readonly entries: TabEntry[] = [];\n private selected: TabEntry | null = null;\n /** The provenance of the last state native told us about. Starts before native's first. */\n private provenance = 0;\n\n protected readonly fill = { flex: 1 };\n\n // --- the bar itself -----------------------------------------------------\n\n /** Hide the bar without unmounting the tabs, for a screen that wants the whole window. */\n readonly barHidden = input(undefined, { transform: optionalBoolean });\n /** The colour of the selected item. iOS. */\n readonly tintColor = input<string | number>();\n /** Behind the tabs, where a screen does not paint. */\n readonly backgroundColor = input<string | number>();\n /** Force the bar's light or dark appearance, whatever the app's is. */\n readonly colorScheme = input<'inherit' | 'light' | 'dark'>();\n /** iOS 26: let the bar shrink out of the way as content scrolls. */\n readonly minimizeBehavior = input<'automatic' | 'never' | 'onScrollDown' | 'onScrollUp'>();\n /** iOS: `tabSidebar` is the iPad layout, with the tabs down the side. */\n readonly controllerMode = input<'automatic' | 'tabBar' | 'tabSidebar'>();\n /** Android: keep the bar above the keyboard rather than behind it. */\n readonly respectsKeyboard = input(undefined, { transform: optionalBoolean });\n\n constructor() {\n ownHost(this.host.nativeElement);\n // In the constructor rather than a lifecycle hook: the router activates a child route as soon\n // as this component exists, which is before any hook has run.\n this.parentContexts.onChildOutletCreated(this.name, this);\n\n this.renderer.listen(\n this.host.nativeElement,\n 'tabSelected',\n (event: { nativeEvent?: TabSelection }) => this.onNativeSelect(event?.nativeEvent),\n );\n\n // A push inside a tab's own stack reuses the tab's route, so this outlet is never activated\n // for it; the router's navigation is the only sign. Without this, coming back to the tab\n // returns it to its root and pops the screens the user left it on.\n const following = this.router?.events?.subscribe((event) => {\n if (event instanceof NavigationEnd) this.follow(event.urlAfterRedirects);\n });\n\n // Android's back button, and `NativeNavigation.back()`, once the stack in front, if the tab\n // has one, is at its root. Subscribed before any tab's own stack, so it is asked after them.\n const unsubscribeBack = inject(NativeBack).handle(() => this.goBack());\n\n inject(DestroyRef).onDestroy(() => {\n unsubscribeBack();\n following?.unsubscribe();\n for (const entry of this.entries) {\n entry.unbind();\n entry.ref?.destroy();\n }\n this.entries.length = 0;\n this.parentContexts.onChildOutletDestroyed(this.name);\n });\n }\n\n /** A bar nobody has navigated into yet still has to show a selected item. */\n ngAfterContentInit(): void {\n this.readTabs();\n }\n\n /** The tabs in the bar, in template order. Exposed for tests. */\n get tabKeys(): string[] {\n return this.readTabs().map((entry) => entry.key);\n }\n\n /** Which tab native is showing. Exposed for tests. */\n get selectedKey(): string | null {\n return this.selected?.key ?? null;\n }\n\n // --- RouterOutletContract ----------------------------------------------\n\n get isActivated(): boolean {\n return this.selected?.ref != null && !this.selected.detached;\n }\n\n get component(): Object | null {\n return this.isActivated ? (this.selected!.ref!.instance as Object) : null;\n }\n\n get activatedRoute(): ActivatedRoute | null {\n return this.isActivated ? this.selected!.route : null;\n }\n\n get activatedRouteData(): Data {\n return this.activatedRoute?.snapshot.data ?? {};\n }\n\n /** The input binder reads this to bind route params to component inputs. */\n get activatedComponentRef(): ComponentRef<unknown> | null {\n return this.selected?.ref ?? null;\n }\n\n activateWith(route: ActivatedRoute, environmentInjector: EnvironmentInjector): void {\n const component = (route.component ?? route.snapshot.component) as Type<unknown> | null;\n if (!component) {\n throw new Error('[angular-native] route has no component to activate');\n }\n const entry = this.entryFor(route);\n\n // The tab screen is owned by <native-tab>, not by this route component. If the route uses\n // deactivate() rather than detach(), Angular removes the component host on destroy. Giving\n // the component its own child host keeps that removal from taking the tab bar item with it.\n const content = ownHost(this.renderer.createElement('view'));\n this.renderer.setStyle(content, 'flex', 1);\n this.renderer.appendChild(entry.tab.screen, content);\n\n let ref: ComponentRef<unknown> | undefined;\n try {\n ref = createComponent(component, {\n environmentInjector,\n elementInjector: this.outletInjector(route, entry.inFront),\n hostElement: content as Element,\n });\n this.applicationRef.attachView(ref.hostView);\n // Route params onto inputs before the first change detection, as the stack outlet does: a\n // required input still unset when the page first renders throws NG0950.\n entry.unbind = this.bindInputs(ref, route);\n // Before anything can commit. Creating the page's nodes dirties the tree, which schedules a\n // commit of its own - and that commit would capture the page with its elements created and\n // its bindings not yet applied. On iOS the props catch up a frame later and nobody sees it;\n // on Android the flattening decision is made from the props a view is created with, so a\n // content wrapper that arrives without its `collapsable: false` is flattened for good and\n // its children end up parented to a scroll view that allows exactly one.\n ref.changeDetectorRef.detectChanges();\n } catch (error) {\n this.abandon(entry, ref, content);\n this.errors.handleError(error);\n if (error !== null && typeof error === 'object') this.reported.add(error);\n throw error;\n }\n\n entry.ref = ref;\n entry.route = route;\n entry.detached = false;\n this.select(entry);\n }\n\n deactivate(): void {\n const entry = this.selected;\n if (!entry?.ref) return;\n entry.unbind();\n entry.unbind = () => {};\n entry.ref.destroy();\n entry.ref = null;\n entry.route = null;\n }\n\n /**\n * Hand the live `ComponentRef` back to the router. The tab's screen stays where it is with the\n * component still inside it, so the tab keeps running while another one is in front.\n */\n detach(): ComponentRef<unknown> {\n const entry = this.selected;\n if (!entry?.ref) throw new Error('[angular-native] outlet is not activated, nothing to detach');\n entry.detached = true;\n entry.inFront.set(false);\n // As in the stack outlet: stop change detection without unmounting the views.\n entry.ref.hostView.detach();\n return entry.ref;\n }\n\n attach(ref: ComponentRef<unknown>, route: ActivatedRoute): void {\n const entry = this.entryFor(route);\n\n // A ref from a previous instance of this outlet still lives on its old tab screen, which is\n // gone; re-parent it onto this one's rather than rebuild the tab.\n if (entry.ref !== ref) {\n this.renderer.appendChild(entry.tab.screen, ref.location.nativeElement);\n this.applicationRef.attachView(ref.hostView);\n entry.ref = ref;\n }\n\n entry.route = route;\n entry.detached = false;\n // The route object is new even when the url is not, so the inputs follow the new one.\n entry.unbind();\n entry.unbind = this.bindInputs(ref, route);\n ref.hostView.reattach();\n this.select(entry);\n }\n\n // --- native tabs --------------------------------------------------------\n\n /**\n * Undo a tab page that threw on its first render, which fails the navigation: its component\n * and its content view go, and the tab that was showing carries on, change detection and all.\n */\n private abandon(entry: TabEntry, ref: ComponentRef<unknown> | undefined, content: unknown): void {\n entry.unbind();\n entry.unbind = () => {};\n ref?.destroy();\n this.renderer.removeChild(entry.tab.screen, content);\n const showing = this.selected;\n if (showing?.detached && showing.ref) {\n showing.detached = false;\n showing.ref.hostView.reattach();\n }\n }\n\n /** Route params onto inputs only under `withComponentInputBinding()`, as in the stack outlet. */\n private bindInputs(ref: ComponentRef<unknown>, route: ActivatedRoute): () => void {\n return this.router?.componentInputBindingEnabled ? bindRouteInputs(ref, route) : () => {};\n }\n\n /**\n * Take the bar from the `<native-tab>` elements in the content, once.\n *\n * On demand rather than in a lifecycle hook, because the router activates a child route during\n * this component's creation, before any hook has run - but after the content nodes themselves\n * exist, which is all this needs.\n */\n private readTabs(): TabEntry[] {\n if (this.entries.length > 0) return this.entries;\n\n const children = this.route.routeConfig?.children ?? [];\n for (const tab of this.declared()) {\n const key = tab.path();\n this.entries.push({\n tab,\n key,\n url: '',\n ref: null,\n route: null,\n detached: false,\n unbind: () => {},\n inFront: signal(false),\n });\n // The reuse strategy detaches a tab whole, and has only the route to go on.\n // A path can have multiple route configs selected by canMatch (for example an iPad split\n // layout and an iPhone stack). Mark every variant: the reuse strategy sees the config\n // Angular actually matched, which may not be the first one in the route array.\n for (const config of children) {\n if (config.path === key) markTabRoute(config);\n }\n }\n\n if (this.entries.length === 0) {\n throw new Error(\n '[angular-native] <native-tabs-outlet> has no tabs. Put a <native-tab path=\"...\"> ' +\n 'inside it for each child route the bar should reach.',\n );\n }\n // Before the first navigation nothing is selected, which is not a state a tab bar has.\n this.request(this.entries[0]!);\n return this.entries;\n }\n\n private entryFor(route: ActivatedRoute): TabEntry {\n const path = (route.routeConfig ?? route.snapshot.routeConfig)?.path;\n const entry = this.readTabs().find((candidate) => candidate.key === path);\n if (!entry) {\n throw new Error(\n `[angular-native] no <native-tab> has path=\"${path}\". Every child route the bar can ` +\n `reach needs one, and its path is what native identifies the tab by.`,\n );\n }\n return entry;\n }\n\n /** Remember where this tab is, and tell native to show it. */\n private select(entry: TabEntry): void {\n this.selected = entry;\n for (const other of this.entries) other.inFront.set(other === entry);\n entry.url = this.router?.url ?? entry.url;\n this.request(entry);\n }\n\n /**\n * A back with nothing left to pop inside the tab: go to the first tab, where it was left, which\n * is what a bottom navigation bar does on Android. From the first tab, decline, so the platform\n * leaves the app. The order tabs were visited in is not retraced, so a back never lands on a\n * tab the user did not expect. Not while the bar is covered by a screen pushed over it: that\n * screen's stack answers instead.\n */\n private goBack(): boolean {\n const first = this.entries[0];\n const selected = this.selected;\n if (!first || !selected || selected === first || !this.router) return false;\n if (!this.isActivated || !isShowing(this.router, selected.route)) return false;\n void this.router.navigateByUrl(first.url || this.pathOf(first));\n return true;\n }\n\n /** Keep the tab in front on the url it has reached, as long as the url is still inside it. */\n private follow(url: string): void {\n const entry = this.selected;\n if (!entry) return;\n const root = this.pathOf(entry);\n if (url === root || url.startsWith(`${root}/`) || url.startsWith(`${root}?`)) entry.url = url;\n }\n\n private request(entry: TabEntry): void {\n this.renderer.setProperty(this.host.nativeElement, 'navStateRequest', {\n selectedScreenKey: entry.key,\n baseProvenance: this.provenance,\n });\n }\n\n /**\n * Native switched tab. Acknowledge the new state, then navigate: the router is authoritative\n * about what is on screen (ADR 0003), so the tap becomes a navigation to where that tab was,\n * and the activation that follows is what fills the tab in.\n */\n private onNativeSelect(selection: TabSelection | undefined): void {\n if (!selection) return;\n this.provenance = selection.provenance;\n\n const entry = this.readTabs().find((tab) => tab.key === selection.selectedScreenKey);\n if (!entry || entry === this.selected) return;\n\n // Returning to a tab returns to the url it was last on, which is the whole point of keeping\n // it mounted. A tab never visited starts at its own path.\n // A navigation that fails is reported, unless activation reported it already: a page that\n // does not load, caught here, would otherwise leave nothing but a tab that will not open.\n void this.router?.navigateByUrl(entry.url || this.pathOf(entry)).then(\n (arrived) => this.revertUnless(arrived),\n (error: unknown) => {\n if (error === null || typeof error !== 'object' || !this.reported.has(error)) {\n this.errors.handleError(error);\n }\n this.revertUnless(false);\n },\n );\n }\n\n /**\n * Native switched tab before the router agreed to. When a guard refuses, or the navigation\n * fails, the router stays where it was and so must the bar, or it shows a tab nobody is on.\n * Not while another navigation is under way: that one superseded this, and its own activation\n * selects the tab it lands on.\n */\n private revertUnless(arrived: boolean): void {\n if (arrived || !this.selected || this.router?.getCurrentNavigation()) return;\n this.request(this.selected);\n }\n\n /** A tab's own url, from the outlet's route down. */\n private pathOf(entry: TabEntry): string {\n const parent = this.route.snapshot.pathFromRoot\n .flatMap((snapshot) => snapshot.url.map((segment) => segment.path))\n .join('/');\n return `/${[parent, entry.key].filter(Boolean).join('/')}`;\n }\n\n /** As the stack outlet's: the route, the outlet contexts, and whether the tab is showing. */\n private outletInjector(route: ActivatedRoute, inFront: WritableSignal<boolean>): Injector {\n const outer = this.injector.get(SCREEN_IN_FRONT);\n return Injector.create({\n parent: this.injector,\n providers: [\n { provide: SCREEN_IN_FRONT, useValue: computed(() => outer() && inFront()) },\n { provide: ActivatedRoute, useValue: route },\n {\n provide: ChildrenOutletContexts,\n useValue: this.parentContexts.getOrCreateContext(this.name).children,\n },\n ],\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"native-tabs-outlet.js","sourceRoot":"","sources":["../src/native-tabs-outlet.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,SAAS,EACT,UAAU,EACV,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,eAAe,EACf,eAAe,EACf,MAAM,EACN,KAAK,EACL,MAAM,GAMP,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,aAAa,EACb,cAAc,EACd,MAAM,GAGP,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;;AAqCxD,MAAM,OAAO,gBAAgB;IAC3B,IAAI,GAAG,cAAc,CAAC;IAEb,gCAAgC,GAAG,IAAI,CAAC;IAEhC,cAAc,GAAG,MAAM,CAAC,sBAAsB,CAAC,CAAC;IAChD,QAAQ,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAC7B,cAAc,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;IACxC,cAAc,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAC3C,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC5B,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;IAC1B,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,CAAC;IAChD,iGAAiG;IAChF,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC;IAC/C,sGAAsG;IACrF,QAAQ,GAAG,IAAI,OAAO,EAAU,CAAC;IAClD,iFAAiF;IAC9D,QAAQ,GAAG,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAEzC,QAAQ,GAAG,eAAe,CAAC,SAAS;iFAAC,CAAC;IACtC,OAAO,GAAe,EAAE,CAAC;IAClC,QAAQ,GAAoB,IAAI,CAAC;IACzC,2FAA2F;IACnF,UAAU,GAAG,CAAC,CAAC;IAEJ,IAAI,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAEtC,2EAA2E;IAE3E,0FAA0F;IACjF,SAAS,GAAG,KAAK,CAAC,SAAS,iFAAI,SAAS,EAAE,eAAe,GAAG,CAAC;IACtE,4CAA4C;IACnC,SAAS,GAAG,KAAK;6FAAmB,CAAC;IAC9C,sDAAsD;IAC7C,eAAe,GAAG,KAAK;mGAAmB,CAAC;IACpD,uEAAuE;IAC9D,WAAW,GAAG,KAAK;+FAAgC,CAAC;IAC7D,oEAAoE;IAC3D,gBAAgB,GAAG,KAAK;oGAAyD,CAAC;IAC3F,yEAAyE;IAChE,cAAc,GAAG,KAAK;kGAAyC,CAAC;IACzE,sEAAsE;IAC7D,gBAAgB,GAAG,KAAK,CAAC,SAAS,wFAAI,SAAS,EAAE,eAAe,GAAG,CAAC;IAE7E;QACE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACjC,8FAA8F;QAC9F,8DAA8D;QAC9D,IAAI,CAAC,cAAc,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAE1D,IAAI,CAAC,QAAQ,CAAC,MAAM,CAClB,IAAI,CAAC,IAAI,CAAC,aAAa,EACvB,aAAa,EACb,CAAC,KAAqC,EAAE,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,WAAW,CAAC,CACnF,CAAC;QAEF,4FAA4F;QAC5F,yFAAyF;QACzF,mEAAmE;QACnE,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,KAAK,EAAE,EAAE;YACzD,+EAA+E;YAC/E,IAAI,KAAK,YAAY,aAAa,EAAE,CAAC;gBACnC,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,IAAI,CAAC,MAAO,EAAE,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC;YACvE,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,4FAA4F;QAC5F,6FAA6F;QAC7F,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;QAChC,MAAM,eAAe,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;QACzD,MAAM,cAAc,GAAG,IAAI,CAAC,SAAS,CAAC;YACpC,aAAa,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC;YAC/C,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC;SAClC,CAAC,CAAC;QAEH,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,GAAG,EAAE;YAChC,eAAe,EAAE,CAAC;YAClB,cAAc,EAAE,CAAC;YACjB,SAAS,EAAE,WAAW,EAAE,CAAC;YACzB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBACjC,KAAK,CAAC,MAAM,EAAE,CAAC;gBACf,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,CAAC;YACvB,CAAC;YACD,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;YACxB,IAAI,CAAC,cAAc,CAAC,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;IACL,CAAC;IAED,6EAA6E;IAC7E,kBAAkB;QAChB,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChB,IAAI,CAAC,oBAAoB,EAAE,CAAC;IAC9B,CAAC;IAED;;;;OAIG;IACK,oBAAoB;QAC1B,IAAI,IAAI,CAAC,WAAW;YAAE,OAAO;QAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC1D,IAAI,CAAC,OAAO,EAAE,KAAK;YAAE,OAAO;QAC5B,IAAI,OAAO,CAAC,SAAS;YAAE,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;;YAChE,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC1D,CAAC;IAED,iEAAiE;IACjE,IAAI,OAAO;QACT,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACnD,CAAC;IAED,sDAAsD;IACtD,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,IAAI,CAAC;IACpC,CAAC;IAED,0EAA0E;IAE1E,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAC/D,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,WAAW,CAAC,CAAC,CAAE,IAAI,CAAC,QAAS,CAAC,GAAI,CAAC,QAAmB,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5E,CAAC;IAED,IAAI,cAAc;QAChB,OAAO,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,QAAS,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;IACxD,CAAC;IAED,IAAI,kBAAkB;QACpB,OAAO,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC;IAClD,CAAC;IAED,4EAA4E;IAC5E,IAAI,qBAAqB;QACvB,OAAO,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,IAAI,CAAC;IACpC,CAAC;IAED,YAAY,CAAC,KAAqB,EAAE,mBAAwC;QAC1E,MAAM,SAAS,GAAG,CAAC,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAyB,CAAC;QACxF,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;QACzE,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAEnC,0FAA0F;QAC1F,2FAA2F;QAC3F,4FAA4F;QAC5F,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;QAC7D,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;QAC3C,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAErD,IAAI,GAAsC,CAAC;QAC3C,IAAI,CAAC;YACH,GAAG,GAAG,eAAe,CAAC,SAAS,EAAE;gBAC/B,mBAAmB;gBACnB,eAAe,EAAE,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC;gBAC1D,WAAW,EAAE,OAAkB;aAChC,CAAC,CAAC;YACH,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC7C,0FAA0F;YAC1F,wEAAwE;YACxE,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YAC3C,4FAA4F;YAC5F,2FAA2F;YAC3F,4FAA4F;YAC5F,yFAAyF;YACzF,0FAA0F;YAC1F,yEAAyE;YACzE,GAAG,CAAC,iBAAiB,CAAC,aAAa,EAAE,CAAC;QACxC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;YAClC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YAC/B,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;gBAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAC1E,MAAM,KAAK,CAAC;QACd,CAAC;QAED,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC;QAChB,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;QACpB,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC;QACvB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;IAED,UAAU;QACR,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC5B,IAAI,CAAC,KAAK,EAAE,GAAG;YAAE,OAAO;QACxB,KAAK,CAAC,MAAM,EAAE,CAAC;QACf,KAAK,CAAC,MAAM,GAAG,GAAG,EAAE,GAAE,CAAC,CAAC;QACxB,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACpB,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC;QACjB,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC;IACrB,CAAC;IAED;;;OAGG;IACH,MAAM;QACJ,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC5B,IAAI,CAAC,KAAK,EAAE,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;QAChG,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC;QACtB,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACzB,8EAA8E;QAC9E,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC5B,OAAO,KAAK,CAAC,GAAG,CAAC;IACnB,CAAC;IAED,MAAM,CAAC,GAA0B,EAAE,KAAqB;QACtD,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAEnC,4FAA4F;QAC5F,kEAAkE;QAClE,IAAI,KAAK,CAAC,GAAG,KAAK,GAAG,EAAE,CAAC;YACtB,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;YACxE,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC7C,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC;QAClB,CAAC;QAED,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;QACpB,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC;QACvB,sFAAsF;QACtF,KAAK,CAAC,MAAM,EAAE,CAAC;QACf,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC3C,GAAG,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACxB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;IAED,2EAA2E;IAE3E;;;OAGG;IACK,OAAO,CAAC,KAAe,EAAE,GAAsC,EAAE,OAAgB;QACvF,KAAK,CAAC,MAAM,EAAE,CAAC;QACf,KAAK,CAAC,MAAM,GAAG,GAAG,EAAE,GAAE,CAAC,CAAC;QACxB,GAAG,EAAE,OAAO,EAAE,CAAC;QACf,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrD,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,OAAO,EAAE,QAAQ,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;YACrC,OAAO,CAAC,QAAQ,GAAG,KAAK,CAAC;YACzB,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC;QAClC,CAAC;IACH,CAAC;IAED,iGAAiG;IACzF,UAAU,CAAC,GAA0B,EAAE,KAAqB;QAClE,OAAO,IAAI,CAAC,MAAM,EAAE,4BAA4B,CAAC,CAAC,CAAC,eAAe,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;IAC5F,CAAC;IAED;;;;;;OAMG;IACK,QAAQ;QACd,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC,OAAO,CAAC;QAEjD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,QAAQ,IAAI,EAAE,CAAC;QACxD,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC;YAClC,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;YACvB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;gBAChB,GAAG;gBACH,GAAG;gBACH,GAAG,EAAE,EAAE;gBACP,GAAG,EAAE,IAAI;gBACT,KAAK,EAAE,IAAI;gBACX,QAAQ,EAAE,KAAK;gBACf,MAAM,EAAE,GAAG,EAAE,GAAE,CAAC;gBAChB,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC;aACvB,CAAC,CAAC;YACH,4EAA4E;YAC5E,yFAAyF;YACzF,sFAAsF;YACtF,+EAA+E;YAC/E,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;gBAC9B,IAAI,MAAM,CAAC,IAAI,KAAK,GAAG;oBAAE,YAAY,CAAC,MAAM,CAAC,CAAC;YAChD,CAAC;QACH,CAAC;QAED,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CACb,mFAAmF;gBACjF,sDAAsD,CACzD,CAAC;QACJ,CAAC;QACD,uFAAuF;QACvF,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,CAAC;QAC/B,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAEO,QAAQ,CAAC,KAAqB;QACpC,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,IAAI,CAAC;QACrE,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC;QAC1E,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CACb,8CAA8C,IAAI,mCAAmC;gBACnF,qEAAqE,CACxE,CAAC;QACJ,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,8DAA8D;IACtD,MAAM,CAAC,KAAe;QAC5B,IAAI,CAAC,QAAQ,GAAG,KAAK,CAAC;QACtB,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO;YAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC;QACrE,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,IAAI,KAAK,CAAC,GAAG,CAAC;QAC1C,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IAED;;;;;;OAMG;IACK,MAAM;QACZ,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC/B,IAAI,CAAC,KAAK,IAAI,CAAC,QAAQ,IAAI,QAAQ,KAAK,KAAK,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAC5E,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QAC/E,KAAK,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAChE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,8FAA8F;IACtF,MAAM,CAAC,GAAW;QACxB,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC5B,IAAI,CAAC,KAAK;YAAE,OAAO;QACnB,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAChC,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC;YAAE,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC;IAChG,CAAC;IAED;;;OAGG;IACK,aAAa,CAAC,GAAW;QAC/B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjC,IAAI,KAAK,CAAC,GAAG;gBAAE,SAAS;YACxB,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAChC,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC;gBAAE,OAAO,IAAI,CAAC;QAC9C,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,gFAAgF;IACxE,MAAM,CAAC,GAAW;QACxB,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;YACjC,IAAI,KAAK,KAAK,IAAI,CAAC,QAAQ;gBAAE,OAAO,KAAK,CAAC;YAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAChC,OAAO,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,OAAO,CAAC,KAAe;QAC7B,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,iBAAiB,EAAE;YACpE,iBAAiB,EAAE,KAAK,CAAC,GAAG;YAC5B,cAAc,EAAE,IAAI,CAAC,UAAU;SAChC,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACK,cAAc,CAAC,SAAmC;QACxD,IAAI,CAAC,SAAS;YAAE,OAAO;QACvB,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC,UAAU,CAAC;QAEvC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,KAAK,SAAS,CAAC,iBAAiB,CAAC,CAAC;QACrF,IAAI,CAAC,KAAK,IAAI,KAAK,KAAK,IAAI,CAAC,QAAQ;YAAE,OAAO;QAE9C,4FAA4F;QAC5F,0DAA0D;QAC1D,0FAA0F;QAC1F,0FAA0F;QAC1F,KAAK,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CACnE,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,EACvC,CAAC,KAAc,EAAE,EAAE;YACjB,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC7E,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YACjC,CAAC;YACD,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC,CACF,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACK,YAAY,CAAC,OAAgB;QACnC,IAAI,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,MAAM,EAAE,oBAAoB,EAAE;YAAE,OAAO;QAC7E,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC5B,6FAA6F;QAC7F,4FAA4F;QAC5F,kEAAkE;QAClE,IAAI,CAAC,cAAc,CAAC,YAAY,EAAE,CAAC;IACrC,CAAC;IAED,qDAAqD;IAC7C,MAAM,CAAC,KAAe;QAC5B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,YAAY;aAC5C,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;aAClE,IAAI,CAAC,GAAG,CAAC,CAAC;QACb,OAAO,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IAC7D,CAAC;IAED,6FAA6F;IACrF,cAAc,CAAC,KAAqB,EAAE,OAAgC;QAC5E,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QACjD,OAAO,QAAQ,CAAC,MAAM,CAAC;YACrB,MAAM,EAAE,IAAI,CAAC,QAAQ;YACrB,SAAS,EAAE;gBACT,EAAE,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,QAAQ,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,IAAI,OAAO,EAAE,CAAC,EAAE;gBAC5E,EAAE,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,KAAK,EAAE;gBAC5C;oBACE,OAAO,EAAE,sBAAsB;oBAC/B,QAAQ,EAAE,IAAI,CAAC,cAAc,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ;iBACrE;aACF;SACF,CAAC,CAAC;IACL,CAAC;uGAjbU,gBAAgB;2FAAhB,gBAAgB,ggDAoBiB,SAAS,6CAhC3C,gBAAgB;;2FAYf,gBAAgB;kBAd5B,SAAS;mBAAC;oBACT,QAAQ,EAAE,oBAAoB;oBAC9B,QAAQ,EAAE,gBAAgB;oBAC1B,IAAI,EAAE;wBACJ,SAAS,EAAE,MAAM;wBACjB,gBAAgB,EAAE,aAAa;wBAC/B,mBAAmB,EAAE,qCAAqC;wBAC1D,kCAAkC,EAAE,iDAAiD;wBACrF,eAAe,EAAE,yCAAyC;wBAC1D,0BAA0B,EAAE,oBAAoB;wBAChD,wBAAwB,EAAE,kBAAkB;wBAC5C,2BAA2B,EAAE,oBAAoB;qBAClD;iBACF;0HAqB6C,SAAS","sourcesContent":["/**\n * A `RouterOutlet` that renders a native tab bar: a `UITabBarController` on iOS, a bottom\n * navigation bar on Android.\n *\n * The outlet's host element *is* the tabs host, and each `<native-tab>` in its content is one\n * item and the screen behind it. An activated route's component is created on a child view of\n * the matching tab screen. The screen belongs to the tab declaration and must survive the route\n * component being destroyed; Angular removes a component's host node when it destroys the ref.\n *\n * ```html\n * <native-tabs-outlet>\n * <native-tab path=\"library\" title=\"Library\" sfSymbol=\"books.vertical.fill\" />\n * <native-tab path=\"inbox\" title=\"Inbox\" sfSymbol=\"tray\" [badge]=\"unread()\" />\n * </native-tabs-outlet>\n * ```\n *\n * **Native holds the selection, not us.** The tabs host owns its navigation state and reports\n * every change - a tap, or one we asked for - through `tabSelected`, carrying a `provenance`\n * number that increments per change. We send `navStateRequest` with the provenance of the last\n * state we acknowledged, which is how native tells a stale request from a current one when a tap\n * and a navigation cross in flight. The alternative, holding the selection in JS, means a tap is\n * only reflected after a round trip, and the bar lags the finger. See ADR 0006.\n *\n * **Each tab keeps its own screen mounted**, so switching away and back finds a tab exactly as it\n * was: its scroll offset, its text, and its own stack if it has one. That is the reuse strategy\n * detaching the tab's whole subtree rather than only its leaf.\n */\nimport {\n ApplicationRef,\n ChangeDetectorRef,\n Component,\n DestroyRef,\n ElementRef,\n ErrorHandler,\n Injector,\n Renderer2,\n computed,\n contentChildren,\n createComponent,\n inject,\n input,\n signal,\n type AfterContentInit,\n type ComponentRef,\n type EnvironmentInjector,\n type Type,\n type WritableSignal,\n} from '@angular/core';\nimport { SCREEN_IN_FRONT } from '@ng-native/device';\nimport {\n ActivatedRoute,\n ChildrenOutletContexts,\n NavigationEnd,\n PRIMARY_OUTLET,\n Router,\n type Data,\n type RouterOutletContract,\n} from '@angular/router';\nimport { bindRouteInputs } from './bind-route-inputs.ts';\nimport { NATIVE_TAB_DEFAULTS } from './native-bar-defaults.ts';\nimport { NativeBack, isShowing } from './native-back.ts';\nimport { optionalBoolean } from './transforms.ts';\nimport { NativeTab } from './native-tab.ts';\nimport { ownHost } from './own-host.ts';\nimport { markTabRoute } from './tab-routes.ts';\nimport { withoutPresented } from './presented-route.ts';\n\ninterface TabEntry {\n readonly tab: NativeTab;\n /** The key native identifies this tab by: its path, unique among the bar's items. */\n readonly key: string;\n /** The url this tab was last on, so returning to it returns where the user left. */\n url: string;\n ref: ComponentRef<unknown> | null;\n route: ActivatedRoute | null;\n detached: boolean;\n /** Stops feeding route params into the component's inputs. */\n unbind: () => void;\n /** Whether this tab is the one showing, for what its screens hold: see `SCREEN_IN_FRONT`. */\n readonly inFront: WritableSignal<boolean>;\n}\n\n/** What `tabSelected` carries. Only two fields matter here; the rest is diagnostics. */\ninterface TabSelection {\n readonly selectedScreenKey: string;\n readonly provenance: number;\n}\n\n@Component({\n selector: 'native-tabs-outlet',\n template: '<ng-content />',\n host: {\n '[style]': 'fill',\n '[tabBarHidden]': 'barHidden()',\n '[tabBarTintColor]': 'tintColor() ?? defaults().tintColor',\n '[nativeContainerBackgroundColor]': 'backgroundColor() ?? defaults().backgroundColor',\n '[colorScheme]': 'colorScheme() ?? defaults().colorScheme',\n '[tabBarMinimizeBehavior]': 'minimizeBehavior()',\n '[tabBarControllerMode]': 'controllerMode()',\n '[tabBarRespectsIMEInsets]': 'respectsKeyboard()',\n },\n})\nexport class NativeTabsOutlet implements RouterOutletContract, AfterContentInit {\n name = PRIMARY_OUTLET;\n\n readonly supportsBindingToComponentInputs = true;\n\n private readonly parentContexts = inject(ChildrenOutletContexts);\n private readonly renderer = inject(Renderer2);\n private readonly applicationRef = inject(ApplicationRef);\n private readonly changeDetector = inject(ChangeDetectorRef);\n private readonly injector = inject(Injector);\n private readonly host = inject(ElementRef);\n private readonly route = inject(ActivatedRoute);\n /** Optional so the outlet can be driven through `RouterOutletContract` directly, as tests do. */\n private readonly router = inject(Router, { optional: true });\n private readonly errors = inject(ErrorHandler);\n /** What activation has reported already, so the failed navigation it causes is not reported again. */\n private readonly reported = new WeakSet<object>();\n /** The app's `withTabDefaults`, for whatever the outlet does not bind itself. */\n protected readonly defaults = inject(NATIVE_TAB_DEFAULTS);\n\n private readonly declared = contentChildren(NativeTab);\n private readonly entries: TabEntry[] = [];\n private selected: TabEntry | null = null;\n /** The provenance of the last state native told us about. Starts before native's first. */\n private provenance = 0;\n\n protected readonly fill = { flex: 1 };\n\n // --- the bar itself -----------------------------------------------------\n\n /** Hide the bar without unmounting the tabs, for a screen that wants the whole window. */\n readonly barHidden = input(undefined, { transform: optionalBoolean });\n /** The colour of the selected item. iOS. */\n readonly tintColor = input<string | number>();\n /** Behind the tabs, where a screen does not paint. */\n readonly backgroundColor = input<string | number>();\n /** Force the bar's light or dark appearance, whatever the app's is. */\n readonly colorScheme = input<'inherit' | 'light' | 'dark'>();\n /** iOS 26: let the bar shrink out of the way as content scrolls. */\n readonly minimizeBehavior = input<'automatic' | 'never' | 'onScrollDown' | 'onScrollUp'>();\n /** iOS: `tabSidebar` is the iPad layout, with the tabs down the side. */\n readonly controllerMode = input<'automatic' | 'tabBar' | 'tabSidebar'>();\n /** Android: keep the bar above the keyboard rather than behind it. */\n readonly respectsKeyboard = input(undefined, { transform: optionalBoolean });\n\n constructor() {\n ownHost(this.host.nativeElement);\n // In the constructor rather than a lifecycle hook: the router activates a child route as soon\n // as this component exists, which is before any hook has run.\n this.parentContexts.onChildOutletCreated(this.name, this);\n\n this.renderer.listen(\n this.host.nativeElement,\n 'tabSelected',\n (event: { nativeEvent?: TabSelection }) => this.onNativeSelect(event?.nativeEvent),\n );\n\n // A push inside a tab's own stack reuses the tab's route, so this outlet is never activated\n // for it; the router's navigation is the only sign. Without this, coming back to the tab\n // returns it to its root and pops the screens the user left it on.\n const following = this.router?.events?.subscribe((event) => {\n // Without a page presented over the bar, which is no part of where the tab is.\n if (event instanceof NavigationEnd) {\n this.follow(withoutPresented(this.router!, event.urlAfterRedirects));\n }\n });\n\n // Android's back button, and `NativeNavigation.back()`, once the stack in front, if the tab\n // has one, is at its root. Subscribed before any tab's own stack, so it is asked after them.\n const back = inject(NativeBack);\n const unsubscribeBack = back.handle(() => this.goBack());\n const unsubscribeBar = back.addTabBar({\n unopenedTabOf: (url) => this.unopenedTabOf(url),\n behind: (url) => this.behind(url),\n });\n\n inject(DestroyRef).onDestroy(() => {\n unsubscribeBack();\n unsubscribeBar();\n following?.unsubscribe();\n for (const entry of this.entries) {\n entry.unbind();\n entry.ref?.destroy();\n }\n this.entries.length = 0;\n this.parentContexts.onChildOutletDestroyed(this.name);\n });\n }\n\n /** A bar nobody has navigated into yet still has to show a selected item. */\n ngAfterContentInit(): void {\n this.readTabs();\n this.activateCurrentRoute();\n }\n\n /**\n * The route the router is already on, for an outlet created after the navigation that reached\n * it, as `NativeStackOutlet` takes its own: a tab bar held behind a condition until a session\n * is ready. Once the tabs are read, since activating one needs to know which they are.\n */\n private activateCurrentRoute(): void {\n if (this.isActivated) return;\n const context = this.parentContexts.getContext(this.name);\n if (!context?.route) return;\n if (context.attachRef) this.attach(context.attachRef, context.route);\n else this.activateWith(context.route, context.injector);\n }\n\n /** The tabs in the bar, in template order. Exposed for tests. */\n get tabKeys(): string[] {\n return this.readTabs().map((entry) => entry.key);\n }\n\n /** Which tab native is showing. Exposed for tests. */\n get selectedKey(): string | null {\n return this.selected?.key ?? null;\n }\n\n // --- RouterOutletContract ----------------------------------------------\n\n get isActivated(): boolean {\n return this.selected?.ref != null && !this.selected.detached;\n }\n\n get component(): Object | null {\n return this.isActivated ? (this.selected!.ref!.instance as Object) : null;\n }\n\n get activatedRoute(): ActivatedRoute | null {\n return this.isActivated ? this.selected!.route : null;\n }\n\n get activatedRouteData(): Data {\n return this.activatedRoute?.snapshot.data ?? {};\n }\n\n /** The input binder reads this to bind route params to component inputs. */\n get activatedComponentRef(): ComponentRef<unknown> | null {\n return this.selected?.ref ?? null;\n }\n\n activateWith(route: ActivatedRoute, environmentInjector: EnvironmentInjector): void {\n const component = (route.component ?? route.snapshot.component) as Type<unknown> | null;\n if (!component) {\n throw new Error('[angular-native] route has no component to activate');\n }\n const entry = this.entryFor(route);\n\n // The tab screen is owned by <native-tab>, not by this route component. If the route uses\n // deactivate() rather than detach(), Angular removes the component host on destroy. Giving\n // the component its own child host keeps that removal from taking the tab bar item with it.\n const content = ownHost(this.renderer.createElement('view'));\n this.renderer.setStyle(content, 'flex', 1);\n this.renderer.appendChild(entry.tab.screen, content);\n\n let ref: ComponentRef<unknown> | undefined;\n try {\n ref = createComponent(component, {\n environmentInjector,\n elementInjector: this.outletInjector(route, entry.inFront),\n hostElement: content as Element,\n });\n this.applicationRef.attachView(ref.hostView);\n // Route params onto inputs before the first change detection, as the stack outlet does: a\n // required input still unset when the page first renders throws NG0950.\n entry.unbind = this.bindInputs(ref, route);\n // Before anything can commit. Creating the page's nodes dirties the tree, which schedules a\n // commit of its own - and that commit would capture the page with its elements created and\n // its bindings not yet applied. On iOS the props catch up a frame later and nobody sees it;\n // on Android the flattening decision is made from the props a view is created with, so a\n // content wrapper that arrives without its `collapsable: false` is flattened for good and\n // its children end up parented to a scroll view that allows exactly one.\n ref.changeDetectorRef.detectChanges();\n } catch (error) {\n this.abandon(entry, ref, content);\n this.errors.handleError(error);\n if (error !== null && typeof error === 'object') this.reported.add(error);\n throw error;\n }\n\n entry.ref = ref;\n entry.route = route;\n entry.detached = false;\n this.select(entry);\n }\n\n deactivate(): void {\n const entry = this.selected;\n if (!entry?.ref) return;\n entry.unbind();\n entry.unbind = () => {};\n entry.ref.destroy();\n entry.ref = null;\n entry.route = null;\n }\n\n /**\n * Hand the live `ComponentRef` back to the router. The tab's screen stays where it is with the\n * component still inside it, so the tab keeps running while another one is in front.\n */\n detach(): ComponentRef<unknown> {\n const entry = this.selected;\n if (!entry?.ref) throw new Error('[angular-native] outlet is not activated, nothing to detach');\n entry.detached = true;\n entry.inFront.set(false);\n // As in the stack outlet: stop change detection without unmounting the views.\n entry.ref.hostView.detach();\n return entry.ref;\n }\n\n attach(ref: ComponentRef<unknown>, route: ActivatedRoute): void {\n const entry = this.entryFor(route);\n\n // A ref from a previous instance of this outlet still lives on its old tab screen, which is\n // gone; re-parent it onto this one's rather than rebuild the tab.\n if (entry.ref !== ref) {\n this.renderer.appendChild(entry.tab.screen, ref.location.nativeElement);\n this.applicationRef.attachView(ref.hostView);\n entry.ref = ref;\n }\n\n entry.route = route;\n entry.detached = false;\n // The route object is new even when the url is not, so the inputs follow the new one.\n entry.unbind();\n entry.unbind = this.bindInputs(ref, route);\n ref.hostView.reattach();\n this.select(entry);\n }\n\n // --- native tabs --------------------------------------------------------\n\n /**\n * Undo a tab page that threw on its first render, which fails the navigation: its component\n * and its content view go, and the tab that was showing carries on, change detection and all.\n */\n private abandon(entry: TabEntry, ref: ComponentRef<unknown> | undefined, content: unknown): void {\n entry.unbind();\n entry.unbind = () => {};\n ref?.destroy();\n this.renderer.removeChild(entry.tab.screen, content);\n const showing = this.selected;\n if (showing?.detached && showing.ref) {\n showing.detached = false;\n showing.ref.hostView.reattach();\n }\n }\n\n /** Route params onto inputs only under `withComponentInputBinding()`, as in the stack outlet. */\n private bindInputs(ref: ComponentRef<unknown>, route: ActivatedRoute): () => void {\n return this.router?.componentInputBindingEnabled ? bindRouteInputs(ref, route) : () => {};\n }\n\n /**\n * Take the bar from the `<native-tab>` elements in the content, once.\n *\n * On demand rather than in a lifecycle hook, because the router activates a child route during\n * this component's creation, before any hook has run - but after the content nodes themselves\n * exist, which is all this needs.\n */\n private readTabs(): TabEntry[] {\n if (this.entries.length > 0) return this.entries;\n\n const children = this.route.routeConfig?.children ?? [];\n for (const tab of this.declared()) {\n const key = tab.path();\n this.entries.push({\n tab,\n key,\n url: '',\n ref: null,\n route: null,\n detached: false,\n unbind: () => {},\n inFront: signal(false),\n });\n // The reuse strategy detaches a tab whole, and has only the route to go on.\n // A path can have multiple route configs selected by canMatch (for example an iPad split\n // layout and an iPhone stack). Mark every variant: the reuse strategy sees the config\n // Angular actually matched, which may not be the first one in the route array.\n for (const config of children) {\n if (config.path === key) markTabRoute(config);\n }\n }\n\n if (this.entries.length === 0) {\n throw new Error(\n '[angular-native] <native-tabs-outlet> has no tabs. Put a <native-tab path=\"...\"> ' +\n 'inside it for each child route the bar should reach.',\n );\n }\n // Before the first navigation nothing is selected, which is not a state a tab bar has.\n this.request(this.entries[0]!);\n return this.entries;\n }\n\n private entryFor(route: ActivatedRoute): TabEntry {\n const path = (route.routeConfig ?? route.snapshot.routeConfig)?.path;\n const entry = this.readTabs().find((candidate) => candidate.key === path);\n if (!entry) {\n throw new Error(\n `[angular-native] no <native-tab> has path=\"${path}\". Every child route the bar can ` +\n `reach needs one, and its path is what native identifies the tab by.`,\n );\n }\n return entry;\n }\n\n /** Remember where this tab is, and tell native to show it. */\n private select(entry: TabEntry): void {\n this.selected = entry;\n for (const other of this.entries) other.inFront.set(other === entry);\n entry.url = this.router?.url ?? entry.url;\n this.request(entry);\n }\n\n /**\n * A back with nothing left to pop inside the tab: go to the first tab, where it was left, which\n * is what a bottom navigation bar does on Android. From the first tab, decline, so the platform\n * leaves the app. The order tabs were visited in is not retraced, so a back never lands on a\n * tab the user did not expect. Not while the bar is covered by a screen pushed over it: that\n * screen's stack answers instead.\n */\n private goBack(): boolean {\n const first = this.entries[0];\n const selected = this.selected;\n if (!first || !selected || selected === first || !this.router) return false;\n if (!this.isActivated || !isShowing(this.router, selected.route)) return false;\n void this.router.navigateByUrl(first.url || this.pathOf(first));\n return true;\n }\n\n /** Keep the tab in front on the url it has reached, as long as the url is still inside it. */\n private follow(url: string): void {\n const entry = this.selected;\n if (!entry) return;\n const root = this.pathOf(entry);\n if (url === root || url.startsWith(`${root}/`) || url.startsWith(`${root}?`)) entry.url = url;\n }\n\n /**\n * The url of the tab `url` is a page inside, when nothing has opened that tab: the tab's own\n * first screen, which a push into the tab from outside it shows first.\n */\n private unopenedTabOf(url: string): string | null {\n for (const entry of this.entries) {\n if (entry.ref) continue;\n const root = this.pathOf(entry);\n if (url.startsWith(`${root}/`)) return root;\n }\n return null;\n }\n\n /** Whether `url` is a tab other than the one in front, or a page inside one. */\n private behind(url: string): boolean {\n return this.entries.some((entry) => {\n if (entry === this.selected) return false;\n const root = this.pathOf(entry);\n return url === root || url.startsWith(`${root}/`);\n });\n }\n\n private request(entry: TabEntry): void {\n this.renderer.setProperty(this.host.nativeElement, 'navStateRequest', {\n selectedScreenKey: entry.key,\n baseProvenance: this.provenance,\n });\n }\n\n /**\n * Native switched tab. Acknowledge the new state, then navigate: the router is authoritative\n * about what is on screen (ADR 0003), so the tap becomes a navigation to where that tab was,\n * and the activation that follows is what fills the tab in.\n */\n private onNativeSelect(selection: TabSelection | undefined): void {\n if (!selection) return;\n this.provenance = selection.provenance;\n\n const entry = this.readTabs().find((tab) => tab.key === selection.selectedScreenKey);\n if (!entry || entry === this.selected) return;\n\n // Returning to a tab returns to the url it was last on, which is the whole point of keeping\n // it mounted. A tab never visited starts at its own path.\n // A navigation that fails is reported, unless activation reported it already: a page that\n // does not load, caught here, would otherwise leave nothing but a tab that will not open.\n void this.router?.navigateByUrl(entry.url || this.pathOf(entry)).then(\n (arrived) => this.revertUnless(arrived),\n (error: unknown) => {\n if (error === null || typeof error !== 'object' || !this.reported.has(error)) {\n this.errors.handleError(error);\n }\n this.revertUnless(false);\n },\n );\n }\n\n /**\n * Native switched tab before the router agreed to. When a guard refuses, or the navigation\n * fails, the router stays where it was and so must the bar, or it shows a tab nobody is on.\n * Not while another navigation is under way: that one superseded this, and its own activation\n * selects the tab it lands on.\n */\n private revertUnless(arrived: boolean): void {\n if (arrived || !this.selected || this.router?.getCurrentNavigation()) return;\n this.request(this.selected);\n // A failed navigation ends in a promise callback, which schedules no change detection, and a\n // prop reaches native only with the commit at the end of a pass. Without one the bar stayed\n // on the tab that failed until something else happened to render.\n this.changeDetector.markForCheck();\n }\n\n /** A tab's own url, from the outlet's route down. */\n private pathOf(entry: TabEntry): string {\n const parent = this.route.snapshot.pathFromRoot\n .flatMap((snapshot) => snapshot.url.map((segment) => segment.path))\n .join('/');\n return `/${[parent, entry.key].filter(Boolean).join('/')}`;\n }\n\n /** As the stack outlet's: the route, the outlet contexts, and whether the tab is showing. */\n private outletInjector(route: ActivatedRoute, inFront: WritableSignal<boolean>): Injector {\n const outer = this.injector.get(SCREEN_IN_FRONT);\n return Injector.create({\n parent: this.injector,\n providers: [\n { provide: SCREEN_IN_FRONT, useValue: computed(() => outer() && inFront()) },\n { provide: ActivatedRoute, useValue: route },\n {\n provide: ChildrenOutletContexts,\n useValue: this.parentContexts.getOrCreateContext(this.name).children,\n },\n ],\n });\n }\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A page presented over whatever is showing, when its url belongs to a tab that is not.
|
|
3
|
+
*
|
|
4
|
+
* The router puts a route where its url sits in the route tree: `/invoices/7` is the Invoices
|
|
5
|
+
* tab's, so navigating there selects that tab and shows the page in its stack. A sheet over the
|
|
6
|
+
* tab the user is on has to be somewhere else, and the router has a place for that, a named
|
|
7
|
+
* outlet at the root. So the page's route gets a copy there, made the first time it is presented,
|
|
8
|
+
* and `present()` navigates to the copy: `/home(presented:invoices/7)`. The router still runs the
|
|
9
|
+
* navigation, so the page's guards, resolvers, lazy component and params are what they are in its
|
|
10
|
+
* tab, and a back goes through history as any other does.
|
|
11
|
+
*/
|
|
12
|
+
import { type NavigationExtras, type Router, type UrlTree } from '@angular/router';
|
|
13
|
+
/** The root outlet a page of another tab is presented in. */
|
|
14
|
+
export declare const PRESENTED = "presented";
|
|
15
|
+
/**
|
|
16
|
+
* The commands that present the page at `path` in the root's presented outlet, having given its
|
|
17
|
+
* route a copy there, or null when the config is not one this follows.
|
|
18
|
+
*/
|
|
19
|
+
export declare function presentedCommands(router: Router, path: string): Promise<unknown[] | null>;
|
|
20
|
+
/** `url` without the page presented over it: where the app is, under the page. */
|
|
21
|
+
export declare function withoutPresented(router: Router, url: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Where `commands` lead with the presented page left behind, when one is up and the commands are
|
|
24
|
+
* not a presentation themselves; null otherwise. A navigation from a presented page goes to its
|
|
25
|
+
* url's own place, which the page is over: left up, it would hide where the app went.
|
|
26
|
+
*/
|
|
27
|
+
export declare function leavingPresented(router: Router, commands: readonly unknown[], extras: NavigationExtras): UrlTree | null;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A page presented over whatever is showing, when its url belongs to a tab that is not.
|
|
3
|
+
*
|
|
4
|
+
* The router puts a route where its url sits in the route tree: `/invoices/7` is the Invoices
|
|
5
|
+
* tab's, so navigating there selects that tab and shows the page in its stack. A sheet over the
|
|
6
|
+
* tab the user is on has to be somewhere else, and the router has a place for that, a named
|
|
7
|
+
* outlet at the root. So the page's route gets a copy there, made the first time it is presented,
|
|
8
|
+
* and `present()` navigates to the copy: `/home(presented:invoices/7)`. The router still runs the
|
|
9
|
+
* navigation, so the page's guards, resolvers, lazy component and params are what they are in its
|
|
10
|
+
* tab, and a back goes through history as any other does.
|
|
11
|
+
*/
|
|
12
|
+
import { PRIMARY_OUTLET, } from '@angular/router';
|
|
13
|
+
/** The root outlet a page of another tab is presented in. */
|
|
14
|
+
export const PRESENTED = 'presented';
|
|
15
|
+
/**
|
|
16
|
+
* How many of `segments` a route's path takes, or null when it does not match them or is one this
|
|
17
|
+
* does not follow: a redirect, a custom matcher, a wildcard, or a route of another outlet.
|
|
18
|
+
*
|
|
19
|
+
* ponytail: static and `:param` segments only, which is what a page's url is made of. The rest
|
|
20
|
+
* falls back to presenting the page where its url puts it, as before.
|
|
21
|
+
*/
|
|
22
|
+
function taken(route, segments) {
|
|
23
|
+
if (route.outlet && route.outlet !== PRIMARY_OUTLET)
|
|
24
|
+
return null;
|
|
25
|
+
if (route.redirectTo !== undefined || route.matcher || route.path === undefined)
|
|
26
|
+
return null;
|
|
27
|
+
const parts = route.path ? route.path.split('/') : [];
|
|
28
|
+
if (parts.includes('**') || parts.length > segments.length)
|
|
29
|
+
return null;
|
|
30
|
+
const matches = parts.every((part, index) => part.startsWith(':') || part === segments[index]);
|
|
31
|
+
return matches ? parts.length : null;
|
|
32
|
+
}
|
|
33
|
+
/** The children each lazily loaded route answered with, or null when they are not plain routes. */
|
|
34
|
+
const lazyChildren = new WeakMap();
|
|
35
|
+
/**
|
|
36
|
+
* A route's children, loading them when they are lazy. The router loads them again when it
|
|
37
|
+
* navigates, from the same function, which a dynamic import answers from its cache.
|
|
38
|
+
*
|
|
39
|
+
* ponytail: a loader that answers routes, or a module whose default export is routes, as
|
|
40
|
+
* `loadChildren: () => import('./invoices.routes')` does. An NgModule or an observable falls back
|
|
41
|
+
* to presenting the page where its url puts it.
|
|
42
|
+
*/
|
|
43
|
+
function childrenOf(route) {
|
|
44
|
+
if (route.children || !route.loadChildren)
|
|
45
|
+
return route.children;
|
|
46
|
+
let loading = lazyChildren.get(route);
|
|
47
|
+
if (!loading) {
|
|
48
|
+
loading = Promise.resolve()
|
|
49
|
+
.then(() => route.loadChildren())
|
|
50
|
+
.then((answer) => {
|
|
51
|
+
const routes = answer?.default ?? answer;
|
|
52
|
+
return Array.isArray(routes) ? routes : null;
|
|
53
|
+
})
|
|
54
|
+
// A loader that fails is the router's to report, when it navigates there as before.
|
|
55
|
+
.catch(() => null);
|
|
56
|
+
lazyChildren.set(route, loading);
|
|
57
|
+
}
|
|
58
|
+
return loading;
|
|
59
|
+
}
|
|
60
|
+
/** A route on the way that the copy cannot stand in for: the search stops, with no copy made. */
|
|
61
|
+
const STOP = Symbol('stop');
|
|
62
|
+
/** A route's children, or `STOP` where the copy cannot follow it: see `chainTo`. */
|
|
63
|
+
async function followable(route) {
|
|
64
|
+
if (route.canMatch?.length)
|
|
65
|
+
return STOP;
|
|
66
|
+
return (await childrenOf(route)) ?? (route.loadChildren ? STOP : undefined);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The routes from the root to the one that shows `segments`, null when none of `routes` leads
|
|
70
|
+
* there, or `STOP` at a route with a `canMatch` guard. The router runs that guard against the
|
|
71
|
+
* route and the segments it was matched with, which a copy under another path cannot give it,
|
|
72
|
+
* and leaving it out would let the page match where its own route refuses to.
|
|
73
|
+
*/
|
|
74
|
+
async function chainTo(routes, segments) {
|
|
75
|
+
for (const route of routes) {
|
|
76
|
+
const count = taken(route, segments);
|
|
77
|
+
if (count === null)
|
|
78
|
+
continue;
|
|
79
|
+
const rest = segments.slice(count);
|
|
80
|
+
const children = await followable(route);
|
|
81
|
+
if (children === STOP)
|
|
82
|
+
return STOP;
|
|
83
|
+
const inside = children && (await chainTo(children, rest));
|
|
84
|
+
if (inside === STOP)
|
|
85
|
+
return STOP;
|
|
86
|
+
if (inside)
|
|
87
|
+
return [route, ...inside];
|
|
88
|
+
// A page of its own: a component, and no children to choose among.
|
|
89
|
+
if (!rest.length && !children && (route.component || route.loadComponent))
|
|
90
|
+
return [route];
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The page's route as a root route of the presented outlet: its own component, title, data and
|
|
96
|
+
* resolvers, under the whole path, with the guards and providers of the routes it sat inside.
|
|
97
|
+
* Their components are left out: the tab bar and the tab's stack are not what is presented.
|
|
98
|
+
*/
|
|
99
|
+
function presentedCopy(chain) {
|
|
100
|
+
const page = chain.at(-1);
|
|
101
|
+
const above = chain.slice(0, -1);
|
|
102
|
+
return {
|
|
103
|
+
...page,
|
|
104
|
+
// Its own children are in the copy by now, where they were lazy.
|
|
105
|
+
loadChildren: undefined,
|
|
106
|
+
path: chain
|
|
107
|
+
.map((route) => route.path)
|
|
108
|
+
.filter(Boolean)
|
|
109
|
+
.join('/'),
|
|
110
|
+
pathMatch: undefined,
|
|
111
|
+
outlet: PRESENTED,
|
|
112
|
+
canActivate: [
|
|
113
|
+
...above.flatMap((route) => [
|
|
114
|
+
...(route.canActivate ?? []),
|
|
115
|
+
...(route.canActivateChild ?? []),
|
|
116
|
+
]),
|
|
117
|
+
...(page.canActivate ?? []),
|
|
118
|
+
],
|
|
119
|
+
providers: [...above.flatMap((route) => route.providers ?? []), ...(page.providers ?? [])],
|
|
120
|
+
resolve: Object.assign({}, ...above.map((route) => route.resolve), page.resolve),
|
|
121
|
+
data: Object.assign({}, ...above.map((route) => route.data), page.data),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/** The copies each router has, by the path they match. */
|
|
125
|
+
const copies = new WeakMap();
|
|
126
|
+
/**
|
|
127
|
+
* The commands that present the page at `path` in the root's presented outlet, having given its
|
|
128
|
+
* route a copy there, or null when the config is not one this follows.
|
|
129
|
+
*/
|
|
130
|
+
export async function presentedCommands(router, path) {
|
|
131
|
+
const segments = path.split('/').filter(Boolean);
|
|
132
|
+
const chain = await chainTo(router.config, segments);
|
|
133
|
+
if (!chain || chain === STOP)
|
|
134
|
+
return null;
|
|
135
|
+
const copy = presentedCopy(chain);
|
|
136
|
+
const known = copies.get(router) ?? new Set();
|
|
137
|
+
copies.set(router, known);
|
|
138
|
+
if (!known.has(copy.path)) {
|
|
139
|
+
known.add(copy.path);
|
|
140
|
+
router.config.push(copy);
|
|
141
|
+
}
|
|
142
|
+
return [{ outlets: { [PRESENTED]: segments } }];
|
|
143
|
+
}
|
|
144
|
+
/** `url` without the page presented over it: where the app is, under the page. */
|
|
145
|
+
export function withoutPresented(router, url) {
|
|
146
|
+
// A named outlet is written `(name:path)`, so a url without that has no page to take off.
|
|
147
|
+
if (!url.includes(`${PRESENTED}:`))
|
|
148
|
+
return url;
|
|
149
|
+
const tree = router.parseUrl(url);
|
|
150
|
+
delete tree.root.children[PRESENTED];
|
|
151
|
+
return router.serializeUrl(tree);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Where `commands` lead with the presented page left behind, when one is up and the commands are
|
|
155
|
+
* not a presentation themselves; null otherwise. A navigation from a presented page goes to its
|
|
156
|
+
* url's own place, which the page is over: left up, it would hide where the app went.
|
|
157
|
+
*/
|
|
158
|
+
export function leavingPresented(router, commands, extras) {
|
|
159
|
+
// A named outlet is written `(name:path)`, so a url without that has no page up.
|
|
160
|
+
if (!router.url?.includes(`${PRESENTED}:`))
|
|
161
|
+
return null;
|
|
162
|
+
const first = commands[0];
|
|
163
|
+
if (first?.outlets && PRESENTED in first.outlets)
|
|
164
|
+
return null;
|
|
165
|
+
const tree = router.createUrlTree([...commands], extras);
|
|
166
|
+
// Commands relative to the page itself, as a link inside it gives them, resolve inside the
|
|
167
|
+
// presented outlet: where they lead is that path in its own place, not beside the page.
|
|
168
|
+
const beside = extras.relativeTo?.pathFromRoot.some((route) => route.outlet === PRESENTED)
|
|
169
|
+
? tree.root.children[PRESENTED]
|
|
170
|
+
: undefined;
|
|
171
|
+
if (beside) {
|
|
172
|
+
const path = beside.segments.map((segment) => segment.path);
|
|
173
|
+
const { queryParams, fragment } = tree;
|
|
174
|
+
const own = router.createUrlTree(['/', ...path], {
|
|
175
|
+
queryParams,
|
|
176
|
+
fragment: fragment ?? undefined,
|
|
177
|
+
});
|
|
178
|
+
delete own.root.children[PRESENTED];
|
|
179
|
+
return own;
|
|
180
|
+
}
|
|
181
|
+
delete tree.root.children[PRESENTED];
|
|
182
|
+
return tree;
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=presented-route.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"presented-route.js","sourceRoot":"","sources":["../src/presented-route.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A page presented over whatever is showing, when its url belongs to a tab that is not.\n *\n * The router puts a route where its url sits in the route tree: `/invoices/7` is the Invoices\n * tab's, so navigating there selects that tab and shows the page in its stack. A sheet over the\n * tab the user is on has to be somewhere else, and the router has a place for that, a named\n * outlet at the root. So the page's route gets a copy there, made the first time it is presented,\n * and `present()` navigates to the copy: `/home(presented:invoices/7)`. The router still runs the\n * navigation, so the page's guards, resolvers, lazy component and params are what they are in its\n * tab, and a back goes through history as any other does.\n */\nimport {\n PRIMARY_OUTLET,\n type NavigationExtras,\n type Route,\n type Router,\n type Routes,\n type UrlTree,\n} from '@angular/router';\n\n/** The root outlet a page of another tab is presented in. */\nexport const PRESENTED = 'presented';\n\n/**\n * How many of `segments` a route's path takes, or null when it does not match them or is one this\n * does not follow: a redirect, a custom matcher, a wildcard, or a route of another outlet.\n *\n * ponytail: static and `:param` segments only, which is what a page's url is made of. The rest\n * falls back to presenting the page where its url puts it, as before.\n */\nfunction taken(route: Route, segments: readonly string[]): number | null {\n if (route.outlet && route.outlet !== PRIMARY_OUTLET) return null;\n if (route.redirectTo !== undefined || route.matcher || route.path === undefined) return null;\n const parts = route.path ? route.path.split('/') : [];\n if (parts.includes('**') || parts.length > segments.length) return null;\n const matches = parts.every((part, index) => part.startsWith(':') || part === segments[index]);\n return matches ? parts.length : null;\n}\n\n/** The children each lazily loaded route answered with, or null when they are not plain routes. */\nconst lazyChildren = new WeakMap<Route, Promise<Routes | null>>();\n\n/**\n * A route's children, loading them when they are lazy. The router loads them again when it\n * navigates, from the same function, which a dynamic import answers from its cache.\n *\n * ponytail: a loader that answers routes, or a module whose default export is routes, as\n * `loadChildren: () => import('./invoices.routes')` does. An NgModule or an observable falls back\n * to presenting the page where its url puts it.\n */\nfunction childrenOf(route: Route): Promise<Routes | null> | Routes | undefined {\n if (route.children || !route.loadChildren) return route.children;\n let loading = lazyChildren.get(route);\n if (!loading) {\n loading = Promise.resolve()\n .then(() => (route.loadChildren as () => unknown)())\n .then((answer) => {\n const routes = (answer as { default?: unknown } | null)?.default ?? answer;\n return Array.isArray(routes) ? (routes as Routes) : null;\n })\n // A loader that fails is the router's to report, when it navigates there as before.\n .catch(() => null);\n lazyChildren.set(route, loading);\n }\n return loading;\n}\n\n/** A route on the way that the copy cannot stand in for: the search stops, with no copy made. */\nconst STOP = Symbol('stop');\n\n/** A route's children, or `STOP` where the copy cannot follow it: see `chainTo`. */\nasync function followable(route: Route): Promise<Routes | undefined | typeof STOP> {\n if (route.canMatch?.length) return STOP;\n return (await childrenOf(route)) ?? (route.loadChildren ? STOP : undefined);\n}\n\n/**\n * The routes from the root to the one that shows `segments`, null when none of `routes` leads\n * there, or `STOP` at a route with a `canMatch` guard. The router runs that guard against the\n * route and the segments it was matched with, which a copy under another path cannot give it,\n * and leaving it out would let the page match where its own route refuses to.\n */\nasync function chainTo(\n routes: Routes,\n segments: readonly string[],\n): Promise<Route[] | null | typeof STOP> {\n for (const route of routes) {\n const count = taken(route, segments);\n if (count === null) continue;\n const rest = segments.slice(count);\n const children = await followable(route);\n if (children === STOP) return STOP;\n const inside = children && (await chainTo(children, rest));\n if (inside === STOP) return STOP;\n if (inside) return [route, ...inside];\n // A page of its own: a component, and no children to choose among.\n if (!rest.length && !children && (route.component || route.loadComponent)) return [route];\n }\n return null;\n}\n\n/**\n * The page's route as a root route of the presented outlet: its own component, title, data and\n * resolvers, under the whole path, with the guards and providers of the routes it sat inside.\n * Their components are left out: the tab bar and the tab's stack are not what is presented.\n */\nfunction presentedCopy(chain: readonly Route[]): Route {\n const page = chain.at(-1)!;\n const above = chain.slice(0, -1);\n return {\n ...page,\n // Its own children are in the copy by now, where they were lazy.\n loadChildren: undefined,\n path: chain\n .map((route) => route.path)\n .filter(Boolean)\n .join('/'),\n pathMatch: undefined,\n outlet: PRESENTED,\n canActivate: [\n ...above.flatMap((route) => [\n ...(route.canActivate ?? []),\n ...(route.canActivateChild ?? []),\n ]),\n ...(page.canActivate ?? []),\n ],\n providers: [...above.flatMap((route) => route.providers ?? []), ...(page.providers ?? [])],\n resolve: Object.assign({}, ...above.map((route) => route.resolve), page.resolve),\n data: Object.assign({}, ...above.map((route) => route.data), page.data),\n };\n}\n\n/** The copies each router has, by the path they match. */\nconst copies = new WeakMap<Router, Set<string>>();\n\n/**\n * The commands that present the page at `path` in the root's presented outlet, having given its\n * route a copy there, or null when the config is not one this follows.\n */\nexport async function presentedCommands(router: Router, path: string): Promise<unknown[] | null> {\n const segments = path.split('/').filter(Boolean);\n const chain = await chainTo(router.config, segments);\n if (!chain || chain === STOP) return null;\n const copy = presentedCopy(chain);\n const known = copies.get(router) ?? new Set<string>();\n copies.set(router, known);\n if (!known.has(copy.path!)) {\n known.add(copy.path!);\n router.config.push(copy);\n }\n return [{ outlets: { [PRESENTED]: segments } }];\n}\n\n/** `url` without the page presented over it: where the app is, under the page. */\nexport function withoutPresented(router: Router, url: string): string {\n // A named outlet is written `(name:path)`, so a url without that has no page to take off.\n if (!url.includes(`${PRESENTED}:`)) return url;\n const tree = router.parseUrl(url);\n delete tree.root.children[PRESENTED];\n return router.serializeUrl(tree);\n}\n\n/**\n * Where `commands` lead with the presented page left behind, when one is up and the commands are\n * not a presentation themselves; null otherwise. A navigation from a presented page goes to its\n * url's own place, which the page is over: left up, it would hide where the app went.\n */\nexport function leavingPresented(\n router: Router,\n commands: readonly unknown[],\n extras: NavigationExtras,\n): UrlTree | null {\n // A named outlet is written `(name:path)`, so a url without that has no page up.\n if (!router.url?.includes(`${PRESENTED}:`)) return null;\n const first = commands[0] as { outlets?: Record<string, unknown> } | undefined;\n if (first?.outlets && PRESENTED in first.outlets) return null;\n const tree = router.createUrlTree([...commands], extras);\n // Commands relative to the page itself, as a link inside it gives them, resolve inside the\n // presented outlet: where they lead is that path in its own place, not beside the page.\n const beside = extras.relativeTo?.pathFromRoot.some((route) => route.outlet === PRESENTED)\n ? tree.root.children[PRESENTED]\n : undefined;\n if (beside) {\n const path = beside.segments.map((segment) => segment.path);\n const { queryParams, fragment } = tree;\n const own = router.createUrlTree(['/', ...path], {\n queryParams,\n fragment: fragment ?? undefined,\n });\n delete own.root.children[PRESENTED];\n return own;\n }\n delete tree.root.children[PRESENTED];\n return tree;\n}\n"]}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { PlatformLocation } from '@angular/common';
|
|
16
16
|
import { NavigationCancel, NavigationEnd, NavigationError, RouteReuseStrategy, Router, provideRouter, } from '@angular/router';
|
|
17
|
-
import { computed, inject, makeEnvironmentProviders, provideEnvironmentInitializer, } from '@angular/core';
|
|
17
|
+
import { ErrorHandler, computed, inject, makeEnvironmentProviders, provideEnvironmentInitializer, } from '@angular/core';
|
|
18
18
|
import { ColorScheme, DeepLinks } from '@ng-native/device';
|
|
19
19
|
import { NATIVE_HEADER_DEFAULTS, NATIVE_TAB_DEFAULTS, } from "./native-bar-defaults.js";
|
|
20
20
|
import { followLink, linkAncestry } from "./native-links.js";
|
|
@@ -115,13 +115,17 @@ function nativeProviders(parentOf) {
|
|
|
115
115
|
provideEnvironmentInitializer(() => {
|
|
116
116
|
const router = inject(Router);
|
|
117
117
|
const links = inject(DeepLinks);
|
|
118
|
+
const errors = inject(ErrorHandler);
|
|
119
|
+
// A link nobody awaits: a page that fails to load is reported, as a tab tap's is, rather
|
|
120
|
+
// than left as an unhandled rejection. The app's navigation error handler hears it too.
|
|
121
|
+
const follow = (url) => followLink(router, url, parentOf).catch((error) => errors.handleError(error));
|
|
118
122
|
const initial = links.initialUrl();
|
|
119
123
|
if (initial && parentOf(initial)) {
|
|
120
124
|
const first = router.events.subscribe((event) => {
|
|
121
125
|
if (!(event instanceof NavigationEnd))
|
|
122
126
|
return;
|
|
123
127
|
first.unsubscribe();
|
|
124
|
-
void
|
|
128
|
+
void follow(initial);
|
|
125
129
|
});
|
|
126
130
|
}
|
|
127
131
|
// A launch link arrives the way any other does, once `getInitialURL()` settles, which is
|
|
@@ -137,7 +141,7 @@ function nativeProviders(parentOf) {
|
|
|
137
141
|
resolve();
|
|
138
142
|
});
|
|
139
143
|
});
|
|
140
|
-
links.subscribe((url) => void settled.then(() =>
|
|
144
|
+
links.subscribe((url) => void settled.then(() => follow(url)));
|
|
141
145
|
}),
|
|
142
146
|
...shared,
|
|
143
147
|
];
|