fix(modal): swipe to close on content blocks scroll in ion-nav (#25300)
resolves #25298
@@ -1,9 +1,9 @@
|
||||
import type { Animation } from '../../../interface';
|
||||
import { getTimeGivenProgression } from '../../../utils/animation/cubic-bezier';
|
||||
import { isIonContent } from '../../../utils/content';
|
||||
import { isIonContent, findClosestIonContent } from '../../../utils/content';
|
||||
import type { GestureDetail } from '../../../utils/gesture';
|
||||
import { createGesture } from '../../../utils/gesture';
|
||||
import { clamp } from '../../../utils/helpers';
|
||||
import { clamp, getElementRoot } from '../../../utils/helpers';
|
||||
|
||||
import { calculateSpringStep, handleCanDismiss } from './utils';
|
||||
|
||||
@@ -12,20 +12,16 @@ export const SwipeToCloseDefaults = {
|
||||
MIN_PRESENTING_SCALE: 0.93,
|
||||
};
|
||||
|
||||
export const createSwipeToCloseGesture = (
|
||||
el: HTMLIonModalElement,
|
||||
contentEl: HTMLElement,
|
||||
scrollEl: HTMLElement,
|
||||
animation: Animation,
|
||||
onDismiss: () => void
|
||||
) => {
|
||||
export const createSwipeToCloseGesture = (el: HTMLIonModalElement, animation: Animation, onDismiss: () => void) => {
|
||||
const height = el.offsetHeight;
|
||||
let isOpen = false;
|
||||
let canDismissBlocksGesture = false;
|
||||
let contentEl: HTMLElement | null = null;
|
||||
let scrollEl: HTMLElement | null = null;
|
||||
const canDismissMaxStep = 0.2;
|
||||
const hasRefresherInContent = !!contentEl.querySelector('ion-refresher');
|
||||
let initialScrollY = true;
|
||||
const getScrollY = () => {
|
||||
if (isIonContent(contentEl)) {
|
||||
if (contentEl && isIonContent(contentEl)) {
|
||||
return (contentEl as HTMLIonContentElement).scrollY;
|
||||
/**
|
||||
* Custom scroll containers are intended to be
|
||||
@@ -36,9 +32,12 @@ export const createSwipeToCloseGesture = (
|
||||
return true;
|
||||
}
|
||||
};
|
||||
const initialScrollY = getScrollY();
|
||||
|
||||
const disableContentScroll = () => {
|
||||
if (!contentEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isIonContent(contentEl)) {
|
||||
(contentEl as HTMLIonContentElement).scrollY = false;
|
||||
} else {
|
||||
@@ -47,6 +46,10 @@ export const createSwipeToCloseGesture = (
|
||||
};
|
||||
|
||||
const resetContentScroll = () => {
|
||||
if (!contentEl) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isIonContent(contentEl)) {
|
||||
(contentEl as HTMLIonContentElement).scrollY = initialScrollY;
|
||||
} else {
|
||||
@@ -67,9 +70,17 @@ export const createSwipeToCloseGesture = (
|
||||
* the content is scrolled all the way
|
||||
* to the top so that we do not interfere
|
||||
* with scrolling.
|
||||
*
|
||||
* We cannot assume that the `ion-content`
|
||||
* target will remain consistent between
|
||||
* swipes. For example, when using
|
||||
* ion-nav within a card modal it is
|
||||
* possible to swipe, push a view, and then
|
||||
* swipe again. The target content will not
|
||||
* be the same between swipes.
|
||||
*/
|
||||
const content = target.closest('ion-content');
|
||||
if (content) {
|
||||
contentEl = findClosestIonContent(target);
|
||||
if (contentEl) {
|
||||
/**
|
||||
* The card should never swipe to close
|
||||
* on the content with a refresher.
|
||||
@@ -78,8 +89,21 @@ export const createSwipeToCloseGesture = (
|
||||
* than the refresher gesture as the iOS native
|
||||
* refresh gesture uses a scroll listener in
|
||||
* addition to a gesture.
|
||||
*
|
||||
* Note: Do not use getScrollElement here
|
||||
* because we need this to be a synchronous
|
||||
* operation, and getScrollElement is
|
||||
* asynchronous.
|
||||
*/
|
||||
return !hasRefresherInContent && scrollEl.scrollTop === 0;
|
||||
if (isIonContent(contentEl)) {
|
||||
const root = getElementRoot(contentEl);
|
||||
scrollEl = root.querySelector('.inner-scroll');
|
||||
} else {
|
||||
scrollEl = contentEl;
|
||||
}
|
||||
|
||||
const hasRefresherInContent = !!contentEl.querySelector('ion-refresher');
|
||||
return !hasRefresherInContent && scrollEl!.scrollTop === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,6 +120,14 @@ export const createSwipeToCloseGesture = (
|
||||
|
||||
const onStart = (detail: GestureDetail) => {
|
||||
const { deltaY } = detail;
|
||||
|
||||
/**
|
||||
* Get the initial scrollY value so
|
||||
* that we can correctly reset the scrollY
|
||||
* prop when the gesture ends.
|
||||
*/
|
||||
initialScrollY = getScrollY();
|
||||
|
||||
/**
|
||||
* If canDismiss is anything other than `true`
|
||||
* then users should be able to swipe down
|
||||
|
||||
@@ -15,7 +15,7 @@ import type {
|
||||
OverlayEventDetail,
|
||||
OverlayInterface,
|
||||
} from '../../interface';
|
||||
import { getScrollElement, findIonContent, printIonContentErrorMsg } from '../../utils/content';
|
||||
import { findIonContent, printIonContentErrorMsg } from '../../utils/content';
|
||||
import { CoreDelegate, attachComponent, detachComponent } from '../../utils/framework-delegate';
|
||||
import { raf } from '../../utils/helpers';
|
||||
import { KEYBOARD_DID_OPEN } from '../../utils/keyboard/keyboard';
|
||||
@@ -511,7 +511,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
this.currentTransition = undefined;
|
||||
}
|
||||
|
||||
private async initSwipeToClose() {
|
||||
private initSwipeToClose() {
|
||||
if (getIonMode(this) !== 'ios') {
|
||||
return;
|
||||
}
|
||||
@@ -529,9 +529,8 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
printIonContentErrorMsg(el);
|
||||
return;
|
||||
}
|
||||
const scrollEl = await getScrollElement(contentEl);
|
||||
|
||||
this.gesture = createSwipeToCloseGesture(el, contentEl, scrollEl, ani, () => {
|
||||
this.gesture = createSwipeToCloseGesture(el, ani, () => {
|
||||
/**
|
||||
* While the gesture animation is finishing
|
||||
* it is possible for a user to tap the backdrop.
|
||||
|
||||
@@ -21,6 +21,11 @@
|
||||
ion-modal#custom {
|
||||
--border-radius: 50px !important;
|
||||
}
|
||||
.content-wrapper {
|
||||
height: 100%;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
@@ -48,71 +53,73 @@
|
||||
</ion-app>
|
||||
|
||||
<script>
|
||||
const renderContent = () => {
|
||||
return `<ion-content class="ion-padding">
|
||||
Hello World!
|
||||
|
||||
<br />
|
||||
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vitae lobortis felis, eu sodales enim. Nam
|
||||
risus nibh, placerat at rutrum ac, vehicula vel velit. Lorem ipsum dolor sit amet, consectetur adipiscing
|
||||
elit. Vestibulum quis elementum ligula, ac aliquet nulla. Mauris non placerat mauris. Aenean dignissim lacinia
|
||||
porttitor. Praesent fringilla at est et ullamcorper. In ac ante ac massa porta venenatis ut id nibh. Fusce
|
||||
felis neque, aliquet in velit vitae, venenatis euismod libero. Donec vulputate, urna sed sagittis tempor, mi
|
||||
arcu tristique lacus, eget fringilla urna sem eget felis. Fusce dignissim lacus a scelerisque vehicula. Nulla
|
||||
nec enim nunc. Quisque nec dui eu nibh pulvinar bibendum quis ut nunc. Duis ex odio, sollicitudin ac mollis
|
||||
nec, fringilla non lacus. Maecenas sed tincidunt urna. Nunc feugiat maximus venenatis. Donec porttitor, felis
|
||||
eget porttitor tempor, quam nulla dapibus nisl, sit amet posuere sapien sapien malesuada tortor. Pellentesque
|
||||
habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque luctus, sapien nec
|
||||
tincidunt efficitur, nibh turpis faucibus felis, in sodales massa augue nec erat. Morbi sollicitudin nisi ex,
|
||||
et gravida nisi euismod eu. Suspendisse hendrerit dapibus orci, non viverra neque vestibulum id. Quisque vitae
|
||||
interdum ligula, quis consectetur nibh. Phasellus in mi at erat ultrices semper. Fusce sollicitudin at dolor
|
||||
ac lobortis. Morbi sit amet sem quis nulla pellentesque imperdiet. Nullam eu sem a enim maximus eleifend non
|
||||
vulputate leo. Proin quis congue lacus. Pellentesque placerat, quam at tempus pulvinar, nisl ligula tempor
|
||||
risus, quis pretium arcu odio et nulla. Nullam mollis consequat pharetra. Phasellus dictum velit sed purus
|
||||
mattis maximus. In molestie eget massa ut dignissim. In a interdum elit. In finibus nibh a mauris lobortis
|
||||
aliquet. Proin rutrum varius consequat. In mollis dapibus nisl, eu finibus urna viverra ac. Quisque
|
||||
scelerisque nisl eu suscipit consectetur.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vitae lobortis felis, eu sodales enim. Nam
|
||||
risus nibh, placerat at rutrum ac, vehicula vel velit. Lorem ipsum dolor sit amet, consectetur adipiscing
|
||||
elit. Vestibulum quis elementum ligula, ac aliquet nulla. Mauris non placerat mauris. Aenean dignissim lacinia
|
||||
porttitor. Praesent fringilla at est et ullamcorper. In ac ante ac massa porta venenatis ut id nibh. Fusce
|
||||
felis neque, aliquet in velit vitae, venenatis euismod libero. Donec vulputate, urna sed sagittis tempor, mi
|
||||
arcu tristique lacus, eget fringilla urna sem eget felis. Fusce dignissim lacus a scelerisque vehicula. Nulla
|
||||
nec enim nunc. Quisque nec dui eu nibh pulvinar bibendum quis ut nunc. Duis ex odio, sollicitudin ac mollis
|
||||
nec, fringilla non lacus. Maecenas sed tincidunt urna. Nunc feugiat maximus venenatis. Donec porttitor, felis
|
||||
eget porttitor tempor, quam nulla dapibus nisl, sit amet posuere sapien sapien malesuada tortor. Pellentesque
|
||||
habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque luctus, sapien nec
|
||||
tincidunt efficitur, nibh turpis faucibus felis, in sodales massa augue nec erat. Morbi sollicitudin nisi ex,
|
||||
et gravida nisi euismod eu. Suspendisse hendrerit dapibus orci, non viverra neque vestibulum id. Quisque vitae
|
||||
interdum ligula, quis consectetur nibh. Phasellus in mi at erat ultrices semper. Fusce sollicitudin at dolor
|
||||
ac lobortis. Morbi sit amet sem quis nulla pellentesque imperdiet. Nullam eu sem a enim maximus eleifend non
|
||||
vulputate leo. Proin quis congue lacus. Pellentesque placerat, quam at tempus pulvinar, nisl ligula tempor
|
||||
risus, quis pretium arcu odio et nulla. Nullam mollis consequat pharetra. Phasellus dictum velit sed purus
|
||||
mattis maximus. In molestie eget massa ut dignissim. In a interdum elit. In finibus nibh a mauris lobortis
|
||||
aliquet. Proin rutrum varius consequat. In mollis dapibus nisl, eu finibus urna viverra ac. Quisque
|
||||
scelerisque nisl eu suscipit consectetur.
|
||||
</p>
|
||||
|
||||
</ion-content>`;
|
||||
};
|
||||
async function createModal(presentingEl, opts) {
|
||||
// create component to open
|
||||
const element = document.createElement('div');
|
||||
element.innerHTML = `
|
||||
<ion-header id="modal-header">
|
||||
<ion-toolbar>
|
||||
<ion-title>Contacts</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button class="add">
|
||||
<ion-icon name="add" slot="icon-only"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-button class="add">Add</ion-button>
|
||||
<ion-button class="dismiss">Close</ion-button>
|
||||
<ion-button class="replace">Replace</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
Hello World!
|
||||
<ion-button class="dismiss">Dismiss Modal</ion-button>
|
||||
|
||||
<br />
|
||||
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vitae lobortis felis, eu sodales enim. Nam
|
||||
risus nibh, placerat at rutrum ac, vehicula vel velit. Lorem ipsum dolor sit amet, consectetur adipiscing
|
||||
elit. Vestibulum quis elementum ligula, ac aliquet nulla. Mauris non placerat mauris. Aenean dignissim lacinia
|
||||
porttitor. Praesent fringilla at est et ullamcorper. In ac ante ac massa porta venenatis ut id nibh. Fusce
|
||||
felis neque, aliquet in velit vitae, venenatis euismod libero. Donec vulputate, urna sed sagittis tempor, mi
|
||||
arcu tristique lacus, eget fringilla urna sem eget felis. Fusce dignissim lacus a scelerisque vehicula. Nulla
|
||||
nec enim nunc. Quisque nec dui eu nibh pulvinar bibendum quis ut nunc. Duis ex odio, sollicitudin ac mollis
|
||||
nec, fringilla non lacus. Maecenas sed tincidunt urna. Nunc feugiat maximus venenatis. Donec porttitor, felis
|
||||
eget porttitor tempor, quam nulla dapibus nisl, sit amet posuere sapien sapien malesuada tortor. Pellentesque
|
||||
habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque luctus, sapien nec
|
||||
tincidunt efficitur, nibh turpis faucibus felis, in sodales massa augue nec erat. Morbi sollicitudin nisi ex,
|
||||
et gravida nisi euismod eu. Suspendisse hendrerit dapibus orci, non viverra neque vestibulum id. Quisque vitae
|
||||
interdum ligula, quis consectetur nibh. Phasellus in mi at erat ultrices semper. Fusce sollicitudin at dolor
|
||||
ac lobortis. Morbi sit amet sem quis nulla pellentesque imperdiet. Nullam eu sem a enim maximus eleifend non
|
||||
vulputate leo. Proin quis congue lacus. Pellentesque placerat, quam at tempus pulvinar, nisl ligula tempor
|
||||
risus, quis pretium arcu odio et nulla. Nullam mollis consequat pharetra. Phasellus dictum velit sed purus
|
||||
mattis maximus. In molestie eget massa ut dignissim. In a interdum elit. In finibus nibh a mauris lobortis
|
||||
aliquet. Proin rutrum varius consequat. In mollis dapibus nisl, eu finibus urna viverra ac. Quisque
|
||||
scelerisque nisl eu suscipit consectetur.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vitae lobortis felis, eu sodales enim. Nam
|
||||
risus nibh, placerat at rutrum ac, vehicula vel velit. Lorem ipsum dolor sit amet, consectetur adipiscing
|
||||
elit. Vestibulum quis elementum ligula, ac aliquet nulla. Mauris non placerat mauris. Aenean dignissim lacinia
|
||||
porttitor. Praesent fringilla at est et ullamcorper. In ac ante ac massa porta venenatis ut id nibh. Fusce
|
||||
felis neque, aliquet in velit vitae, venenatis euismod libero. Donec vulputate, urna sed sagittis tempor, mi
|
||||
arcu tristique lacus, eget fringilla urna sem eget felis. Fusce dignissim lacus a scelerisque vehicula. Nulla
|
||||
nec enim nunc. Quisque nec dui eu nibh pulvinar bibendum quis ut nunc. Duis ex odio, sollicitudin ac mollis
|
||||
nec, fringilla non lacus. Maecenas sed tincidunt urna. Nunc feugiat maximus venenatis. Donec porttitor, felis
|
||||
eget porttitor tempor, quam nulla dapibus nisl, sit amet posuere sapien sapien malesuada tortor. Pellentesque
|
||||
habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque luctus, sapien nec
|
||||
tincidunt efficitur, nibh turpis faucibus felis, in sodales massa augue nec erat. Morbi sollicitudin nisi ex,
|
||||
et gravida nisi euismod eu. Suspendisse hendrerit dapibus orci, non viverra neque vestibulum id. Quisque vitae
|
||||
interdum ligula, quis consectetur nibh. Phasellus in mi at erat ultrices semper. Fusce sollicitudin at dolor
|
||||
ac lobortis. Morbi sit amet sem quis nulla pellentesque imperdiet. Nullam eu sem a enim maximus eleifend non
|
||||
vulputate leo. Proin quis congue lacus. Pellentesque placerat, quam at tempus pulvinar, nisl ligula tempor
|
||||
risus, quis pretium arcu odio et nulla. Nullam mollis consequat pharetra. Phasellus dictum velit sed purus
|
||||
mattis maximus. In molestie eget massa ut dignissim. In a interdum elit. In finibus nibh a mauris lobortis
|
||||
aliquet. Proin rutrum varius consequat. In mollis dapibus nisl, eu finibus urna viverra ac. Quisque
|
||||
scelerisque nisl eu suscipit consectetur.
|
||||
</p>
|
||||
|
||||
</ion-content>
|
||||
<div class="content-wrapper">${renderContent()}</div>
|
||||
|
||||
<ion-footer>
|
||||
<ion-toolbar>
|
||||
@@ -134,6 +141,12 @@
|
||||
presentModal(topModal, opts);
|
||||
});
|
||||
|
||||
const wrapper = element.querySelector('.content-wrapper');
|
||||
const replace = element.querySelector('ion-button.replace');
|
||||
replace.addEventListener('click', () => {
|
||||
wrapper.innerHTML = renderContent();
|
||||
});
|
||||
|
||||
// present the modal
|
||||
const modalElement = await modalController.create({
|
||||
presentingElement: presentingEl,
|
||||
|
||||
@@ -88,6 +88,21 @@ test.describe('card modal', () => {
|
||||
await content.waitForElementState('stable');
|
||||
expect(modal).toBeVisible();
|
||||
});
|
||||
test('it should not swipe to close when swiped on the content but the content is scrolled even when content is replaced', async ({
|
||||
page,
|
||||
}) => {
|
||||
const modal = await cardModalPage.openModalByTrigger('#card');
|
||||
|
||||
await page.click('ion-button.replace');
|
||||
|
||||
const content = (await page.$('ion-modal ion-content'))!;
|
||||
await content.evaluate((el: HTMLIonContentElement) => el.scrollToBottom(0));
|
||||
|
||||
await cardModalPage.swipeToCloseModal('ion-modal ion-content', false);
|
||||
|
||||
await content.waitForElementState('stable');
|
||||
expect(modal).toBeVisible();
|
||||
});
|
||||
test('content should be scrollable after gesture ends', async ({ page }) => {
|
||||
await cardModalPage.openModalByTrigger('#card');
|
||||
await cardModalPage.swipeToCloseModal('ion-modal ion-content', false, 20);
|
||||
@@ -152,6 +167,21 @@ test.describe('card modal', () => {
|
||||
await content.waitForElementState('stable');
|
||||
expect(modal).toBeVisible();
|
||||
});
|
||||
test('it should not swipe to close when swiped on the content but the content is scrolled even when content is replaced', async ({
|
||||
page,
|
||||
}) => {
|
||||
const modal = await cardModalPage.openModalByTrigger('#card');
|
||||
|
||||
await page.click('ion-button.replace');
|
||||
|
||||
const content = (await page.$('ion-modal ion-content'))!;
|
||||
await content.evaluate((el: HTMLIonContentElement) => el.scrollToBottom(0));
|
||||
|
||||
await cardModalPage.swipeToCloseModal('ion-modal ion-content', false);
|
||||
|
||||
await content.waitForElementState('stable');
|
||||
expect(modal).toBeVisible();
|
||||
});
|
||||
test('content should be scrollable after gesture ends', async ({ page }) => {
|
||||
await cardModalPage.openModalByTrigger('#card');
|
||||
await cardModalPage.swipeToCloseModal('ion-modal ion-content', false, 20);
|
||||
|
||||
|
Before Width: | Height: | Size: 371 KiB After Width: | Height: | Size: 392 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 234 KiB |
|
Before Width: | Height: | Size: 372 KiB After Width: | Height: | Size: 394 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 234 KiB |
|
Before Width: | Height: | Size: 682 KiB After Width: | Height: | Size: 719 KiB |
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 198 KiB |
|
Before Width: | Height: | Size: 439 KiB After Width: | Height: | Size: 458 KiB |
|
Before Width: | Height: | Size: 680 KiB After Width: | Height: | Size: 718 KiB |
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 199 KiB |
|
Before Width: | Height: | Size: 439 KiB After Width: | Height: | Size: 458 KiB |
|
Before Width: | Height: | Size: 373 KiB After Width: | Height: | Size: 394 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 225 KiB After Width: | Height: | Size: 235 KiB |
|
Before Width: | Height: | Size: 374 KiB After Width: | Height: | Size: 395 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 225 KiB After Width: | Height: | Size: 235 KiB |
|
Before Width: | Height: | Size: 689 KiB After Width: | Height: | Size: 727 KiB |
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 199 KiB |
|
Before Width: | Height: | Size: 440 KiB After Width: | Height: | Size: 459 KiB |
|
Before Width: | Height: | Size: 688 KiB After Width: | Height: | Size: 726 KiB |
|
Before Width: | Height: | Size: 194 KiB After Width: | Height: | Size: 200 KiB |
|
Before Width: | Height: | Size: 440 KiB After Width: | Height: | Size: 459 KiB |
|
Before Width: | Height: | Size: 369 KiB After Width: | Height: | Size: 391 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 232 KiB |
|
Before Width: | Height: | Size: 371 KiB After Width: | Height: | Size: 392 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 232 KiB |
|
Before Width: | Height: | Size: 673 KiB After Width: | Height: | Size: 711 KiB |
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 196 KiB |
|
Before Width: | Height: | Size: 431 KiB After Width: | Height: | Size: 451 KiB |
|
Before Width: | Height: | Size: 671 KiB After Width: | Height: | Size: 710 KiB |
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 196 KiB |
|
Before Width: | Height: | Size: 431 KiB After Width: | Height: | Size: 451 KiB |
|
Before Width: | Height: | Size: 370 KiB After Width: | Height: | Size: 391 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 232 KiB |
|
Before Width: | Height: | Size: 371 KiB After Width: | Height: | Size: 392 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 232 KiB |
|
Before Width: | Height: | Size: 679 KiB After Width: | Height: | Size: 716 KiB |
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 196 KiB |
|
Before Width: | Height: | Size: 432 KiB After Width: | Height: | Size: 451 KiB |
|
Before Width: | Height: | Size: 678 KiB After Width: | Height: | Size: 715 KiB |
|
Before Width: | Height: | Size: 191 KiB After Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 431 KiB After Width: | Height: | Size: 451 KiB |