Skip to content

Commit

Permalink
fix(ios): swipe to go back now works in rtl mode (#24866)
Browse files Browse the repository at this point in the history
resolves #19488
  • Loading branch information
liamdebeasi authored Mar 7, 2022
1 parent 331ce6d commit 2ac9105
Show file tree
Hide file tree
Showing 6 changed files with 179 additions and 23 deletions.
36 changes: 27 additions & 9 deletions core/src/components/content/content.scss
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@
*/
top: 0;
bottom: 0;

margin-top: calc(var(--offset-top) * -1);
margin-bottom: calc(var(--offset-bottom) * -1);
}
Expand All @@ -173,10 +173,6 @@
display: none;
position: absolute;

/* stylelint-disable property-disallowed-list */
left: -100%;
/* stylelint-enable property-disallowed-list */

width: 100%;
height: 100vh;

Expand All @@ -185,6 +181,18 @@
pointer-events: none;
}

:host(.content-ltr) .transition-effect {
/* stylelint-disable property-disallowed-list */
left: -100%;
/* stylelint-enable property-disallowed-list */
}

:host(.content-rtl) .transition-effect {
/* stylelint-disable property-disallowed-list */
right: -100%;
/* stylelint-enable property-disallowed-list */
}

.transition-cover {
position: absolute;

Expand All @@ -204,10 +212,6 @@
display: block;
position: absolute;

/* stylelint-disable property-disallowed-list */
right: 0;
/* stylelint-enable property-disallowed-list */

width: 10px;
height: 100%;

Expand All @@ -216,6 +220,20 @@
background-size: 10px 16px;
}

:host(.content-ltr) .transition-shadow {
/* stylelint-disable property-disallowed-list */
right: 0;
/* stylelint-enable property-disallowed-list */
}

:host(.content-rtl) .transition-shadow {
/* stylelint-disable property-disallowed-list */
left: 0;
/* stylelint-enable property-disallowed-list */

transform: scaleX(-1);
}


// Content: Fixed
// --------------------------------------------------
Expand Down
7 changes: 5 additions & 2 deletions core/src/components/content/content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getIonMode } from '../../global/ionic-global';
import { Color, ScrollBaseDetail, ScrollDetail } from '../../interface';
import { componentOnReady } from '../../utils/helpers';
import { isPlatform } from '../../utils/platform';
import { isRTL } from '../../utils/rtl';
import { createColorClasses, hostContext } from '../../utils/theme';

/**
Expand Down Expand Up @@ -311,7 +312,8 @@ export class Content implements ComponentInterface {
}

render() {
const { isMainContent, scrollX, scrollY } = this;
const { isMainContent, scrollX, scrollY, el } = this;
const rtl = isRTL(el) ? 'rtl' : 'ltr';
const mode = getIonMode(this);
const forceOverscroll = this.shouldForceOverscroll();
const transitionShadow = mode === 'ios';
Expand All @@ -325,6 +327,7 @@ export class Content implements ComponentInterface {
[mode]: true,
'content-sizing': hostContext('ion-popover', this.el),
'overscroll': forceOverscroll,
[`content-${rtl}`]: true
})}
style={{
'--offset-top': `${this.cTop}px`,
Expand All @@ -339,7 +342,7 @@ export class Content implements ComponentInterface {
'scroll-y': scrollY,
'overscroll': (scrollX || scrollY) && forceOverscroll
}}
ref={(el: HTMLElement) => this.scrollEl = el!}
ref={(scrollEl: HTMLElement) => this.scrollEl = scrollEl!}
onScroll={(this.scrollEvents) ? (ev: UIEvent) => this.onScroll(ev) : undefined}
part="scroll"
>
Expand Down
37 changes: 32 additions & 5 deletions core/src/utils/gesture/swipe-back.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { clamp } from '../helpers';
import { isRTL } from '../rtl';

import { Gesture, GestureDetail, createGesture } from './index';

Expand All @@ -10,26 +11,52 @@ export const createSwipeBackGesture = (
onEndHandler: (shouldComplete: boolean, step: number, dur: number) => void,
): Gesture => {
const win = el.ownerDocument!.defaultView!;
const rtl = isRTL(el);

/**
* Determine if a gesture is near the edge
* of the screen. If true, then the swipe
* to go back gesture should proceed.
*/
const isAtEdge = (detail: GestureDetail) => {
const threshold = 50;
const { startX } = detail;

if (rtl) {
return startX >= win.innerWidth - threshold;
}

return startX <= threshold;
}

const getDeltaX = (detail: GestureDetail) => {
return rtl ? -detail.deltaX : detail.deltaX;
}

const getVelocityX = (detail: GestureDetail) => {
return rtl ? -detail.velocityX : detail.velocityX;
}

