@ng-native/router 0.5.0 → 0.7.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/README.md +10 -0
- package/dist/file-routes.d.ts +83 -0
- package/dist/file-routes.js +371 -0
- package/dist/file-routes.js.map +1 -0
- package/dist/in-tab.d.ts +20 -0
- package/dist/in-tab.js +13 -0
- package/dist/in-tab.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/markdown-page.d.ts +39 -0
- package/dist/markdown-page.js +56 -0
- package/dist/markdown-page.js.map +1 -0
- package/dist/native-back.d.ts +8 -0
- package/dist/native-back.js +8 -0
- package/dist/native-back.js.map +1 -1
- package/dist/native-navigation.d.ts +11 -1
- package/dist/native-navigation.js +66 -2
- package/dist/native-navigation.js.map +1 -1
- package/dist/native-platform-location.d.ts +5 -0
- package/dist/native-platform-location.js +4 -0
- package/dist/native-platform-location.js.map +1 -1
- package/dist/native-router-link.js +8 -6
- package/dist/native-router-link.js.map +1 -1
- package/dist/native-stack-outlet.d.ts +6 -0
- package/dist/native-stack-outlet.js +25 -2
- package/dist/native-stack-outlet.js.map +1 -1
- package/dist/native-stack-reuse-strategy.d.ts +9 -0
- package/dist/native-stack-reuse-strategy.js +28 -8
- package/dist/native-stack-reuse-strategy.js.map +1 -1
- package/dist/native-tab.d.ts +6 -0
- package/dist/native-tab.js +11 -2
- package/dist/native-tab.js.map +1 -1
- package/dist/native-tabs-outlet.d.ts +29 -1
- package/dist/native-tabs-outlet.js +113 -28
- package/dist/native-tabs-outlet.js.map +1 -1
- package/dist/native-toolbar.d.ts +39 -0
- package/dist/native-toolbar.js +108 -0
- package/dist/native-toolbar.js.map +1 -0
- package/dist/presented-route.d.ts +9 -1
- package/dist/presented-route.js +1 -1
- package/dist/presented-route.js.map +1 -1
- package/dist/provide-native-router.js +86 -2
- package/dist/provide-native-router.js.map +1 -1
- package/dist/route-parking.d.ts +30 -0
- package/dist/route-parking.js +60 -0
- package/dist/route-parking.js.map +1 -0
- package/dist/screens.js +23 -0
- package/dist/screens.js.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
export declare class NativeToolbar {
|
|
3
|
+
constructor();
|
|
4
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NativeToolbar, never>;
|
|
5
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<NativeToolbar, "native-toolbar", never, {}, {}, never, ["*"], true, never>;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* What a toolbar item is. Unset, a button. `fluidSpacer` takes all the room there is, which
|
|
9
|
+
* pushes what follows to the trailing edge, and `fixedSpacer` is a gap of `width` points.
|
|
10
|
+
* `searchBar` is where the screen's `native-search-bar` goes when iOS 26 integrates it into the
|
|
11
|
+
* toolbar, so buttons can sit either side of it.
|
|
12
|
+
*/
|
|
13
|
+
export type ToolbarItemType = 'normal' | 'fixedSpacer' | 'fluidSpacer' | 'searchBar';
|
|
14
|
+
export declare class NativeToolbarItem {
|
|
15
|
+
readonly type: import("@angular/core").InputSignal<ToolbarItemType | undefined>;
|
|
16
|
+
readonly title: import("@angular/core").InputSignal<string | undefined>;
|
|
17
|
+
/** An SF Symbol name. */
|
|
18
|
+
readonly systemImageName: import("@angular/core").InputSignal<string | undefined>;
|
|
19
|
+
readonly tintColor: import("@angular/core").InputSignal<string | undefined>;
|
|
20
|
+
/** `prominent` is the filled style iOS 26 gives a screen's main action. */
|
|
21
|
+
readonly barButtonItemStyle: import("@angular/core").InputSignal<"prominent" | "plain" | undefined>;
|
|
22
|
+
/** iOS 26: draw this item with no glass behind it. */
|
|
23
|
+
readonly hidesSharedBackground: import("@angular/core").InputSignalWithTransform<boolean | undefined, unknown>;
|
|
24
|
+
/** iOS 26: whether this item shares its glass with the items beside it, which it does unset. */
|
|
25
|
+
readonly sharesBackground: import("@angular/core").InputSignalWithTransform<boolean | undefined, unknown>;
|
|
26
|
+
/** The width of a `fixedSpacer`, in points. */
|
|
27
|
+
readonly width: import("@angular/core").InputSignalWithTransform<number | undefined, unknown>;
|
|
28
|
+
/** Leave the item out of the toolbar while keeping its place among the others. */
|
|
29
|
+
readonly hidden: import("@angular/core").InputSignalWithTransform<boolean | undefined, unknown>;
|
|
30
|
+
readonly disabled: import("@angular/core").InputSignalWithTransform<boolean | undefined, unknown>;
|
|
31
|
+
/** What a screen reader says for a button with a symbol and no title. */
|
|
32
|
+
readonly accessibilityLabel: import("@angular/core").InputSignal<string | undefined>;
|
|
33
|
+
/** The button was tapped. */
|
|
34
|
+
readonly press: import("@angular/core").OutputEmitterRef<void>;
|
|
35
|
+
protected readonly identifier: string;
|
|
36
|
+
constructor();
|
|
37
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NativeToolbarItem, never>;
|
|
38
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<NativeToolbarItem, "native-toolbar-item", never, { "type": { "alias": "type"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "systemImageName": { "alias": "systemImageName"; "required": false; "isSignal": true; }; "tintColor": { "alias": "tintColor"; "required": false; "isSignal": true; }; "barButtonItemStyle": { "alias": "barButtonItemStyle"; "required": false; "isSignal": true; }; "hidesSharedBackground": { "alias": "hidesSharedBackground"; "required": false; "isSignal": true; }; "sharesBackground": { "alias": "sharesBackground"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "hidden": { "alias": "hidden"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "accessibilityLabel": { "alias": "accessibilityLabel"; "required": false; "isSignal": true; }; }, { "press": "press"; }, never, never, true, never>;
|
|
39
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A screen's bottom toolbar, iOS only: the bar a navigation controller floats over the content
|
|
3
|
+
* and the tab bar, where Mail has its filter, its search field and its compose button.
|
|
4
|
+
*
|
|
5
|
+
* ```html
|
|
6
|
+
* <native-toolbar>
|
|
7
|
+
* <native-toolbar-item systemImageName="line.3.horizontal.decrease" (press)="filter()" />
|
|
8
|
+
* <native-toolbar-item type="searchBar" />
|
|
9
|
+
* <native-toolbar-item systemImageName="square.and.pencil" (press)="compose()" />
|
|
10
|
+
* </native-toolbar>
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* react-native-screens has no toolbar, so these commit as Expo Router's native toolbar views,
|
|
14
|
+
* `ExpoRouterToolbarModule`'s: the host finds the react-native-screens screen it is inside, gives
|
|
15
|
+
* its view controller the items and shows the toolbar. An app that uses them installs
|
|
16
|
+
* `expo-router` for that native half; none of its JavaScript is imported. Expo Go has it built in.
|
|
17
|
+
*
|
|
18
|
+
* It needs iOS 18. Written anywhere in a screen's page, it is that screen's toolbar, and it goes
|
|
19
|
+
* when the element does. A presented screen has no navigation controller, so no toolbar either.
|
|
20
|
+
*/
|
|
21
|
+
import { Component, DestroyRef, ElementRef, inject, input, output } from '@angular/core';
|
|
22
|
+
import { HostEngine } from '@ng-native/fabric';
|
|
23
|
+
import { ownHost } from "./own-host.js";
|
|
24
|
+
import { optionalBoolean, optionalNumber } from "./transforms.js";
|
|
25
|
+
import * as i0 from "@angular/core";
|
|
26
|
+
export class NativeToolbar {
|
|
27
|
+
constructor() {
|
|
28
|
+
ownHost(inject(ElementRef).nativeElement, this.constructor);
|
|
29
|
+
}
|
|
30
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: NativeToolbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
31
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "22.2.0", type: NativeToolbar, isStandalone: true, selector: "native-toolbar", host: { properties: { "style.position": "\"absolute\"", "style.top": "0", "style.left": "0", "style.width": "1", "style.height": "1" } }, ngImport: i0, template: '<ng-content />', isInline: true });
|
|
32
|
+
}
|
|
33
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: NativeToolbar, decorators: [{
|
|
34
|
+
type: Component,
|
|
35
|
+
args: [{
|
|
36
|
+
selector: 'native-toolbar',
|
|
37
|
+
template: '<ng-content />',
|
|
38
|
+
// Out of the screen's layout, as Expo Router's own wrapper styles it: the items are bar button
|
|
39
|
+
// items, which the host hands to the screen rather than draws.
|
|
40
|
+
host: {
|
|
41
|
+
'[style.position]': '"absolute"',
|
|
42
|
+
'[style.top]': '0',
|
|
43
|
+
'[style.left]': '0',
|
|
44
|
+
'[style.width]': '1',
|
|
45
|
+
'[style.height]': '1',
|
|
46
|
+
},
|
|
47
|
+
}]
|
|
48
|
+
}], ctorParameters: () => [] });
|
|
49
|
+
/** The identifier native keys each item by. Any text will do, as long as no two items share it. */
|
|
50
|
+
let created = 0;
|
|
51
|
+
export class NativeToolbarItem {
|
|
52
|
+
type = input(/* @ts-ignore */
|
|
53
|
+
...(ngDevMode ? [undefined, { debugName: "type" }] : /* istanbul ignore next */ []));
|
|
54
|
+
title = input(/* @ts-ignore */
|
|
55
|
+
...(ngDevMode ? [undefined, { debugName: "title" }] : /* istanbul ignore next */ []));
|
|
56
|
+
/** An SF Symbol name. */
|
|
57
|
+
systemImageName = input(/* @ts-ignore */
|
|
58
|
+
...(ngDevMode ? [undefined, { debugName: "systemImageName" }] : /* istanbul ignore next */ []));
|
|
59
|
+
tintColor = input(/* @ts-ignore */
|
|
60
|
+
...(ngDevMode ? [undefined, { debugName: "tintColor" }] : /* istanbul ignore next */ []));
|
|
61
|
+
/** `prominent` is the filled style iOS 26 gives a screen's main action. */
|
|
62
|
+
barButtonItemStyle = input(/* @ts-ignore */
|
|
63
|
+
...(ngDevMode ? [undefined, { debugName: "barButtonItemStyle" }] : /* istanbul ignore next */ []));
|
|
64
|
+
/** iOS 26: draw this item with no glass behind it. */
|
|
65
|
+
hidesSharedBackground = input(undefined, { ...(ngDevMode ? { debugName: "hidesSharedBackground" } : /* istanbul ignore next */ {}), transform: optionalBoolean });
|
|
66
|
+
/** iOS 26: whether this item shares its glass with the items beside it, which it does unset. */
|
|
67
|
+
sharesBackground = input(undefined, { ...(ngDevMode ? { debugName: "sharesBackground" } : /* istanbul ignore next */ {}), transform: optionalBoolean });
|
|
68
|
+
/** The width of a `fixedSpacer`, in points. */
|
|
69
|
+
width = input(undefined, { ...(ngDevMode ? { debugName: "width" } : /* istanbul ignore next */ {}), transform: optionalNumber });
|
|
70
|
+
/** Leave the item out of the toolbar while keeping its place among the others. */
|
|
71
|
+
hidden = input(undefined, { ...(ngDevMode ? { debugName: "hidden" } : /* istanbul ignore next */ {}), transform: optionalBoolean });
|
|
72
|
+
disabled = input(undefined, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: optionalBoolean });
|
|
73
|
+
/** What a screen reader says for a button with a symbol and no title. */
|
|
74
|
+
accessibilityLabel = input(/* @ts-ignore */
|
|
75
|
+
...(ngDevMode ? [undefined, { debugName: "accessibilityLabel" }] : /* istanbul ignore next */ []));
|
|
76
|
+
/** The button was tapped. */
|
|
77
|
+
press = output();
|
|
78
|
+
identifier = `native-toolbar-item-${created++}`;
|
|
79
|
+
constructor() {
|
|
80
|
+
const node = ownHost(inject(ElementRef).nativeElement, NativeToolbarItem);
|
|
81
|
+
const engine = node.host ?? inject(HostEngine);
|
|
82
|
+
inject(DestroyRef).onDestroy(engine.setEventListener(node, 'topSelected', () => this.press.emit()));
|
|
83
|
+
}
|
|
84
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: NativeToolbarItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
85
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.2.0", type: NativeToolbarItem, isStandalone: true, selector: "native-toolbar-item", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, systemImageName: { classPropertyName: "systemImageName", publicName: "systemImageName", isSignal: true, isRequired: false, transformFunction: null }, tintColor: { classPropertyName: "tintColor", publicName: "tintColor", isSignal: true, isRequired: false, transformFunction: null }, barButtonItemStyle: { classPropertyName: "barButtonItemStyle", publicName: "barButtonItemStyle", isSignal: true, isRequired: false, transformFunction: null }, hidesSharedBackground: { classPropertyName: "hidesSharedBackground", publicName: "hidesSharedBackground", isSignal: true, isRequired: false, transformFunction: null }, sharesBackground: { classPropertyName: "sharesBackground", publicName: "sharesBackground", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, hidden: { classPropertyName: "hidden", publicName: "hidden", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, accessibilityLabel: { classPropertyName: "accessibilityLabel", publicName: "accessibilityLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { press: "press" }, host: { properties: { "identifier": "identifier", "type": "type()", "title": "title()", "systemImageName": "systemImageName()", "tintColor": "tintColor()", "barButtonItemStyle": "barButtonItemStyle()", "hidesSharedBackground": "hidesSharedBackground()", "sharesBackground": "sharesBackground()", "width": "width()", "hidden": "hidden()", "disabled": "disabled()", "accessibilityLabel": "accessibilityLabel()" } }, ngImport: i0, template: '', isInline: true });
|
|
86
|
+
}
|
|
87
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: NativeToolbarItem, decorators: [{
|
|
88
|
+
type: Component,
|
|
89
|
+
args: [{
|
|
90
|
+
selector: 'native-toolbar-item',
|
|
91
|
+
template: '',
|
|
92
|
+
host: {
|
|
93
|
+
'[identifier]': 'identifier',
|
|
94
|
+
'[type]': 'type()',
|
|
95
|
+
'[title]': 'title()',
|
|
96
|
+
'[systemImageName]': 'systemImageName()',
|
|
97
|
+
'[tintColor]': 'tintColor()',
|
|
98
|
+
'[barButtonItemStyle]': 'barButtonItemStyle()',
|
|
99
|
+
'[hidesSharedBackground]': 'hidesSharedBackground()',
|
|
100
|
+
'[sharesBackground]': 'sharesBackground()',
|
|
101
|
+
'[width]': 'width()',
|
|
102
|
+
'[hidden]': 'hidden()',
|
|
103
|
+
'[disabled]': 'disabled()',
|
|
104
|
+
'[accessibilityLabel]': 'accessibilityLabel()',
|
|
105
|
+
},
|
|
106
|
+
}]
|
|
107
|
+
}], ctorParameters: () => [], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], systemImageName: [{ type: i0.Input, args: [{ isSignal: true, alias: "systemImageName", required: false }] }], tintColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "tintColor", required: false }] }], barButtonItemStyle: [{ type: i0.Input, args: [{ isSignal: true, alias: "barButtonItemStyle", required: false }] }], hidesSharedBackground: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidesSharedBackground", required: false }] }], sharesBackground: [{ type: i0.Input, args: [{ isSignal: true, alias: "sharesBackground", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], hidden: [{ type: i0.Input, args: [{ isSignal: true, alias: "hidden", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], accessibilityLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "accessibilityLabel", required: false }] }], press: [{ type: i0.Output, args: ["press"] }] } });
|
|
108
|
+
//# sourceMappingURL=native-toolbar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-toolbar.js","sourceRoot":"","sources":["../src/native-toolbar.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A screen's bottom toolbar, iOS only: the bar a navigation controller floats over the content\n * and the tab bar, where Mail has its filter, its search field and its compose button.\n *\n * ```html\n * <native-toolbar>\n * <native-toolbar-item systemImageName=\"line.3.horizontal.decrease\" (press)=\"filter()\" />\n * <native-toolbar-item type=\"searchBar\" />\n * <native-toolbar-item systemImageName=\"square.and.pencil\" (press)=\"compose()\" />\n * </native-toolbar>\n * ```\n *\n * react-native-screens has no toolbar, so these commit as Expo Router's native toolbar views,\n * `ExpoRouterToolbarModule`'s: the host finds the react-native-screens screen it is inside, gives\n * its view controller the items and shows the toolbar. An app that uses them installs\n * `expo-router` for that native half; none of its JavaScript is imported. Expo Go has it built in.\n *\n * It needs iOS 18. Written anywhere in a screen's page, it is that screen's toolbar, and it goes\n * when the element does. A presented screen has no navigation controller, so no toolbar either.\n */\nimport { Component, DestroyRef, ElementRef, inject, input, output } from '@angular/core';\nimport { type EngineNode, HostEngine } from '@ng-native/fabric';\nimport { ownHost } from './own-host.ts';\nimport { optionalBoolean, optionalNumber } from './transforms.ts';\n\n@Component({\n selector: 'native-toolbar',\n template: '<ng-content />',\n // Out of the screen's layout, as Expo Router's own wrapper styles it: the items are bar button\n // items, which the host hands to the screen rather than draws.\n host: {\n '[style.position]': '\"absolute\"',\n '[style.top]': '0',\n '[style.left]': '0',\n '[style.width]': '1',\n '[style.height]': '1',\n },\n})\nexport class NativeToolbar {\n constructor() {\n ownHost(inject(ElementRef).nativeElement as EngineNode, this.constructor);\n }\n}\n\n/**\n * What a toolbar item is. Unset, a button. `fluidSpacer` takes all the room there is, which\n * pushes what follows to the trailing edge, and `fixedSpacer` is a gap of `width` points.\n * `searchBar` is where the screen's `native-search-bar` goes when iOS 26 integrates it into the\n * toolbar, so buttons can sit either side of it.\n */\nexport type ToolbarItemType = 'normal' | 'fixedSpacer' | 'fluidSpacer' | 'searchBar';\n\n/** The identifier native keys each item by. Any text will do, as long as no two items share it. */\nlet created = 0;\n\n@Component({\n selector: 'native-toolbar-item',\n template: '',\n host: {\n '[identifier]': 'identifier',\n '[type]': 'type()',\n '[title]': 'title()',\n '[systemImageName]': 'systemImageName()',\n '[tintColor]': 'tintColor()',\n '[barButtonItemStyle]': 'barButtonItemStyle()',\n '[hidesSharedBackground]': 'hidesSharedBackground()',\n '[sharesBackground]': 'sharesBackground()',\n '[width]': 'width()',\n '[hidden]': 'hidden()',\n '[disabled]': 'disabled()',\n '[accessibilityLabel]': 'accessibilityLabel()',\n },\n})\nexport class NativeToolbarItem {\n readonly type = input<ToolbarItemType>();\n readonly title = input<string>();\n /** An SF Symbol name. */\n readonly systemImageName = input<string>();\n readonly tintColor = input<string>();\n /** `prominent` is the filled style iOS 26 gives a screen's main action. */\n readonly barButtonItemStyle = input<'plain' | 'prominent'>();\n /** iOS 26: draw this item with no glass behind it. */\n readonly hidesSharedBackground = input(undefined, { transform: optionalBoolean });\n /** iOS 26: whether this item shares its glass with the items beside it, which it does unset. */\n readonly sharesBackground = input(undefined, { transform: optionalBoolean });\n /** The width of a `fixedSpacer`, in points. */\n readonly width = input(undefined, { transform: optionalNumber });\n /** Leave the item out of the toolbar while keeping its place among the others. */\n readonly hidden = input(undefined, { transform: optionalBoolean });\n readonly disabled = input(undefined, { transform: optionalBoolean });\n /** What a screen reader says for a button with a symbol and no title. */\n readonly accessibilityLabel = input<string>();\n\n /** The button was tapped. */\n readonly press = output<void>();\n\n protected readonly identifier = `native-toolbar-item-${created++}`;\n\n constructor() {\n const node = ownHost(inject(ElementRef).nativeElement as EngineNode, NativeToolbarItem);\n const engine = node.host ?? inject(HostEngine);\n inject(DestroyRef).onDestroy(\n engine.setEventListener(node, 'topSelected', () => this.press.emit()),\n );\n }\n}\n"]}
|
|
@@ -9,9 +9,17 @@
|
|
|
9
9
|
* navigation, so the page's guards, resolvers, lazy component and params are what they are in its
|
|
10
10
|
* tab, and a back goes through history as any other does.
|
|
11
11
|
*/
|
|
12
|
-
import { type NavigationExtras, type Router, type UrlTree } from '@angular/router';
|
|
12
|
+
import { type NavigationExtras, type Route, type Router, type UrlTree } from '@angular/router';
|
|
13
13
|
/** The root outlet a page of another tab is presented in. */
|
|
14
14
|
export declare 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
|
+
export declare function taken(route: Route, segments: readonly string[]): number | null;
|
|
15
23
|
/**
|
|
16
24
|
* The commands that present the page at `path` in the root's presented outlet, having given its
|
|
17
25
|
* route a copy there, or null when the config is not one this follows.
|
package/dist/presented-route.js
CHANGED
|
@@ -19,7 +19,7 @@ export const PRESENTED = 'presented';
|
|
|
19
19
|
* ponytail: static and `:param` segments only, which is what a page's url is made of. The rest
|
|
20
20
|
* falls back to presenting the page where its url puts it, as before.
|
|
21
21
|
*/
|
|
22
|
-
function taken(route, segments) {
|
|
22
|
+
export function taken(route, segments) {
|
|
23
23
|
if (route.outlet && route.outlet !== PRIMARY_OUTLET)
|
|
24
24
|
return null;
|
|
25
25
|
if (route.redirectTo !== undefined || route.matcher || route.path === undefined)
|
|
@@ -1 +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"]}
|
|
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 */\nexport function 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,13 +14,14 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { PlatformLocation } from '@angular/common';
|
|
16
16
|
import { NavigationCancel, NavigationEnd, NavigationError, RouteReuseStrategy, Router, provideRouter, } from '@angular/router';
|
|
17
|
-
import { ErrorHandler, computed, inject, makeEnvironmentProviders, provideEnvironmentInitializer, } from '@angular/core';
|
|
17
|
+
import { DestroyRef, 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";
|
|
21
|
-
import { NativeNavigation } from "./native-navigation.js";
|
|
21
|
+
import { NATIVE_INTENT, NativeNavigation, carryIntentAcrossRedirects, intentOf, } from "./native-navigation.js";
|
|
22
22
|
import { NativePlatformLocation } from "./native-platform-location.js";
|
|
23
23
|
import { NativeStackReuseStrategy } from "./native-stack-reuse-strategy.js";
|
|
24
|
+
import { ROUTE_PARKING } from "./route-parking.js";
|
|
24
25
|
import { registerScreenComponents } from "./screens.js";
|
|
25
26
|
/**
|
|
26
27
|
* The page a deep link opens on top of, or null to open it alone. Without it a link to
|
|
@@ -89,6 +90,8 @@ function nativeProviders(parentOf) {
|
|
|
89
90
|
// Without this the router never detaches, so a pushed-away screen is destroyed and rebuilt.
|
|
90
91
|
{ provide: RouteReuseStrategy, useClass: NativeStackReuseStrategy },
|
|
91
92
|
NativeNavigation,
|
|
93
|
+
provideEnvironmentInitializer(carryIntentAcrossRedirects),
|
|
94
|
+
provideEnvironmentInitializer(keepHistoryAcrossReloads),
|
|
92
95
|
];
|
|
93
96
|
if (!parentOf) {
|
|
94
97
|
return [
|
|
@@ -146,6 +149,87 @@ function nativeProviders(parentOf) {
|
|
|
146
149
|
...shared,
|
|
147
150
|
];
|
|
148
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* A history without its round trips: each page once, reached the way it last was. An app that went
|
|
154
|
+
* from one tab to another and back has a history three entries long and is one page deep, and the
|
|
155
|
+
* app that comes back has no use for the detour: gone through at the speed of a reload, the tab
|
|
156
|
+
* bar is still on its way to the second tab when the third navigation asks for the first.
|
|
157
|
+
*/
|
|
158
|
+
function direct(pages) {
|
|
159
|
+
const route = [];
|
|
160
|
+
for (const page of pages) {
|
|
161
|
+
const before = route.findIndex((one) => one.url === page.url);
|
|
162
|
+
if (before !== -1)
|
|
163
|
+
route.length = before;
|
|
164
|
+
route.push(page);
|
|
165
|
+
}
|
|
166
|
+
return route;
|
|
167
|
+
}
|
|
168
|
+
/** A push's animation, with room to spare: 350ms on iOS, about the same on Android. */
|
|
169
|
+
const PUSH_SETTLES = 450;
|
|
170
|
+
/**
|
|
171
|
+
* In development, come back from a reload on the page it left: see `route-parking.ts`.
|
|
172
|
+
*
|
|
173
|
+
* The history a reload left is gone through again once the first navigation has ended, as a
|
|
174
|
+
* launch link is and for the same reason. The hook is what `mount`'s reload waits on.
|
|
175
|
+
*/
|
|
176
|
+
function keepHistoryAcrossReloads() {
|
|
177
|
+
// The bare identifier, so a release build folds the rest away.
|
|
178
|
+
if (typeof __DEV__ === 'undefined' || !__DEV__)
|
|
179
|
+
return;
|
|
180
|
+
const parking = inject(ROUTE_PARKING);
|
|
181
|
+
if (!parking)
|
|
182
|
+
return;
|
|
183
|
+
const router = inject(Router);
|
|
184
|
+
const history = inject(PlatformLocation);
|
|
185
|
+
const errors = inject(ErrorHandler);
|
|
186
|
+
const scope = globalThis;
|
|
187
|
+
// A page keeps how it was presented, a sheet or a modal, and nothing else of its navigation:
|
|
188
|
+
// a replace has already happened to the history, and an app's own state may not survive JSON.
|
|
189
|
+
const pages = () => history instanceof NativePlatformLocation
|
|
190
|
+
? history.ɵpages().map(({ url, state }) => {
|
|
191
|
+
const presentation = intentOf(state)?.presentation;
|
|
192
|
+
return presentation ? { url, presentation } : { url };
|
|
193
|
+
})
|
|
194
|
+
: [{ url: router.url }];
|
|
195
|
+
const park = () => parking.park(direct(pages()));
|
|
196
|
+
scope.__angularNativePark = park;
|
|
197
|
+
// `mount` keeps the app out of sight until this settles, so the pages arrive unseen.
|
|
198
|
+
const restoring = parking
|
|
199
|
+
.collect()
|
|
200
|
+
.then(async (parked) => {
|
|
201
|
+
if (!parked)
|
|
202
|
+
return;
|
|
203
|
+
await new Promise((resolve) => {
|
|
204
|
+
if (router.navigated)
|
|
205
|
+
return resolve();
|
|
206
|
+
const first = router.events.subscribe((event) => {
|
|
207
|
+
if (!isEnd(event))
|
|
208
|
+
return;
|
|
209
|
+
first.unsubscribe();
|
|
210
|
+
resolve();
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
let moved = false;
|
|
214
|
+
for (const { url, presentation } of parked) {
|
|
215
|
+
if (url === router.url)
|
|
216
|
+
continue;
|
|
217
|
+
const state = presentation && { [NATIVE_INTENT]: { stack: 'push', presentation } };
|
|
218
|
+
moved = (await router.navigateByUrl(url, { state })) || moved;
|
|
219
|
+
}
|
|
220
|
+
// The last page is still sliding in when its navigation ends.
|
|
221
|
+
if (moved)
|
|
222
|
+
await new Promise((resolve) => setTimeout(resolve, PUSH_SETTLES));
|
|
223
|
+
})
|
|
224
|
+
.catch((error) => errors.handleError(error));
|
|
225
|
+
scope.__angularNativeRestoring = restoring;
|
|
226
|
+
inject(DestroyRef).onDestroy(() => {
|
|
227
|
+
if (scope.__angularNativePark === park)
|
|
228
|
+
delete scope.__angularNativePark;
|
|
229
|
+
if (scope.__angularNativeRestoring === restoring)
|
|
230
|
+
delete scope.__angularNativeRestoring;
|
|
231
|
+
});
|
|
232
|
+
}
|
|
149
233
|
/** A navigation's last event, whichever way it went. */
|
|
150
234
|
function isEnd(event) {
|
|
151
235
|
return (event instanceof NavigationEnd ||
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provide-native-router.js","sourceRoot":"","sources":["../src/provide-native-router.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Angular's Router, on native navigation: `provideRouter` and the native pieces under it, in one\n * call.\n *\n * `provideNativeRouter(routes, withComponentInputBinding())` takes the same routes and the same\n * router features as `provideRouter` - it calls it - and adds what native needs on top: a\n * `PlatformLocation` over an in-memory history, a `RouteReuseStrategy` that detaches a pushed-away\n * screen instead of destroying it, and `NativeNavigation`. Those come after `provideRouter`'s own\n * providers, so they win over the web defaults without the app having to order anything.\n *\n * Deep links and Android's back button come from `@ng-native/device` and need no wiring: off a\n * device both report nothing, and navigation works either way. The native options - `withLinkParent`,\n * `withHeaderDefaults` and `withTabDefaults` - are passed like any other feature.\n */\nimport { PlatformLocation } from '@angular/common';\nimport {\n NavigationCancel,\n NavigationEnd,\n NavigationError,\n RouteReuseStrategy,\n Router,\n provideRouter,\n type RouterFeatures,\n type Routes,\n} from '@angular/router';\nimport {\n ErrorHandler,\n computed,\n inject,\n makeEnvironmentProviders,\n provideEnvironmentInitializer,\n type EnvironmentProviders,\n type InjectionToken,\n type Provider,\n type Signal,\n} from '@angular/core';\nimport { ColorScheme, DeepLinks, type Scheme } from '@ng-native/device';\nimport {\n NATIVE_HEADER_DEFAULTS,\n NATIVE_TAB_DEFAULTS,\n type HeaderDefaults,\n type SchemeDefaults,\n type TabDefaults,\n} from './native-bar-defaults.ts';\nimport { followLink, linkAncestry, type LinkParent } from './native-links.ts';\nimport { NativeNavigation } from './native-navigation.ts';\nimport { NativePlatformLocation } from './native-platform-location.ts';\nimport { NativeStackReuseStrategy } from './native-stack-reuse-strategy.ts';\nimport { registerScreenComponents } from './screens.ts';\n\n/** A native-only router option, passed to `provideNativeRouter` beside Angular's own features. */\nexport interface NativeRouterFeature {\n readonly ɵnativeRouter: { readonly linkParent?: LinkParent; readonly providers?: Provider[] };\n}\n\n/**\n * The page a deep link opens on top of, or null to open it alone. Without it a link to\n * `/settings/notifications` opens that page with no Back. See native-links.ts.\n */\nexport function withLinkParent(parentOf: LinkParent): NativeRouterFeature {\n return { ɵnativeRouter: { linkParent: parentOf } };\n}\n\n/**\n * How every `<native-header>` looks when it does not say: its colours, blur, shadow, fonts and\n * light or dark style. A header's own binding always wins.\n *\n * ```ts\n * provideNativeRouter(\n * routes,\n * withHeaderDefaults((scheme) => ({\n * backgroundColor: scheme === 'dark' ? '#101014' : '#f4f4f7',\n * titleColor: scheme === 'dark' ? '#ffffff' : '#101014',\n * color: '#3b6ef5',\n * })),\n * );\n * ```\n *\n * Colours it leaves out still come from `NATIVE_HEADER_PALETTE`.\n */\nexport function withHeaderDefaults(defaults: SchemeDefaults<HeaderDefaults>): NativeRouterFeature {\n return { ɵnativeRouter: { providers: [bySchemeProvider(NATIVE_HEADER_DEFAULTS, defaults)] } };\n}\n\n/**\n * How every `<native-tabs-outlet>` and `<native-tab>` looks when it does not say: the tint, the\n * background behind the tabs, the light or dark style, and each tab's bar appearance. An outlet's\n * or a tab's own binding always wins.\n */\nexport function withTabDefaults(defaults: SchemeDefaults<TabDefaults>): NativeRouterFeature {\n return { ɵnativeRouter: { providers: [bySchemeProvider(NATIVE_TAB_DEFAULTS, defaults)] } };\n}\n\n/** One signal for the whole app, rather than every bar calling the function again. */\nfunction bySchemeProvider<T>(\n token: InjectionToken<Signal<T>>,\n defaults: SchemeDefaults<T>,\n): Provider {\n return {\n provide: token,\n useFactory: () => {\n if (typeof defaults !== 'function') return computed(() => defaults);\n const scheme = inject(ColorScheme);\n return computed(() => (defaults as (scheme: Scheme) => T)(scheme.current()));\n },\n };\n}\n\nfunction isNative(feature: RouterFeatures | NativeRouterFeature): feature is NativeRouterFeature {\n return 'ɵnativeRouter' in feature;\n}\n\nexport function provideNativeRouter(\n routes: Routes,\n ...features: (RouterFeatures | NativeRouterFeature)[]\n): EnvironmentProviders {\n const angular = features.filter((feature): feature is RouterFeatures => !isNative(feature));\n const native = features.filter(isNative).map((feature) => feature.ɵnativeRouter);\n const parentOf = native.find((feature) => feature.linkParent)?.linkParent;\n return makeEnvironmentProviders([\n provideRouter(routes, ...angular),\n ...nativeProviders(parentOf),\n ...native.flatMap((feature) => feature.providers ?? []),\n ]);\n}\n\nfunction nativeProviders(parentOf: LinkParent | undefined): (Provider | EnvironmentProviders)[] {\n // The engine needs the element-to-view-name mapping before the first navigation renders.\n registerScreenComponents();\n\n const shared = [\n // Without this the router never detaches, so a pushed-away screen is destroyed and rebuilt.\n { provide: RouteReuseStrategy, useClass: NativeStackReuseStrategy },\n NativeNavigation,\n ];\n if (!parentOf) {\n return [\n {\n provide: PlatformLocation,\n useFactory: () => new NativePlatformLocation(inject(DeepLinks)),\n },\n ...shared,\n ];\n }\n\n return [\n {\n provide: PlatformLocation,\n // The app launches on the page the link belongs under; the link follows once it is shown.\n // Links while running go through the router below rather than straight into history.\n useFactory: () => {\n const initial = inject(DeepLinks).initialUrl();\n return new NativePlatformLocation({\n initialUrl: () => (initial && linkAncestry(initial, parentOf)[0]) || initial,\n subscribe: () => () => {},\n });\n },\n },\n provideEnvironmentInitializer(() => {\n const router = inject(Router);\n const links = inject(DeepLinks);\n const errors = inject(ErrorHandler);\n // A link nobody awaits: a page that fails to load is reported, as a tab tap's is, rather\n // than left as an unhandled rejection. The app's navigation error handler hears it too.\n const follow = (url: string) =>\n followLink(router, url, parentOf).catch((error: unknown) => errors.handleError(error));\n const initial = links.initialUrl();\n if (initial && parentOf(initial)) {\n const first = router.events.subscribe((event) => {\n if (!(event instanceof NavigationEnd)) return;\n first.unsubscribe();\n void follow(initial);\n });\n }\n // A launch link arrives the way any other does, once `getInitialURL()` settles, which is\n // usually while the router's first navigation is still going: following it then cancels that\n // navigation, and the app opens with no root screen under the link. So links wait for it.\n const settled = new Promise<void>((resolve) => {\n if (router.navigated) return resolve();\n const first = router.events.subscribe((event) => {\n if (!isEnd(event)) return;\n first.unsubscribe();\n resolve();\n });\n });\n links.subscribe((url) => void settled.then(() => follow(url)));\n }),\n ...shared,\n ];\n}\n\n/** A navigation's last event, whichever way it went. */\nfunction isEnd(event: unknown): boolean {\n return (\n event instanceof NavigationEnd ||\n event instanceof NavigationCancel ||\n event instanceof NavigationError\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"provide-native-router.js","sourceRoot":"","sources":["../src/provide-native-router.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Angular's Router, on native navigation: `provideRouter` and the native pieces under it, in one\n * call.\n *\n * `provideNativeRouter(routes, withComponentInputBinding())` takes the same routes and the same\n * router features as `provideRouter` - it calls it - and adds what native needs on top: a\n * `PlatformLocation` over an in-memory history, a `RouteReuseStrategy` that detaches a pushed-away\n * screen instead of destroying it, and `NativeNavigation`. Those come after `provideRouter`'s own\n * providers, so they win over the web defaults without the app having to order anything.\n *\n * Deep links and Android's back button come from `@ng-native/device` and need no wiring: off a\n * device both report nothing, and navigation works either way. The native options - `withLinkParent`,\n * `withHeaderDefaults` and `withTabDefaults` - are passed like any other feature.\n */\nimport { PlatformLocation } from '@angular/common';\nimport {\n NavigationCancel,\n NavigationEnd,\n NavigationError,\n RouteReuseStrategy,\n Router,\n provideRouter,\n type RouterFeatures,\n type Routes,\n} from '@angular/router';\nimport {\n DestroyRef,\n ErrorHandler,\n computed,\n inject,\n makeEnvironmentProviders,\n provideEnvironmentInitializer,\n type EnvironmentProviders,\n type InjectionToken,\n type Provider,\n type Signal,\n} from '@angular/core';\nimport { ColorScheme, DeepLinks, type Scheme } from '@ng-native/device';\nimport {\n NATIVE_HEADER_DEFAULTS,\n NATIVE_TAB_DEFAULTS,\n type HeaderDefaults,\n type SchemeDefaults,\n type TabDefaults,\n} from './native-bar-defaults.ts';\nimport { followLink, linkAncestry, type LinkParent } from './native-links.ts';\nimport {\n NATIVE_INTENT,\n NativeNavigation,\n carryIntentAcrossRedirects,\n intentOf,\n} from './native-navigation.ts';\nimport { NativePlatformLocation } from './native-platform-location.ts';\nimport { NativeStackReuseStrategy } from './native-stack-reuse-strategy.ts';\nimport { ROUTE_PARKING, type ParkedPage } from './route-parking.ts';\nimport { registerScreenComponents } from './screens.ts';\n\n/** A native-only router option, passed to `provideNativeRouter` beside Angular's own features. */\nexport interface NativeRouterFeature {\n readonly ɵnativeRouter: { readonly linkParent?: LinkParent; readonly providers?: Provider[] };\n}\n\n/**\n * The page a deep link opens on top of, or null to open it alone. Without it a link to\n * `/settings/notifications` opens that page with no Back. See native-links.ts.\n */\nexport function withLinkParent(parentOf: LinkParent): NativeRouterFeature {\n return { ɵnativeRouter: { linkParent: parentOf } };\n}\n\n/**\n * How every `<native-header>` looks when it does not say: its colours, blur, shadow, fonts and\n * light or dark style. A header's own binding always wins.\n *\n * ```ts\n * provideNativeRouter(\n * routes,\n * withHeaderDefaults((scheme) => ({\n * backgroundColor: scheme === 'dark' ? '#101014' : '#f4f4f7',\n * titleColor: scheme === 'dark' ? '#ffffff' : '#101014',\n * color: '#3b6ef5',\n * })),\n * );\n * ```\n *\n * Colours it leaves out still come from `NATIVE_HEADER_PALETTE`.\n */\nexport function withHeaderDefaults(defaults: SchemeDefaults<HeaderDefaults>): NativeRouterFeature {\n return { ɵnativeRouter: { providers: [bySchemeProvider(NATIVE_HEADER_DEFAULTS, defaults)] } };\n}\n\n/**\n * How every `<native-tabs-outlet>` and `<native-tab>` looks when it does not say: the tint, the\n * background behind the tabs, the light or dark style, and each tab's bar appearance. An outlet's\n * or a tab's own binding always wins.\n */\nexport function withTabDefaults(defaults: SchemeDefaults<TabDefaults>): NativeRouterFeature {\n return { ɵnativeRouter: { providers: [bySchemeProvider(NATIVE_TAB_DEFAULTS, defaults)] } };\n}\n\n/** One signal for the whole app, rather than every bar calling the function again. */\nfunction bySchemeProvider<T>(\n token: InjectionToken<Signal<T>>,\n defaults: SchemeDefaults<T>,\n): Provider {\n return {\n provide: token,\n useFactory: () => {\n if (typeof defaults !== 'function') return computed(() => defaults);\n const scheme = inject(ColorScheme);\n return computed(() => (defaults as (scheme: Scheme) => T)(scheme.current()));\n },\n };\n}\n\nfunction isNative(feature: RouterFeatures | NativeRouterFeature): feature is NativeRouterFeature {\n return 'ɵnativeRouter' in feature;\n}\n\nexport function provideNativeRouter(\n routes: Routes,\n ...features: (RouterFeatures | NativeRouterFeature)[]\n): EnvironmentProviders {\n const angular = features.filter((feature): feature is RouterFeatures => !isNative(feature));\n const native = features.filter(isNative).map((feature) => feature.ɵnativeRouter);\n const parentOf = native.find((feature) => feature.linkParent)?.linkParent;\n return makeEnvironmentProviders([\n provideRouter(routes, ...angular),\n ...nativeProviders(parentOf),\n ...native.flatMap((feature) => feature.providers ?? []),\n ]);\n}\n\nfunction nativeProviders(parentOf: LinkParent | undefined): (Provider | EnvironmentProviders)[] {\n // The engine needs the element-to-view-name mapping before the first navigation renders.\n registerScreenComponents();\n\n const shared = [\n // Without this the router never detaches, so a pushed-away screen is destroyed and rebuilt.\n { provide: RouteReuseStrategy, useClass: NativeStackReuseStrategy },\n NativeNavigation,\n provideEnvironmentInitializer(carryIntentAcrossRedirects),\n provideEnvironmentInitializer(keepHistoryAcrossReloads),\n ];\n if (!parentOf) {\n return [\n {\n provide: PlatformLocation,\n useFactory: () => new NativePlatformLocation(inject(DeepLinks)),\n },\n ...shared,\n ];\n }\n\n return [\n {\n provide: PlatformLocation,\n // The app launches on the page the link belongs under; the link follows once it is shown.\n // Links while running go through the router below rather than straight into history.\n useFactory: () => {\n const initial = inject(DeepLinks).initialUrl();\n return new NativePlatformLocation({\n initialUrl: () => (initial && linkAncestry(initial, parentOf)[0]) || initial,\n subscribe: () => () => {},\n });\n },\n },\n provideEnvironmentInitializer(() => {\n const router = inject(Router);\n const links = inject(DeepLinks);\n const errors = inject(ErrorHandler);\n // A link nobody awaits: a page that fails to load is reported, as a tab tap's is, rather\n // than left as an unhandled rejection. The app's navigation error handler hears it too.\n const follow = (url: string) =>\n followLink(router, url, parentOf).catch((error: unknown) => errors.handleError(error));\n const initial = links.initialUrl();\n if (initial && parentOf(initial)) {\n const first = router.events.subscribe((event) => {\n if (!(event instanceof NavigationEnd)) return;\n first.unsubscribe();\n void follow(initial);\n });\n }\n // A launch link arrives the way any other does, once `getInitialURL()` settles, which is\n // usually while the router's first navigation is still going: following it then cancels that\n // navigation, and the app opens with no root screen under the link. So links wait for it.\n const settled = new Promise<void>((resolve) => {\n if (router.navigated) return resolve();\n const first = router.events.subscribe((event) => {\n if (!isEnd(event)) return;\n first.unsubscribe();\n resolve();\n });\n });\n links.subscribe((url) => void settled.then(() => follow(url)));\n }),\n ...shared,\n ];\n}\n\n/**\n * A history without its round trips: each page once, reached the way it last was. An app that went\n * from one tab to another and back has a history three entries long and is one page deep, and the\n * app that comes back has no use for the detour: gone through at the speed of a reload, the tab\n * bar is still on its way to the second tab when the third navigation asks for the first.\n */\nfunction direct(pages: ParkedPage[]): ParkedPage[] {\n const route: ParkedPage[] = [];\n for (const page of pages) {\n const before = route.findIndex((one) => one.url === page.url);\n if (before !== -1) route.length = before;\n route.push(page);\n }\n return route;\n}\n\ndeclare const __DEV__: boolean | undefined;\n\n/** A push's animation, with room to spare: 350ms on iOS, about the same on Android. */\nconst PUSH_SETTLES = 450;\n\n/**\n * In development, come back from a reload on the page it left: see `route-parking.ts`.\n *\n * The history a reload left is gone through again once the first navigation has ended, as a\n * launch link is and for the same reason. The hook is what `mount`'s reload waits on.\n */\nfunction keepHistoryAcrossReloads(): void {\n // The bare identifier, so a release build folds the rest away.\n if (typeof __DEV__ === 'undefined' || !__DEV__) return;\n const parking = inject(ROUTE_PARKING);\n if (!parking) return;\n const router = inject(Router);\n const history = inject(PlatformLocation);\n const errors = inject(ErrorHandler);\n const scope = globalThis as {\n __angularNativePark?: () => Promise<void>;\n __angularNativeRestoring?: Promise<void>;\n };\n // A page keeps how it was presented, a sheet or a modal, and nothing else of its navigation:\n // a replace has already happened to the history, and an app's own state may not survive JSON.\n const pages = (): ParkedPage[] =>\n history instanceof NativePlatformLocation\n ? history.ɵpages().map(({ url, state }) => {\n const presentation = intentOf(state)?.presentation;\n return presentation ? { url, presentation } : { url };\n })\n : [{ url: router.url }];\n const park = () => parking.park(direct(pages()));\n scope.__angularNativePark = park;\n\n // `mount` keeps the app out of sight until this settles, so the pages arrive unseen.\n const restoring = parking\n .collect()\n .then(async (parked) => {\n if (!parked) return;\n await new Promise<void>((resolve) => {\n if (router.navigated) return resolve();\n const first = router.events.subscribe((event) => {\n if (!isEnd(event)) return;\n first.unsubscribe();\n resolve();\n });\n });\n let moved = false;\n for (const { url, presentation } of parked) {\n if (url === router.url) continue;\n const state = presentation && { [NATIVE_INTENT]: { stack: 'push', presentation } };\n moved = (await router.navigateByUrl(url, { state })) || moved;\n }\n // The last page is still sliding in when its navigation ends.\n if (moved) await new Promise((resolve) => setTimeout(resolve, PUSH_SETTLES));\n })\n .catch((error: unknown) => errors.handleError(error));\n scope.__angularNativeRestoring = restoring;\n inject(DestroyRef).onDestroy(() => {\n if (scope.__angularNativePark === park) delete scope.__angularNativePark;\n if (scope.__angularNativeRestoring === restoring) delete scope.__angularNativeRestoring;\n });\n}\n\n/** A navigation's last event, whichever way it went. */\nfunction isEnd(event: unknown): boolean {\n return (\n event instanceof NavigationEnd ||\n event instanceof NavigationCancel ||\n event instanceof NavigationError\n );\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a development reload leaves the app's history, and finds it again.
|
|
3
|
+
*
|
|
4
|
+
* An edit that cannot be applied to the running app reloads it, and the reload takes the
|
|
5
|
+
* JavaScript runtime with it, the history included: the app came back on its first route, however
|
|
6
|
+
* deep the page being worked on was. Nothing in the app outlives the reload, so the history is
|
|
7
|
+
* left with the dev server, which `withAngularNative` teaches to hold it, and collected by the app
|
|
8
|
+
* that comes back. That app goes through it again, one navigation a page, so each page is a
|
|
9
|
+
* screen with the ones before it beneath and Back retraces them.
|
|
10
|
+
*
|
|
11
|
+
* Only a reload the app started parks anything, and what is parked is handed back once: an app
|
|
12
|
+
* launched afresh, or reloaded from the dev menu, starts where it always does.
|
|
13
|
+
*
|
|
14
|
+
* Page state is not history. A reload still makes every component and service again.
|
|
15
|
+
*/
|
|
16
|
+
import { InjectionToken } from '@angular/core';
|
|
17
|
+
import type { ScreenPresentation } from './screen-presentation.ts';
|
|
18
|
+
/** One page of the history: where it is, and how it arrived if not as a plain push. */
|
|
19
|
+
export interface ParkedPage {
|
|
20
|
+
url: string;
|
|
21
|
+
presentation?: ScreenPresentation;
|
|
22
|
+
}
|
|
23
|
+
export interface RouteParking {
|
|
24
|
+
/** Leave the history, oldest first, for the app that comes back. */
|
|
25
|
+
park(pages: ParkedPage[]): Promise<void>;
|
|
26
|
+
/** The history a reload left, once; null when none did. */
|
|
27
|
+
collect(): Promise<ParkedPage[] | null>;
|
|
28
|
+
}
|
|
29
|
+
/** Null where there is no dev server to park with: a release build, a test, the web. */
|
|
30
|
+
export declare const ROUTE_PARKING: InjectionToken<RouteParking | null>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a development reload leaves the app's history, and finds it again.
|
|
3
|
+
*
|
|
4
|
+
* An edit that cannot be applied to the running app reloads it, and the reload takes the
|
|
5
|
+
* JavaScript runtime with it, the history included: the app came back on its first route, however
|
|
6
|
+
* deep the page being worked on was. Nothing in the app outlives the reload, so the history is
|
|
7
|
+
* left with the dev server, which `withAngularNative` teaches to hold it, and collected by the app
|
|
8
|
+
* that comes back. That app goes through it again, one navigation a page, so each page is a
|
|
9
|
+
* screen with the ones before it beneath and Back retraces them.
|
|
10
|
+
*
|
|
11
|
+
* Only a reload the app started parks anything, and what is parked is handed back once: an app
|
|
12
|
+
* launched afresh, or reloaded from the dev menu, starts where it always does.
|
|
13
|
+
*
|
|
14
|
+
* Page state is not history. A reload still makes every component and service again.
|
|
15
|
+
*/
|
|
16
|
+
import { InjectionToken } from '@angular/core';
|
|
17
|
+
/** Long enough for a dev server on the same network, and short enough not to hold a reload up. */
|
|
18
|
+
const PARKS_WITHIN = 1000;
|
|
19
|
+
/**
|
|
20
|
+
* Longer, for an app that is starting: its thread is busy loading itself, and a timer that ran
|
|
21
|
+
* out in the meantime is answered before the response that arrived first. On an Android emulator
|
|
22
|
+
* a second was not enough, and the history was taken from the dev server and thrown away.
|
|
23
|
+
*/
|
|
24
|
+
const COLLECTS_WITHIN = 4000;
|
|
25
|
+
/** Null where there is no dev server to park with: a release build, a test, the web. */
|
|
26
|
+
export const ROUTE_PARKING = new InjectionToken('angular-native.routeParking', { factory: devServerParking });
|
|
27
|
+
function devServerParking() {
|
|
28
|
+
if (typeof __DEV__ === 'undefined' || !__DEV__)
|
|
29
|
+
return null;
|
|
30
|
+
let bundle;
|
|
31
|
+
try {
|
|
32
|
+
// Required, and by its own path: React Native does not export it, and ships Flow, which Node
|
|
33
|
+
// cannot parse, so nothing here can be imported where a test runs.
|
|
34
|
+
// @ts-ignore `require` has a type only where the app's tsconfig loads one.
|
|
35
|
+
const module = require('react-native/Libraries/Core/Devtools/getDevServer');
|
|
36
|
+
const server = (module.default ?? module)();
|
|
37
|
+
if (server.bundleLoadedFromServer)
|
|
38
|
+
bundle = server.fullBundleUrl ?? server.url;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// No React Native under us.
|
|
42
|
+
}
|
|
43
|
+
const origin = /^https?:\/\/[^/]+/.exec(bundle ?? '')?.[0];
|
|
44
|
+
if (!origin)
|
|
45
|
+
return null;
|
|
46
|
+
const at = `${origin}/__ng-native/route?app=${/[?&]platform=(\w+)/.exec(bundle)?.[1] ?? ''}`;
|
|
47
|
+
const ask = (within, init) => Promise.race([
|
|
48
|
+
fetch(at, init),
|
|
49
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('timed out')), within)),
|
|
50
|
+
]);
|
|
51
|
+
return {
|
|
52
|
+
park: (pages) => ask(PARKS_WITHIN, { method: 'POST', body: JSON.stringify(pages) }).then(() => { }),
|
|
53
|
+
// A dev server that does not know the address answers with a page, not a history.
|
|
54
|
+
collect: () => ask(COLLECTS_WITHIN)
|
|
55
|
+
.then((response) => (response.ok ? response.json() : null))
|
|
56
|
+
.then((pages) => (Array.isArray(pages) ? pages : null))
|
|
57
|
+
.catch(() => null),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=route-parking.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-parking.js","sourceRoot":"","sources":["../src/route-parking.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAkB/C,kGAAkG;AAClG,MAAM,YAAY,GAAG,IAAI,CAAC;AAC1B;;;;GAIG;AACH,MAAM,eAAe,GAAG,IAAI,CAAC;AAE7B,wFAAwF;AACxF,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,cAAc,CAC7C,6BAA6B,EAC7B,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAC9B,CAAC;AAEF,SAAS,gBAAgB;IACvB,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC5D,IAAI,MAA0B,CAAC;IAC/B,IAAI,CAAC;QACH,6FAA6F;QAC7F,mEAAmE;QACnE,2EAA2E;QAC3E,MAAM,MAAM,GAAG,OAAO,CAAC,mDAAmD,CAAc,CAAC;QACzF,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,EAAE,CAAC;QAC5C,IAAI,MAAM,CAAC,sBAAsB;YAAE,MAAM,GAAG,MAAM,CAAC,aAAa,IAAI,MAAM,CAAC,GAAG,CAAC;IACjF,CAAC;IAAC,MAAM,CAAC;QACP,4BAA4B;IAC9B,CAAC;IACD,MAAM,MAAM,GAAG,mBAAmB,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3D,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,MAAM,EAAE,GAAG,GAAG,MAAM,0BAA0B,oBAAoB,CAAC,IAAI,CAAC,MAAO,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;IAC9F,MAAM,GAAG,GAAG,CAAC,MAAc,EAAE,IAAkB,EAAE,EAAE,CACjD,OAAO,CAAC,IAAI,CAAC;QACX,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC;QACf,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;KAC5F,CAAC,CAAC;IACL,OAAO;QACL,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CACd,GAAG,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;QACnF,kFAAkF;QAClF,OAAO,EAAE,GAAG,EAAE,CACZ,GAAG,CAAC,eAAe,CAAC;aACjB,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;aAC1D,IAAI,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,KAAsB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;aACjF,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC;KACvB,CAAC;AACJ,CAAC","sourcesContent":["/**\n * Where a development reload leaves the app's history, and finds it again.\n *\n * An edit that cannot be applied to the running app reloads it, and the reload takes the\n * JavaScript runtime with it, the history included: the app came back on its first route, however\n * deep the page being worked on was. Nothing in the app outlives the reload, so the history is\n * left with the dev server, which `withAngularNative` teaches to hold it, and collected by the app\n * that comes back. That app goes through it again, one navigation a page, so each page is a\n * screen with the ones before it beneath and Back retraces them.\n *\n * Only a reload the app started parks anything, and what is parked is handed back once: an app\n * launched afresh, or reloaded from the dev menu, starts where it always does.\n *\n * Page state is not history. A reload still makes every component and service again.\n */\nimport { InjectionToken } from '@angular/core';\nimport type { ScreenPresentation } from './screen-presentation.ts';\n\n/** One page of the history: where it is, and how it arrived if not as a plain push. */\nexport interface ParkedPage {\n url: string;\n presentation?: ScreenPresentation;\n}\n\nexport interface RouteParking {\n /** Leave the history, oldest first, for the app that comes back. */\n park(pages: ParkedPage[]): Promise<void>;\n /** The history a reload left, once; null when none did. */\n collect(): Promise<ParkedPage[] | null>;\n}\n\ndeclare const __DEV__: boolean | undefined;\n\n/** Long enough for a dev server on the same network, and short enough not to hold a reload up. */\nconst PARKS_WITHIN = 1000;\n/**\n * Longer, for an app that is starting: its thread is busy loading itself, and a timer that ran\n * out in the meantime is answered before the response that arrived first. On an Android emulator\n * a second was not enough, and the history was taken from the dev server and thrown away.\n */\nconst COLLECTS_WITHIN = 4000;\n\n/** Null where there is no dev server to park with: a release build, a test, the web. */\nexport const ROUTE_PARKING = new InjectionToken<RouteParking | null>(\n 'angular-native.routeParking',\n { factory: devServerParking },\n);\n\nfunction devServerParking(): RouteParking | null {\n if (typeof __DEV__ === 'undefined' || !__DEV__) return null;\n let bundle: string | undefined;\n try {\n // Required, and by its own path: React Native does not export it, and ships Flow, which Node\n // cannot parse, so nothing here can be imported where a test runs.\n // @ts-ignore `require` has a type only where the app's tsconfig loads one.\n const module = require('react-native/Libraries/Core/Devtools/getDevServer') as DevServer;\n const server = (module.default ?? module)();\n if (server.bundleLoadedFromServer) bundle = server.fullBundleUrl ?? server.url;\n } catch {\n // No React Native under us.\n }\n const origin = /^https?:\\/\\/[^/]+/.exec(bundle ?? '')?.[0];\n if (!origin) return null;\n const at = `${origin}/__ng-native/route?app=${/[?&]platform=(\\w+)/.exec(bundle!)?.[1] ?? ''}`;\n const ask = (within: number, init?: RequestInit) =>\n Promise.race([\n fetch(at, init),\n new Promise<never>((_, reject) => setTimeout(() => reject(new Error('timed out')), within)),\n ]);\n return {\n park: (pages) =>\n ask(PARKS_WITHIN, { method: 'POST', body: JSON.stringify(pages) }).then(() => {}),\n // A dev server that does not know the address answers with a page, not a history.\n collect: () =>\n ask(COLLECTS_WITHIN)\n .then((response) => (response.ok ? response.json() : null))\n .then((pages: unknown) => (Array.isArray(pages) ? (pages as ParkedPage[]) : null))\n .catch(() => null),\n };\n}\n\ntype GetDevServer = () => { url: string; fullBundleUrl?: string; bundleLoadedFromServer: boolean };\ntype DevServer = GetDevServer & { default?: GetDevServer };\n"]}
|
package/dist/screens.js
CHANGED
|
@@ -80,6 +80,19 @@ export const ActivityState = {
|
|
|
80
80
|
/** The screen the user is on. */
|
|
81
81
|
Active: 2,
|
|
82
82
|
};
|
|
83
|
+
/**
|
|
84
|
+
* The Fabric name of one of `ExpoRouterToolbarModule`'s views, as `requireNativeView` in
|
|
85
|
+
* `expo-modules-core` computes it. Expo Go runs many projects in one binary and names every view
|
|
86
|
+
* after the one it is running; a build of the app's own has no such suffix.
|
|
87
|
+
*
|
|
88
|
+
* `@ng-native/expo` derives the same name for its own views. The Angular-facing packages do not
|
|
89
|
+
* import each other, and this is three lines.
|
|
90
|
+
*/
|
|
91
|
+
function expoRouterToolbarView(view) {
|
|
92
|
+
const identifier = globalThis.expo
|
|
93
|
+
?.__expo_app_identifier__;
|
|
94
|
+
return `ViewManagerAdapter_ExpoRouterToolbarModule_${view}${identifier ? `_${identifier}` : ''}`;
|
|
95
|
+
}
|
|
83
96
|
/**
|
|
84
97
|
* Call once at startup, before the first navigation.
|
|
85
98
|
*
|
|
@@ -100,6 +113,16 @@ export function registerScreenComponents() {
|
|
|
100
113
|
for (const [element, [ios, android, defaults]] of Object.entries(TAB_VIEW_NAMES)) {
|
|
101
114
|
registerViewName(element, { ios, android }, defaults);
|
|
102
115
|
}
|
|
116
|
+
// A screen's bottom toolbar is Expo Router's, which react-native-screens has none of: see
|
|
117
|
+
// `native-toolbar.ts`. Android has no such bar, so the elements are plain views there.
|
|
118
|
+
registerViewName('native-toolbar', {
|
|
119
|
+
ios: expoRouterToolbarView('RouterToolbarHostView'),
|
|
120
|
+
android: 'RCTView',
|
|
121
|
+
});
|
|
122
|
+
registerViewName('native-toolbar-item', {
|
|
123
|
+
ios: expoRouterToolbarView('RouterToolbarItemView'),
|
|
124
|
+
android: 'RCTView',
|
|
125
|
+
});
|
|
103
126
|
// `findHeaderConfig` in `RNSScreen.mm` looks at the screen's direct children and no deeper, so
|
|
104
127
|
// a header a page wrote inside a `safe-area-view` or a component of its own commits beside it.
|
|
105
128
|
// Nothing in `RNSScreen.mm` updates the bar when its config is unmounted, so a header an `@if`
|