mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-22 05:21:52 +08:00
fix(ios): swipe to go back now works in rtl mode (#24866)
resolves #19488
This commit is contained in:
@ -173,10 +173,6 @@
|
||||
display: none;
|
||||
position: absolute;
|
||||
|
||||
/* stylelint-disable property-disallowed-list */
|
||||
left: -100%;
|
||||
/* stylelint-enable property-disallowed-list */
|
||||
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -204,10 +212,6 @@
|
||||
display: block;
|
||||
position: absolute;
|
||||
|
||||
/* stylelint-disable property-disallowed-list */
|
||||
right: 0;
|
||||
/* stylelint-enable property-disallowed-list */
|
||||
|
||||
width: 10px;
|
||||
height: 100%;
|
||||
|
||||
@ -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
|
||||
// --------------------------------------------------
|
||||
|
@ -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';
|
||||
|
||||
/**
|
||||
@ -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';
|
||||
@ -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`,
|
||||
@ -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"
|
||||
>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { clamp } from '../helpers';
|
||||
import { isRTL } from '../rtl';
|
||||
|
||||
import { Gesture, GestureDetail, createGesture } from './index';
|
||||
|
||||
@ -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;
|
||||
|
36
core/src/utils/gesture/test/e2e.ts
Normal file
36
core/src/utils/gesture/test/e2e.ts
Normal file
@ -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
core/src/utils/gesture/test/index.html
Normal file
64
core/src/utils/gesture/test/index.html
Normal file
@ -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>
|
@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user