const canStart = (detail: GestureDetail) => {
return detail.startX <= 50 && canStartHandler();
return isAtEdge(detail) && canStartHandler();
};

const onMove = (detail: GestureDetail) => {
// set the transition animation's progress
const delta = detail.deltaX;
const delta = getDeltaX(detail);
const stepValue = delta / win.innerWidth;
onMoveHandler(stepValue);
};

const onEnd = (detail: GestureDetail) => {
// the swipe back gesture has ended
const delta = detail.deltaX;
const delta = getDeltaX(detail);
const width = win.innerWidth;
const stepValue = delta / width;
const velocity = detail.velocityX;
const velocity = getVelocityX(detail);
const z = width / 2.0;
const shouldComplete =
velocity >= 0 && (velocity > 0.2 || detail.deltaX > z);
velocity >= 0 && (velocity > 0.2 || delta > z);

const missing = shouldComplete ? 1 - stepValue : stepValue;
const missingDistance = missing * width;
Expand Down
36 changes: 36 additions & 0 deletions core/src/utils/gesture/test/e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { newE2EPage } from '@stencil/core/testing';
import { dragElementBy } from '@utils/test';

test('swipe to go back should complete', async () => {
const page = await newE2EPage({ url: '/src/utils/gesture/test?ionic:mode=ios' });

const nav = await page.find('ion-nav');
const ionNavDidChange = await nav.spyOnEvent('ionNavDidChange');

await page.click('.next');
await ionNavDidChange.next();

const content = await page.$('.page-two-content');

const width = await page.evaluate(() => window.innerWidth);
await dragElementBy(content, page, width, 0, { x: 25, y: 100 });

await ionNavDidChange.next();
});

test('swipe to go back should complete in rtl', async () => {
const page = await newE2EPage({ url: '/src/utils/gesture/test?rtl=true&ionic:mode=ios' });

const nav = await page.find('ion-nav');
const ionNavDidChange = await nav.spyOnEvent('ionNavDidChange');

await page.click('.next');
await ionNavDidChange.next();

const width = await page.evaluate(() => window.innerWidth);

const content = await page.$('.page-two-content');
await dragElementBy(content, page, -width, 0, { x: width - 25, y: 100 });

await ionNavDidChange.next();
});
64 changes: 64 additions & 0 deletions core/src/utils/gesture/test/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swipe to go back</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
<script src="../../../../../scripts/testing/scripts.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<script>
class PageOne extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-title>Page One</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<h1>Page One</h1>
<ion-nav-link router-direction="forward" component="page-two">
<ion-button class="next">Go to Page Two</ion-button>
</ion-nav-link>
</ion-content>
`;
}
}
class PageTwo extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Page Two</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="page-two-content ion-padding">
<h1>Page Two</h1>
</ion-content>
`;
}
}
customElements.define('page-one', PageOne);
customElements.define('page-two', PageTwo);
</script>
</head>

<body>
<ion-app>
<ion-nav root="page-one"></ion-nav>
</ion-app>

<script>
window.Ionic = {
config: {
mode: 'ios'
}
}
</script>
</body>
</html>
22 changes: 15 additions & 7 deletions core/src/utils/test/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,26 +77,34 @@ export const listenForEvent = async (page: any, eventType: string, element: any,
* @param page - The Puppeteer 'page' object
* @param x: number - Amount to drag `element` by on the x-axis
* @param y: number - Amount to drag `element` by on the y-axis
* @param startCoordinates (optional) - Coordinates of where to start the drag
* gesture. If not provided, the drag gesture will start in the middle of the
* element.
*/
export const dragElementBy = async (element: any, page: any, x = 0, y = 0): Promise<void> => {
export const dragElementBy = async (
element: any,
page: any,
x = 0,
y = 0,
startCoordinates?: { x: number, y: number }
): Promise<void> => {
try {
const boundingBox = await element.boundingBox();

const startX = boundingBox.x + boundingBox.width / 2;
const startY = boundingBox.y + boundingBox.height / 2;
const startX = (startCoordinates?.x === undefined) ? boundingBox.x + boundingBox.width / 2 : startCoordinates.x;
const startY = (startCoordinates?.y === undefined) ? boundingBox.y + boundingBox.height / 2 : startCoordinates.y;

const midX = startX + (x / 2);
const midY = startY + (y / 2);

const endX = startX + x;
const endY = startY + y;

const midX = endX / 2;
const midY = endY / 2;

await page.mouse.move(startX, startY);
await page.mouse.down();
await page.mouse.move(midX, midY);
await page.mouse.move(endX, endY);
await page.mouse.up();

} catch (err) {
throw err;
}
Expand Down

0 comments on commit 2ac9105

Please sign in to comment.