@ng-native/router 0.4.0 → 0.6.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 +359 -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-header.d.ts +4 -0
- package/dist/native-header.js +17 -4
- package/dist/native-header.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-stack-outlet.d.ts +6 -0
- package/dist/native-stack-outlet.js +18 -0
- 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 +23 -4
- 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/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 +85 -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/screen-header.d.ts +10 -0
- package/dist/screen-header.js +11 -0
- package/dist/screen-header.js.map +1 -0
- package/dist/screen-safe-area-view.d.ts +18 -0
- package/dist/screen-safe-area-view.js +74 -0
- package/dist/screen-safe-area-view.js.map +1 -0
- package/dist/screens.js +3 -2
- package/dist/screens.js.map +1 -1
- package/dist/tab-safe-area-view.d.ts +4 -11
- package/dist/tab-safe-area-view.js +8 -43
- package/dist/tab-safe-area-view.js.map +1 -1
- package/package.json +3 -3
package/dist/native-tab.js.map
CHANGED
|
@@ -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, 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"]}
|
|
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, 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/**\n * The key native identifies a tab by: its path, or `/` for the tab at path `''`, since iOS reads an\n * empty key as none and fails on the tap that selects it. No route path starts with `/`.\n */\nexport function screenKeyOf(path: string): string {\n return path || '/';\n}\n\n@Component({\n selector: 'native-tab',\n template: '',\n host: {\n '[screenKey]': 'screenKey()',\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 protected readonly screenKey = computed(() => screenKeyOf(this.path()));\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"]}
|
|
@@ -87,6 +87,12 @@ export declare class NativeTabsOutlet implements RouterOutletContract, AfterCont
|
|
|
87
87
|
/** The input binder reads this to bind route params to component inputs. */
|
|
88
88
|
get activatedComponentRef(): ComponentRef<unknown> | null;
|
|
89
89
|
activateWith(route: ActivatedRoute, environmentInjector: EnvironmentInjector): void;
|
|
90
|
+
/**
|
|
91
|
+
* Destroy the tab page in front: nothing once the router has detached it, as Angular's own
|
|
92
|
+
* outlet does nothing then. A tab under a route with no component of its own, a `loadChildren`
|
|
93
|
+
* wrapper or a group, has the router detach the page and then deactivate this same outlet, and
|
|
94
|
+
* that page is the tab, kept for when it is selected again.
|
|
95
|
+
*/
|
|
90
96
|
deactivate(): void;
|
|
91
97
|
/**
|
|
92
98
|
* Hand the live `ComponentRef` back to the router. The tab's screen stays where it is with the
|
|
@@ -109,9 +115,19 @@ export declare class NativeTabsOutlet implements RouterOutletContract, AfterCont
|
|
|
109
115
|
* exist, which is all this needs.
|
|
110
116
|
*/
|
|
111
117
|
private readTabs;
|
|
118
|
+
/**
|
|
119
|
+
* The tab `route` is the page of, by its path from this outlet's route down to it. A route with
|
|
120
|
+
* no component above the page is part of that path: a `loadChildren` wrapper takes the tab's
|
|
121
|
+
* path and leaves `''` to the page under it, and a group is a `''` of its own.
|
|
122
|
+
*/
|
|
112
123
|
private entryFor;
|
|
113
124
|
/** Remember where this tab is, and tell native to show it. */
|
|
114
125
|
private select;
|
|
126
|
+
/**
|
|
127
|
+
* Another tab came in front of `entry`: its stacks dismiss what they presented, and it is
|
|
128
|
+
* remembered at the page beneath, where the outermost stack that dismissed something is left.
|
|
129
|
+
*/
|
|
130
|
+
private leave;
|
|
115
131
|
/**
|
|
116
132
|
* A back with nothing left to pop inside the tab: go to the first tab, where it was left, which
|
|
117
133
|
* is what a bottom navigation bar does on Android. From the first tab, decline, so the platform
|
|
@@ -129,6 +145,15 @@ export declare class NativeTabsOutlet implements RouterOutletContract, AfterCont
|
|
|
129
145
|
private unopenedTabOf;
|
|
130
146
|
/** Whether `url` is a tab other than the one in front, or a page inside one. */
|
|
131
147
|
private behind;
|
|
148
|
+
/**
|
|
149
|
+
* The tab `url` is in: the one with the longest url that starts it. A tab at `''` has the bar's
|
|
150
|
+
* own url, which at the root starts every url, pages outside the bar included, so it takes only
|
|
151
|
+
* a url its routes lead to, as far as they are loaded. A wildcard or a custom matcher is not
|
|
152
|
+
* followed, as `taken` follows neither: a page only one of them shows is in no tab.
|
|
153
|
+
*/
|
|
154
|
+
private tabOf;
|
|
155
|
+
/** Whether the routes of the tab at `''` lead to `rest`, the path under the bar's own url. */
|
|
156
|
+
private leadsInto;
|
|
132
157
|
private request;
|
|
133
158
|
/**
|
|
134
159
|
* Native switched tab. Acknowledge the new state, then navigate: the router is authoritative
|
|
@@ -145,7 +170,10 @@ export declare class NativeTabsOutlet implements RouterOutletContract, AfterCont
|
|
|
145
170
|
private revertUnless;
|
|
146
171
|
/** A tab's own url, from the outlet's route down. */
|
|
147
172
|
private pathOf;
|
|
148
|
-
/**
|
|
173
|
+
/**
|
|
174
|
+
* As the stack outlet's: the route, the outlet contexts, and whether the tab is showing. And the
|
|
175
|
+
* tab itself, for its stacks to hear when another comes in front.
|
|
176
|
+
*/
|
|
149
177
|
private outletInjector;
|
|
150
178
|
static ɵfac: i0.ɵɵFactoryDeclaration<NativeTabsOutlet, never>;
|
|
151
179
|
static ɵcmp: i0.ɵɵComponentDeclaration<NativeTabsOutlet, "native-tabs-outlet", never, { "barHidden": { "alias": "barHidden"; "required": false; "isSignal": true; }; "tintColor": { "alias": "tintColor"; "required": false; "isSignal": true; }; "backgroundColor": { "alias": "backgroundColor"; "required": false; "isSignal": true; }; "colorScheme": { "alias": "colorScheme"; "required": false; "isSignal": true; }; "minimizeBehavior": { "alias": "minimizeBehavior"; "required": false; "isSignal": true; }; "controllerMode": { "alias": "controllerMode"; "required": false; "isSignal": true; }; "respectsKeyboard": { "alias": "respectsKeyboard"; "required": false; "isSignal": true; }; }, {}, ["declared"], ["*"], true, never>;
|
|
@@ -32,11 +32,33 @@ import { bindRouteInputs } from "./bind-route-inputs.js";
|
|
|
32
32
|
import { NATIVE_TAB_DEFAULTS } from "./native-bar-defaults.js";
|
|
33
33
|
import { NativeBack, isShowing } from "./native-back.js";
|
|
34
34
|
import { optionalBoolean } from "./transforms.js";
|
|
35
|
-
import { NativeTab } from "./native-tab.js";
|
|
35
|
+
import { NativeTab, screenKeyOf } from "./native-tab.js";
|
|
36
36
|
import { ownHost } from "./own-host.js";
|
|
37
37
|
import { markTabRoute } from "./tab-routes.js";
|
|
38
|
-
import {
|
|
38
|
+
import { IN_TAB } from "./in-tab.js";
|
|
39
|
+
import { taken, withoutPresented } from "./presented-route.js";
|
|
39
40
|
import * as i0 from "@angular/core";
|
|
41
|
+
/** A url without its query or fragment. */
|
|
42
|
+
const pathOnly = (url) => url.split(/[?#]/, 1)[0];
|
|
43
|
+
/**
|
|
44
|
+
* Whether one of `routes` matches `segments`, whole or up to lazy children not loaded yet, which a
|
|
45
|
+
* route that took a static segment of its own is taken to lead to. A parameter alone is not: it
|
|
46
|
+
* takes any segment, pages outside the bar included, and its children may match none of the rest.
|
|
47
|
+
*/
|
|
48
|
+
function reaches(routes, segments) {
|
|
49
|
+
return routes.some((route) => {
|
|
50
|
+
const count = taken(route, segments);
|
|
51
|
+
if (count === null)
|
|
52
|
+
return false;
|
|
53
|
+
const rest = segments.slice(count);
|
|
54
|
+
if (rest.length === 0)
|
|
55
|
+
return true;
|
|
56
|
+
if (route.children)
|
|
57
|
+
return reaches(route.children, rest);
|
|
58
|
+
const named = route.path.split('/').some((part) => part && !part.startsWith(':'));
|
|
59
|
+
return named && route.loadChildren !== undefined;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
40
62
|
export class NativeTabsOutlet {
|
|
41
63
|
name = PRIMARY_OUTLET;
|
|
42
64
|
supportsBindingToComponentInputs = true;
|
|
@@ -178,7 +200,7 @@ export class NativeTabsOutlet {
|
|
|
178
200
|
try {
|
|
179
201
|
ref = createComponent(component, {
|
|
180
202
|
environmentInjector,
|
|
181
|
-
elementInjector: this.outletInjector(route, entry
|
|
203
|
+
elementInjector: this.outletInjector(route, entry),
|
|
182
204
|
hostElement: content,
|
|
183
205
|
});
|
|
184
206
|
this.applicationRef.attachView(ref.hostView);
|
|
@@ -205,10 +227,16 @@ export class NativeTabsOutlet {
|
|
|
205
227
|
entry.detached = false;
|
|
206
228
|
this.select(entry);
|
|
207
229
|
}
|
|
230
|
+
/**
|
|
231
|
+
* Destroy the tab page in front: nothing once the router has detached it, as Angular's own
|
|
232
|
+
* outlet does nothing then. A tab under a route with no component of its own, a `loadChildren`
|
|
233
|
+
* wrapper or a group, has the router detach the page and then deactivate this same outlet, and
|
|
234
|
+
* that page is the tab, kept for when it is selected again.
|
|
235
|
+
*/
|
|
208
236
|
deactivate() {
|
|
209
|
-
|
|
210
|
-
if (!entry?.ref)
|
|
237
|
+
if (!this.isActivated)
|
|
211
238
|
return;
|
|
239
|
+
const entry = this.selected;
|
|
212
240
|
entry.unbind();
|
|
213
241
|
entry.unbind = () => { };
|
|
214
242
|
entry.ref.destroy();
|
|
@@ -288,6 +316,7 @@ export class NativeTabsOutlet {
|
|
|
288
316
|
detached: false,
|
|
289
317
|
unbind: () => { },
|
|
290
318
|
inFront: signal(false),
|
|
319
|
+
leaving: new Set(),
|
|
291
320
|
});
|
|
292
321
|
// The reuse strategy detaches a tab whole, and has only the route to go on.
|
|
293
322
|
// A path can have multiple route configs selected by canMatch (for example an iPad split
|
|
@@ -306,23 +335,53 @@ export class NativeTabsOutlet {
|
|
|
306
335
|
this.request(this.entries[0]);
|
|
307
336
|
return this.entries;
|
|
308
337
|
}
|
|
338
|
+
/**
|
|
339
|
+
* The tab `route` is the page of, by its path from this outlet's route down to it. A route with
|
|
340
|
+
* no component above the page is part of that path: a `loadChildren` wrapper takes the tab's
|
|
341
|
+
* path and leaves `''` to the page under it, and a group is a `''` of its own.
|
|
342
|
+
*/
|
|
309
343
|
entryFor(route) {
|
|
310
|
-
const
|
|
344
|
+
const config = route.routeConfig ?? route.snapshot.routeConfig;
|
|
345
|
+
const paths = [config?.path];
|
|
346
|
+
for (let above = route.parent; above && !above.component; above = above.parent) {
|
|
347
|
+
paths.unshift(above.routeConfig?.path);
|
|
348
|
+
}
|
|
349
|
+
const path = paths.filter(Boolean).join('/');
|
|
311
350
|
const entry = this.readTabs().find((candidate) => candidate.key === path);
|
|
312
351
|
if (!entry) {
|
|
313
352
|
throw new Error(`[angular-native] no <native-tab> has path="${path}". Every child route the bar can ` +
|
|
314
353
|
`reach needs one, and its path is what native identifies the tab by.`);
|
|
315
354
|
}
|
|
355
|
+
// Under a wrapper the page's config is not one of the children `readTabs` marked, and it is
|
|
356
|
+
// the one the reuse strategy is asked to detach whole.
|
|
357
|
+
if (config)
|
|
358
|
+
markTabRoute(config);
|
|
316
359
|
return entry;
|
|
317
360
|
}
|
|
318
361
|
/** Remember where this tab is, and tell native to show it. */
|
|
319
362
|
select(entry) {
|
|
363
|
+
const left = this.selected;
|
|
364
|
+
if (left && left !== entry)
|
|
365
|
+
this.leave(left);
|
|
320
366
|
this.selected = entry;
|
|
321
367
|
for (const other of this.entries)
|
|
322
368
|
other.inFront.set(other === entry);
|
|
323
369
|
entry.url = this.router?.url ?? entry.url;
|
|
324
370
|
this.request(entry);
|
|
325
371
|
}
|
|
372
|
+
/**
|
|
373
|
+
* Another tab came in front of `entry`: its stacks dismiss what they presented, and it is
|
|
374
|
+
* remembered at the page beneath, where the outermost stack that dismissed something is left.
|
|
375
|
+
*/
|
|
376
|
+
leave(entry) {
|
|
377
|
+
let url = null;
|
|
378
|
+
for (const dismiss of entry.leaving) {
|
|
379
|
+
const beneath = dismiss();
|
|
380
|
+
url ??= beneath;
|
|
381
|
+
}
|
|
382
|
+
if (url !== null)
|
|
383
|
+
entry.url = url;
|
|
384
|
+
}
|
|
326
385
|
/**
|
|
327
386
|
* A back with nothing left to pop inside the tab: go to the first tab, where it was left, which
|
|
328
387
|
* is what a bottom navigation bar does on Android. From the first tab, decline, so the platform
|
|
@@ -343,10 +402,7 @@ export class NativeTabsOutlet {
|
|
|
343
402
|
/** Keep the tab in front on the url it has reached, as long as the url is still inside it. */
|
|
344
403
|
follow(url) {
|
|
345
404
|
const entry = this.selected;
|
|
346
|
-
if (
|
|
347
|
-
return;
|
|
348
|
-
const root = this.pathOf(entry);
|
|
349
|
-
if (url === root || url.startsWith(`${root}/`) || url.startsWith(`${root}?`))
|
|
405
|
+
if (entry && this.tabOf(url) === entry)
|
|
350
406
|
entry.url = url;
|
|
351
407
|
}
|
|
352
408
|
/**
|
|
@@ -354,27 +410,49 @@ export class NativeTabsOutlet {
|
|
|
354
410
|
* first screen, which a push into the tab from outside it shows first.
|
|
355
411
|
*/
|
|
356
412
|
unopenedTabOf(url) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
return root;
|
|
363
|
-
}
|
|
364
|
-
return null;
|
|
413
|
+
const entry = this.tabOf(url);
|
|
414
|
+
if (!entry || entry.ref)
|
|
415
|
+
return null;
|
|
416
|
+
const root = this.pathOf(entry);
|
|
417
|
+
return pathOnly(url) === root ? null : root;
|
|
365
418
|
}
|
|
366
419
|
/** Whether `url` is a tab other than the one in front, or a page inside one. */
|
|
367
420
|
behind(url) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
421
|
+
const entry = this.tabOf(url);
|
|
422
|
+
return entry !== null && entry !== this.selected;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* The tab `url` is in: the one with the longest url that starts it. A tab at `''` has the bar's
|
|
426
|
+
* own url, which at the root starts every url, pages outside the bar included, so it takes only
|
|
427
|
+
* a url its routes lead to, as far as they are loaded. A wildcard or a custom matcher is not
|
|
428
|
+
* followed, as `taken` follows neither: a page only one of them shows is in no tab.
|
|
429
|
+
*/
|
|
430
|
+
tabOf(url) {
|
|
431
|
+
const path = pathOnly(url);
|
|
432
|
+
let found = null;
|
|
433
|
+
for (const entry of this.entries) {
|
|
371
434
|
const root = this.pathOf(entry);
|
|
372
|
-
|
|
373
|
-
|
|
435
|
+
const inside = path === root || path.startsWith(root.endsWith('/') ? root : `${root}/`);
|
|
436
|
+
if (!inside || (found && this.pathOf(found).length >= root.length))
|
|
437
|
+
continue;
|
|
438
|
+
if (!entry.key && !this.leadsInto(entry, path.slice(root.length)))
|
|
439
|
+
continue;
|
|
440
|
+
found = entry;
|
|
441
|
+
}
|
|
442
|
+
return found;
|
|
443
|
+
}
|
|
444
|
+
/** Whether the routes of the tab at `''` lead to `rest`, the path under the bar's own url. */
|
|
445
|
+
leadsInto(entry, rest) {
|
|
446
|
+
const segments = rest.split('/').filter(Boolean);
|
|
447
|
+
const own = entry.route?.snapshot.routeConfig;
|
|
448
|
+
const routes = own
|
|
449
|
+
? [own]
|
|
450
|
+
: (this.route.routeConfig?.children ?? []).filter((config) => config.path === '');
|
|
451
|
+
return segments.length === 0 || reaches(routes, segments);
|
|
374
452
|
}
|
|
375
453
|
request(entry) {
|
|
376
454
|
this.renderer.setProperty(this.host.nativeElement, 'navStateRequest', {
|
|
377
|
-
selectedScreenKey: entry.key,
|
|
455
|
+
selectedScreenKey: screenKeyOf(entry.key),
|
|
378
456
|
baseProvenance: this.provenance,
|
|
379
457
|
});
|
|
380
458
|
}
|
|
@@ -387,7 +465,7 @@ export class NativeTabsOutlet {
|
|
|
387
465
|
if (!selection)
|
|
388
466
|
return;
|
|
389
467
|
this.provenance = selection.provenance;
|
|
390
|
-
const entry = this.readTabs().find((tab) => tab.key === selection.selectedScreenKey);
|
|
468
|
+
const entry = this.readTabs().find((tab) => screenKeyOf(tab.key) === selection.selectedScreenKey);
|
|
391
469
|
if (!entry || entry === this.selected)
|
|
392
470
|
return;
|
|
393
471
|
// Returning to a tab returns to the url it was last on, which is the whole point of keeping
|
|
@@ -423,13 +501,20 @@ export class NativeTabsOutlet {
|
|
|
423
501
|
.join('/');
|
|
424
502
|
return `/${[parent, entry.key].filter(Boolean).join('/')}`;
|
|
425
503
|
}
|
|
426
|
-
/**
|
|
427
|
-
|
|
504
|
+
/**
|
|
505
|
+
* As the stack outlet's: the route, the outlet contexts, and whether the tab is showing. And the
|
|
506
|
+
* tab itself, for its stacks to hear when another comes in front.
|
|
507
|
+
*/
|
|
508
|
+
outletInjector(route, entry) {
|
|
428
509
|
const outer = this.injector.get(SCREEN_IN_FRONT);
|
|
510
|
+
const tab = {
|
|
511
|
+
onLeave: (leave) => (entry.leaving.add(leave), () => entry.leaving.delete(leave)),
|
|
512
|
+
};
|
|
429
513
|
return Injector.create({
|
|
430
514
|
parent: this.injector,
|
|
431
515
|
providers: [
|
|
432
|
-
{ provide: SCREEN_IN_FRONT, useValue: computed(() => outer() && inFront()) },
|
|
516
|
+
{ provide: SCREEN_IN_FRONT, useValue: computed(() => outer() && entry.inFront()) },
|
|
517
|
+
{ provide: IN_TAB, useValue: tab },
|
|
433
518
|
{ provide: ActivatedRoute, useValue: route },
|
|
434
519
|
{
|
|
435
520
|
provide: ChildrenOutletContexts,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-tabs-outlet.js","sourceRoot":"","sources":["../src/native-tabs-outlet.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A `RouterOutlet` that renders a native tab bar: a `UITabBarController` on iOS, a bottom\n * navigation bar on Android.\n *\n * The outlet's host element *is* the tabs host, and each `<native-tab>` in its content is one\n * item and the screen behind it. An activated route's component is created on a child view of\n * the matching tab screen. The screen belongs to the tab declaration and must survive the route\n * component being destroyed; Angular removes a component's host node when it destroys the ref.\n *\n * ```html\n * <native-tabs-outlet>\n * <native-tab path=\"library\" title=\"Library\" sfSymbol=\"books.vertical.fill\" />\n * <native-tab path=\"inbox\" title=\"Inbox\" sfSymbol=\"tray\" [badge]=\"unread()\" />\n * </native-tabs-outlet>\n * ```\n *\n * **Native holds the selection, not us.** The tabs host owns its navigation state and reports\n * every change - a tap, or one we asked for - through `tabSelected`, carrying a `provenance`\n * number that increments per change. We send `navStateRequest` with the provenance of the last\n * state we acknowledged, which is how native tells a stale request from a current one when a tap\n * and a navigation cross in flight. The alternative, holding the selection in JS, means a tap is\n * only reflected after a round trip, and the bar lags the finger. See ADR 0006.\n *\n * **Each tab keeps its own screen mounted**, so switching away and back finds a tab exactly as it\n * was: its scroll offset, its text, and its own stack if it has one. That is the reuse strategy\n * detaching the tab's whole subtree rather than only its leaf.\n */\nimport {\n ApplicationRef,\n ChangeDetectorRef,\n Component,\n DestroyRef,\n ElementRef,\n ErrorHandler,\n Injector,\n Renderer2,\n computed,\n contentChildren,\n createComponent,\n inject,\n input,\n signal,\n type AfterContentInit,\n type ComponentRef,\n type EnvironmentInjector,\n type Type,\n type WritableSignal,\n} from '@angular/core';\nimport { SCREEN_IN_FRONT } from '@ng-native/device';\nimport {\n ActivatedRoute,\n ChildrenOutletContexts,\n NavigationEnd,\n PRIMARY_OUTLET,\n Router,\n type Data,\n type RouterOutletContract,\n} from '@angular/router';\nimport { bindRouteInputs } from './bind-route-inputs.ts';\nimport { NATIVE_TAB_DEFAULTS } from './native-bar-defaults.ts';\nimport { NativeBack, isShowing } from './native-back.ts';\nimport { optionalBoolean } from './transforms.ts';\nimport { NativeTab } from './native-tab.ts';\nimport { ownHost } from './own-host.ts';\nimport { markTabRoute } from './tab-routes.ts';\nimport { withoutPresented } from './presented-route.ts';\n\ninterface TabEntry {\n readonly tab: NativeTab;\n /** The key native identifies this tab by: its path, unique among the bar's items. */\n readonly key: string;\n /** The url this tab was last on, so returning to it returns where the user left. */\n url: string;\n ref: ComponentRef<unknown> | null;\n route: ActivatedRoute | null;\n detached: boolean;\n /** Stops feeding route params into the component's inputs. */\n unbind: () => void;\n /** Whether this tab is the one showing, for what its screens hold: see `SCREEN_IN_FRONT`. */\n readonly inFront: WritableSignal<boolean>;\n}\n\n/** What `tabSelected` carries. Only two fields matter here; the rest is diagnostics. */\ninterface TabSelection {\n readonly selectedScreenKey: string;\n readonly provenance: number;\n}\n\n@Component({\n selector: 'native-tabs-outlet',\n template: '<ng-content />',\n host: {\n '[style]': 'fill',\n '[tabBarHidden]': 'barHidden()',\n '[tabBarTintColor]': 'tintColor() ?? defaults().tintColor',\n '[nativeContainerBackgroundColor]': 'backgroundColor() ?? defaults().backgroundColor',\n '[colorScheme]': 'colorScheme() ?? defaults().colorScheme',\n '[tabBarMinimizeBehavior]': 'minimizeBehavior()',\n '[tabBarControllerMode]': 'controllerMode()',\n '[tabBarRespectsIMEInsets]': 'respectsKeyboard()',\n },\n})\nexport class NativeTabsOutlet implements RouterOutletContract, AfterContentInit {\n name = PRIMARY_OUTLET;\n\n readonly supportsBindingToComponentInputs = true;\n\n private readonly parentContexts = inject(ChildrenOutletContexts);\n private readonly renderer = inject(Renderer2);\n private readonly applicationRef = inject(ApplicationRef);\n private readonly changeDetector = inject(ChangeDetectorRef);\n private readonly injector = inject(Injector);\n private readonly host = inject(ElementRef);\n private readonly route = inject(ActivatedRoute);\n /** Optional so the outlet can be driven through `RouterOutletContract` directly, as tests do. */\n private readonly router = inject(Router, { optional: true });\n private readonly errors = inject(ErrorHandler);\n /** What activation has reported already, so the failed navigation it causes is not reported again. */\n private readonly reported = new WeakSet<object>();\n /** The app's `withTabDefaults`, for whatever the outlet does not bind itself. */\n protected readonly defaults = inject(NATIVE_TAB_DEFAULTS);\n\n private readonly declared = contentChildren(NativeTab);\n private readonly entries: TabEntry[] = [];\n private selected: TabEntry | null = null;\n /** The provenance of the last state native told us about. Starts before native's first. */\n private provenance = 0;\n\n protected readonly fill = { flex: 1 };\n\n // --- the bar itself -----------------------------------------------------\n\n /** Hide the bar without unmounting the tabs, for a screen that wants the whole window. */\n readonly barHidden = input(undefined, { transform: optionalBoolean });\n /** The colour of the selected item. iOS. */\n readonly tintColor = input<string | number>();\n /** Behind the tabs, where a screen does not paint. */\n readonly backgroundColor = input<string | number>();\n /** Force the bar's light or dark appearance, whatever the app's is. */\n readonly colorScheme = input<'inherit' | 'light' | 'dark'>();\n /** iOS 26: let the bar shrink out of the way as content scrolls. */\n readonly minimizeBehavior = input<'automatic' | 'never' | 'onScrollDown' | 'onScrollUp'>();\n /** iOS: `tabSidebar` is the iPad layout, with the tabs down the side. */\n readonly controllerMode = input<'automatic' | 'tabBar' | 'tabSidebar'>();\n /** Android: keep the bar above the keyboard rather than behind it. */\n readonly respectsKeyboard = input(undefined, { transform: optionalBoolean });\n\n constructor() {\n ownHost(this.host.nativeElement);\n // In the constructor rather than a lifecycle hook: the router activates a child route as soon\n // as this component exists, which is before any hook has run.\n this.parentContexts.onChildOutletCreated(this.name, this);\n\n this.renderer.listen(\n this.host.nativeElement,\n 'tabSelected',\n (event: { nativeEvent?: TabSelection }) => this.onNativeSelect(event?.nativeEvent),\n );\n\n // A push inside a tab's own stack reuses the tab's route, so this outlet is never activated\n // for it; the router's navigation is the only sign. Without this, coming back to the tab\n // returns it to its root and pops the screens the user left it on.\n const following = this.router?.events?.subscribe((event) => {\n // Without a page presented over the bar, which is no part of where the tab is.\n if (event instanceof NavigationEnd) {\n this.follow(withoutPresented(this.router!, event.urlAfterRedirects));\n }\n });\n\n // Android's back button, and `NativeNavigation.back()`, once the stack in front, if the tab\n // has one, is at its root. Subscribed before any tab's own stack, so it is asked after them.\n const back = inject(NativeBack);\n const unsubscribeBack = back.handle(() => this.goBack());\n const unsubscribeBar = back.addTabBar({\n unopenedTabOf: (url) => this.unopenedTabOf(url),\n behind: (url) => this.behind(url),\n });\n\n inject(DestroyRef).onDestroy(() => {\n unsubscribeBack();\n unsubscribeBar();\n following?.unsubscribe();\n for (const entry of this.entries) {\n entry.unbind();\n entry.ref?.destroy();\n }\n this.entries.length = 0;\n this.parentContexts.onChildOutletDestroyed(this.name);\n });\n }\n\n /** A bar nobody has navigated into yet still has to show a selected item. */\n ngAfterContentInit(): void {\n this.readTabs();\n this.activateCurrentRoute();\n }\n\n /**\n * The route the router is already on, for an outlet created after the navigation that reached\n * it, as `NativeStackOutlet` takes its own: a tab bar held behind a condition until a session\n * is ready. Once the tabs are read, since activating one needs to know which they are.\n */\n private activateCurrentRoute(): void {\n if (this.isActivated) return;\n const context = this.parentContexts.getContext(this.name);\n if (!context?.route) return;\n if (context.attachRef) this.attach(context.attachRef, context.route);\n else this.activateWith(context.route, context.injector);\n }\n\n /** The tabs in the bar, in template order. Exposed for tests. */\n get tabKeys(): string[] {\n return this.readTabs().map((entry) => entry.key);\n }\n\n /** Which tab native is showing. Exposed for tests. */\n get selectedKey(): string | null {\n return this.selected?.key ?? null;\n }\n\n // --- RouterOutletContract ----------------------------------------------\n\n get isActivated(): boolean {\n return this.selected?.ref != null && !this.selected.detached;\n }\n\n get component(): Object | null {\n return this.isActivated ? (this.selected!.ref!.instance as Object) : null;\n }\n\n get activatedRoute(): ActivatedRoute | null {\n return this.isActivated ? this.selected!.route : null;\n }\n\n get activatedRouteData(): Data {\n return this.activatedRoute?.snapshot.data ?? {};\n }\n\n /** The input binder reads this to bind route params to component inputs. */\n get activatedComponentRef(): ComponentRef<unknown> | null {\n return this.selected?.ref ?? null;\n }\n\n activateWith(route: ActivatedRoute, environmentInjector: EnvironmentInjector): void {\n const component = (route.component ?? route.snapshot.component) as Type<unknown> | null;\n if (!component) {\n throw new Error('[angular-native] route has no component to activate');\n }\n const entry = this.entryFor(route);\n\n // The tab screen is owned by <native-tab>, not by this route component. If the route uses\n // deactivate() rather than detach(), Angular removes the component host on destroy. Giving\n // the component its own child host keeps that removal from taking the tab bar item with it.\n const content = ownHost(this.renderer.createElement('view'));\n this.renderer.setStyle(content, 'flex', 1);\n this.renderer.appendChild(entry.tab.screen, content);\n\n let ref: ComponentRef<unknown> | undefined;\n try {\n ref = createComponent(component, {\n environmentInjector,\n elementInjector: this.outletInjector(route, entry.inFront),\n hostElement: content as Element,\n });\n this.applicationRef.attachView(ref.hostView);\n // Route params onto inputs before the first change detection, as the stack outlet does: a\n // required input still unset when the page first renders throws NG0950.\n entry.unbind = this.bindInputs(ref, route);\n // Before anything can commit. Creating the page's nodes dirties the tree, which schedules a\n // commit of its own - and that commit would capture the page with its elements created and\n // its bindings not yet applied. On iOS the props catch up a frame later and nobody sees it;\n // on Android the flattening decision is made from the props a view is created with, so a\n // content wrapper that arrives without its `collapsable: false` is flattened for good and\n // its children end up parented to a scroll view that allows exactly one.\n ref.changeDetectorRef.detectChanges();\n } catch (error) {\n this.abandon(entry, ref, content);\n this.errors.handleError(error);\n if (error !== null && typeof error === 'object') this.reported.add(error);\n throw error;\n }\n\n entry.ref = ref;\n entry.route = route;\n entry.detached = false;\n this.select(entry);\n }\n\n deactivate(): void {\n const entry = this.selected;\n if (!entry?.ref) return;\n entry.unbind();\n entry.unbind = () => {};\n entry.ref.destroy();\n entry.ref = null;\n entry.route = null;\n }\n\n /**\n * Hand the live `ComponentRef` back to the router. The tab's screen stays where it is with the\n * component still inside it, so the tab keeps running while another one is in front.\n */\n detach(): ComponentRef<unknown> {\n const entry = this.selected;\n if (!entry?.ref) throw new Error('[angular-native] outlet is not activated, nothing to detach');\n entry.detached = true;\n entry.inFront.set(false);\n // As in the stack outlet: stop change detection without unmounting the views.\n entry.ref.hostView.detach();\n return entry.ref;\n }\n\n attach(ref: ComponentRef<unknown>, route: ActivatedRoute): void {\n const entry = this.entryFor(route);\n\n // A ref from a previous instance of this outlet still lives on its old tab screen, which is\n // gone; re-parent it onto this one's rather than rebuild the tab.\n if (entry.ref !== ref) {\n this.renderer.appendChild(entry.tab.screen, ref.location.nativeElement);\n this.applicationRef.attachView(ref.hostView);\n entry.ref = ref;\n }\n\n entry.route = route;\n entry.detached = false;\n // The route object is new even when the url is not, so the inputs follow the new one.\n entry.unbind();\n entry.unbind = this.bindInputs(ref, route);\n ref.hostView.reattach();\n this.select(entry);\n }\n\n // --- native tabs --------------------------------------------------------\n\n /**\n * Undo a tab page that threw on its first render, which fails the navigation: its component\n * and its content view go, and the tab that was showing carries on, change detection and all.\n */\n private abandon(entry: TabEntry, ref: ComponentRef<unknown> | undefined, content: unknown): void {\n entry.unbind();\n entry.unbind = () => {};\n ref?.destroy();\n this.renderer.removeChild(entry.tab.screen, content);\n const showing = this.selected;\n if (showing?.detached && showing.ref) {\n showing.detached = false;\n showing.ref.hostView.reattach();\n }\n }\n\n /** Route params onto inputs only under `withComponentInputBinding()`, as in the stack outlet. */\n private bindInputs(ref: ComponentRef<unknown>, route: ActivatedRoute): () => void {\n return this.router?.componentInputBindingEnabled ? bindRouteInputs(ref, route) : () => {};\n }\n\n /**\n * Take the bar from the `<native-tab>` elements in the content, once.\n *\n * On demand rather than in a lifecycle hook, because the router activates a child route during\n * this component's creation, before any hook has run - but after the content nodes themselves\n * exist, which is all this needs.\n */\n private readTabs(): TabEntry[] {\n if (this.entries.length > 0) return this.entries;\n\n const children = this.route.routeConfig?.children ?? [];\n for (const tab of this.declared()) {\n const key = tab.path();\n this.entries.push({\n tab,\n key,\n url: '',\n ref: null,\n route: null,\n detached: false,\n unbind: () => {},\n inFront: signal(false),\n });\n // The reuse strategy detaches a tab whole, and has only the route to go on.\n // A path can have multiple route configs selected by canMatch (for example an iPad split\n // layout and an iPhone stack). Mark every variant: the reuse strategy sees the config\n // Angular actually matched, which may not be the first one in the route array.\n for (const config of children) {\n if (config.path === key) markTabRoute(config);\n }\n }\n\n if (this.entries.length === 0) {\n throw new Error(\n '[angular-native] <native-tabs-outlet> has no tabs. Put a <native-tab path=\"...\"> ' +\n 'inside it for each child route the bar should reach.',\n );\n }\n // Before the first navigation nothing is selected, which is not a state a tab bar has.\n this.request(this.entries[0]!);\n return this.entries;\n }\n\n private entryFor(route: ActivatedRoute): TabEntry {\n const path = (route.routeConfig ?? route.snapshot.routeConfig)?.path;\n const entry = this.readTabs().find((candidate) => candidate.key === path);\n if (!entry) {\n throw new Error(\n `[angular-native] no <native-tab> has path=\"${path}\". Every child route the bar can ` +\n `reach needs one, and its path is what native identifies the tab by.`,\n );\n }\n return entry;\n }\n\n /** Remember where this tab is, and tell native to show it. */\n private select(entry: TabEntry): void {\n this.selected = entry;\n for (const other of this.entries) other.inFront.set(other === entry);\n entry.url = this.router?.url ?? entry.url;\n this.request(entry);\n }\n\n /**\n * A back with nothing left to pop inside the tab: go to the first tab, where it was left, which\n * is what a bottom navigation bar does on Android. From the first tab, decline, so the platform\n * leaves the app. The order tabs were visited in is not retraced, so a back never lands on a\n * tab the user did not expect. Not while the bar is covered by a screen pushed over it: that\n * screen's stack answers instead.\n */\n private goBack(): boolean {\n const first = this.entries[0];\n const selected = this.selected;\n if (!first || !selected || selected === first || !this.router) return false;\n if (!this.isActivated || !isShowing(this.router, selected.route)) return false;\n void this.router.navigateByUrl(first.url || this.pathOf(first));\n return true;\n }\n\n /** Keep the tab in front on the url it has reached, as long as the url is still inside it. */\n private follow(url: string): void {\n const entry = this.selected;\n if (!entry) return;\n const root = this.pathOf(entry);\n if (url === root || url.startsWith(`${root}/`) || url.startsWith(`${root}?`)) entry.url = url;\n }\n\n /**\n * The url of the tab `url` is a page inside, when nothing has opened that tab: the tab's own\n * first screen, which a push into the tab from outside it shows first.\n */\n private unopenedTabOf(url: string): string | null {\n for (const entry of this.entries) {\n if (entry.ref) continue;\n const root = this.pathOf(entry);\n if (url.startsWith(`${root}/`)) return root;\n }\n return null;\n }\n\n /** Whether `url` is a tab other than the one in front, or a page inside one. */\n private behind(url: string): boolean {\n return this.entries.some((entry) => {\n if (entry === this.selected) return false;\n const root = this.pathOf(entry);\n return url === root || url.startsWith(`${root}/`);\n });\n }\n\n private request(entry: TabEntry): void {\n this.renderer.setProperty(this.host.nativeElement, 'navStateRequest', {\n selectedScreenKey: entry.key,\n baseProvenance: this.provenance,\n });\n }\n\n /**\n * Native switched tab. Acknowledge the new state, then navigate: the router is authoritative\n * about what is on screen (ADR 0003), so the tap becomes a navigation to where that tab was,\n * and the activation that follows is what fills the tab in.\n */\n private onNativeSelect(selection: TabSelection | undefined): void {\n if (!selection) return;\n this.provenance = selection.provenance;\n\n const entry = this.readTabs().find((tab) => tab.key === selection.selectedScreenKey);\n if (!entry || entry === this.selected) return;\n\n // Returning to a tab returns to the url it was last on, which is the whole point of keeping\n // it mounted. A tab never visited starts at its own path.\n // A navigation that fails is reported, unless activation reported it already: a page that\n // does not load, caught here, would otherwise leave nothing but a tab that will not open.\n void this.router?.navigateByUrl(entry.url || this.pathOf(entry)).then(\n (arrived) => this.revertUnless(arrived),\n (error: unknown) => {\n if (error === null || typeof error !== 'object' || !this.reported.has(error)) {\n this.errors.handleError(error);\n }\n this.revertUnless(false);\n },\n );\n }\n\n /**\n * Native switched tab before the router agreed to. When a guard refuses, or the navigation\n * fails, the router stays where it was and so must the bar, or it shows a tab nobody is on.\n * Not while another navigation is under way: that one superseded this, and its own activation\n * selects the tab it lands on.\n */\n private revertUnless(arrived: boolean): void {\n if (arrived || !this.selected || this.router?.getCurrentNavigation()) return;\n this.request(this.selected);\n // A failed navigation ends in a promise callback, which schedules no change detection, and a\n // prop reaches native only with the commit at the end of a pass. Without one the bar stayed\n // on the tab that failed until something else happened to render.\n this.changeDetector.markForCheck();\n }\n\n /** A tab's own url, from the outlet's route down. */\n private pathOf(entry: TabEntry): string {\n const parent = this.route.snapshot.pathFromRoot\n .flatMap((snapshot) => snapshot.url.map((segment) => segment.path))\n .join('/');\n return `/${[parent, entry.key].filter(Boolean).join('/')}`;\n }\n\n /** As the stack outlet's: the route, the outlet contexts, and whether the tab is showing. */\n private outletInjector(route: ActivatedRoute, inFront: WritableSignal<boolean>): Injector {\n const outer = this.injector.get(SCREEN_IN_FRONT);\n return Injector.create({\n parent: this.injector,\n providers: [\n { provide: SCREEN_IN_FRONT, useValue: computed(() => outer() && inFront()) },\n { provide: ActivatedRoute, useValue: route },\n {\n provide: ChildrenOutletContexts,\n useValue: this.parentContexts.getOrCreateContext(this.name).children,\n },\n ],\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"native-tabs-outlet.js","sourceRoot":"","sources":["../src/native-tabs-outlet.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * A `RouterOutlet` that renders a native tab bar: a `UITabBarController` on iOS, a bottom\n * navigation bar on Android.\n *\n * The outlet's host element *is* the tabs host, and each `<native-tab>` in its content is one\n * item and the screen behind it. An activated route's component is created on a child view of\n * the matching tab screen. The screen belongs to the tab declaration and must survive the route\n * component being destroyed; Angular removes a component's host node when it destroys the ref.\n *\n * ```html\n * <native-tabs-outlet>\n * <native-tab path=\"library\" title=\"Library\" sfSymbol=\"books.vertical.fill\" />\n * <native-tab path=\"inbox\" title=\"Inbox\" sfSymbol=\"tray\" [badge]=\"unread()\" />\n * </native-tabs-outlet>\n * ```\n *\n * **Native holds the selection, not us.** The tabs host owns its navigation state and reports\n * every change - a tap, or one we asked for - through `tabSelected`, carrying a `provenance`\n * number that increments per change. We send `navStateRequest` with the provenance of the last\n * state we acknowledged, which is how native tells a stale request from a current one when a tap\n * and a navigation cross in flight. The alternative, holding the selection in JS, means a tap is\n * only reflected after a round trip, and the bar lags the finger. See ADR 0006.\n *\n * **Each tab keeps its own screen mounted**, so switching away and back finds a tab exactly as it\n * was: its scroll offset, its text, and its own stack if it has one. That is the reuse strategy\n * detaching the tab's whole subtree rather than only its leaf.\n */\nimport {\n ApplicationRef,\n ChangeDetectorRef,\n Component,\n DestroyRef,\n ElementRef,\n ErrorHandler,\n Injector,\n Renderer2,\n computed,\n contentChildren,\n createComponent,\n inject,\n input,\n signal,\n type AfterContentInit,\n type ComponentRef,\n type EnvironmentInjector,\n type Type,\n type WritableSignal,\n} from '@angular/core';\nimport { SCREEN_IN_FRONT } from '@ng-native/device';\nimport {\n ActivatedRoute,\n ChildrenOutletContexts,\n NavigationEnd,\n PRIMARY_OUTLET,\n Router,\n type Data,\n type Route,\n type RouterOutletContract,\n} from '@angular/router';\nimport { bindRouteInputs } from './bind-route-inputs.ts';\nimport { NATIVE_TAB_DEFAULTS } from './native-bar-defaults.ts';\nimport { NativeBack, isShowing } from './native-back.ts';\nimport { optionalBoolean } from './transforms.ts';\nimport { NativeTab, screenKeyOf } from './native-tab.ts';\nimport { ownHost } from './own-host.ts';\nimport { markTabRoute } from './tab-routes.ts';\nimport { IN_TAB, type InTab } from './in-tab.ts';\nimport { taken, withoutPresented } from './presented-route.ts';\n\n/** A url without its query or fragment. */\nconst pathOnly = (url: string): string => url.split(/[?#]/, 1)[0]!;\n\n/**\n * Whether one of `routes` matches `segments`, whole or up to lazy children not loaded yet, which a\n * route that took a static segment of its own is taken to lead to. A parameter alone is not: it\n * takes any segment, pages outside the bar included, and its children may match none of the rest.\n */\nfunction reaches(routes: readonly Route[], segments: readonly string[]): boolean {\n return routes.some((route) => {\n const count = taken(route, segments);\n if (count === null) return false;\n const rest = segments.slice(count);\n if (rest.length === 0) return true;\n if (route.children) return reaches(route.children, rest);\n const named = route.path!.split('/').some((part) => part && !part.startsWith(':'));\n return named && route.loadChildren !== undefined;\n });\n}\n\ninterface TabEntry {\n readonly tab: NativeTab;\n /** The key native identifies this tab by: its path, unique among the bar's items. */\n readonly key: string;\n /** The url this tab was last on, so returning to it returns where the user left. */\n url: string;\n ref: ComponentRef<unknown> | null;\n route: ActivatedRoute | null;\n detached: boolean;\n /** Stops feeding route params into the component's inputs. */\n unbind: () => void;\n /** Whether this tab is the one showing, for what its screens hold: see `SCREEN_IN_FRONT`. */\n readonly inFront: WritableSignal<boolean>;\n /** What the tab's stacks dismiss when another tab comes in front: see `IN_TAB`. */\n readonly leaving: Set<() => string | null>;\n}\n\n/** What `tabSelected` carries. Only two fields matter here; the rest is diagnostics. */\ninterface TabSelection {\n readonly selectedScreenKey: string;\n readonly provenance: number;\n}\n\n@Component({\n selector: 'native-tabs-outlet',\n template: '<ng-content />',\n host: {\n '[style]': 'fill',\n '[tabBarHidden]': 'barHidden()',\n '[tabBarTintColor]': 'tintColor() ?? defaults().tintColor',\n '[nativeContainerBackgroundColor]': 'backgroundColor() ?? defaults().backgroundColor',\n '[colorScheme]': 'colorScheme() ?? defaults().colorScheme',\n '[tabBarMinimizeBehavior]': 'minimizeBehavior()',\n '[tabBarControllerMode]': 'controllerMode()',\n '[tabBarRespectsIMEInsets]': 'respectsKeyboard()',\n },\n})\nexport class NativeTabsOutlet implements RouterOutletContract, AfterContentInit {\n name = PRIMARY_OUTLET;\n\n readonly supportsBindingToComponentInputs = true;\n\n private readonly parentContexts = inject(ChildrenOutletContexts);\n private readonly renderer = inject(Renderer2);\n private readonly applicationRef = inject(ApplicationRef);\n private readonly changeDetector = inject(ChangeDetectorRef);\n private readonly injector = inject(Injector);\n private readonly host = inject(ElementRef);\n private readonly route = inject(ActivatedRoute);\n /** Optional so the outlet can be driven through `RouterOutletContract` directly, as tests do. */\n private readonly router = inject(Router, { optional: true });\n private readonly errors = inject(ErrorHandler);\n /** What activation has reported already, so the failed navigation it causes is not reported again. */\n private readonly reported = new WeakSet<object>();\n /** The app's `withTabDefaults`, for whatever the outlet does not bind itself. */\n protected readonly defaults = inject(NATIVE_TAB_DEFAULTS);\n\n private readonly declared = contentChildren(NativeTab);\n private readonly entries: TabEntry[] = [];\n private selected: TabEntry | null = null;\n /** The provenance of the last state native told us about. Starts before native's first. */\n private provenance = 0;\n\n protected readonly fill = { flex: 1 };\n\n // --- the bar itself -----------------------------------------------------\n\n /** Hide the bar without unmounting the tabs, for a screen that wants the whole window. */\n readonly barHidden = input(undefined, { transform: optionalBoolean });\n /** The colour of the selected item. iOS. */\n readonly tintColor = input<string | number>();\n /** Behind the tabs, where a screen does not paint. */\n readonly backgroundColor = input<string | number>();\n /** Force the bar's light or dark appearance, whatever the app's is. */\n readonly colorScheme = input<'inherit' | 'light' | 'dark'>();\n /** iOS 26: let the bar shrink out of the way as content scrolls. */\n readonly minimizeBehavior = input<'automatic' | 'never' | 'onScrollDown' | 'onScrollUp'>();\n /** iOS: `tabSidebar` is the iPad layout, with the tabs down the side. */\n readonly controllerMode = input<'automatic' | 'tabBar' | 'tabSidebar'>();\n /** Android: keep the bar above the keyboard rather than behind it. */\n readonly respectsKeyboard = input(undefined, { transform: optionalBoolean });\n\n constructor() {\n ownHost(this.host.nativeElement);\n // In the constructor rather than a lifecycle hook: the router activates a child route as soon\n // as this component exists, which is before any hook has run.\n this.parentContexts.onChildOutletCreated(this.name, this);\n\n this.renderer.listen(\n this.host.nativeElement,\n 'tabSelected',\n (event: { nativeEvent?: TabSelection }) => this.onNativeSelect(event?.nativeEvent),\n );\n\n // A push inside a tab's own stack reuses the tab's route, so this outlet is never activated\n // for it; the router's navigation is the only sign. Without this, coming back to the tab\n // returns it to its root and pops the screens the user left it on.\n const following = this.router?.events?.subscribe((event) => {\n // Without a page presented over the bar, which is no part of where the tab is.\n if (event instanceof NavigationEnd) {\n this.follow(withoutPresented(this.router!, event.urlAfterRedirects));\n }\n });\n\n // Android's back button, and `NativeNavigation.back()`, once the stack in front, if the tab\n // has one, is at its root. Subscribed before any tab's own stack, so it is asked after them.\n const back = inject(NativeBack);\n const unsubscribeBack = back.handle(() => this.goBack());\n const unsubscribeBar = back.addTabBar({\n unopenedTabOf: (url) => this.unopenedTabOf(url),\n behind: (url) => this.behind(url),\n });\n\n inject(DestroyRef).onDestroy(() => {\n unsubscribeBack();\n unsubscribeBar();\n following?.unsubscribe();\n for (const entry of this.entries) {\n entry.unbind();\n entry.ref?.destroy();\n }\n this.entries.length = 0;\n this.parentContexts.onChildOutletDestroyed(this.name);\n });\n }\n\n /** A bar nobody has navigated into yet still has to show a selected item. */\n ngAfterContentInit(): void {\n this.readTabs();\n this.activateCurrentRoute();\n }\n\n /**\n * The route the router is already on, for an outlet created after the navigation that reached\n * it, as `NativeStackOutlet` takes its own: a tab bar held behind a condition until a session\n * is ready. Once the tabs are read, since activating one needs to know which they are.\n */\n private activateCurrentRoute(): void {\n if (this.isActivated) return;\n const context = this.parentContexts.getContext(this.name);\n if (!context?.route) return;\n if (context.attachRef) this.attach(context.attachRef, context.route);\n else this.activateWith(context.route, context.injector);\n }\n\n /** The tabs in the bar, in template order. Exposed for tests. */\n get tabKeys(): string[] {\n return this.readTabs().map((entry) => entry.key);\n }\n\n /** Which tab native is showing. Exposed for tests. */\n get selectedKey(): string | null {\n return this.selected?.key ?? null;\n }\n\n // --- RouterOutletContract ----------------------------------------------\n\n get isActivated(): boolean {\n return this.selected?.ref != null && !this.selected.detached;\n }\n\n get component(): Object | null {\n return this.isActivated ? (this.selected!.ref!.instance as Object) : null;\n }\n\n get activatedRoute(): ActivatedRoute | null {\n return this.isActivated ? this.selected!.route : null;\n }\n\n get activatedRouteData(): Data {\n return this.activatedRoute?.snapshot.data ?? {};\n }\n\n /** The input binder reads this to bind route params to component inputs. */\n get activatedComponentRef(): ComponentRef<unknown> | null {\n return this.selected?.ref ?? null;\n }\n\n activateWith(route: ActivatedRoute, environmentInjector: EnvironmentInjector): void {\n const component = (route.component ?? route.snapshot.component) as Type<unknown> | null;\n if (!component) {\n throw new Error('[angular-native] route has no component to activate');\n }\n const entry = this.entryFor(route);\n\n // The tab screen is owned by <native-tab>, not by this route component. If the route uses\n // deactivate() rather than detach(), Angular removes the component host on destroy. Giving\n // the component its own child host keeps that removal from taking the tab bar item with it.\n const content = ownHost(this.renderer.createElement('view'));\n this.renderer.setStyle(content, 'flex', 1);\n this.renderer.appendChild(entry.tab.screen, content);\n\n let ref: ComponentRef<unknown> | undefined;\n try {\n ref = createComponent(component, {\n environmentInjector,\n elementInjector: this.outletInjector(route, entry),\n hostElement: content as Element,\n });\n this.applicationRef.attachView(ref.hostView);\n // Route params onto inputs before the first change detection, as the stack outlet does: a\n // required input still unset when the page first renders throws NG0950.\n entry.unbind = this.bindInputs(ref, route);\n // Before anything can commit. Creating the page's nodes dirties the tree, which schedules a\n // commit of its own - and that commit would capture the page with its elements created and\n // its bindings not yet applied. On iOS the props catch up a frame later and nobody sees it;\n // on Android the flattening decision is made from the props a view is created with, so a\n // content wrapper that arrives without its `collapsable: false` is flattened for good and\n // its children end up parented to a scroll view that allows exactly one.\n ref.changeDetectorRef.detectChanges();\n } catch (error) {\n this.abandon(entry, ref, content);\n this.errors.handleError(error);\n if (error !== null && typeof error === 'object') this.reported.add(error);\n throw error;\n }\n\n entry.ref = ref;\n entry.route = route;\n entry.detached = false;\n this.select(entry);\n }\n\n /**\n * Destroy the tab page in front: nothing once the router has detached it, as Angular's own\n * outlet does nothing then. A tab under a route with no component of its own, a `loadChildren`\n * wrapper or a group, has the router detach the page and then deactivate this same outlet, and\n * that page is the tab, kept for when it is selected again.\n */\n deactivate(): void {\n if (!this.isActivated) return;\n const entry = this.selected!;\n entry.unbind();\n entry.unbind = () => {};\n entry.ref!.destroy();\n entry.ref = null;\n entry.route = null;\n }\n\n /**\n * Hand the live `ComponentRef` back to the router. The tab's screen stays where it is with the\n * component still inside it, so the tab keeps running while another one is in front.\n */\n detach(): ComponentRef<unknown> {\n const entry = this.selected;\n if (!entry?.ref) throw new Error('[angular-native] outlet is not activated, nothing to detach');\n entry.detached = true;\n entry.inFront.set(false);\n // As in the stack outlet: stop change detection without unmounting the views.\n entry.ref.hostView.detach();\n return entry.ref;\n }\n\n attach(ref: ComponentRef<unknown>, route: ActivatedRoute): void {\n const entry = this.entryFor(route);\n\n // A ref from a previous instance of this outlet still lives on its old tab screen, which is\n // gone; re-parent it onto this one's rather than rebuild the tab.\n if (entry.ref !== ref) {\n this.renderer.appendChild(entry.tab.screen, ref.location.nativeElement);\n this.applicationRef.attachView(ref.hostView);\n entry.ref = ref;\n }\n\n entry.route = route;\n entry.detached = false;\n // The route object is new even when the url is not, so the inputs follow the new one.\n entry.unbind();\n entry.unbind = this.bindInputs(ref, route);\n ref.hostView.reattach();\n this.select(entry);\n }\n\n // --- native tabs --------------------------------------------------------\n\n /**\n * Undo a tab page that threw on its first render, which fails the navigation: its component\n * and its content view go, and the tab that was showing carries on, change detection and all.\n */\n private abandon(entry: TabEntry, ref: ComponentRef<unknown> | undefined, content: unknown): void {\n entry.unbind();\n entry.unbind = () => {};\n ref?.destroy();\n this.renderer.removeChild(entry.tab.screen, content);\n const showing = this.selected;\n if (showing?.detached && showing.ref) {\n showing.detached = false;\n showing.ref.hostView.reattach();\n }\n }\n\n /** Route params onto inputs only under `withComponentInputBinding()`, as in the stack outlet. */\n private bindInputs(ref: ComponentRef<unknown>, route: ActivatedRoute): () => void {\n return this.router?.componentInputBindingEnabled ? bindRouteInputs(ref, route) : () => {};\n }\n\n /**\n * Take the bar from the `<native-tab>` elements in the content, once.\n *\n * On demand rather than in a lifecycle hook, because the router activates a child route during\n * this component's creation, before any hook has run - but after the content nodes themselves\n * exist, which is all this needs.\n */\n private readTabs(): TabEntry[] {\n if (this.entries.length > 0) return this.entries;\n\n const children = this.route.routeConfig?.children ?? [];\n for (const tab of this.declared()) {\n const key = tab.path();\n this.entries.push({\n tab,\n key,\n url: '',\n ref: null,\n route: null,\n detached: false,\n unbind: () => {},\n inFront: signal(false),\n leaving: new Set(),\n });\n // The reuse strategy detaches a tab whole, and has only the route to go on.\n // A path can have multiple route configs selected by canMatch (for example an iPad split\n // layout and an iPhone stack). Mark every variant: the reuse strategy sees the config\n // Angular actually matched, which may not be the first one in the route array.\n for (const config of children) {\n if (config.path === key) markTabRoute(config);\n }\n }\n\n if (this.entries.length === 0) {\n throw new Error(\n '[angular-native] <native-tabs-outlet> has no tabs. Put a <native-tab path=\"...\"> ' +\n 'inside it for each child route the bar should reach.',\n );\n }\n // Before the first navigation nothing is selected, which is not a state a tab bar has.\n this.request(this.entries[0]!);\n return this.entries;\n }\n\n /**\n * The tab `route` is the page of, by its path from this outlet's route down to it. A route with\n * no component above the page is part of that path: a `loadChildren` wrapper takes the tab's\n * path and leaves `''` to the page under it, and a group is a `''` of its own.\n */\n private entryFor(route: ActivatedRoute): TabEntry {\n const config = route.routeConfig ?? route.snapshot.routeConfig;\n const paths = [config?.path];\n for (let above = route.parent; above && !above.component; above = above.parent) {\n paths.unshift(above.routeConfig?.path);\n }\n const path = paths.filter(Boolean).join('/');\n const entry = this.readTabs().find((candidate) => candidate.key === path);\n if (!entry) {\n throw new Error(\n `[angular-native] no <native-tab> has path=\"${path}\". Every child route the bar can ` +\n `reach needs one, and its path is what native identifies the tab by.`,\n );\n }\n // Under a wrapper the page's config is not one of the children `readTabs` marked, and it is\n // the one the reuse strategy is asked to detach whole.\n if (config) markTabRoute(config);\n return entry;\n }\n\n /** Remember where this tab is, and tell native to show it. */\n private select(entry: TabEntry): void {\n const left = this.selected;\n if (left && left !== entry) this.leave(left);\n this.selected = entry;\n for (const other of this.entries) other.inFront.set(other === entry);\n entry.url = this.router?.url ?? entry.url;\n this.request(entry);\n }\n\n /**\n * Another tab came in front of `entry`: its stacks dismiss what they presented, and it is\n * remembered at the page beneath, where the outermost stack that dismissed something is left.\n */\n private leave(entry: TabEntry): void {\n let url: string | null = null;\n for (const dismiss of entry.leaving) {\n const beneath = dismiss();\n url ??= beneath;\n }\n if (url !== null) entry.url = url;\n }\n\n /**\n * A back with nothing left to pop inside the tab: go to the first tab, where it was left, which\n * is what a bottom navigation bar does on Android. From the first tab, decline, so the platform\n * leaves the app. The order tabs were visited in is not retraced, so a back never lands on a\n * tab the user did not expect. Not while the bar is covered by a screen pushed over it: that\n * screen's stack answers instead.\n */\n private goBack(): boolean {\n const first = this.entries[0];\n const selected = this.selected;\n if (!first || !selected || selected === first || !this.router) return false;\n if (!this.isActivated || !isShowing(this.router, selected.route)) return false;\n void this.router.navigateByUrl(first.url || this.pathOf(first));\n return true;\n }\n\n /** Keep the tab in front on the url it has reached, as long as the url is still inside it. */\n private follow(url: string): void {\n const entry = this.selected;\n if (entry && this.tabOf(url) === entry) entry.url = url;\n }\n\n /**\n * The url of the tab `url` is a page inside, when nothing has opened that tab: the tab's own\n * first screen, which a push into the tab from outside it shows first.\n */\n private unopenedTabOf(url: string): string | null {\n const entry = this.tabOf(url);\n if (!entry || entry.ref) return null;\n const root = this.pathOf(entry);\n return pathOnly(url) === root ? null : root;\n }\n\n /** Whether `url` is a tab other than the one in front, or a page inside one. */\n private behind(url: string): boolean {\n const entry = this.tabOf(url);\n return entry !== null && entry !== this.selected;\n }\n\n /**\n * The tab `url` is in: the one with the longest url that starts it. A tab at `''` has the bar's\n * own url, which at the root starts every url, pages outside the bar included, so it takes only\n * a url its routes lead to, as far as they are loaded. A wildcard or a custom matcher is not\n * followed, as `taken` follows neither: a page only one of them shows is in no tab.\n */\n private tabOf(url: string): TabEntry | null {\n const path = pathOnly(url);\n let found: TabEntry | null = null;\n for (const entry of this.entries) {\n const root = this.pathOf(entry);\n const inside = path === root || path.startsWith(root.endsWith('/') ? root : `${root}/`);\n if (!inside || (found && this.pathOf(found).length >= root.length)) continue;\n if (!entry.key && !this.leadsInto(entry, path.slice(root.length))) continue;\n found = entry;\n }\n return found;\n }\n\n /** Whether the routes of the tab at `''` lead to `rest`, the path under the bar's own url. */\n private leadsInto(entry: TabEntry, rest: string): boolean {\n const segments = rest.split('/').filter(Boolean);\n const own = entry.route?.snapshot.routeConfig;\n const routes = own\n ? [own]\n : (this.route.routeConfig?.children ?? []).filter((config) => config.path === '');\n return segments.length === 0 || reaches(routes, segments);\n }\n\n private request(entry: TabEntry): void {\n this.renderer.setProperty(this.host.nativeElement, 'navStateRequest', {\n selectedScreenKey: screenKeyOf(entry.key),\n baseProvenance: this.provenance,\n });\n }\n\n /**\n * Native switched tab. Acknowledge the new state, then navigate: the router is authoritative\n * about what is on screen (ADR 0003), so the tap becomes a navigation to where that tab was,\n * and the activation that follows is what fills the tab in.\n */\n private onNativeSelect(selection: TabSelection | undefined): void {\n if (!selection) return;\n this.provenance = selection.provenance;\n\n const entry = this.readTabs().find(\n (tab) => screenKeyOf(tab.key) === selection.selectedScreenKey,\n );\n if (!entry || entry === this.selected) return;\n\n // Returning to a tab returns to the url it was last on, which is the whole point of keeping\n // it mounted. A tab never visited starts at its own path.\n // A navigation that fails is reported, unless activation reported it already: a page that\n // does not load, caught here, would otherwise leave nothing but a tab that will not open.\n void this.router?.navigateByUrl(entry.url || this.pathOf(entry)).then(\n (arrived) => this.revertUnless(arrived),\n (error: unknown) => {\n if (error === null || typeof error !== 'object' || !this.reported.has(error)) {\n this.errors.handleError(error);\n }\n this.revertUnless(false);\n },\n );\n }\n\n /**\n * Native switched tab before the router agreed to. When a guard refuses, or the navigation\n * fails, the router stays where it was and so must the bar, or it shows a tab nobody is on.\n * Not while another navigation is under way: that one superseded this, and its own activation\n * selects the tab it lands on.\n */\n private revertUnless(arrived: boolean): void {\n if (arrived || !this.selected || this.router?.getCurrentNavigation()) return;\n this.request(this.selected);\n // A failed navigation ends in a promise callback, which schedules no change detection, and a\n // prop reaches native only with the commit at the end of a pass. Without one the bar stayed\n // on the tab that failed until something else happened to render.\n this.changeDetector.markForCheck();\n }\n\n /** A tab's own url, from the outlet's route down. */\n private pathOf(entry: TabEntry): string {\n const parent = this.route.snapshot.pathFromRoot\n .flatMap((snapshot) => snapshot.url.map((segment) => segment.path))\n .join('/');\n return `/${[parent, entry.key].filter(Boolean).join('/')}`;\n }\n\n /**\n * As the stack outlet's: the route, the outlet contexts, and whether the tab is showing. And the\n * tab itself, for its stacks to hear when another comes in front.\n */\n private outletInjector(route: ActivatedRoute, entry: TabEntry): Injector {\n const outer = this.injector.get(SCREEN_IN_FRONT);\n const tab: InTab = {\n onLeave: (leave) => (entry.leaving.add(leave), () => entry.leaving.delete(leave)),\n };\n return Injector.create({\n parent: this.injector,\n providers: [\n { provide: SCREEN_IN_FRONT, useValue: computed(() => outer() && entry.inFront()) },\n { provide: IN_TAB, useValue: tab },\n { provide: ActivatedRoute, useValue: route },\n {\n provide: ChildrenOutletContexts,\n useValue: this.parentContexts.getOrCreateContext(this.name).children,\n },\n ],\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)
|