Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
285 changes: 22 additions & 263 deletions BREAKING.md

Large diffs are not rendered by default.

282 changes: 282 additions & 0 deletions BREAKING_ARCHIVE/v8.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions core/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1589,6 +1589,7 @@ ion-router-outlet,shadow
ion-router-outlet,prop,animated,boolean,true,false,false
ion-router-outlet,prop,animation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false
ion-router-outlet,prop,mode,"ios" | "md",getIonMode(this),false,false
ion-router-outlet,prop,swipeGesture,boolean | undefined,undefined,false,false

ion-row,shadow

Expand Down
9 changes: 9 additions & 0 deletions core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2915,6 +2915,10 @@ export namespace Components {
*/
"mode": "ios" | "md";
"setRouteId": (id: string, params: ComponentProps | undefined, direction: RouterDirection, animation?: AnimationBuilder) => Promise<RouteWrite>;
/**
* If `true`, the router-outlet should allow navigation via swipe-to-go-back gesture. Defaults to `true` for `"ios"` mode and `false` for `"md"` mode.
*/
"swipeGesture"?: boolean;
"swipeHandler"?: SwipeGestureHandler;
}
interface IonRow {
Expand Down Expand Up @@ -8229,6 +8233,10 @@ declare namespace LocalJSX {
"onIonNavDidChange"?: (event: IonRouterOutletCustomEvent<void>) => void;
"onIonNavWillChange"?: (event: IonRouterOutletCustomEvent<void>) => void;
"onIonNavWillLoad"?: (event: IonRouterOutletCustomEvent<void>) => void;
/**
* If `true`, the router-outlet should allow navigation via swipe-to-go-back gesture. Defaults to `true` for `"ios"` mode and `false` for `"md"` mode.
*/
"swipeGesture"?: boolean;
"swipeHandler"?: SwipeGestureHandler;
}
interface IonRow {
Expand Down Expand Up @@ -9723,6 +9731,7 @@ declare namespace LocalJSX {
interface IonRouterOutletAttributes {
"mode": "ios" | "md";
"animated": boolean;
"swipeGesture": boolean;
}
interface IonSearchbarAttributes {
"color": Color;
Expand Down
27 changes: 23 additions & 4 deletions core/src/components/router-outlet/router-outlet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,21 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
/** This property allows to create custom transition using AnimationBuilder functions. */
@Prop() animation?: AnimationBuilder;

/**
* If `true`, the router-outlet should allow navigation via swipe-to-go-back gesture.
* Defaults to `true` for `"ios"` mode and `false` for `"md"` mode.
*/
@Prop({ mutable: true }) swipeGesture?: boolean;
@Watch('swipeGesture')
swipeGestureChanged() {
this.updateGestureEnabled();
}

/** @internal */
@Prop() swipeHandler?: SwipeGestureHandler;
@Watch('swipeHandler')
swipeHandlerChanged() {
if (this.gesture) {
this.gesture.enable(this.swipeHandler !== undefined);
}
this.updateGestureEnabled();
}

/** @internal */
Expand Down Expand Up @@ -121,13 +129,24 @@ export class RouterOutlet implements ComponentInterface, NavOutlet {
}
}
);
this.swipeHandlerChanged();

if (this.swipeGesture === undefined) {
this.swipeGesture = config.getBoolean('swipeBackEnabled', this.mode === 'ios');
} else {
this.updateGestureEnabled();
}
}

componentWillLoad() {
this.ionNavWillLoad.emit();
}

private updateGestureEnabled() {
if (this.gesture) {
this.gesture.enable(this.swipeHandler !== undefined && this.swipeGesture === true);
}
}

disconnectedCallback() {
if (this.gesture) {
this.gesture.destroy();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ export abstract class IonRouterOutlet implements OnDestroy, OnInit {
onEnd: (shouldContinue) => this.stackCtrl.endBackTransition(shouldContinue),
}
: undefined;

this.nativeEl.swipeGesture = swipe;
}

constructor(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { test } from '@playwright/test';
import { ionSwipeToGoBack } from '../../utils/drag-utils';
import { ionPageVisible, ionPageHidden } from '../../utils/test-utils';

test.describe('Swipe Gesture Disabled', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/standalone/swipe-gesture-disabled?ionic:mode=ios');
});

test('should not swipe back when swipeGesture is false', async ({ page }) => {
await ionPageVisible(page, 'app-swipe-gesture-disabled-main');

await page.locator('#swipe-disabled-details').click();
await ionPageVisible(page, 'app-swipe-gesture-disabled-details');
await ionPageHidden(page, 'app-swipe-gesture-disabled-main');

await ionSwipeToGoBack(page, true);

await ionPageVisible(page, 'app-swipe-gesture-disabled-details');
await ionPageHidden(page, 'app-swipe-gesture-disabled-main');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ export const routes: Routes = [
]
},
{ path: 'tabs-basic', loadComponent: () => import('../tabs-basic/tabs-basic.component').then(c => c.TabsBasicComponent) },
{
path: 'swipe-gesture-disabled',
loadComponent: () => import('../swipe-gesture-disabled/swipe-gesture-disabled.component').then(c => c.SwipeGestureDisabledComponent),
children: [
{ path: '', loadComponent: () => import('../swipe-gesture-disabled/swipe-gesture-disabled-main.component').then(c => c.SwipeGestureDisabledMainComponent) },
{ path: 'details', loadComponent: () => import('../swipe-gesture-disabled/swipe-gesture-disabled-details.component').then(c => c.SwipeGestureDisabledDetailsComponent) }
]
},
{
path: 'validation',
children: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@
Tabs Basic Test
</ion-label>
</ion-item>
<ion-item routerLink="/standalone/swipe-gesture-disabled">
<ion-label>
Swipe Gesture Disabled Test
</ion-label>
</ion-item>
</ion-list>

<ion-list>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Component } from '@angular/core';
import { IonBackButton, IonButtons, IonContent, IonHeader, IonTitle, IonToolbar } from '@ionic/angular/standalone';

@Component({
selector: 'app-swipe-gesture-disabled-details',
template: `
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button defaultHref="/standalone/swipe-gesture-disabled"></ion-back-button>
</ion-buttons>
<ion-title>Details</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<div>Details (swipe disabled)</div>
</ion-content>
`,
standalone: true,
imports: [IonBackButton, IonButtons, IonContent, IonHeader, IonTitle, IonToolbar]
})
export class SwipeGestureDisabledDetailsComponent {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Component } from '@angular/core';
import { RouterModule } from '@angular/router';
import { IonContent, IonItem, IonLabel, IonRouterLink } from '@ionic/angular/standalone';

@Component({
selector: 'app-swipe-gesture-disabled-main',
template: `
<ion-content>
<ion-item routerLink="/standalone/swipe-gesture-disabled/details" id="swipe-disabled-details">
<ion-label>Details</ion-label>
</ion-item>
</ion-content>
`,
standalone: true,
imports: [IonContent, IonItem, IonLabel, IonRouterLink, RouterModule]
})
export class SwipeGestureDisabledMainComponent {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Component } from '@angular/core';
import { IonBackButton, IonButtons, IonContent, IonHeader, IonRouterOutlet, IonTitle, IonToolbar } from '@ionic/angular/standalone';

@Component({
selector: 'app-swipe-gesture-disabled',
template: `
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button defaultHref="/standalone"></ion-back-button>
</ion-buttons>
<ion-title>Swipe Gesture Disabled</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-router-outlet [swipeGesture]="false"></ion-router-outlet>
</ion-content>
`,
standalone: true,
imports: [IonBackButton, IonButtons, IonContent, IonHeader, IonRouterOutlet, IonTitle, IonToolbar]
})
export class SwipeGestureDisabledComponent {}
8 changes: 1 addition & 7 deletions packages/react-router/src/ReactRouter/StackManager.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { RouteInfo, StackContextState, ViewItem } from '@ionic/react';
import { RouteManagerContext, StackContext, generateId, getConfig } from '@ionic/react';
import { RouteManagerContext, StackContext, generateId } from '@ionic/react';
import React from 'react';

import { clonePageElement } from './clonePageElement';
Expand Down Expand Up @@ -211,12 +211,6 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa

async setupRouterOutlet(routerOutlet: HTMLIonRouterOutletElement) {
const canStart = () => {
const config = getConfig();
const swipeEnabled = config && config.get('swipeBackEnabled', routerOutlet.mode === 'ios');
if (!swipeEnabled) {
return false;
}

const { routeInfo } = this.props;

const propsToUse =
Expand Down
3 changes: 2 additions & 1 deletion packages/react-router/test/base/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import NestedOutlet2 from './pages/nested-outlet/NestedOutlet2';
import ReplaceAction from './pages/replace-action/Replace';
import TabsContext from './pages/tab-context/TabContext';
import { OutletRef } from './pages/outlet-ref/OutletRef';
import { SwipeToGoBack } from './pages/swipe-to-go-back/SwipToGoBack';
import { SwipeToGoBack, SwipeToGoBackDisabled } from './pages/swipe-to-go-back/SwipToGoBack';
import Refs from './pages/refs/Refs';
import DynamicIonpageClassnames from './pages/dynamic-ionpage-classnames/DynamicIonpageClassnames';
import Tabs from './pages/tabs/Tabs';
Expand All @@ -57,6 +57,7 @@ const App: React.FC = () => {
<Route path="/tab-context" component={TabsContext} />
<Route path="/outlet-ref" component={OutletRef} />
<Route path="/swipe-to-go-back" component={SwipeToGoBack} />
<Route path="/swipe-to-go-back-disabled" component={SwipeToGoBackDisabled} />
<Route path="/dynamic-ionpage-classnames" component={DynamicIonpageClassnames} />
<Route path="/tabs" component={Tabs} />
<Route path="/tabs-secondary" component={TabsSecondary} />
Expand Down
3 changes: 3 additions & 0 deletions packages/react-router/test/base/src/pages/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ const Main: React.FC<MainProps> = () => {
<IonItem routerLink="/swipe-to-go-back">
<IonLabel>Swipe to go back</IonLabel>
</IonItem>
<IonItem routerLink="/swipe-to-go-back-disabled">
<IonLabel>Swipe to go back (disabled)</IonLabel>
</IonItem>
<IonItem routerLink="/dynamic-ionpage-classnames">
<IonLabel>Dynamic IonPage Classnames</IonLabel>
</IonItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,48 @@ const Main: React.FC = () => {
);
};

export const SwipeToGoBackDisabled: React.FC = () => {
return (
<IonRouterOutlet id="swipe-to-go-back-disabled" swipeGesture={false}>
<Route path="/swipe-to-go-back-disabled" component={DisabledMain} exact />
<Route path="/swipe-to-go-back-disabled/details" component={DisabledDetails} />
</IonRouterOutlet>
);
};

const DisabledMain: React.FC = () => {
return (
<IonPage data-pageid="disabled-main">
<IonHeader>
<IonToolbar>
<IonTitle>Disabled Main</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<IonItem routerLink="/swipe-to-go-back-disabled/details">Details</IonItem>
</IonContent>
</IonPage>
);
};

const DisabledDetails: React.FC = () => {
return (
<IonPage data-pageid="disabled-details">
<IonHeader>
<IonToolbar>
<IonButtons>
<IonBackButton></IonBackButton>
</IonButtons>
<IonTitle>Disabled Details</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<div>Details (swipe disabled)</div>
</IonContent>
</IonPage>
);
};

const Details: React.FC = () => {
return (
<IonPage data-pageid="details">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,17 @@ describe('Swipe To Go Back', () => {

cy.ionPageVisible('params-1');
})

it('should not swipe back when swipeGesture is false', () => {
cy.visit(`http://localhost:${port}/swipe-to-go-back-disabled?${IOS_MODE}`);
cy.ionPageVisible('disabled-main');

cy.ionNav('ion-item', 'Details');
cy.ionPageVisible('disabled-details');
cy.ionPageHidden('disabled-main');

cy.ionSwipeToGoBack(true, 'ion-router-outlet#swipe-to-go-back-disabled');
cy.ionPageVisible('disabled-details');
cy.ionPageHidden('disabled-main');
});
});
25 changes: 17 additions & 8 deletions packages/vue/src/components/IonRouterOutlet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
import type { InjectionKey, Ref } from "vue";
import { matchedRouteKey, routeLocationKey, useRoute } from "vue-router";

import { fireLifecycle, generateId, getConfig } from "../utils";
import { fireLifecycle, generateId } from "../utils";

// TODO(FW-2969): types

Expand All @@ -34,6 +34,12 @@ const isViewVisible = (enteringEl: HTMLElement) => {
const viewDepthKey: InjectionKey<0> = Symbol(0);
export const IonRouterOutlet = /*@__PURE__*/ defineComponent({
name: "IonRouterOutlet",
props: {
swipeGesture: {
type: Boolean,
default: undefined,
},
},
setup() {
defineCustomElement();

Expand Down Expand Up @@ -127,12 +133,6 @@ export const IonRouterOutlet = /*@__PURE__*/ defineComponent({
);

const canStart = () => {
const config = getConfig();
const swipeEnabled =
config &&
config.get("swipeBackEnabled", ionRouterOutlet.value.mode === "ios");
if (!swipeEnabled) return false;

const stack = viewStacks.getViewStack(id);
if (!stack || stack.length <= 1) return false;

Expand Down Expand Up @@ -547,9 +547,18 @@ See https://ionicframework.com/docs/vue/navigation#ionpage for more information.
render() {
const { components, registerIonPage, injectedRoute } = this;

/**
* Forward props selectively to avoid setting undefined values
* that would override the web component's config-based defaults.
*/
const routerOutletProps: Record<string, any> = { ref: "ionRouterOutlet" };
if (this.$props.swipeGesture !== undefined) {
routerOutletProps.swipeGesture = this.$props.swipeGesture;
}

return h(
"ion-router-outlet",
{ ref: "ionRouterOutlet" },
routerOutletProps,
components &&
components.map((c: any) => {
let props = {
Expand Down
Loading
Loading