chore(): sync feature-6.1 with main

This commit is contained in:
Liam DeBeasi
2022-04-04 15:27:16 -04:00
940 changed files with 91904 additions and 108189 deletions

View File

@ -1,4 +1,3 @@
export interface RefresherEventDetail {
complete(): void;
}

View File

@ -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>
);
}
}

View File

@ -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')
);
};

View File

@ -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);
});
});
});

View File

@ -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>

View File

@ -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();

View File

@ -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>

View File

@ -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 };