@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.
@@ -10,7 +10,16 @@ import { BaseRouteReuseStrategy, type ActivatedRouteSnapshot, type DetachedRoute
10
10
  */
11
11
  export declare function reuseScreen<T extends Route>(route: T): T;
12
12
  export declare class NativeStackReuseStrategy extends BaseRouteReuseStrategy {
13
+ /**
14
+ * The screens kept for each url, oldest first. More than one where the same url is on the stack
15
+ * twice: a customer, one of its jobs, and the customer again from the job.
16
+ */
13
17
  private readonly handles;
18
+ /**
19
+ * For the navigation in flight, read when it is asked for: the router is built from this. Null
20
+ * where the strategy is made by hand, outside an injector, and every navigation is then plain.
21
+ */
22
+ private readonly injector;
14
23
  /**
15
24
  * Only leaf routes become screens; a parent with children is layout, not a destination.
16
25
  *
@@ -24,7 +33,6 @@ export declare class NativeStackReuseStrategy extends BaseRouteReuseStrategy {
24
33
  */
25
34
  shouldDetach(route: ActivatedRouteSnapshot): boolean;
26
35
  store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle | null): void;
27
- /** Refs whose destruction already drops their handles. */
28
36
  private readonly watched;
29
37
  /**
30
38
  * Drop a screen's handles the moment its component is destroyed.
@@ -46,12 +54,16 @@ export declare class NativeStackReuseStrategy extends BaseRouteReuseStrategy {
46
54
  * parent that is only layout is reused as before, so its stack keeps what is in it.
47
55
  */
48
56
  shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean;
49
- shouldAttach(route: ActivatedRouteSnapshot): boolean;
50
57
  /**
51
- * The router only ever tells the strategy about a screen it detached, never about one the
52
- * outlet destroyed on a pop, so a handle can outlive its component. Pushing A, then B, going
53
- * back and pushing B again would otherwise attach B's destroyed ref: a screen that renders and
54
- * never updates. The ref knows it is dead, so ask it rather than couple the two.
58
+ * Whether the router goes back to a kept screen for this url. Not for a push: a push to a url
59
+ * already on the stack is a new screen over it, as a native stack makes one, and Back returns
60
+ * to where the push came from. Going back to the kept one is a back, or `popTo`.
61
+ *
62
+ * Never for a route with no component, which is never kept: the router asks for every route,
63
+ * and a group at path `''` has the same url as a page at `''` beside it, whose screen it would
64
+ * otherwise be handed.
55
65
  */
66
+ shouldAttach(route: ActivatedRouteSnapshot): boolean;
67
+ private pushing;
56
68
  retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null;
57
69
  }
@@ -1,4 +1,17 @@
1
- import { BaseRouteReuseStrategy, } from '@angular/router';
1
+ /**
2
+ * The reuse strategy that turns navigation into a stack.
3
+ *
4
+ * Angular's default strategy never detaches, so navigating away destroys the component and its
5
+ * native views, and coming back rebuilds them from scratch: a list is back at the top, a form is
6
+ * empty. Detaching instead hands the live `ComponentRef` to the outlet, which keeps its screen
7
+ * mounted. That is the whole reason `RouterOutletContract.detach()` exists.
8
+ *
9
+ * No decorators, so tests can import it and so it can be provided with `useClass`.
10
+ */
11
+ import { Injector, inject } from '@angular/core';
12
+ import { BaseRouteReuseStrategy, Router, } from '@angular/router';
13
+ import { intentOf } from "./native-navigation.js";
14
+ import { PRESENTED } from "./presented-route.js";
2
15
  import { isScreenRoute, isTabRoute } from "./tab-routes.js";
3
16
  /**
4
17
  * Where `reuseScreen` marks a route. On the config's `data` rather than in a set of configs,
@@ -33,8 +46,25 @@ function keyOf(route) {
33
46
  }
34
47
  return JSON.stringify(levels);
35
48
  }
49
+ function injectorHere() {
50
+ try {
51
+ return inject(Injector);
52
+ }
53
+ catch {
54
+ return null;
55
+ }
56
+ }
36
57
  export class NativeStackReuseStrategy extends BaseRouteReuseStrategy {
58
+ /**
59
+ * The screens kept for each url, oldest first. More than one where the same url is on the stack
60
+ * twice: a customer, one of its jobs, and the customer again from the job.
61
+ */
37
62
  handles = new Map();
63
+ /**
64
+ * For the navigation in flight, read when it is asked for: the router is built from this. Null
65
+ * where the strategy is made by hand, outside an injector, and every navigation is then plain.
66
+ */
67
+ injector = injectorHere();
38
68
  /**
39
69
  * Only leaf routes become screens; a parent with children is layout, not a destination.
40
70
  *
@@ -49,21 +79,28 @@ export class NativeStackReuseStrategy extends BaseRouteReuseStrategy {
49
79
  shouldDetach(route) {
50
80
  if (route.component === null)
51
81
  return false;
82
+ // A page presented over the tabs is dismissed, not kept: nothing goes back to it.
83
+ if (route.outlet === PRESENTED)
84
+ return false;
52
85
  return (route.children.length === 0 ||
53
86
  isTabRoute(route.routeConfig) ||
54
87
  isScreenRoute(route.routeConfig));
55
88
  }
56
89
  store(route, handle) {
57
90
  const key = keyOf(route);
58
- // A null handle is the router telling us the screen is gone for good.
91
+ const kept = this.handles.get(key) ?? [];
59
92
  if (handle) {
60
- this.handles.set(key, handle);
93
+ // The same screen stored again takes its place at the end, not a second one.
94
+ this.handles.set(key, [...kept.filter((one) => one !== handle), handle]);
61
95
  this.forgetOnDestroy(handle.componentRef);
96
+ return;
62
97
  }
98
+ // The router has re-attached the one `retrieve` answered with: the last.
99
+ if (kept.length > 1)
100
+ this.handles.set(key, kept.slice(0, -1));
63
101
  else
64
102
  this.handles.delete(key);
65
103
  }
66
- /** Refs whose destruction already drops their handles. */
67
104
  watched = new WeakSet();
68
105
  /**
69
106
  * Drop a screen's handles the moment its component is destroyed.
@@ -79,8 +116,11 @@ export class NativeStackReuseStrategy extends BaseRouteReuseStrategy {
79
116
  return;
80
117
  this.watched.add(ref);
81
118
  ref.onDestroy(() => {
82
- for (const [key, handle] of this.handles) {
83
- if (handle.componentRef === ref)
119
+ for (const [key, kept] of this.handles) {
120
+ const left = kept.filter((handle) => handle.componentRef !== ref);
121
+ if (left.length)
122
+ this.handles.set(key, left);
123
+ else
84
124
  this.handles.delete(key);
85
125
  }
86
126
  });
@@ -102,25 +142,37 @@ export class NativeStackReuseStrategy extends BaseRouteReuseStrategy {
102
142
  return true;
103
143
  return keyOf(future) === keyOf(curr);
104
144
  }
145
+ /**
146
+ * Whether the router goes back to a kept screen for this url. Not for a push: a push to a url
147
+ * already on the stack is a new screen over it, as a native stack makes one, and Back returns
148
+ * to where the push came from. Going back to the kept one is a back, or `popTo`.
149
+ *
150
+ * Never for a route with no component, which is never kept: the router asks for every route,
151
+ * and a group at path `''` has the same url as a page at `''` beside it, whose screen it would
152
+ * otherwise be handed.
153
+ */
105
154
  shouldAttach(route) {
155
+ if (route.component === null || this.pushing())
156
+ return false;
106
157
  return this.retrieve(route) !== null;
107
158
  }
108
- /**
109
- * The router only ever tells the strategy about a screen it detached, never about one the
110
- * outlet destroyed on a pop, so a handle can outlive its component. Pushing A, then B, going
111
- * back and pushing B again would otherwise attach B's destroyed ref: a screen that renders and
112
- * never updates. The ref knows it is dead, so ask it rather than couple the two.
113
- */
159
+ pushing() {
160
+ const navigation = this.injector?.get(Router).currentNavigation();
161
+ // A back restores the state its history entry was pushed with, intent and all: that is the
162
+ // screen being returned to, not a push.
163
+ if (!navigation || navigation.trigger === 'popstate')
164
+ return false;
165
+ return intentOf(navigation.extras.state)?.stack === 'push';
166
+ }
114
167
  retrieve(route) {
115
168
  const key = keyOf(route);
116
- const handle = this.handles.get(key);
117
- if (!handle)
118
- return null;
119
- if (handle.componentRef?.hostView.destroyed) {
169
+ const kept = (this.handles.get(key) ?? []).filter((handle) => !handle.componentRef?.hostView.destroyed);
170
+ if (kept.length)
171
+ this.handles.set(key, kept);
172
+ else
120
173
  this.handles.delete(key);
121
- return null;
122
- }
123
- return handle;
174
+ // The newest: the one a back from the top of the stack returns to.
175
+ return kept.at(-1) ?? null;
124
176
  }
