diff --git a/BREAKING.md b/BREAKING.md
index 8076a0dc981..0c7b726aad1 100644
--- a/BREAKING.md
+++ b/BREAKING.md
@@ -20,6 +20,7 @@ This is a comprehensive list of the breaking changes introduced in the major ver
- [Input](#version-9x-input)
- [Legacy Picker](#version-9x-legacy-picker)
- [Modal](#version-9x-modal)
+ - [Nav](#version-9x-nav)
- [Router Outlet](#version-9x-router-outlet)
- [Searchbar](#version-9x-searchbar)
- [Select](#version-9x-select)
@@ -98,6 +99,32 @@ Sheet modals that relied on the handle being inert should set `handleBehavior="n
```
+
Nav
+
+`ion-nav` no longer integrates with `ion-router`. It is now a standalone imperative stack navigation component, driven only through its own API (`root`, `push`, `pop`, `setRoot`, etc.) and `ion-nav-link`.
+
+The following behaviors have been removed:
+
+- The router no longer discovers or drives an `ion-nav`. Placing an `ion-nav` inside an `ion-router` no longer turns it into a routed outlet.
+- Navigating an `ion-nav` (via `push`, `pop`, `ion-nav-link`, or the swipe-to-go-back gesture) no longer updates the URL, and the router's navigation guards no longer run for `ion-nav` transitions.
+- The internal `setRouteId()` and `getRouteId()` methods and the `updateURL` nav option have been removed.
+
+Apps that relied on `ion-nav` to update the URL (for example, pushing components and expecting the browser URL to change) should use `ion-router-outlet` for URL-based routing:
+
+```diff
+-
+-
+-
++
+```
+
+An `ion-nav` can still be used inside a routed page for local, URL-less stack navigation:
+
+```html
+
+
+```
+
Router Outlet
`ion-router-outlet` now exposes a `swipeGesture` property that controls the swipe-to-go-back gesture per outlet. This property defaults to `true` in `"ios"` mode and `false` in `"md"` mode.
diff --git a/core/src/components.d.ts b/core/src/components.d.ts
index ac1c904ff6d..342d525d792 100644
--- a/core/src/components.d.ts
+++ b/core/src/components.d.ts
@@ -2115,10 +2115,6 @@ export namespace Components {
* @param view The view to get.
*/
"getPrevious": (view?: ViewController) => Promise;
- /**
- * Called by to retrieve the current component.
- */
- "getRouteId": () => Promise;
/**
* Inserts a component into the navigation stack at the specified index. This is useful to add a component at any point in the navigation stack.
* @param insertIndex The index to insert the component at in the stack.
@@ -2194,15 +2190,6 @@ export namespace Components {
* @param done The transition complete function.
*/
"setRoot": (component: T, componentProps?: ComponentProps | null, opts?: NavOptions | null, done?: TransitionDoneFn) => Promise;
- /**
- * Called by the router to update the view.
- * @param id The component tag.
- * @param params The component params.
- * @param direction A direction hint.
- * @param animation an AnimationBuilder.
- * @return the status.
- */
- "setRouteId": (id: string, params: ComponentProps | undefined, direction: RouterDirection, animation?: AnimationBuilder) => Promise;
/**
* If the nav component should allow for swipe-to-go-back.
*/
@@ -2724,7 +2711,7 @@ export namespace Components {
*/
"beforeLeave"?: NavigationHookCallback;
/**
- * Name of the component to load/select in the navigation outlet (`ion-tabs`, `ion-nav`) when the route matches. The value of this property is not always the tagname of the component to load, in `ion-tabs` it actually refers to the name of the `ion-tab` to select.
+ * Name of the component to load/select in the navigation outlet (`ion-tabs`, `ion-router-outlet`) when the route matches. The value of this property is not always the tagname of the component to load, in `ion-tabs` it actually refers to the name of the `ion-tab` to select.
*/
"component": string;
/**
@@ -7897,7 +7884,7 @@ declare namespace LocalJSX {
*/
"beforeLeave"?: NavigationHookCallback;
/**
- * Name of the component to load/select in the navigation outlet (`ion-tabs`, `ion-nav`) when the route matches. The value of this property is not always the tagname of the component to load, in `ion-tabs` it actually refers to the name of the `ion-tab` to select.
+ * Name of the component to load/select in the navigation outlet (`ion-tabs`, `ion-router-outlet`) when the route matches. The value of this property is not always the tagname of the component to load, in `ion-tabs` it actually refers to the name of the `ion-tab` to select.
*/
"component": string;
/**
diff --git a/core/src/components/nav/nav-interface.ts b/core/src/components/nav/nav-interface.ts
index caacb39b2ec..07515625e75 100644
--- a/core/src/components/nav/nav-interface.ts
+++ b/core/src/components/nav/nav-interface.ts
@@ -42,7 +42,6 @@ export interface RouterOutletOptions {
export interface NavOptions extends RouterOutletOptions {
progressAnimation?: boolean;
- updateURL?: boolean;
delegate?: FrameworkDelegate;
viewIsReady?: (enteringEl: HTMLElement) => Promise;
}
diff --git a/core/src/components/nav/nav.tsx b/core/src/components/nav/nav.tsx
index 3ecd39c2aa6..3b40c13e427 100644
--- a/core/src/components/nav/nav.tsx
+++ b/core/src/components/nav/nav.tsx
@@ -1,5 +1,5 @@
-import type { EventEmitter } from '@stencil/core';
-import { Build, Component, Element, Event, Method, Prop, Watch, h } from '@stencil/core';
+import type { ComponentInterface, EventEmitter } from '@stencil/core';
+import { Component, Element, Event, Method, Prop, Watch, h } from '@stencil/core';
import { getTimeGivenProgression } from '@utils/animation/cubic-bezier';
import { assert } from '@utils/helpers';
import { printIonWarning } from '@utils/logging';
@@ -9,7 +9,6 @@ import { lifecycle, setPageHidden, transition } from '@utils/transition';
import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global';
import type { Animation, AnimationBuilder, ComponentProps, FrameworkDelegate, Gesture } from '../../interface';
-import type { NavOutlet, RouteID, RouteWrite, RouterDirection } from '../router/utils/interface';
import { LIFECYCLE_DID_LEAVE, LIFECYCLE_WILL_LEAVE, LIFECYCLE_WILL_UNLOAD } from './constants';
import type {
@@ -21,18 +20,17 @@ import type {
TransitionInstruction,
} from './nav-interface';
import type { ViewController } from './view-controller';
-import { VIEW_STATE_ATTACHED, VIEW_STATE_DESTROYED, VIEW_STATE_NEW, convertToViews, matches } from './view-controller';
+import { VIEW_STATE_ATTACHED, VIEW_STATE_DESTROYED, VIEW_STATE_NEW, convertToViews } from './view-controller';
@Component({
tag: 'ion-nav',
styleUrl: 'nav.scss',
shadow: true,
})
-export class Nav implements NavOutlet {
+export class Nav implements ComponentInterface {
private transInstr: TransitionInstruction[] = [];
private sbAni?: Animation;
private gestureOrAnimationInProgress = false;
- private useRouter = false;
private isTransitioning = false;
private destroyed = false;
private views: ViewController[] = [];
@@ -78,8 +76,6 @@ export class Nav implements NavOutlet {
@Watch('root')
rootChanged() {
- const isDev = Build.isDev;
-
if (this.root === undefined) {
return;
}
@@ -92,13 +88,7 @@ export class Nav implements NavOutlet {
return;
}
- if (!this.useRouter) {
- if (this.root !== undefined) {
- this.setRoot(this.root, this.rootParams);
- }
- } else if (isDev) {
- printIonWarning('[ion-nav] - A root attribute is not supported when using ion-router.', this.el);
- }
+ this.setRoot(this.root, this.rootParams);
}
/**
@@ -116,8 +106,6 @@ export class Nav implements NavOutlet {
@Event({ bubbles: false }) ionNavDidChange!: EventEmitter;
componentWillLoad() {
- this.useRouter = document.querySelector('ion-router') !== null && this.el.closest('[no-router]') === null;
-
if (this.swipeGesture === undefined) {
const mode = getIonMode(this);
this.swipeGesture = config.getBoolean('swipeBackEnabled', mode === 'ios');
@@ -352,99 +340,6 @@ export class Nav implements NavOutlet {
);
}
- /**
- * Called by the router to update the view.
- *
- * @param id The component tag.
- * @param params The component params.
- * @param direction A direction hint.
- * @param animation an AnimationBuilder.
- *
- * @return the status.
- * @internal
- */
- @Method()
- setRouteId(
- id: string,
- params: ComponentProps | undefined,
- direction: RouterDirection,
- animation?: AnimationBuilder
- ): Promise {
- const active = this.getActiveSync();
- if (matches(active, id, params)) {
- return Promise.resolve({
- changed: false,
- element: active.element,
- });
- }
-
- let resolve: (result: RouteWrite) => void;
- const promise = new Promise((r) => (resolve = r));
- let finish: Promise;
- const commonOpts: NavOptions = {
- updateURL: false,
- viewIsReady: (enteringEl) => {
- let mark: () => void;
- const p = new Promise((r) => (mark = r));
- resolve({
- changed: true,
- element: enteringEl,
- markVisible: async () => {
- mark();
- await finish;
- },
- });
- return p;
- },
- };
-
- if (direction === 'root') {
- finish = this.setRoot(id, params, commonOpts);
- } else {
- // Look for a view matching the target in the view stack.
- const viewController = this.views.find((v) => matches(v, id, params));
-
- if (viewController) {
- finish = this.popTo(viewController, {
- ...commonOpts,
- direction: 'back',
- animationBuilder: animation,
- });
- } else if (direction === 'forward') {
- finish = this.push(id, params, {
- ...commonOpts,
- animationBuilder: animation,
- });
- } else if (direction === 'back') {
- finish = this.setRoot(id, params, {
- ...commonOpts,
- direction: 'back',
- animated: true,
- animationBuilder: animation,
- });
- }
- }
- return promise;
- }
-
- /**
- * Called by to retrieve the current component.
- *
- * @internal
- */
- @Method()
- async getRouteId(): Promise {
- const active = this.getActiveSync();
- if (active) {
- return {
- id: active.element!.tagName,
- params: active.params,
- element: active.element,
- };
- }
- return undefined;
- }
-
/**
* Get the active view.
*/
@@ -524,26 +419,6 @@ export class Nav implements NavOutlet {
});
ti.done = done;
- /**
- * If using router, check to see if navigation hooks
- * will allow us to perform this transition. This
- * is required in order for hooks to work with
- * the ion-back-button or swipe to go back.
- */
- if (ti.opts && ti.opts.updateURL !== false && this.useRouter) {
- const router = document.querySelector('ion-router');
- if (router) {
- const canTransition = await router.canTransition();
- if (canTransition === false) {
- return false;
- }
- if (typeof canTransition === 'string') {
- router.push(canTransition, ti.opts!.direction || 'back');
- return false;
- }
- }
- }
-
// Normalize empty
if (ti.insertViews?.length === 0) {
ti.insertViews = undefined;
@@ -575,14 +450,6 @@ export class Nav implements NavOutlet {
);
}
ti.resolve!(result.hasCompleted);
-
- if (ti.opts!.updateURL !== false && this.useRouter) {
- const router = document.querySelector('ion-router');
- if (router) {
- const direction = result.direction === 'back' ? 'back' : 'forward';
- router.navChanged(direction);
- }
- }
}
private failed(rejectReason: any, ti: TransitionInstruction) {
diff --git a/core/src/components/nav/test/routing/index.html b/core/src/components/nav/test/routing/index.html
index 4621a08a9d7..d4aa5c53607 100644
--- a/core/src/components/nav/test/routing/index.html
+++ b/core/src/components/nav/test/routing/index.html
@@ -15,14 +15,21 @@
+
-
+
-
-
+