mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-09 16:16:41 +08:00
chore(): sync feature-6.1 with main
This commit is contained in:
@ -1,4 +1,3 @@
|
||||
|
||||
export interface RefresherEventDetail {
|
||||
complete(): void;
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Method, Prop, State, Watch, h, readTask, writeTask } from '@stencil/core';
|
||||
import type { ComponentInterface, EventEmitter } from '@stencil/core';
|
||||
import { Component, Element, Event, Host, Method, Prop, State, Watch, h, readTask, writeTask } from '@stencil/core';
|
||||
import { findClosestIonContent, getScrollElement, printIonContentErrorMsg } from '@utils/content';
|
||||
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { Animation, Gesture, GestureDetail, RefresherEventDetail } from '../../interface';
|
||||
import type { Animation, Gesture, GestureDetail, RefresherEventDetail } from '../../interface';
|
||||
import { getTimeGivenProgression } from '../../utils/animation/cubic-bezier';
|
||||
import { clamp, getElementRoot, raf, transitionEndAsync } from '../../utils/helpers';
|
||||
import { hapticImpact } from '../../utils/native/haptic';
|
||||
@ -15,18 +17,17 @@ import {
|
||||
handleScrollWhileRefreshing,
|
||||
setSpinnerOpacity,
|
||||
shouldUseNativeRefresher,
|
||||
translateElement
|
||||
translateElement,
|
||||
} from './refresher.utils';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-refresher',
|
||||
styleUrls: {
|
||||
ios: 'refresher.ios.scss',
|
||||
md: 'refresher.md.scss'
|
||||
}
|
||||
md: 'refresher.md.scss',
|
||||
},
|
||||
})
|
||||
export class Refresher implements ComponentInterface {
|
||||
|
||||
private appliedStyles = false;
|
||||
private didStart = false;
|
||||
private progress = 0;
|
||||
@ -165,24 +166,29 @@ export class Refresher implements ComponentInterface {
|
||||
this.didRefresh = false;
|
||||
this.needsCompletion = false;
|
||||
this.pointerDown = false;
|
||||
this.animations.forEach(ani => ani.destroy());
|
||||
this.animations.forEach((ani) => ani.destroy());
|
||||
this.animations = [];
|
||||
this.progress = 0;
|
||||
|
||||
this.state = RefresherState.Inactive;
|
||||
}
|
||||
|
||||
private async setupiOSNativeRefresher(pullingSpinner: HTMLIonSpinnerElement, refreshingSpinner: HTMLIonSpinnerElement) {
|
||||
private async setupiOSNativeRefresher(
|
||||
pullingSpinner: HTMLIonSpinnerElement,
|
||||
refreshingSpinner: HTMLIonSpinnerElement
|
||||
) {
|
||||
this.elementToTransform = this.scrollEl!;
|
||||
const ticks = pullingSpinner.shadowRoot!.querySelectorAll('svg');
|
||||
let MAX_PULL = this.scrollEl!.clientHeight * 0.16;
|
||||
const NUM_TICKS = ticks.length;
|
||||
|
||||
writeTask(() => ticks.forEach(el => el.style.setProperty('animation', 'none')));
|
||||
writeTask(() => ticks.forEach((el) => el.style.setProperty('animation', 'none')));
|
||||
|
||||
this.scrollListenerCallback = () => {
|
||||
// If pointer is not on screen or refresher is not active, ignore scroll
|
||||
if (!this.pointerDown && this.state === RefresherState.Inactive) { return; }
|
||||
if (!this.pointerDown && this.state === RefresherState.Inactive) {
|
||||
return;
|
||||
}
|
||||
|
||||
readTask(() => {
|
||||
// PTR should only be active when overflow scrolling at the top
|
||||
@ -222,8 +228,8 @@ export class Refresher implements ComponentInterface {
|
||||
* gesture before the refresher completes, we want the
|
||||
* refresher tick marks to quickly fade out.
|
||||
*/
|
||||
const offset = (this.didStart) ? 30 : 0;
|
||||
const pullAmount = this.progress = clamp(0, (Math.abs(scrollTop) - offset) / MAX_PULL, 1);
|
||||
const offset = this.didStart ? 30 : 0;
|
||||
const pullAmount = (this.progress = clamp(0, (Math.abs(scrollTop) - offset) / MAX_PULL, 1));
|
||||
const shouldShowRefreshingSpinner = this.state === RefresherState.Refreshing || pullAmount === 1;
|
||||
|
||||
if (shouldShowRefreshingSpinner) {
|
||||
@ -318,7 +324,10 @@ export class Refresher implements ComponentInterface {
|
||||
gesturePriority: 31,
|
||||
direction: 'y',
|
||||
threshold: 5,
|
||||
canStart: () => this.state !== RefresherState.Refreshing && this.state !== RefresherState.Completing && this.scrollEl!.scrollTop === 0,
|
||||
canStart: () =>
|
||||
this.state !== RefresherState.Refreshing &&
|
||||
this.state !== RefresherState.Completing &&
|
||||
this.scrollEl!.scrollTop === 0,
|
||||
onStart: (ev: GestureDetail) => {
|
||||
ev.data = { animation: undefined, didStart: false, cancelled: false };
|
||||
},
|
||||
@ -351,20 +360,20 @@ export class Refresher implements ComponentInterface {
|
||||
this.ionPull.emit();
|
||||
},
|
||||
onEnd: (ev: GestureDetail) => {
|
||||
if (!ev.data.didStart) { return; }
|
||||
if (!ev.data.didStart) {
|
||||
return;
|
||||
}
|
||||
|
||||
writeTask(() => this.scrollEl!.style.removeProperty('--overflow'));
|
||||
if (this.progress <= 0.4) {
|
||||
this.gesture!.enable(false);
|
||||
|
||||
ev.data.animation
|
||||
.progressEnd(0, this.progress, 500)
|
||||
.onFinish(() => {
|
||||
this.animations.forEach(ani => ani.destroy());
|
||||
this.animations = [];
|
||||
this.gesture!.enable(true);
|
||||
this.state = RefresherState.Inactive;
|
||||
});
|
||||
ev.data.animation.progressEnd(0, this.progress, 500).onFinish(() => {
|
||||
this.animations.forEach((ani) => ani.destroy());
|
||||
this.animations = [];
|
||||
this.gesture!.enable(true);
|
||||
this.state = RefresherState.Inactive;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@ -372,13 +381,13 @@ export class Refresher implements ComponentInterface {
|
||||
const snapBackAnimation = createSnapBackAnimation(pullingRefresherIcon);
|
||||
this.animations.push(snapBackAnimation);
|
||||
writeTask(async () => {
|
||||
pullingRefresherIcon.style.setProperty('--ion-pulling-refresher-translate', `${(progress * 100)}px`);
|
||||
pullingRefresherIcon.style.setProperty('--ion-pulling-refresher-translate', `${progress * 100}px`);
|
||||
ev.data.animation.progressEnd();
|
||||
await snapBackAnimation.play();
|
||||
this.beginRefresh();
|
||||
ev.data.animation.destroy();
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this.disabledChanged();
|
||||
@ -400,8 +409,12 @@ export class Refresher implements ComponentInterface {
|
||||
|
||||
this.nativeRefresher = true;
|
||||
|
||||
const pullingSpinner = this.el.querySelector('ion-refresher-content .refresher-pulling ion-spinner') as HTMLIonSpinnerElement;
|
||||
const refreshingSpinner = this.el.querySelector('ion-refresher-content .refresher-refreshing ion-spinner') as HTMLIonSpinnerElement;
|
||||
const pullingSpinner = this.el.querySelector(
|
||||
'ion-refresher-content .refresher-pulling ion-spinner'
|
||||
) as HTMLIonSpinnerElement;
|
||||
const refreshingSpinner = this.el.querySelector(
|
||||
'ion-refresher-content .refresher-refreshing ion-spinner'
|
||||
) as HTMLIonSpinnerElement;
|
||||
|
||||
if (getIonMode(this) === 'ios') {
|
||||
this.setupiOSNativeRefresher(pullingSpinner, refreshingSpinner);
|
||||
@ -452,7 +465,7 @@ export class Refresher implements ComponentInterface {
|
||||
passive: false,
|
||||
canStart: () => this.canStart(),
|
||||
onStart: () => this.onStart(),
|
||||
onMove: ev => this.onMove(ev),
|
||||
onMove: (ev) => this.onMove(ev),
|
||||
onEnd: () => this.onEnd(),
|
||||
});
|
||||
|
||||
@ -561,7 +574,7 @@ export class Refresher implements ComponentInterface {
|
||||
return;
|
||||
}
|
||||
|
||||
const pullFactor = (Number.isNaN(this.pullFactor) || this.pullFactor < 0) ? 1 : this.pullFactor;
|
||||
const pullFactor = Number.isNaN(this.pullFactor) || this.pullFactor < 0 ? 1 : this.pullFactor;
|
||||
const deltaY = detail.deltaY * pullFactor;
|
||||
// don't bother if they're scrolling up
|
||||
// and have not already started dragging
|
||||
@ -650,7 +663,6 @@ export class Refresher implements ComponentInterface {
|
||||
if (this.state === RefresherState.Ready) {
|
||||
// they pulled down far enough, so it's ready to refresh
|
||||
this.beginRefresh();
|
||||
|
||||
} else if (this.state === RefresherState.Pulling) {
|
||||
// they were pulling down, but didn't pull down far enough
|
||||
// set the content back to it's original location
|
||||
@ -671,12 +683,11 @@ export class Refresher implements ComponentInterface {
|
||||
// emit "refresh" because it was pulled down far enough
|
||||
// and they let go to begin refreshing
|
||||
this.ionRefresh.emit({
|
||||
complete: this.complete.bind(this)
|
||||
complete: this.complete.bind(this),
|
||||
});
|
||||
}
|
||||
|
||||
private close(state: RefresherState, delay: string) {
|
||||
|
||||
// create fallback timer incase something goes wrong with transitionEnd event
|
||||
setTimeout(() => {
|
||||
this.state = RefresherState.Inactive;
|
||||
@ -694,17 +705,19 @@ export class Refresher implements ComponentInterface {
|
||||
}
|
||||
|
||||
private setCss(y: number, duration: string, overflowVisible: boolean, delay: string) {
|
||||
if (this.nativeRefresher) { return; }
|
||||
if (this.nativeRefresher) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.appliedStyles = (y > 0);
|
||||
this.appliedStyles = y > 0;
|
||||
writeTask(() => {
|
||||
if (this.scrollEl && this.backgroundContentEl) {
|
||||
const scrollStyle = this.scrollEl.style;
|
||||
const backgroundStyle = this.backgroundContentEl.style;
|
||||
scrollStyle.transform = backgroundStyle.transform = ((y > 0) ? `translateY(${y}px) translateZ(0px)` : '');
|
||||
scrollStyle.transform = backgroundStyle.transform = y > 0 ? `translateY(${y}px) translateZ(0px)` : '';
|
||||
scrollStyle.transitionDuration = backgroundStyle.transitionDuration = duration;
|
||||
scrollStyle.transitionDelay = backgroundStyle.transitionDelay = delay;
|
||||
scrollStyle.overflow = (overflowVisible ? 'hidden' : '');
|
||||
scrollStyle.overflow = overflowVisible ? 'hidden' : '';
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -727,8 +740,7 @@ export class Refresher implements ComponentInterface {
|
||||
'refresher-cancelling': this.state === RefresherState.Cancelling,
|
||||
'refresher-completing': this.state === RefresherState.Completing,
|
||||
}}
|
||||
>
|
||||
</Host>
|
||||
></Host>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,8 +15,14 @@ export const getRefresherAnimationType = (contentEl: HTMLElement): RefresherAnim
|
||||
return hasHeader ? 'translate' : 'scale';
|
||||
};
|
||||
|
||||
export const createPullingAnimation = (type: RefresherAnimationType, pullingSpinner: HTMLElement, refresherEl: HTMLElement) => {
|
||||
return type === 'scale' ? createScaleAnimation(pullingSpinner, refresherEl) : createTranslateAnimation(pullingSpinner, refresherEl);
|
||||
export const createPullingAnimation = (
|
||||
type: RefresherAnimationType,
|
||||
pullingSpinner: HTMLElement,
|
||||
refresherEl: HTMLElement
|
||||
) => {
|
||||
return type === 'scale'
|
||||
? createScaleAnimation(pullingSpinner, refresherEl)
|
||||
: createTranslateAnimation(pullingSpinner, refresherEl);
|
||||
};
|
||||
|
||||
const createBaseAnimation = (pullingRefresherIcon: HTMLElement) => {
|
||||
@ -24,11 +30,9 @@ const createBaseAnimation = (pullingRefresherIcon: HTMLElement) => {
|
||||
const circle = spinner!.shadowRoot!.querySelector('circle') as any;
|
||||
const spinnerArrowContainer = pullingRefresherIcon.querySelector('.spinner-arrow-container') as HTMLElement;
|
||||
const arrowContainer = pullingRefresherIcon!.querySelector('.arrow-container');
|
||||
const arrow = (arrowContainer) ? arrowContainer!.querySelector('ion-icon') as HTMLElement : null;
|
||||
const arrow = arrowContainer ? (arrowContainer!.querySelector('ion-icon') as HTMLElement) : null;
|
||||
|
||||
const baseAnimation = createAnimation()
|
||||
.duration(1000)
|
||||
.easing('ease-out');
|
||||
const baseAnimation = createAnimation().duration(1000).easing('ease-out');
|
||||
|
||||
const spinnerArrowContainerAnimation = createAnimation()
|
||||
.addElement(spinnerArrowContainer)
|
||||
@ -36,23 +40,23 @@ const createBaseAnimation = (pullingRefresherIcon: HTMLElement) => {
|
||||
{ offset: 0, opacity: '0.3' },
|
||||
{ offset: 0.45, opacity: '0.3' },
|
||||
{ offset: 0.55, opacity: '1' },
|
||||
{ offset: 1, opacity: '1' }
|
||||
{ offset: 1, opacity: '1' },
|
||||
]);
|
||||
|
||||
const circleInnerAnimation = createAnimation()
|
||||
.addElement(circle)
|
||||
.keyframes([
|
||||
{ offset: 0, strokeDasharray: '1px, 200px' },
|
||||
{ offset: 0.20, strokeDasharray: '1px, 200px' },
|
||||
{ offset: 0.2, strokeDasharray: '1px, 200px' },
|
||||
{ offset: 0.55, strokeDasharray: '100px, 200px' },
|
||||
{ offset: 1, strokeDasharray: '100px, 200px' }
|
||||
{ offset: 1, strokeDasharray: '100px, 200px' },
|
||||
]);
|
||||
|
||||
const circleOuterAnimation = createAnimation()
|
||||
.addElement(spinner)
|
||||
.keyframes([
|
||||
{ offset: 0, transform: 'rotate(-90deg)' },
|
||||
{ offset: 1, transform: 'rotate(210deg)' }
|
||||
{ offset: 1, transform: 'rotate(210deg)' },
|
||||
]);
|
||||
|
||||
/**
|
||||
@ -65,18 +69,18 @@ const createBaseAnimation = (pullingRefresherIcon: HTMLElement) => {
|
||||
.addElement(arrowContainer)
|
||||
.keyframes([
|
||||
{ offset: 0, transform: 'rotate(0deg)' },
|
||||
{ offset: 0.30, transform: 'rotate(0deg)' },
|
||||
{ offset: 0.3, transform: 'rotate(0deg)' },
|
||||
{ offset: 0.55, transform: 'rotate(280deg)' },
|
||||
{ offset: 1, transform: 'rotate(400deg)' }
|
||||
{ offset: 1, transform: 'rotate(400deg)' },
|
||||
]);
|
||||
|
||||
const arrowAnimation = createAnimation()
|
||||
.addElement(arrow)
|
||||
.keyframes([
|
||||
{ offset: 0, transform: 'translateX(2px) scale(0)' },
|
||||
{ offset: 0.30, transform: 'translateX(2px) scale(0)' },
|
||||
{ offset: 0.3, transform: 'translateX(2px) scale(0)' },
|
||||
{ offset: 0.55, transform: 'translateX(-1.5px) scale(1)' },
|
||||
{ offset: 1, transform: 'translateX(-1.5px) scale(1)' }
|
||||
{ offset: 1, transform: 'translateX(-1.5px) scale(1)' },
|
||||
]);
|
||||
|
||||
baseAnimation.addAnimation([arrowContainerAnimation, arrowAnimation]);
|
||||
@ -100,7 +104,7 @@ const createScaleAnimation = (pullingRefresherIcon: HTMLElement, refresherEl: HT
|
||||
.addElement(pullingRefresherIcon)
|
||||
.keyframes([
|
||||
{ offset: 0, transform: `scale(0) translateY(-${height}px)` },
|
||||
{ offset: 1, transform: 'scale(1) translateY(100px)' }
|
||||
{ offset: 1, transform: 'scale(1) translateY(100px)' },
|
||||
]);
|
||||
|
||||
return createBaseAnimation(pullingRefresherIcon).addAnimation([spinnerAnimation]);
|
||||
@ -121,7 +125,7 @@ const createTranslateAnimation = (pullingRefresherIcon: HTMLElement, refresherEl
|
||||
.addElement(pullingRefresherIcon)
|
||||
.keyframes([
|
||||
{ offset: 0, transform: `translateY(-${height}px)` },
|
||||
{ offset: 1, transform: 'translateY(100px)' }
|
||||
{ offset: 1, transform: 'translateY(100px)' },
|
||||
]);
|
||||
|
||||
return createBaseAnimation(pullingRefresherIcon).addAnimation([spinnerAnimation]);
|
||||
@ -141,11 +145,7 @@ export const setSpinnerOpacity = (spinner: HTMLElement, opacity: number) => {
|
||||
spinner.style.setProperty('opacity', opacity.toString());
|
||||
};
|
||||
|
||||
export const handleScrollWhilePulling = (
|
||||
ticks: NodeListOf<SVGElement>,
|
||||
numTicks: number,
|
||||
pullAmount: number
|
||||
) => {
|
||||
export const handleScrollWhilePulling = (ticks: NodeListOf<SVGElement>, numTicks: number, pullAmount: number) => {
|
||||
const max = 1;
|
||||
writeTask(() => {
|
||||
ticks.forEach((el, i) => {
|
||||
@ -165,19 +165,18 @@ export const handleScrollWhilePulling = (
|
||||
});
|
||||
};
|
||||
|
||||
export const handleScrollWhileRefreshing = (
|
||||
spinner: HTMLElement,
|
||||
lastVelocityY: number
|
||||
) => {
|
||||
export const handleScrollWhileRefreshing = (spinner: HTMLElement, lastVelocityY: number) => {
|
||||
writeTask(() => {
|
||||
// If user pulls down quickly, the spinner should spin faster
|
||||
spinner.style.setProperty('--refreshing-rotation-duration', (lastVelocityY >= 1.0) ? '0.5s' : '2s');
|
||||
spinner.style.setProperty('--refreshing-rotation-duration', lastVelocityY >= 1.0 ? '0.5s' : '2s');
|
||||
spinner.style.setProperty('opacity', '1');
|
||||
});
|
||||
};
|
||||
|
||||
export const translateElement = (el?: HTMLElement, value?: string, duration = 200) => {
|
||||
if (!el) { return Promise.resolve(); }
|
||||
if (!el) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const trans = transitionEndAsync(el, duration);
|
||||
|
||||
@ -199,9 +198,11 @@ export const translateElement = (el?: HTMLElement, value?: string, duration = 20
|
||||
|
||||
export const shouldUseNativeRefresher = async (referenceEl: HTMLIonRefresherElement, mode: string) => {
|
||||
const refresherContent = referenceEl.querySelector('ion-refresher-content');
|
||||
if (!refresherContent) { return Promise.resolve(false); }
|
||||
if (!refresherContent) {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
await new Promise(resolve => componentOnReady(refresherContent, resolve));
|
||||
await new Promise((resolve) => componentOnReady(refresherContent, resolve));
|
||||
|
||||
const pullingSpinner = referenceEl.querySelector('ion-refresher-content .refresher-pulling ion-spinner');
|
||||
const refreshingSpinner = referenceEl.querySelector('ion-refresher-content .refresher-refreshing ion-spinner');
|
||||
@ -209,10 +210,7 @@ export const shouldUseNativeRefresher = async (referenceEl: HTMLIonRefresherElem
|
||||
return (
|
||||
pullingSpinner !== null &&
|
||||
refreshingSpinner !== null &&
|
||||
(
|
||||
(mode === 'ios' && isPlatform('mobile') && (referenceEl.style as any).webkitOverflowScrolling !== undefined) ||
|
||||
mode === 'md'
|
||||
)
|
||||
|
||||
((mode === 'ios' && isPlatform('mobile') && (referenceEl.style as any).webkitOverflowScrolling !== undefined) ||
|
||||
mode === 'md')
|
||||
);
|
||||
};
|
||||
|
||||
@ -4,12 +4,11 @@ import { newE2EPage } from '@stencil/core/testing';
|
||||
import { pullToRefresh } from '../test.utils';
|
||||
|
||||
describe('refresher: basic', () => {
|
||||
|
||||
let page: E2EPage;
|
||||
|
||||
beforeEach(async () => {
|
||||
page = await newE2EPage({
|
||||
url: '/src/components/refresher/test/basic?ionic:_testing=true'
|
||||
url: '/src/components/refresher/test/basic?ionic:_testing=true',
|
||||
});
|
||||
});
|
||||
|
||||
@ -19,7 +18,6 @@ describe('refresher: basic', () => {
|
||||
});
|
||||
|
||||
describe('legacy refresher', () => {
|
||||
|
||||
it('should load more items when performing a pull-to-refresh', async () => {
|
||||
const initialItems = await page.findAll('ion-item');
|
||||
expect(initialItems.length).toBe(30);
|
||||
@ -29,11 +27,9 @@ describe('refresher: basic', () => {
|
||||
const items = await page.findAll('ion-item');
|
||||
expect(items.length).toBe(60);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('native refresher', () => {
|
||||
|
||||
it('should load more items when performing a pull-to-refresh', async () => {
|
||||
const refresherContent = await page.$('ion-refresher-content');
|
||||
refresherContent.evaluate((el: any) => {
|
||||
@ -51,7 +47,5 @@ describe('refresher: basic', () => {
|
||||
const items = await page.findAll('ion-item');
|
||||
expect(items.length).toBe(60);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@ -1,81 +1,83 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Refresher - Basic</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 nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
</head>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Refresher - Basic</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 nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Pull To Refresh</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Pull To Refresh</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-refresher id="refresher" slot="fixed">
|
||||
<ion-refresher-content
|
||||
pulling-icon="arrow-down-outline"
|
||||
pulling-text="Pull to refresh..."
|
||||
refreshing-text="Refreshing..."
|
||||
refreshing-spinner="circles"
|
||||
>
|
||||
</ion-refresher-content>
|
||||
</ion-refresher>
|
||||
|
||||
<ion-content>
|
||||
<ion-refresher id="refresher" slot="fixed">
|
||||
<ion-refresher-content pulling-icon="arrow-down-outline" pulling-text="Pull to refresh..."
|
||||
refreshing-text="Refreshing..." refreshing-spinner="circles">
|
||||
</ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<ion-list id="list"></ion-list>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
<ion-menu-controller></ion-menu-controller>
|
||||
|
||||
<ion-list id="list"></ion-list>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
<ion-menu-controller></ion-menu-controller>
|
||||
|
||||
<script>
|
||||
|
||||
let items = [];
|
||||
for (var i = 0; i < 30; i++) {
|
||||
items.push(i + 1);
|
||||
}
|
||||
|
||||
const list = document.getElementById('list');
|
||||
const refresher = document.getElementById('refresher');
|
||||
|
||||
refresher.addEventListener('ionRefresh', async function () {
|
||||
const data = await getAsyncData();
|
||||
items = items.concat(data);
|
||||
refresher.complete();
|
||||
render();
|
||||
// Custom event consumed by e2e tests
|
||||
document.dispatchEvent(new CustomEvent('ionRefreshComplete'));
|
||||
});
|
||||
|
||||
function render() {
|
||||
let html = '';
|
||||
for (let item of items) {
|
||||
html += `<ion-item button>${item}</ion-item>`;
|
||||
<script>
|
||||
let items = [];
|
||||
for (var i = 0; i < 30; i++) {
|
||||
items.push(i + 1);
|
||||
}
|
||||
list.innerHTML = html;
|
||||
}
|
||||
|
||||
function getAsyncData() {
|
||||
// async return mock data
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
let data = [];
|
||||
for (var i = 0; i < 30; i++) {
|
||||
data.push(i);
|
||||
}
|
||||
resolve(data);
|
||||
}, 500);
|
||||
const list = document.getElementById('list');
|
||||
const refresher = document.getElementById('refresher');
|
||||
|
||||
refresher.addEventListener('ionRefresh', async function () {
|
||||
const data = await getAsyncData();
|
||||
items = items.concat(data);
|
||||
refresher.complete();
|
||||
render();
|
||||
// Custom event consumed by e2e tests
|
||||
document.dispatchEvent(new CustomEvent('ionRefreshComplete'));
|
||||
});
|
||||
}
|
||||
|
||||
render();
|
||||
function render() {
|
||||
let html = '';
|
||||
for (let item of items) {
|
||||
html += `<ion-item button>${item}</ion-item>`;
|
||||
}
|
||||
list.innerHTML = html;
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
function getAsyncData() {
|
||||
// async return mock data
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
let data = [];
|
||||
for (var i = 0; i < 30; i++) {
|
||||
data.push(i);
|
||||
}
|
||||
resolve(data);
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
render();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -2,7 +2,7 @@ import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
test('refresher: spec', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/refresher/test/spec?ionic:_testing=true'
|
||||
url: '/src/components/refresher/test/spec?ionic:_testing=true',
|
||||
});
|
||||
|
||||
const compare = await page.compareScreenshot();
|
||||
|
||||
@ -1,68 +1,72 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Refresher - Spec</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 nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script></head>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Refresher - Spec</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 nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
</head>
|
||||
<style>
|
||||
ion-spinner {
|
||||
color: #555;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button default-href="/" text="Mailboxes"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>All Inboxes</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button>Edit</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<ion-refresher id="refresher" slot="fixed">
|
||||
<ion-refresher-content></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
|
||||
<ion-header collapse="condense">
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title size="large">All Inboxes</ion-title>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar>
|
||||
<ion-searchbar></ion-searchbar>
|
||||
<ion-buttons slot="start">
|
||||
<ion-back-button default-href="/" text="Mailboxes"></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>All Inboxes</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<ion-button>Edit</ion-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-list id="list"></ion-list>
|
||||
|
||||
<ion-fab vertical="bottom" horizontal="end" slot="fixed">
|
||||
<ion-fab-button onclick="toggleHeader()">
|
||||
<ion-icon name="settings"></ion-icon>
|
||||
</ion-fab-button>
|
||||
</ion-fab>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
<ion-content>
|
||||
<ion-refresher id="refresher" slot="fixed">
|
||||
<ion-refresher-content></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
|
||||
<ion-header collapse="condense">
|
||||
<ion-toolbar>
|
||||
<ion-title size="large">All Inboxes</ion-title>
|
||||
</ion-toolbar>
|
||||
<ion-toolbar>
|
||||
<ion-searchbar></ion-searchbar>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<script>
|
||||
|
||||
const toggleHeader = () => {
|
||||
const app = document.querySelector('ion-app');
|
||||
const header = app.querySelector('ion-header');
|
||||
|
||||
if (header) {
|
||||
header.parentNode.removeChild(header);
|
||||
} else {
|
||||
app.insertAdjacentHTML("afterbegin", `
|
||||
<ion-list id="list"></ion-list>
|
||||
|
||||
<ion-fab vertical="bottom" horizontal="end" slot="fixed">
|
||||
<ion-fab-button onclick="toggleHeader()">
|
||||
<ion-icon name="settings"></ion-icon>
|
||||
</ion-fab-button>
|
||||
</ion-fab>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
|
||||
<script>
|
||||
const toggleHeader = () => {
|
||||
const app = document.querySelector('ion-app');
|
||||
const header = app.querySelector('ion-header');
|
||||
|
||||
if (header) {
|
||||
header.parentNode.removeChild(header);
|
||||
} else {
|
||||
app.insertAdjacentHTML(
|
||||
'afterbegin',
|
||||
`
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
@ -74,53 +78,54 @@
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
let items = [];
|
||||
for (var i = 0; i < 30; i++) {
|
||||
items.push(i + 1);
|
||||
}
|
||||
const list = document.getElementById('list');
|
||||
const refresher = document.getElementById('refresher');
|
||||
|
||||
refresher.addEventListener('ionPull', () => {
|
||||
console.log('ionPull');
|
||||
});
|
||||
|
||||
refresher.addEventListener('ionStart', () => {
|
||||
console.log('ionStart');
|
||||
});
|
||||
|
||||
refresher.addEventListener('ionRefresh', async function () {
|
||||
console.log('Loading data...');
|
||||
const data = await getAsyncData();
|
||||
items = items.concat(data);
|
||||
refresher.complete();
|
||||
render();
|
||||
console.log('Done');
|
||||
});
|
||||
function render() {
|
||||
let html = '';
|
||||
for (let item of items) {
|
||||
html += `<ion-item button>${item}</ion-item>`;
|
||||
`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let items = [];
|
||||
for (var i = 0; i < 30; i++) {
|
||||
items.push(i + 1);
|
||||
}
|
||||
list.innerHTML = html;
|
||||
}
|
||||
function getAsyncData() {
|
||||
// async return mock data
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
let data = [];
|
||||
for (var i = 0; i < 30; i++) {
|
||||
data.push(i);
|
||||
}
|
||||
resolve(data);
|
||||
}, 5000);
|
||||
const list = document.getElementById('list');
|
||||
const refresher = document.getElementById('refresher');
|
||||
|
||||
refresher.addEventListener('ionPull', () => {
|
||||
console.log('ionPull');
|
||||
});
|
||||
}
|
||||
render();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
refresher.addEventListener('ionStart', () => {
|
||||
console.log('ionStart');
|
||||
});
|
||||
|
||||
refresher.addEventListener('ionRefresh', async function () {
|
||||
console.log('Loading data...');
|
||||
const data = await getAsyncData();
|
||||
items = items.concat(data);
|
||||
refresher.complete();
|
||||
render();
|
||||
console.log('Done');
|
||||
});
|
||||
function render() {
|
||||
let html = '';
|
||||
for (let item of items) {
|
||||
html += `<ion-item button>${item}</ion-item>`;
|
||||
}
|
||||
list.innerHTML = html;
|
||||
}
|
||||
function getAsyncData() {
|
||||
// async return mock data
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
let data = [];
|
||||
for (var i = 0; i < 30; i++) {
|
||||
data.push(i);
|
||||
}
|
||||
resolve(data);
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
render();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import type { E2EPage } from '@stencil/core/testing';
|
||||
|
||||
import { dragElementBy } from '@utils/test';
|
||||
|
||||
/**
|
||||
@ -18,6 +17,6 @@ const pullToRefresh = async (page: E2EPage, selector = 'body') => {
|
||||
await dragElementBy(target, page, 0, 200);
|
||||
const ev = await page.spyOnEvent('ionRefreshComplete', 'document');
|
||||
await ev.next();
|
||||
}
|
||||
};
|
||||
|
||||
export { pullToRefresh };
|
||||
|
||||
Reference in New Issue
Block a user