125
177
  }
126
178
  //# sourceMappingURL=native-stack-reuse-strategy.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"native-stack-reuse-strategy.js","sourceRoot":"","sources":["../src/native-stack-reuse-strategy.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The reuse strategy that turns navigation into a stack.\n *\n * Angular's default strategy never detaches, so navigating away destroys the component and its\n * native views, and coming back rebuilds them from scratch: a list is back at the top, a form is\n * empty. Detaching instead hands the live `ComponentRef` to the outlet, which keeps its screen\n * mounted. That is the whole reason `RouterOutletContract.detach()` exists.\n *\n * No decorators, so tests can import it and so it can be provided with `useClass`.\n */\nimport type { ComponentRef } from '@angular/core';\nimport {\n BaseRouteReuseStrategy,\n type ActivatedRouteSnapshot,\n type DetachedRouteHandle,\n type Route,\n} from '@angular/router';\nimport { isScreenRoute, isTabRoute } from './tab-routes.ts';\n\n/**\n * Where `reuseScreen` marks a route. On the config's `data` rather than in a set of configs,\n * because the router copies every route config it is given, so the object an app wrote is never\n * the one a snapshot points at.\n */\nconst REUSE_SCREEN = 'ɵreuseScreen';\n\n/**\n * Keep one screen for a route whatever its parameters, updating it in place as Angular's own\n * strategy does, instead of pushing a new screen for each url.\n *\n * `reuseScreen({ path: 'photo/:index', component: Photo })` for a route whose parameter picks\n * what one screen shows - a pager, a step through a list - rather than naming another screen to\n * go to. Everything else stacks: `/user/1` then `/user/2` is two screens, and back returns to the\n * first.\n */\nexport function reuseScreen<T extends Route>(route: T): T {\n return { ...route, data: { ...route.data, [REUSE_SCREEN]: true } };\n}\n\n/** What the router actually stores: `DetachedRouteHandle` is typed as an opaque object. */\ninterface StoredHandle {\n readonly componentRef?: ComponentRef<unknown>;\n}\n\n/**\n * Identify a screen by its resolved url rather than its route config, so `/user/1` and `/user/2`\n * are two screens on the stack rather than one that gets reused.\n *\n * One entry per level of the route tree, empty ones included. Flattened to a url, a tab at\n * `/tabs/library` and the list at path `''` beneath it are the same string, and the router, asking\n * for the list, is handed the tab's whole tree and makes the tab its own child.\n */\nfunction keyOf(route: ActivatedRouteSnapshot): string {\n const levels: string[] = [];\n for (let current: ActivatedRouteSnapshot | null = route; current; current = current.parent) {\n levels.unshift(current.url.map((segment) => segment.toString()).join('/'));\n }\n return JSON.stringify(levels);\n}\n\nexport class NativeStackReuseStrategy extends BaseRouteReuseStrategy {\n private readonly handles = new Map<string, DetachedRouteHandle>();\n\n /**\n * Only leaf routes become screens; a parent with children is layout, not a destination.\n *\n * A tab is the exception, and has to be. Detaching only its leaf would rebuild the tab itself\n * on every switch, and with it the stack outlet inside: a tab two screens deep would come back\n * one screen deep, having lost the screens the user could go back to. Detaching the tab keeps\n * its whole subtree, which is what makes each tab remember where it was.\n *\n * So is a parent a native stack put on screen, such as a root stack's tab bar: a sheet presented\n * over it has to leave it standing (see `markScreenRoute`).\n */\n override shouldDetach(route: ActivatedRouteSnapshot): boolean {\n if (route.component === null) return false;\n return (\n route.children.length === 0 ||\n isTabRoute(route.routeConfig) ||\n isScreenRoute(route.routeConfig)\n );\n }\n\n override store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle | null): void {\n const key = keyOf(route);\n // A null handle is the router telling us the screen is gone for good.\n if (handle) {\n this.handles.set(key, handle);\n this.forgetOnDestroy((handle as StoredHandle).componentRef);\n } else this.handles.delete(key);\n }\n\n /** Refs whose destruction already drops their handles. */\n private readonly watched = new WeakSet<ComponentRef<unknown>>();\n\n /**\n * Drop a screen's handles the moment its component is destroyed.\n *\n * The router stores a handle for every screen it navigates away from, the one being popped\n * included, and the outlet then destroys that screen. A handle outlives its component, and holds\n * the component's whole view and every native node in it; `retrieve` notices only when the same\n * url is asked for again, so a url never revisited kept its last screen for as long as the app\n * ran.\n */\n private forgetOnDestroy(ref: ComponentRef<unknown> | undefined): void {\n if (!ref || this.watched.has(ref)) return;\n this.watched.add(ref);\n ref.onDestroy(() => {\n for (const [key, handle] of this.handles) {\n if ((handle as StoredHandle).componentRef === ref) this.handles.delete(key);\n }\n });\n }\n\n /**\n * Angular reuses a route whenever its config is the same, so on the web `/user/1` to `/user/2`\n * keeps the one component and feeds it the new params. On a native stack that turns a push into\n * an update in place: no push animation, one screen, and back skipping past `/user/1`. A screen\n * a stack put on screen is reused only when its url is the same too, so a different parameter\n * is a different screen, which the router detaches and stores as it does any screen pushed\n * away. A query or fragment change is not part of the url that identifies a screen, and a\n * parent that is only layout is reused as before, so its stack keeps what is in it.\n */\n override shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {\n if (future.routeConfig !== curr.routeConfig) return false;\n const config = curr.routeConfig;\n if (!isScreenRoute(config) || config?.data?.[REUSE_SCREEN] === true) return true;\n return keyOf(future) === keyOf(curr);\n }\n\n override shouldAttach(route: ActivatedRouteSnapshot): boolean {\n return this.retrieve(route) !== null;\n }\n\n /**\n * The router only ever tells the strategy about a screen it detached, never about one the\n * outlet destroyed on a pop, so a handle can outlive its component. Pushing A, then B, going\n * back and pushing B again would otherwise attach B's destroyed ref: a screen that renders and\n * never updates. The ref knows it is dead, so ask it rather than couple the two.\n */\n override retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {\n const key = keyOf(route);\n const handle = this.handles.get(key) as StoredHandle | undefined;\n if (!handle) return null;\n if (handle.componentRef?.hostView.destroyed) {\n this.handles.delete(key);\n return null;\n }\n return handle;\n }\n}\n"]}
1
+ {"version":3,"file":"native-stack-reuse-strategy.js","sourceRoot":"","sources":["../src/native-stack-reuse-strategy.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The reuse strategy that turns navigation into a stack.\n *\n * Angular's default strategy never detaches, so navigating away destroys the component and its\n * native views, and coming back rebuilds them from scratch: a list is back at the top, a form is\n * empty. Detaching instead hands the live `ComponentRef` to the outlet, which keeps its screen\n * mounted. That is the whole reason `RouterOutletContract.detach()` exists.\n *\n * No decorators, so tests can import it and so it can be provided with `useClass`.\n */\nimport { Injector, inject, type ComponentRef } from '@angular/core';\nimport {\n BaseRouteReuseStrategy,\n type ActivatedRouteSnapshot,\n type DetachedRouteHandle,\n Router,\n type Route,\n} from '@angular/router';\nimport { intentOf } from './native-navigation.ts';\nimport { PRESENTED } from './presented-route.ts';\nimport { isScreenRoute, isTabRoute } from './tab-routes.ts';\n\n/**\n * Where `reuseScreen` marks a route. On the config's `data` rather than in a set of configs,\n * because the router copies every route config it is given, so the object an app wrote is never\n * the one a snapshot points at.\n */\nconst REUSE_SCREEN = 'ɵreuseScreen';\n\n/**\n * Keep one screen for a route whatever its parameters, updating it in place as Angular's own\n * strategy does, instead of pushing a new screen for each url.\n *\n * `reuseScreen({ path: 'photo/:index', component: Photo })` for a route whose parameter picks\n * what one screen shows - a pager, a step through a list - rather than naming another screen to\n * go to. Everything else stacks: `/user/1` then `/user/2` is two screens, and back returns to the\n * first.\n */\nexport function reuseScreen<T extends Route>(route: T): T {\n return { ...route, data: { ...route.data, [REUSE_SCREEN]: true } };\n}\n\n/** What the router actually stores: `DetachedRouteHandle` is typed as an opaque object. */\ninterface StoredHandle {\n readonly componentRef?: ComponentRef<unknown>;\n}\n\n/**\n * Identify a screen by its resolved url rather than its route config, so `/user/1` and `/user/2`\n * are two screens on the stack rather than one that gets reused.\n *\n * One entry per level of the route tree, empty ones included. Flattened to a url, a tab at\n * `/tabs/library` and the list at path `''` beneath it are the same string, and the router, asking\n * for the list, is handed the tab's whole tree and makes the tab its own child.\n */\nfunction keyOf(route: ActivatedRouteSnapshot): string {\n const levels: string[] = [];\n for (let current: ActivatedRouteSnapshot | null = route; current; current = current.parent) {\n levels.unshift(current.url.map((segment) => segment.toString()).join('/'));\n }\n return JSON.stringify(levels);\n}\n\nfunction injectorHere(): Injector | null {\n try {\n return inject(Injector);\n } catch {\n return null;\n }\n}\n\nexport class NativeStackReuseStrategy extends BaseRouteReuseStrategy {\n /**\n * The screens kept for each url, oldest first. More than one where the same url is on the stack\n * twice: a customer, one of its jobs, and the customer again from the job.\n */\n private readonly handles = new Map<string, DetachedRouteHandle[]>();\n /**\n * For the navigation in flight, read when it is asked for: the router is built from this. Null\n * where the strategy is made by hand, outside an injector, and every navigation is then plain.\n */\n private readonly injector = injectorHere();\n\n /**\n * Only leaf routes become screens; a parent with children is layout, not a destination.\n *\n * A tab is the exception, and has to be. Detaching only its leaf would rebuild the tab itself\n * on every switch, and with it the stack outlet inside: a tab two screens deep would come back\n * one screen deep, having lost the screens the user could go back to. Detaching the tab keeps\n * its whole subtree, which is what makes each tab remember where it was.\n *\n * So is a parent a native stack put on screen, such as a root stack's tab bar: a sheet presented\n * over it has to leave it standing (see `markScreenRoute`).\n */\n override shouldDetach(route: ActivatedRouteSnapshot): boolean {\n if (route.component === null) return false;\n // A page presented over the tabs is dismissed, not kept: nothing goes back to it.\n if (route.outlet === PRESENTED) return false;\n return (\n route.children.length === 0 ||\n isTabRoute(route.routeConfig) ||\n isScreenRoute(route.routeConfig)\n );\n }\n\n override store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle | null): void {\n const key = keyOf(route);\n const kept = this.handles.get(key) ?? [];\n if (handle) {\n // The same screen stored again takes its place at the end, not a second one.\n this.handles.set(key, [...kept.filter((one) => one !== handle), handle]);\n this.forgetOnDestroy((handle as StoredHandle).componentRef);\n return;\n }\n // The router has re-attached the one `retrieve` answered with: the last.\n if (kept.length > 1) this.handles.set(key, kept.slice(0, -1));\n else this.handles.delete(key);\n }\n\n private readonly watched = new WeakSet<ComponentRef<unknown>>();\n\n /**\n * Drop a screen's handles the moment its component is destroyed.\n *\n * The router stores a handle for every screen it navigates away from, the one being popped\n * included, and the outlet then destroys that screen. A handle outlives its component, and holds\n * the component's whole view and every native node in it; `retrieve` notices only when the same\n * url is asked for again, so a url never revisited kept its last screen for as long as the app\n * ran.\n */\n private forgetOnDestroy(ref: ComponentRef<unknown> | undefined): void {\n if (!ref || this.watched.has(ref)) return;\n this.watched.add(ref);\n ref.onDestroy(() => {\n for (const [key, kept] of this.handles) {\n const left = kept.filter((handle) => (handle as StoredHandle).componentRef !== ref);\n if (left.length) this.handles.set(key, left);\n else this.handles.delete(key);\n }\n });\n }\n\n /**\n * Angular reuses a route whenever its config is the same, so on the web `/user/1` to `/user/2`\n * keeps the one component and feeds it the new params. On a native stack that turns a push into\n * an update in place: no push animation, one screen, and back skipping past `/user/1`. A screen\n * a stack put on screen is reused only when its url is the same too, so a different parameter\n * is a different screen, which the router detaches and stores as it does any screen pushed\n * away. A query or fragment change is not part of the url that identifies a screen, and a\n * parent that is only layout is reused as before, so its stack keeps what is in it.\n */\n override shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {\n if (future.routeConfig !== curr.routeConfig) return false;\n const config = curr.routeConfig;\n if (!isScreenRoute(config) || config?.data?.[REUSE_SCREEN] === true) return true;\n return keyOf(future) === keyOf(curr);\n }\n\n /**\n * Whether the router goes back to a kept screen for this url. Not for a push: a push to a url\n * already on the stack is a new screen over it, as a native stack makes one, and Back returns\n * to where the push came from. Going back to the kept one is a back, or `popTo`.\n *\n * Never for a route with no component, which is never kept: the router asks for every route,\n * and a group at path `''` has the same url as a page at `''` beside it, whose screen it would\n * otherwise be handed.\n */\n override shouldAttach(route: ActivatedRouteSnapshot): boolean {\n if (route.component === null || this.pushing()) return false;\n return this.retrieve(route) !== null;\n }\n\n private pushing(): boolean {\n const navigation = this.injector?.get(Router).currentNavigation();\n // A back restores the state its history entry was pushed with, intent and all: that is the\n // screen being returned to, not a push.\n if (!navigation || navigation.trigger === 'popstate') return false;\n return intentOf(navigation.extras.state)?.stack === 'push';\n }\n\n override retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {\n const key = keyOf(route);\n const kept = (this.handles.get(key) ?? []).filter(\n (handle) => !(handle as StoredHandle).componentRef?.hostView.destroyed,\n );\n if (kept.length) this.handles.set(key, kept);\n else this.handles.delete(key);\n // The newest: the one a back from the top of the stack returns to.\n return kept.at(-1) ?? null;\n }\n}\n"]}
@@ -1,3 +1,32 @@
1
+ /**
2
+ * One item in a native tab bar, and the screen behind it.
3
+ *
4
+ * Declared in the tabs outlet's template, the way a `<native-header>` is declared in a page's:
5
+ *
6
+ * ```html
7
+ * <native-tabs-outlet>
8
+ * <native-tab path="library" title="Library" sfSymbol="books.vertical.fill" />
9
+ * <native-tab path="inbox" title="Inbox" sfSymbol="tray" [badge]="unread()" />
10
+ * </native-tabs-outlet>
11
+ * ```
12
+ *
13
+ * `path` names the child route this tab selects, the same way a `routerLink` names one, and is
14
+ * what native identifies the tab by. Everything else is an ordinary input, which is the point of
15
+ * declaring tabs here rather than in the route config: a badge is a count that changes, a title
16
+ * may be translated, and a tab may not exist at all until a feature flag says so. Route data
17
+ * cannot bind to any of that.
18
+ *
19
+ * **A bar item is not a view.** `UITabBarItem` is a model object - a title, an image and a badge
20
+ * string - and UIKit has no way to put a view in its place; Android's bottom-navigation item is
21
+ * the same. That is why this takes no content, while `<native-header-item>` does: a navigation
22
+ * bar really does hold subviews. What customisation exists is here as inputs: images instead of
23
+ * symbols, a different icon when selected, and the per-state appearance below.
24
+ *
25
+ * The host element *is* the `RNSTabsScreen`. The outlet keeps the screen mounted and creates
26
+ * each activated route's component on a child view, so destroying a route cannot remove this
27
+ * tab item from the bar.
28
+ */
29
+ import { type OnInit } from '@angular/core';
1
30
  import { type EngineNode } from '@ng-native/fabric';
2
31
  import * as i0 from "@angular/core";
3
32
  /**
@@ -88,7 +117,7 @@ export declare function tabIconProps(icon: TabIconProps, selectedIcon: TabIconPr
88
117
  selectedDrawableIconResourceName?: string;
89
118
  selectedImageIconResource?: unknown;
90
119
  };
91
- export declare class NativeTab {
120
+ export declare class NativeTab implements OnInit {
92
121
  private readonly engine;
93
122
  /** The app's `withTabDefaults`, for an appearance this tab does not bind itself. */
94
123
  private readonly defaults;
@@ -123,6 +152,7 @@ export declare class NativeTab {
123
152
  readonly standardAppearance: import("@angular/core").InputSignal<TabAppearance | undefined>;
124
153
  /** The same, for when the scroll view behind the bar is at its edge. */
125
154
  readonly scrollEdgeAppearance: import("@angular/core").InputSignal<TabAppearance | undefined>;
155
+ ngOnInit(): void;
126
156
  protected readonly standard: import("@angular/core").Signal<object | undefined>;
127
157
  protected readonly scrollEdge: import("@angular/core").Signal<object | undefined>;
128
158
  protected readonly icons: import("@angular/core").Signal<TabIconProps & {
@@ -131,7 +161,11 @@ export declare class NativeTab {
131
161
  selectedDrawableIconResourceName?: string;
132
162
  selectedImageIconResource?: unknown;
133
163
  }>;
134
- /** `sfSymbol` and `drawable` say the same thing as `icon`, in the form most tabs want. */
164
+ /**
165
+ * `sfSymbol` and `drawable` say the same thing as `icon`, in the form most tabs want. A tab
166
+ * names both to have an icon on both platforms, so the running platform's own comes first:
167
+ * Android reads only a drawable, and iOS only a symbol.
168
+ */
135
169
  private shorthand;
136
170
  /**
137
171
  * One icon, as the four props native reads it through. A `require()`d asset is an id until the
@@ -57,6 +57,32 @@ export function tabIconProps(icon, selectedIcon) {
57
57
  selectedImageIconResource: selectedIcon.imageIconResource,
58
58
  };
59
59
  }
60
+ /** The paths already warned about, so a tab bar rebuilt by a navigation says it once. */
61
+ const warnedTabs = new Set();
62
+ /**
63
+ * Says, once per path and in development only, that a tab has no icon on the platform running it
64
+ * because it names only the other platform's shorthand. A tab written against the iOS simulator
65
+ * with `sfSymbol` shows a title and nothing above it on Android.
66
+ */
67
+ function warnIfIconOnOnePlatform(tab) {
68
+ if (typeof ngDevMode !== 'undefined' && !ngDevMode)
69
+ return;
70
+ if (tab.icon() || tab.systemItem())
71
+ return;
72
+ const [own, other, missing, platform] = nativePlatform() === 'android'
73
+ ? [tab.drawable(), tab.sfSymbol(), 'drawable', 'Android']
74
+ : nativePlatform() === 'ios'
75
+ ? [tab.sfSymbol(), tab.drawable(), 'sfSymbol', 'iOS']
76
+ : [];
77
+ if (own || !other)
78
+ return;
79
+ const path = tab.path();
80
+ if (warnedTabs.has(path))
81
+ return;
82
+ warnedTabs.add(path);
83
+ console.warn(`[angular-native] <native-tab path="${path}"> has no icon on ${platform}: ${platform} reads ` +
84
+ `only ${missing}. Add ${missing}="...", or bind [icon] for a tab meant for one platform.`);
85
+ }
60
86
  export class NativeTab {
61
87
  engine = inject(HostEngine);
62
88
  /** The app's `withTabDefaults`, for an appearance this tab does not bind itself. */
@@ -105,19 +131,27 @@ export class NativeTab {
105
131
  /** The same, for when the scroll view behind the bar is at its edge. */
106
132
  scrollEdgeAppearance = input(/* @ts-ignore */
107
133
  ...(ngDevMode ? [undefined, { debugName: "scrollEdgeAppearance" }] : /* istanbul ignore next */ []));
134
+ ngOnInit() {
135
+ warnIfIconOnOnePlatform(this);
136
+ }
108
137
  standard = computed(() => this.processAppearance(this.standardAppearance() ?? this.defaults().standardAppearance), /* @ts-ignore */
109
138
  ...(ngDevMode ? [{ debugName: "standard" }] : /* istanbul ignore next */ []));
110
139
  scrollEdge = computed(() => this.processAppearance(this.scrollEdgeAppearance() ?? this.defaults().scrollEdgeAppearance), /* @ts-ignore */
111
140
  ...(ngDevMode ? [{ debugName: "scrollEdge" }] : /* istanbul ignore next */ []));
112
141
  icons = computed(() => tabIconProps(this.iconProps(this.icon() ?? this.shorthand()), this.iconProps(this.selectedIcon())), /* @ts-ignore */
113
142
  ...(ngDevMode ? [{ debugName: "icons" }] : /* istanbul ignore next */ []));
114
- /** `sfSymbol` and `drawable` say the same thing as `icon`, in the form most tabs want. */
143
+ /**
144
+ * `sfSymbol` and `drawable` say the same thing as `icon`, in the form most tabs want. A tab
145
+ * names both to have an icon on both platforms, so the running platform's own comes first:
146
+ * Android reads only a drawable, and iOS only a symbol.
147
+ */
115
148
  shorthand() {
116
149
  const symbol = this.sfSymbol();
117
- if (symbol)
118
- return { sfSymbol: symbol };
119
150
  const drawable = this.drawable();
120
- return drawable ? { drawable } : undefined;
151
+ const icons = [symbol && { sfSymbol: symbol }, drawable && { drawable }];
152
+ if (nativePlatform() === 'android')
153
+ icons.reverse();
154
+ return icons.find((icon) => icon) || undefined;
121
155
  }
122
156
  /**
123
157
  * One icon, as the four props native reads it through. A `require()`d asset is an id until the
@@ -1 +1 @@
1
- {"version":3,"file":"native-tab.js","sourceRoot":"","sources":["../src/native-tab.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * One item in a native tab bar, and the screen behind it.\n *\n * Declared in the tabs outlet's template, the way a `<native-header>` is declared in a page's:\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 * `path` names the child route this tab selects, the same way a `routerLink` names one, and is\n * what native identifies the tab by. Everything else is an ordinary input, which is the point of\n * declaring tabs here rather than in the route config: a badge is a count that changes, a title\n * may be translated, and a tab may not exist at all until a feature flag says so. Route data\n * cannot bind to any of that.\n *\n * **A bar item is not a view.** `UITabBarItem` is a model object - a title, an image and a badge\n * string - and UIKit has no way to put a view in its place; Android's bottom-navigation item is\n * the same. That is why this takes no content, while `<native-header-item>` does: a navigation\n * bar really does hold subviews. What customisation exists is here as inputs: images instead of\n * symbols, a different icon when selected, and the per-state appearance below.\n *\n * The host element *is* the `RNSTabsScreen`. The outlet keeps the screen mounted and creates\n * each activated route's component on a child view, so destroying a route cannot remove this\n * tab item from the bar.\n */\nimport { Component, ElementRef, computed, inject, input } from '@angular/core';\nimport { nativePlatform, type EngineNode, HostEngine } from '@ng-native/fabric';\nimport { NATIVE_TAB_DEFAULTS } from './native-bar-defaults.ts';\nimport { ownHost } from './own-host.ts';\n\n/**\n * Where a tab's picture comes from.\n *\n * `template` is the one worth knowing about: the image is drawn as a mask and tinted to the bar's\n * colour, so it follows selection the way a symbol does. `image` renders as authored, which is\n * what a multi-colour logo wants and what a monochrome icon almost never does.\n */\nexport type TabIcon =\n | { readonly sfSymbol: string }\n /** An image in the iOS asset catalogue, by name. */\n | { readonly xcasset: string }\n /** A `require()`d asset or a `{ uri }`, drawn as authored. */\n | { readonly image: unknown }\n /** The same, drawn as a mask and tinted. */\n | { readonly template: unknown }\n /** Android: a drawable resource name. */\n | { readonly drawable: string };\n\n/** One of Apple's standard items, which brings its own icon and title. */\nexport type TabSystemItem =\n | 'none'\n | 'bookmarks'\n | 'contacts'\n | 'downloads'\n | 'favorites'\n | 'featured'\n | 'history'\n | 'more'\n | 'mostRecent'\n | 'mostViewed'\n | 'recents'\n | 'search'\n | 'topRated';\n\nexport type TabBarBlurEffect =\n | 'none'\n | 'systemDefault'\n | 'extraLight'\n | 'light'\n | 'dark'\n | 'regular'\n | 'prominent'\n | 'systemUltraThinMaterial'\n | 'systemThinMaterial'\n | 'systemMaterial'\n | 'systemThickMaterial'\n | 'systemChromeMaterial';\n\n/**\n * How an item looks in one state. The names are the native ones, spelled as the codegen spec\n * spells them, so this forwards rather than translates and Apple's own documentation still reads\n * across.\n */\nexport interface TabStateAppearance {\n tabBarItemTitleFontFamily?: string;\n tabBarItemTitleFontSize?: number;\n tabBarItemTitleFontWeight?: string | number;\n tabBarItemTitleFontStyle?: string;\n tabBarItemTitleFontColor?: string | number;\n tabBarItemTitlePositionAdjustment?: { horizontal?: number; vertical?: number };\n tabBarItemIconColor?: string | number;\n tabBarItemBadgeBackgroundColor?: string | number;\n}\n\n/** The four states an item can be in. Anything absent falls back to the platform's own. */\nexport interface TabItemAppearance {\n normal?: TabStateAppearance;\n selected?: TabStateAppearance;\n focused?: TabStateAppearance;\n disabled?: TabStateAppearance;\n}\n\n/**\n * How the bar looks, per layout. `stacked` is the usual icon-over-title item; `inline` and\n * `compactInline` are the side-by-side layouts iPad and a compact height use.\n */\nexport interface TabAppearance {\n stacked?: TabItemAppearance;\n inline?: TabItemAppearance;\n compactInline?: TabItemAppearance;\n tabBarBackgroundColor?: string | number;\n tabBarShadowColor?: string | number;\n tabBarBlurEffect?: TabBarBlurEffect;\n}\n\n/** One icon, as the props native reads it through. */\nexport interface TabIconProps {\n iconType?: string;\n iconResourceName?: string;\n iconImageSource?: unknown;\n drawableIconResourceName?: string;\n imageIconResource?: unknown;\n}\n\n/**\n * An icon and its selected counterpart, as one set of props.\n *\n * Native carries a single `iconType` for both states, so the two have to be the same kind of\n * thing: a template image cannot become a symbol when selected. React Native Screens throws on\n * that pairing and so does this, because the alternative - what happened before this checked - is\n * that native quietly keeps the unselected icon and nothing anywhere says why.\n */\nexport function tabIconProps(\n icon: TabIconProps,\n selectedIcon: TabIconProps,\n): TabIconProps & {\n selectedIconResourceName?: string;\n selectedIconImageSource?: unknown;\n selectedDrawableIconResourceName?: string;\n selectedImageIconResource?: unknown;\n} {\n const hasBase = icon.iconType !== undefined || icon.drawableIconResourceName !== undefined;\n const hasSelected =\n selectedIcon.iconType !== undefined || selectedIcon.drawableIconResourceName !== undefined;\n\n if (hasSelected && !hasBase) {\n throw new Error('[angular-native] <native-tab> has a selectedIcon but no icon to select from');\n }\n if (hasSelected && icon.iconType !== selectedIcon.iconType) {\n throw new Error(\n `[angular-native] <native-tab> icon and selectedIcon must be the same kind: native has ` +\n `one iconType for both, so '${icon.iconType}' and '${selectedIcon.iconType}' cannot pair.`,\n );\n }\n\n return {\n ...icon,\n selectedIconResourceName: selectedIcon.iconResourceName,\n selectedIconImageSource: selectedIcon.iconImageSource,\n selectedDrawableIconResourceName: selectedIcon.drawableIconResourceName,\n selectedImageIconResource: selectedIcon.imageIconResource,\n };\n}\n\n@Component({\n selector: 'native-tab',\n template: '',\n host: {\n '[screenKey]': 'path()',\n '[title]': 'title()',\n '[badgeValue]': 'badge()',\n '[tabBarItemAccessibilityLabel]': 'accessibilityLabel()',\n '[systemItem]': 'systemItem()',\n // `isTitleUndefined` is what `TabsScreen.ios.tsx` derives from the title, and it is not a\n // synonym for \"no title\": with a title set and this left at its default, the item renders as\n // an icon with a gap where the label belongs.\n '[isTitleUndefined]': 'title() === undefined',\n '[iconType]': 'icons().iconType',\n '[iconResourceName]': 'icons().iconResourceName',\n '[iconImageSource]': 'icons().iconImageSource',\n '[selectedIconResourceName]': 'icons().selectedIconResourceName',\n '[selectedIconImageSource]': 'icons().selectedIconImageSource',\n '[drawableIconResourceName]': 'icons().drawableIconResourceName',\n '[imageIconResource]': 'icons().imageIconResource',\n '[selectedDrawableIconResourceName]': 'icons().selectedDrawableIconResourceName',\n '[selectedImageIconResource]': 'icons().selectedImageIconResource',\n '[standardAppearance]': 'standard()',\n '[scrollEdgeAppearance]': 'scrollEdge()',\n },\n})\nexport class NativeTab {\n private readonly engine = inject(HostEngine);\n /** The app's `withTabDefaults`, for an appearance this tab does not bind itself. */\n private readonly defaults = inject(NATIVE_TAB_DEFAULTS);\n\n /**\n * The `RNSTabsScreen` this tab is. The outlet creates the activated route's component inside\n * it, which is why it is public: a tab is a slot as well as a bar item.\n */\n readonly screen: EngineNode;\n\n constructor() {\n this.screen = ownHost(inject(ElementRef).nativeElement as EngineNode, this.constructor);\n }\n\n /**\n * The child route this tab selects, as a path segment: `library` for `/tabs/library`. Native\n * identifies the tab by it too, so it must be unique in the bar.\n */\n readonly path = input.required<string>();\n\n /** The label under the icon. Absent gives an icon-only item. */\n readonly title = input<string>();\n\n /** iOS: an SF Symbol name, e.g. `books.vertical.fill`. Shorthand for `[icon]`. */\n readonly sfSymbol = input<string>();\n\n /** Android: a drawable resource name. Shorthand for `[icon]`. */\n readonly drawable = input<string>();\n\n /** Anything a symbol name cannot say: an image, a template image, an asset-catalogue name. */\n readonly icon = input<TabIcon>();\n\n /** Shown while this tab is the selected one. Absent leaves the platform to tint `icon`. */\n readonly selectedIcon = input<TabIcon>();\n\n /** One of Apple's standard items, which supplies its own icon and title. */\n readonly systemItem = input<TabSystemItem>();\n\n /** The badge on the item: a count, or a single character for a dot. */\n readonly badge = input<string>();\n\n /** Announced instead of the title. */\n readonly accessibilityLabel = input<string>();\n\n /** How the item looks. Colours are processed here, because native never sees the object's keys. */\n readonly standardAppearance = input<TabAppearance>();\n\n /** The same, for when the scroll view behind the bar is at its edge. */\n readonly scrollEdgeAppearance = input<TabAppearance>();\n\n protected readonly standard = computed(() =>\n this.processAppearance(this.standardAppearance() ?? this.defaults().standardAppearance),\n );\n protected readonly scrollEdge = computed(() =>\n this.processAppearance(this.scrollEdgeAppearance() ?? this.defaults().scrollEdgeAppearance),\n );\n\n protected readonly icons = computed(() =>\n tabIconProps(\n this.iconProps(this.icon() ?? this.shorthand()),\n this.iconProps(this.selectedIcon()),\n ),\n );\n\n /** `sfSymbol` and `drawable` say the same thing as `icon`, in the form most tabs want. */\n private shorthand(): TabIcon | undefined {\n const symbol = this.sfSymbol();\n if (symbol) return { sfSymbol: symbol };\n const drawable = this.drawable();\n return drawable ? { drawable } : undefined;\n }\n\n /**\n * One icon, as the four props native reads it through. A `require()`d asset is an id until the\n * host resolves it, which is the same thing an `<image src>` needs and the engine only does for\n * props it knows by name.\n */\n private iconProps(icon: TabIcon | undefined): TabIconProps {\n if (!icon) return {};\n if ('sfSymbol' in icon) return { iconType: 'sfSymbol', iconResourceName: icon.sfSymbol };\n if ('xcasset' in icon) return { iconType: 'xcasset', iconResourceName: icon.xcasset };\n if ('drawable' in icon) return { drawableIconResourceName: icon.drawable };\n\n const source = this.engine.resolveAsset('image' in icon ? icon.image : icon.template);\n return nativePlatform() === 'android'\n ? { imageIconResource: source }\n : { iconType: 'image' in icon ? 'image' : 'template', iconImageSource: source };\n }\n\n private processAppearance(appearance: TabAppearance | undefined): object | undefined {\n if (!appearance) return undefined;\n return {\n ...appearance,\n stacked: this.processItem(appearance.stacked),\n inline: this.processItem(appearance.inline),\n compactInline: this.processItem(appearance.compactInline),\n tabBarBackgroundColor: this.engine.color(appearance.tabBarBackgroundColor),\n tabBarShadowColor: this.engine.color(appearance.tabBarShadowColor),\n };\n }\n\n private processItem(item: TabItemAppearance | undefined): object | undefined {\n if (!item) return undefined;\n return {\n normal: this.processState(item.normal),\n selected: this.processState(item.selected),\n focused: this.processState(item.focused),\n disabled: this.processState(item.disabled),\n };\n }\n\n private processState(state: TabStateAppearance | undefined): object | undefined {\n if (!state) return undefined;\n return {\n ...state,\n // Native declares the weight as a string, and `600` is the natural way to write one.\n tabBarItemTitleFontWeight:\n state.tabBarItemTitleFontWeight === undefined\n ? undefined\n : String(state.tabBarItemTitleFontWeight),\n tabBarItemTitleFontColor: this.engine.color(state.tabBarItemTitleFontColor),\n tabBarItemIconColor: this.engine.color(state.tabBarItemIconColor),\n tabBarItemBadgeBackgroundColor: this.engine.color(state.tabBarItemBadgeBackgroundColor),\n };\n }\n}\n"]}
1
+ {"version":3,"file":"native-tab.js","sourceRoot":"","sources":["../src/native-tab.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAe,MAAM,eAAe,CAAC;AAC5F,OAAO,EAAE,cAAc,EAAmB,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAChF,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;;AAgGxC;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAC1B,IAAkB,EAClB,YAA0B;IAO1B,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,KAAK,SAAS,IAAI,IAAI,CAAC,wBAAwB,KAAK,SAAS,CAAC;IAC3F,MAAM,WAAW,GACf,YAAY,CAAC,QAAQ,KAAK,SAAS,IAAI,YAAY,CAAC,wBAAwB,KAAK,SAAS,CAAC;IAE7F,IAAI,WAAW,IAAI,CAAC,OAAO,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,6EAA6E,CAAC,CAAC;IACjG,CAAC;IACD,IAAI,WAAW,IAAI,IAAI,CAAC,QAAQ,KAAK,YAAY,CAAC,QAAQ,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CACb,wFAAwF;YACtF,8BAA8B,IAAI,CAAC,QAAQ,UAAU,YAAY,CAAC,QAAQ,gBAAgB,CAC7F,CAAC;IACJ,CAAC;IAED,OAAO;QACL,GAAG,IAAI;QACP,wBAAwB,EAAE,YAAY,CAAC,gBAAgB;QACvD,uBAAuB,EAAE,YAAY,CAAC,eAAe;QACrD,gCAAgC,EAAE,YAAY,CAAC,wBAAwB;QACvE,yBAAyB,EAAE,YAAY,CAAC,iBAAiB;KAC1D,CAAC;AACJ,CAAC;AAID,yFAAyF;AACzF,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;AAErC;;;;GAIG;AACH,SAAS,uBAAuB,CAAC,GAAc;IAC7C,IAAI,OAAO,SAAS,KAAK,WAAW,IAAI,CAAC,SAAS;QAAE,OAAO;IAC3D,IAAI,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,CAAC,UAAU,EAAE;QAAE,OAAO;IAC3C,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,CAAC,GACnC,cAAc,EAAE,KAAK,SAAS;QAC5B,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE,EAAE,UAAU,EAAE,SAAS,CAAC;QACzD,CAAC,CAAC,cAAc,EAAE,KAAK,KAAK;YAC1B,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,GAAG,CAAC,QAAQ,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC;YACrD,CAAC,CAAC,EAAE,CAAC;IACX,IAAI,GAAG,IAAI,CAAC,KAAK;QAAE,OAAO;IAC1B,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IACxB,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO;IACjC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACrB,OAAO,CAAC,IAAI,CACV,sCAAsC,IAAI,qBAAqB,QAAQ,KAAK,QAAQ,SAAS;QAC3F,QAAQ,OAAO,SAAS,OAAO,0DAA0D,CAC5F,CAAC;AACJ,CAAC;AA4BD,MAAM,OAAO,SAAS;IACH,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;IAC7C,oFAAoF;IACnE,QAAQ,GAAG,MAAM,CAAC,mBAAmB,CAAC,CAAC;IAExD;;;OAGG;IACM,MAAM,CAAa;IAE5B;QACE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,aAA2B,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IAC1F,CAAC;IAED;;;OAGG;IACM,IAAI,GAAG,KAAK,CAAC,QAAQ;6EAAU,CAAC;IAEzC,gEAAgE;IACvD,KAAK,GAAG,KAAK;yFAAU,CAAC;IAEjC,kFAAkF;IACzE,QAAQ,GAAG,KAAK;4FAAU,CAAC;IAEpC,iEAAiE;IACxD,QAAQ,GAAG,KAAK;4FAAU,CAAC;IAEpC,8FAA8F;IACrF,IAAI,GAAG,KAAK;wFAAW,CAAC;IAEjC,2FAA2F;IAClF,YAAY,GAAG,KAAK;gGAAW,CAAC;IAEzC,4EAA4E;IACnE,UAAU,GAAG,KAAK;8FAAiB,CAAC;IAE7C,uEAAuE;IAC9D,KAAK,GAAG,KAAK;yFAAU,CAAC;IAEjC,sCAAsC;IAC7B,kBAAkB,GAAG,KAAK;sGAAU,CAAC;IAE9C,mGAAmG;IAC1F,kBAAkB,GAAG,KAAK;sGAAiB,CAAC;IAErD,wEAAwE;IAC/D,oBAAoB,GAAG,KAAK;wGAAiB,CAAC;IAEvD,QAAQ;QACN,uBAAuB,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IAEkB,QAAQ,GAAG,QAAQ,CAAC,GAAG,EAAE,CAC1C,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,kBAAkB,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC,kBAAkB,CAAC;iFACxF,CAAC;IACiB,UAAU,GAAG,QAAQ,CAAC,GAAG,EAAE,CAC5C,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,oBAAoB,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC,oBAAoB,CAAC;mFAC5F,CAAC;IAEiB,KAAK,GAAG,QAAQ,CAAC,GAAG,EAAE,CACvC,YAAY,CACV,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC,EAC/C,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC,CACpC;8EACF,CAAC;IAEF;;;;OAIG;IACK,SAAS;QACf,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QACjC,MAAM,KAAK,GAAG,CAAC,MAAM,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,QAAQ,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QACzE,IAAI,cAAc,EAAE,KAAK,SAAS;YAAE,KAAK,CAAC,OAAO,EAAE,CAAC;QACpD,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,SAAS,CAAC;IACjD,CAAC;IAED;;;;OAIG;IACK,SAAS,CAAC,IAAyB;QACzC,IAAI,CAAC,IAAI;YAAE,OAAO,EAAE,CAAC;QACrB,IAAI,UAAU,IAAI,IAAI;YAAE,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,gBAAgB,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACzF,IAAI,SAAS,IAAI,IAAI;YAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,gBAAgB,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;QACtF,IAAI,UAAU,IAAI,IAAI;YAAE,OAAO,EAAE,wBAAwB,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC;QAE3E,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtF,OAAO,cAAc,EAAE,KAAK,SAAS;YACnC,CAAC,CAAC,EAAE,iBAAiB,EAAE,MAAM,EAAE;YAC/B,CAAC,CAAC,EAAE,QAAQ,EAAE,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,EAAE,eAAe,EAAE,MAAM,EAAE,CAAC;IACpF,CAAC;IAEO,iBAAiB,CAAC,UAAqC;QAC7D,IAAI,CAAC,UAAU;YAAE,OAAO,SAAS,CAAC;QAClC,OAAO;YACL,GAAG,UAAU;YACb,OAAO,EAAE,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,OAAO,CAAC;YAC7C,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,MAAM,CAAC;YAC3C,aAAa,EAAE,IAAI,CAAC,WAAW,CAAC,UAAU,CAAC,aAAa,CAAC;YACzD,qBAAqB,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,qBAAqB,CAAC;YAC1E,iBAAiB,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC;SACnE,CAAC;IACJ,CAAC;IAEO,WAAW,CAAC,IAAmC;QACrD,IAAI,CAAC,IAAI;YAAE,OAAO,SAAS,CAAC;QAC5B,OAAO;YACL,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC;YACtC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC;YAC1C,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC;YACxC,QAAQ,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC;SAC3C,CAAC;IACJ,CAAC;IAEO,YAAY,CAAC,KAAqC;QACxD,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAC7B,OAAO;YACL,GAAG,KAAK;YACR,qFAAqF;YACrF,yBAAyB,EACvB,KAAK,CAAC,yBAAyB,KAAK,SAAS;gBAC3C,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,yBAAyB,CAAC;YAC7C,wBAAwB,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,wBAAwB,CAAC;YAC3E,mBAAmB,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,mBAAmB,CAAC;YACjE,8BAA8B,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,8BAA8B,CAAC;SACxF,CAAC;IACJ,CAAC;uGAtIU,SAAS;2FAAT,SAAS,i1EAxBV,EAAE;;2FAwBD,SAAS;kBA1BrB,SAAS;mBAAC;oBACT,QAAQ,EAAE,YAAY;oBACtB,QAAQ,EAAE,EAAE;oBACZ,IAAI,EAAE;wBACJ,aAAa,EAAE,QAAQ;wBACvB,SAAS,EAAE,SAAS;wBACpB,cAAc,EAAE,SAAS;wBACzB,gCAAgC,EAAE,sBAAsB;wBACxD,cAAc,EAAE,cAAc;wBAC9B,0FAA0F;wBAC1F,6FAA6F;wBAC7F,8CAA8C;wBAC9C,oBAAoB,EAAE,uBAAuB;wBAC7C,YAAY,EAAE,kBAAkB;wBAChC,oBAAoB,EAAE,0BAA0B;wBAChD,mBAAmB,EAAE,yBAAyB;wBAC9C,4BAA4B,EAAE,kCAAkC;wBAChE,2BAA2B,EAAE,iCAAiC;wBAC9D,4BAA4B,EAAE,kCAAkC;wBAChE,qBAAqB,EAAE,2BAA2B;wBAClD,oCAAoC,EAAE,0CAA0C;wBAChF,6BAA6B,EAAE,mCAAmC;wBAClE,sBAAsB,EAAE,YAAY;wBACpC,wBAAwB,EAAE,cAAc;qBACzC;iBACF","sourcesContent":["/**\n * One item in a native tab bar, and the screen behind it.\n *\n * Declared in the tabs outlet's template, the way a `<native-header>` is declared in a page's:\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 * `path` names the child route this tab selects, the same way a `routerLink` names one, and is\n * what native identifies the tab by. Everything else is an ordinary input, which is the point of\n * declaring tabs here rather than in the route config: a badge is a count that changes, a title\n * may be translated, and a tab may not exist at all until a feature flag says so. Route data\n * cannot bind to any of that.\n *\n * **A bar item is not a view.** `UITabBarItem` is a model object - a title, an image and a badge\n * string - and UIKit has no way to put a view in its place; Android's bottom-navigation item is\n * the same. That is why this takes no content, while `<native-header-item>` does: a navigation\n * bar really does hold subviews. What customisation exists is here as inputs: images instead of\n * symbols, a different icon when selected, and the per-state appearance below.\n *\n * The host element *is* the `RNSTabsScreen`. The outlet keeps the screen mounted and creates\n * each activated route's component on a child view, so destroying a route cannot remove this\n * tab item from the bar.\n */\nimport { Component, ElementRef, computed, inject, input, type OnInit } from '@angular/core';\nimport { nativePlatform, type EngineNode, HostEngine } from '@ng-native/fabric';\nimport { NATIVE_TAB_DEFAULTS } from './native-bar-defaults.ts';\nimport { ownHost } from './own-host.ts';\n\n/**\n * Where a tab's picture comes from.\n *\n * `template` is the one worth knowing about: the image is drawn as a mask and tinted to the bar's\n * colour, so it follows selection the way a symbol does. `image` renders as authored, which is\n * what a multi-colour logo wants and what a monochrome icon almost never does.\n */\nexport type TabIcon =\n | { readonly sfSymbol: string }\n /** An image in the iOS asset catalogue, by name. */\n | { readonly xcasset: string }\n /** A `require()`d asset or a `{ uri }`, drawn as authored. */\n | { readonly image: unknown }\n /** The same, drawn as a mask and tinted. */\n | { readonly template: unknown }\n /** Android: a drawable resource name. */\n | { readonly drawable: string };\n\n/** One of Apple's standard items, which brings its own icon and title. */\nexport type TabSystemItem =\n | 'none'\n | 'bookmarks'\n | 'contacts'\n | 'downloads'\n | 'favorites'\n | 'featured'\n | 'history'\n | 'more'\n | 'mostRecent'\n | 'mostViewed'\n | 'recents'\n | 'search'\n | 'topRated';\n\nexport type TabBarBlurEffect =\n | 'none'\n | 'systemDefault'\n | 'extraLight'\n | 'light'\n | 'dark'\n | 'regular'\n | 'prominent'\n | 'systemUltraThinMaterial'\n | 'systemThinMaterial'\n | 'systemMaterial'\n | 'systemThickMaterial'\n | 'systemChromeMaterial';\n\n/**\n * How an item looks in one state. The names are the native ones, spelled as the codegen spec\n * spells them, so this forwards rather than translates and Apple's own documentation still reads\n * across.\n */\nexport interface TabStateAppearance {\n tabBarItemTitleFontFamily?: string;\n tabBarItemTitleFontSize?: number;\n tabBarItemTitleFontWeight?: string | number;\n tabBarItemTitleFontStyle?: string;\n tabBarItemTitleFontColor?: string | number;\n tabBarItemTitlePositionAdjustment?: { horizontal?: number; vertical?: number };\n tabBarItemIconColor?: string | number;\n tabBarItemBadgeBackgroundColor?: string | number;\n}\n\n/** The four states an item can be in. Anything absent falls back to the platform's own. */\nexport interface TabItemAppearance {\n normal?: TabStateAppearance;\n selected?: TabStateAppearance;\n focused?: TabStateAppearance;\n disabled?: TabStateAppearance;\n}\n\n/**\n * How the bar looks, per layout. `stacked` is the usual icon-over-title item; `inline` and\n * `compactInline` are the side-by-side layouts iPad and a compact height use.\n */\nexport interface TabAppearance {\n stacked?: TabItemAppearance;\n inline?: TabItemAppearance;\n compactInline?: TabItemAppearance;\n tabBarBackgroundColor?: string | number;\n tabBarShadowColor?: string | number;\n tabBarBlurEffect?: TabBarBlurEffect;\n}\n\n/** One icon, as the props native reads it through. */\nexport interface TabIconProps {\n iconType?: string;\n iconResourceName?: string;\n iconImageSource?: unknown;\n drawableIconResourceName?: string;\n imageIconResource?: unknown;\n}\n\n/**\n * An icon and its selected counterpart, as one set of props.\n *\n * Native carries a single `iconType` for both states, so the two have to be the same kind of\n * thing: a template image cannot become a symbol when selected. React Native Screens throws on\n * that pairing and so does this, because the alternative - what happened before this checked - is\n * that native quietly keeps the unselected icon and nothing anywhere says why.\n */\nexport function tabIconProps(\n icon: TabIconProps,\n selectedIcon: TabIconProps,\n): TabIconProps & {\n selectedIconResourceName?: string;\n selectedIconImageSource?: unknown;\n selectedDrawableIconResourceName?: string;\n selectedImageIconResource?: unknown;\n} {\n const hasBase = icon.iconType !== undefined || icon.drawableIconResourceName !== undefined;\n const hasSelected =\n selectedIcon.iconType !== undefined || selectedIcon.drawableIconResourceName !== undefined;\n\n if (hasSelected && !hasBase) {\n throw new Error('[angular-native] <native-tab> has a selectedIcon but no icon to select from');\n }\n if (hasSelected && icon.iconType !== selectedIcon.iconType) {\n throw new Error(\n `[angular-native] <native-tab> icon and selectedIcon must be the same kind: native has ` +\n `one iconType for both, so '${icon.iconType}' and '${selectedIcon.iconType}' cannot pair.`,\n );\n }\n\n return {\n ...icon,\n selectedIconResourceName: selectedIcon.iconResourceName,\n selectedIconImageSource: selectedIcon.iconImageSource,\n selectedDrawableIconResourceName: selectedIcon.drawableIconResourceName,\n selectedImageIconResource: selectedIcon.imageIconResource,\n };\n}\n\ndeclare const ngDevMode: unknown;\n\n/** The paths already warned about, so a tab bar rebuilt by a navigation says it once. */\nconst warnedTabs = new Set<string>();\n\n/**\n * Says, once per path and in development only, that a tab has no icon on the platform running it\n * because it names only the other platform's shorthand. A tab written against the iOS simulator\n * with `sfSymbol` shows a title and nothing above it on Android.\n */\nfunction warnIfIconOnOnePlatform(tab: NativeTab): void {\n if (typeof ngDevMode !== 'undefined' && !ngDevMode) return;\n if (tab.icon() || tab.systemItem()) return;\n const [own, other, missing, platform] =\n nativePlatform() === 'android'\n ? [tab.drawable(), tab.sfSymbol(), 'drawable', 'Android']\n : nativePlatform() === 'ios'\n ? [tab.sfSymbol(), tab.drawable(), 'sfSymbol', 'iOS']\n : [];\n if (own || !other) return;\n const path = tab.path();\n if (warnedTabs.has(path)) return;\n warnedTabs.add(path);\n console.warn(\n `[angular-native] <native-tab path=\"${path}\"> has no icon on ${platform}: ${platform} reads ` +\n `only ${missing}. Add ${missing}=\"...\", or bind [icon] for a tab meant for one platform.`,\n );\n}\n\n@Component({\n selector: 'native-tab',\n template: '',\n host: {\n '[screenKey]': 'path()',\n '[title]': 'title()',\n '[badgeValue]': 'badge()',\n '[tabBarItemAccessibilityLabel]': 'accessibilityLabel()',\n '[systemItem]': 'systemItem()',\n // `isTitleUndefined` is what `TabsScreen.ios.tsx` derives from the title, and it is not a\n // synonym for \"no title\": with a title set and this left at its default, the item renders as\n // an icon with a gap where the label belongs.\n '[isTitleUndefined]': 'title() === undefined',\n '[iconType]': 'icons().iconType',\n '[iconResourceName]': 'icons().iconResourceName',\n '[iconImageSource]': 'icons().iconImageSource',\n '[selectedIconResourceName]': 'icons().selectedIconResourceName',\n '[selectedIconImageSource]': 'icons().selectedIconImageSource',\n '[drawableIconResourceName]': 'icons().drawableIconResourceName',\n '[imageIconResource]': 'icons().imageIconResource',\n '[selectedDrawableIconResourceName]': 'icons().selectedDrawableIconResourceName',\n '[selectedImageIconResource]': 'icons().selectedImageIconResource',\n '[standardAppearance]': 'standard()',\n '[scrollEdgeAppearance]': 'scrollEdge()',\n },\n})\nexport class NativeTab implements OnInit {\n private readonly engine = inject(HostEngine);\n /** The app's `withTabDefaults`, for an appearance this tab does not bind itself. */\n private readonly defaults = inject(NATIVE_TAB_DEFAULTS);\n\n /**\n * The `RNSTabsScreen` this tab is. The outlet creates the activated route's component inside\n * it, which is why it is public: a tab is a slot as well as a bar item.\n */\n readonly screen: EngineNode;\n\n constructor() {\n this.screen = ownHost(inject(ElementRef).nativeElement as EngineNode, this.constructor);\n }\n\n /**\n * The child route this tab selects, as a path segment: `library` for `/tabs/library`. Native\n * identifies the tab by it too, so it must be unique in the bar.\n */\n readonly path = input.required<string>();\n\n /** The label under the icon. Absent gives an icon-only item. */\n readonly title = input<string>();\n\n /** iOS: an SF Symbol name, e.g. `books.vertical.fill`. Shorthand for `[icon]`. */\n readonly sfSymbol = input<string>();\n\n /** Android: a drawable resource name. Shorthand for `[icon]`. */\n readonly drawable = input<string>();\n\n /** Anything a symbol name cannot say: an image, a template image, an asset-catalogue name. */\n readonly icon = input<TabIcon>();\n\n /** Shown while this tab is the selected one. Absent leaves the platform to tint `icon`. */\n readonly selectedIcon = input<TabIcon>();\n\n /** One of Apple's standard items, which supplies its own icon and title. */\n readonly systemItem = input<TabSystemItem>();\n\n /** The badge on the item: a count, or a single character for a dot. */\n readonly badge = input<string>();\n\n /** Announced instead of the title. */\n readonly accessibilityLabel = input<string>();\n\n /** How the item looks. Colours are processed here, because native never sees the object's keys. */\n readonly standardAppearance = input<TabAppearance>();\n\n /** The same, for when the scroll view behind the bar is at its edge. */\n readonly scrollEdgeAppearance = input<TabAppearance>();\n\n ngOnInit(): void {\n warnIfIconOnOnePlatform(this);\n }\n\n protected readonly standard = computed(() =>\n this.processAppearance(this.standardAppearance() ?? this.defaults().standardAppearance),\n );\n protected readonly scrollEdge = computed(() =>\n this.processAppearance(this.scrollEdgeAppearance() ?? this.defaults().scrollEdgeAppearance),\n );\n\n protected readonly icons = computed(() =>\n tabIconProps(\n this.iconProps(this.icon() ?? this.shorthand()),\n this.iconProps(this.selectedIcon()),\n ),\n );\n\n /**\n * `sfSymbol` and `drawable` say the same thing as `icon`, in the form most tabs want. A tab\n * names both to have an icon on both platforms, so the running platform's own comes first:\n * Android reads only a drawable, and iOS only a symbol.\n */\n private shorthand(): TabIcon | undefined {\n const symbol = this.sfSymbol();\n const drawable = this.drawable();\n const icons = [symbol && { sfSymbol: symbol }, drawable && { drawable }];\n if (nativePlatform() === 'android') icons.reverse();\n return icons.find((icon) => icon) || undefined;\n }\n\n /**\n * One icon, as the four props native reads it through. A `require()`d asset is an id until the\n * host resolves it, which is the same thing an `<image src>` needs and the engine only does for\n * props it knows by name.\n */\n private iconProps(icon: TabIcon | undefined): TabIconProps {\n if (!icon) return {};\n if ('sfSymbol' in icon) return { iconType: 'sfSymbol', iconResourceName: icon.sfSymbol };\n if ('xcasset' in icon) return { iconType: 'xcasset', iconResourceName: icon.xcasset };\n if ('drawable' in icon) return { drawableIconResourceName: icon.drawable };\n\n const source = this.engine.resolveAsset('image' in icon ? icon.image : icon.template);\n return nativePlatform() === 'android'\n ? { imageIconResource: source }\n : { iconType: 'image' in icon ? 'image' : 'template', iconImageSource: source };\n }\n\n private processAppearance(appearance: TabAppearance | undefined): object | undefined {\n if (!appearance) return undefined;\n return {\n ...appearance,\n stacked: this.processItem(appearance.stacked),\n inline: this.processItem(appearance.inline),\n compactInline: this.processItem(appearance.compactInline),\n tabBarBackgroundColor: this.engine.color(appearance.tabBarBackgroundColor),\n tabBarShadowColor: this.engine.color(appearance.tabBarShadowColor),\n };\n }\n\n private processItem(item: TabItemAppearance | undefined): object | undefined {\n if (!item) return undefined;\n return {\n normal: this.processState(item.normal),\n selected: this.processState(item.selected),\n focused: this.processState(item.focused),\n disabled: this.processState(item.disabled),\n };\n }\n\n private processState(state: TabStateAppearance | undefined): object | undefined {\n if (!state) return undefined;\n return {\n ...state,\n // Native declares the weight as a string, and `600` is the natural way to write one.\n tabBarItemTitleFontWeight:\n state.tabBarItemTitleFontWeight === undefined\n ? undefined\n : String(state.tabBarItemTitleFontWeight),\n tabBarItemTitleFontColor: this.engine.color(state.tabBarItemTitleFontColor),\n tabBarItemIconColor: this.engine.color(state.tabBarItemIconColor),\n tabBarItemBadgeBackgroundColor: this.engine.color(state.tabBarItemBadgeBackgroundColor),\n };\n }\n}\n"]}
@@ -34,6 +34,7 @@ export declare class NativeTabsOutlet implements RouterOutletContract, AfterCont
34
34
  private readonly parentContexts;
35
35
  private readonly renderer;
36
36
  private readonly applicationRef;
37
+ private readonly changeDetector;
37
38
  private readonly injector;
38
39
  private readonly host;
39
40
  private readonly route;
@@ -69,6 +70,12 @@ export declare class NativeTabsOutlet implements RouterOutletContract, AfterCont
69
70
  constructor();
70
71
  /** A bar nobody has navigated into yet still has to show a selected item. */
71
72
  ngAfterContentInit(): void;
73
+ /**
74
+ * The route the router is already on, for an outlet created after the navigation that reached
75
+ * it, as `NativeStackOutlet` takes its own: a tab bar held behind a condition until a session
76
+ * is ready. Once the tabs are read, since activating one needs to know which they are.
77
+ */
78
+ private activateCurrentRoute;
72
79
  /** The tabs in the bar, in template order. Exposed for tests. */
73
80
  get tabKeys(): string[];
74
81
  /** Which tab native is showing. Exposed for tests. */
@@ -115,6 +122,13 @@ export declare class NativeTabsOutlet implements RouterOutletContract, AfterCont
115
122
  private goBack;
116
123
  /** Keep the tab in front on the url it has reached, as long as the url is still inside it. */
117
124
  private follow;
125
+ /**
126
+ * The url of the tab `url` is a page inside, when nothing has opened that tab: the tab's own
127
+ * first screen, which a push into the tab from outside it shows first.
128
+ */
129
+ private unopenedTabOf;
130
+ /** Whether `url` is a tab other than the one in front, or a page inside one. */
131
+ private behind;
118
132
  private request;
119
133
  /**
120
134
  * Native switched tab. Acknowledge the new state, then navigate: the router is authoritative