feat(refresher): adds refresher

This commit is contained in:
Manu Mtz.-Almeida
2018-01-13 12:36:03 +01:00
parent 512a08f396
commit 81ebc0e71b
10 changed files with 983 additions and 9 deletions

View File

@@ -993,6 +993,7 @@ declare global {
onPress?: GestureCallback;
onStart?: GestureCallback;
onWillStart?: (_: GestureDetail) => Promise<void>;
passive?: boolean;
threshold?: number;
type?: string;
}
@@ -2143,6 +2144,73 @@ declare global {
}
import {
RefresherContent as IonRefresherContent
} from './components/refresher-content/refresher-content';
declare global {
interface HTMLIonRefresherContentElement extends IonRefresherContent, HTMLElement {
}
var HTMLIonRefresherContentElement: {
prototype: HTMLIonRefresherContentElement;
new (): HTMLIonRefresherContentElement;
};
interface HTMLElementTagNameMap {
"ion-refresher-content": HTMLIonRefresherContentElement;
}
interface ElementTagNameMap {
"ion-refresher-content": HTMLIonRefresherContentElement;
}
namespace JSX {
interface IntrinsicElements {
"ion-refresher-content": JSXElements.IonRefresherContentAttributes;
}
}
namespace JSXElements {
export interface IonRefresherContentAttributes extends HTMLAttributes {
pullingIcon?: string;
pullingText?: string;
refreshingSpinner?: string;
refreshingText?: string;
}
}
}
import {
Refresher as IonRefresher
} from './components/refresher/refresher';
declare global {
interface HTMLIonRefresherElement extends IonRefresher, HTMLElement {
}
var HTMLIonRefresherElement: {
prototype: HTMLIonRefresherElement;
new (): HTMLIonRefresherElement;
};
interface HTMLElementTagNameMap {
"ion-refresher": HTMLIonRefresherElement;
}
interface ElementTagNameMap {
"ion-refresher": HTMLIonRefresherElement;
}
namespace JSX {
interface IntrinsicElements {
"ion-refresher": JSXElements.IonRefresherAttributes;
}
}
namespace JSXElements {
export interface IonRefresherAttributes extends HTMLAttributes {
closeDuration?: string;
enabled?: boolean;
pullDelta?: number;
pullMin?: number;
snapbackDuration?: string;
}
}
}
import {
ReorderGroup as IonReorderGroup
} from './components/reorder-group/reorder-group';

View File

@@ -1,4 +1,4 @@
import { Component, Element, Event, EventEmitter, Listen, Prop, Watch } from '@stencil/core';
import { Component, Element, Event, EventEmitter, EventListenerEnable, Listen, Prop, Watch } from '@stencil/core';
import { ElementRef, applyStyles, assert, getElementReference, updateDetail } from '../../utils/helpers';
import { BLOCK_ALL, BlockerDelegate, GestureController, GestureDelegate } from '../gesture-controller/gesture-controller';
import { DomController } from '../../index';
@@ -28,7 +28,7 @@ export class Gesture {
@Element() private el: HTMLElement;
@Prop({ context: 'dom' }) dom: DomController;
@Prop({ context: 'enableListener' }) enableListener: any;
@Prop({ context: 'enableListener' }) enableListener: EventListenerEnable;
@Prop() enabled = true;
@Prop() attachTo: ElementRef = 'child';
@@ -38,6 +38,7 @@ export class Gesture {
@Prop() direction = 'x';
@Prop() gestureName = '';
@Prop() gesturePriority = 0;
@Prop() passive = true;
@Prop() maxAngle = 40;
@Prop() threshold = 10;
@Prop() type = 'pan';
@@ -105,8 +106,8 @@ export class Gesture {
@Watch('enabled')
protected enabledChanged(isEnabled: boolean) {
if (this.pan || this.hasPress) {
this.enableListener(this, 'touchstart', isEnabled, this.attachTo);
this.enableListener(this, 'mousedown', isEnabled, this.attachTo);
this.enableListener(this, 'touchstart', isEnabled, this.attachTo, this.passive);
this.enableListener(this, 'mousedown', isEnabled, this.attachTo, this.passive);
if (!isEnabled) {
this.abortGesture();
}
@@ -433,18 +434,18 @@ export class Gesture {
private enableMouse(shouldEnable: boolean) {
if (this.pan) {
this.enableListener(this, 'document:mousemove', shouldEnable);
this.enableListener(this, 'document:mousemove', shouldEnable, undefined, this.passive);
}
this.enableListener(this, 'document:mouseup', shouldEnable);
this.enableListener(this, 'document:mouseup', shouldEnable, undefined, this.passive);
}
private enableTouch(shouldEnable: boolean) {
if (this.pan) {
this.enableListener(this, 'touchmove', shouldEnable, this.attachTo);
this.enableListener(this, 'touchmove', shouldEnable, this.attachTo, this.passive);
}
this.enableListener(this, 'touchcancel', shouldEnable, this.attachTo);
this.enableListener(this, 'touchend', shouldEnable, this.attachTo);
this.enableListener(this, 'touchcancel', shouldEnable, this.attachTo, this.passive);
this.enableListener(this, 'touchend', shouldEnable, this.attachTo, this.passive);
}

View File

@@ -87,6 +87,11 @@ any
any
#### passive
boolean
#### threshold
number
@@ -179,6 +184,11 @@ any
any
#### passive
boolean
#### threshold
number

View File

@@ -0,0 +1,55 @@
# ion-refresher-content
<!-- Auto Generated Below -->
## Properties
#### pullingIcon
string
#### pullingText
string
#### refreshingSpinner
string
#### refreshingText
string
## Attributes
#### pullingIcon
string
#### pullingText
string
#### refreshingSpinner
string
#### refreshingText
string
----------------------------------------------
*Built by [StencilJS](https://stenciljs.com/)*

View File

@@ -0,0 +1,68 @@
import { Component, Prop } from '@stencil/core';
import { Config } from '../../index';
/**
* @hidden
*/
@Component({
tag: 'ion-refresher-content'
})
export class RefresherContent {
@Prop({ context: 'config' }) config: Config;
/**
* @input {string} a static icon to display when you begin to pull down
*/
@Prop({mutable: true}) pullingIcon: string;
/**
* @input {string} the text you want to display when you begin to pull down
*/
@Prop() pullingText: string;
/**
* @input {string} An animated SVG spinner that shows when refreshing begins
*/
@Prop({mutable: true}) refreshingSpinner: string;
/**
* @input {string} the text you want to display when performing a refresh
*/
@Prop() refreshingText: string;
protected componentDidLoad() {
if (!this.pullingIcon) {
this.pullingIcon = this.config.get('ionPullIcon', 'arrow-down');
}
if (!this.refreshingSpinner) {
this.refreshingSpinner = this.config.get('ionRefreshingSpinner', this.config.get('spinner', 'lines'));
}
}
protected render() {
return [
<div class='refresher-pulling'>
{this.pullingIcon &&
<div class='refresher-pulling-icon'>
<ion-icon name={this.pullingIcon}></ion-icon>
</div>
}
{this.pullingText &&
<div class='refresher-pulling-text' innerHTML={this.pullingText}></div>
}
</div>,
<div class='refresher-refreshing'>
{this.refreshingSpinner &&
<div class='refresher-refreshing-icon'>
<ion-spinner name={this.refreshingSpinner}></ion-spinner>
</div>
}
{this.refreshingText &&
<div class='refresher-refreshing-text' innerHTML={this.refreshingText}></div>
}
</div>
];
}
}

View File

@@ -0,0 +1,105 @@
# ion-refresher-content
<!-- Auto Generated Below -->
## Properties
#### closeDuration
string
#### enabled
boolean
#### pullDelta
number
#### pullMin
number
#### snapbackDuration
string
## Attributes
#### closeDuration
string
#### enabled
boolean
#### pullDelta
number
#### pullMin
number
#### snapbackDuration
string
## Events
#### ionPull
#### ionRefresh
#### ionStart
## Methods
#### cancel()
Changes the refresher's state from `refreshing` to `cancelling`.
#### complete()
Call `complete()` when your async operation has completed.
For example, the `refreshing` state is while the app is performing
an asynchronous operation, such as receiving more data from an
AJAX request. Once the data has been received, you then call this
method to signify that the refreshing has completed and to close
the refresher. This method also changes the refresher's state from
`refreshing` to `completing`.
#### getProgress()
A number representing how far down the user has pulled.
The number `0` represents the user hasn't pulled down at all. The
number `1`, and anything greater than `1`, represents that the user
has pulled far enough down that when they let go then the refresh will
happen. If they let go and the number is less than `1`, then the
refresh will not happen, and the content will return to it's original
position.
----------------------------------------------
*Built by [StencilJS](https://stenciljs.com/)*

View File

@@ -0,0 +1,146 @@
@import "../../themes/ionic.globals";
// Refresher
// --------------------------------------------------
/// @prop - Height of the refresher
$refresher-height: 60px !default;
/// @prop - Color of the refresher icon
$refresher-icon-color: #000 !default;
/// @prop - Font size of the refresher icon
$refresher-icon-font-size: 30px !default;
/// @prop - Text color of the refresher content
$refresher-text-color: #000 !default;
/// @prop - Font size of the refresher content
$refresher-text-font-size: 16px !default;
/// @prop - Border color of the refresher
$refresher-border-color: #ddd !default;
ion-refresher {
@include position(0, null, null, 0);
position: absolute;
top: 0;
z-index: $z-index-refresher;
display: none;
width: 100%;
height: $refresher-height;
&.refresher-active {
display: block;
}
}
.has-refresher > .scroll-content {
// when the refresher is let go or has completed
// this transition is what is used to put the
// scroll content back into it's original location
@include margin(-1px, null, null, null);
border-top: 1px solid $refresher-border-color;
transition: transform 320ms cubic-bezier(.36, .66, .04, 1);
}
// Refresher Content
// --------------------------------------------------
ion-refresher-content {
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
}
.refresher-pulling,
.refresher-refreshing {
display: none;
width: 100%;
}
.refresher-pulling-icon,
.refresher-refreshing-icon {
@include text-align(center);
@include transform-origin(center);
font-size: $refresher-icon-font-size;
color: $refresher-icon-color;
transition: 200ms;
}
.refresher-pulling-text,
.refresher-refreshing-text {
@include text-align(center);
font-size: $refresher-text-font-size;
color: $refresher-text-color;
}
.refresher-refreshing .spinner-ios line,
.refresher-refreshing .spinner-ios-small line,
.refresher-refreshing .spinner-crescent circle {
stroke: $refresher-icon-color;
}
.refresher-refreshing .spinner-bubbles circle,
.refresher-refreshing .spinner-circles circle,
.refresher-refreshing .spinner-dots circle {
fill: $refresher-icon-color;
}
// Refresher Content States
// --------------------------------------------------
.refresher-pulling ion-refresher-content {
.refresher-pulling {
display: block;
}
}
.refresher-ready ion-refresher-content {
.refresher-pulling {
display: block;
}
.refresher-pulling-icon {
transform: rotate(180deg);
}
}
.refresher-refreshing ion-refresher-content {
.refresher-refreshing {
display: block;
}
}
.refresher-cancelling ion-refresher-content {
.refresher-pulling {
display: block;
}
.refresher-pulling-icon {
transform: scale(0);
}
}
.refresher-completing ion-refresher-content {
.refresher-refreshing {
display: block;
}
.refresher-refreshing-icon {
transform: scale(0);
}
}

View File

@@ -0,0 +1,448 @@
import { Component, Element, Event, EventEmitter, Method, Prop, State } from '@stencil/core';
import { DomController, GestureDetail } from '../../index';
const enum RefresherState {
Inactive = 1 << 0,
Pulling = 1 << 1,
Ready = 1 << 2,
Refreshing = 1 << 3,
Cancelling = 1 << 4,
Completing = 1 << 5,
_BUSY_ = Refreshing | Cancelling | Completing,
}
/**
* @name Refresher
* @description
* The Refresher provides pull-to-refresh functionality on a content component.
* Place the `ion-refresher` as the first child of your `ion-content` element.
*
* Pages can then listen to the refresher's various output events. The
* `refresh` output event is fired when the user has pulled down far
* enough to kick off the refreshing process. Once the async operation
* has completed and the refreshing should end, call `complete()`.
*
* Note: Do not wrap the `ion-refresher` in a `*ngIf`. It will not render
* properly this way. Please use the `enabled` property instead to
* display or hide the refresher.
*
* @usage
* ```html
* <ion-content>
*
* <ion-refresher (ionRefresh)="doRefresh($event)">
* <ion-refresher-content></ion-refresher-content>
* </ion-refresher>
*
* </ion-content>
* ```
*
* ```ts
* @Component({...})
* export class NewsFeedPage {
*
* doRefresh(refresher) {
* console.log('Begin async operation', refresher);
*
* setTimeout(() => {
* console.log('Async operation has ended');
* refresher.complete();
* }, 2000);
* }
*
* }
* ```
*
*
* ## Refresher Content
*
* By default, Ionic provides the pulling icon and refreshing spinner that
* looks best for the platform the user is on. However, you can change the
* default icon and spinner, along with adding text for each state by
* adding properties to the child `ion-refresher-content` component.
*
* ```html
* <ion-content>
*
* <ion-refresher (ionRefresh)="doRefresh($event)">
* <ion-refresher-content
* pullingIcon="arrow-dropdown"
* pullingText="Pull to refresh"
* refreshingSpinner="circles"
* refreshingText="Refreshing...">
* </ion-refresher-content>
* </ion-refresher>
*
* </ion-content>
* ```
*
*
* ## Further Customizing Refresher Content
*
* The `ion-refresher` component holds the refresh logic.
* It requires a child component in order to display the content.
* Ionic uses `ion-refresher-content` by default. This component
* displays the refresher and changes the look depending
* on the refresher's state. Separating these components
* allows developers to create their own refresher content
* components. You could replace our default content with
* custom SVG or CSS animations.
*
* @demo /docs/demos/src/refresher/
*
*/
@Component({
tag: 'ion-refresher',
styleUrl: 'refresher.scss'
})
export class Refresher {
private appliedStyles = false;
private didStart = false;
private gestureConfig: any;
private progress = 0;
scrollEl: HTMLElement;
@Prop({ context: 'dom' }) dom: DomController;
/**
* The current state which the refresher is in. The refresher's states include:
*
* - `inactive` - The refresher is not being pulled down or refreshing and is currently hidden.
* - `pulling` - The user is actively pulling down the refresher, but has not reached the point yet that if the user lets go, it'll refresh.
* - `cancelling` - The user pulled down the refresher and let go, but did not pull down far enough to kick off the `refreshing` state. After letting go, the refresher is in the `cancelling` state while it is closing, and will go back to the `inactive` state once closed.
* - `ready` - The user has pulled down the refresher far enough that if they let go, it'll begin the `refreshing` state.
* - `refreshing` - The refresher is actively waiting on the async operation to end. Once the refresh handler calls `complete()` it will begin the `completing` state.
* - `completing` - The `refreshing` state has finished and the refresher is in the process of closing itself. Once closed, the refresher will go back to the `inactive` state.
*/
@State() state: RefresherState = RefresherState.Inactive;
@Element() el: HTMLElement;
/**
* @input {number} The min distance the user must pull down until the
* refresher can go into the `refreshing` state. Default is `60`.
*/
@Prop() pullMin = 60;
/**
* @input {number} The maximum distance of the pull until the refresher
* will automatically go into the `refreshing` state. By default, the pull
* maximum will be the result of `pullMin + 60`.
*/
@Prop() pullDelta = 60;
// TODO: NEVER USED
/**
* @input {number} Time it takes to close the refresher. Default is `280ms`.
*/
@Prop() closeDuration = '280ms';
/**
* @input {string} Time it takes the refresher to to snap back to the `refreshing` state. Default is `280ms`.
*/
@Prop() snapbackDuration = '280ms';
/**
* @input {boolean} If the refresher is enabled or not. This should be used in place of an `ngIf`. Default is `true`.
*/
@Prop() enabled = false;
/**
* @output {event} Emitted when the user lets go and has pulled down
* far enough, which would be farther than the `pullMin`, then your refresh hander if
* fired and the state is updated to `refreshing`. From within your refresh handler,
* you must call the `complete()` method when your async operation has completed.
*/
@Event() ionRefresh: EventEmitter;
/**
* @output {event} Emitted while the user is pulling down the content and exposing the refresher.
*/
@Event() ionPull: EventEmitter;
/**
* @output {event} Emitted when the user begins to start pulling down.
*/
@Event() ionStart: EventEmitter;
constructor() {
this.gestureConfig = {
'canStart': this.canStart.bind(this),
'onStart': this.onStart.bind(this),
'onMove': this.onMove.bind(this),
'onEnd': this.onEnd.bind(this),
'gestureName': 'refresher',
'gesturePriority': 10,
'type': 'pan',
// 'passive': false,
'direction': 'y',
'threshold': 0,
'attachTo': 'body'
};
}
componentDidLoad() {
if (this.el.getAttribute('slot') !== 'fixed') {
console.error('Make sure you use: <ion-refresher slot="fixed">');
return;
}
this.scrollEl = this.el.parentElement.querySelector('ion-scroll') as HTMLElement;
if (!this.scrollEl) {
console.error('ion-refresher didn\'t attached, make sure if parent is a ion-content');
}
}
componentDidUnload() {
this.scrollEl = null;
}
/**
* Call `complete()` when your async operation has completed.
* For example, the `refreshing` state is while the app is performing
* an asynchronous operation, such as receiving more data from an
* AJAX request. Once the data has been received, you then call this
* method to signify that the refreshing has completed and to close
* the refresher. This method also changes the refresher's state from
* `refreshing` to `completing`.
*/
@Method()
complete() {
this.close(RefresherState.Completing, '120ms');
}
/**
* Changes the refresher's state from `refreshing` to `cancelling`.
*/
@Method()
cancel() {
this.close(RefresherState.Cancelling, '');
}
/**
* A number representing how far down the user has pulled.
* The number `0` represents the user hasn't pulled down at all. The
* number `1`, and anything greater than `1`, represents that the user
* has pulled far enough down that when they let go then the refresh will
* happen. If they let go and the number is less than `1`, then the
* refresh will not happen, and the content will return to it's original
* position.
*/
@Method()
getProgress() {
return this.progress;
}
private canStart(): boolean {
if (!this.scrollEl) {
return false;
}
if (this.state !== RefresherState.Inactive) {
return false;
}
// if the scrollTop is greater than zero then it's
// not possible to pull the content down yet
if (this.scrollEl.scrollTop > 0) {
return false;
}
return true;
}
private onStart() {
this.progress = 0;
this.state = RefresherState.Inactive;
}
private onMove(detail: GestureDetail) {
// this method can get called like a bazillion times per second,
// so it's built to be as efficient as possible, and does its
// best to do any DOM read/writes only when absolutely necessary
// if multitouch then get out immediately
const ev = detail.event as TouchEvent;
if (ev.touches && ev.touches.length > 1) {
return 1;
}
// do nothing if it's actively refreshing
// or it's in the process of closing
// or this was never a startY
if (this.state & RefresherState._BUSY_) {
return 2;
}
const deltaY = detail.deltaY;
// don't bother if they're scrolling up
// and have not already started dragging
if (deltaY <= 0) {
// the current Y is higher than the starting Y
// so they scrolled up enough to be ignored
this.progress = 0;
this.state = RefresherState.Inactive;
if (this.appliedStyles) {
// reset the styles only if they were applied
this.setCss(0, '', false, '');
return 5;
}
return 6;
}
if (this.state === RefresherState.Inactive) {
// this refresh is not already actively pulling down
// get the content's scrollTop
const scrollHostScrollTop = this.scrollEl.scrollTop;
// if the scrollTop is greater than zero then it's
// not possible to pull the content down yet
if (scrollHostScrollTop > 0) {
this.progress = 0;
return 7;
}
// content scrolled all the way to the top, and dragging down
this.state = RefresherState.Pulling;
}
// prevent native scroll events
ev.preventDefault();
// the refresher is actively pulling at this point
// move the scroll element within the content element
this.setCss(deltaY, '0ms', true, '');
if (!deltaY) {
// don't continue if there's no delta yet
this.progress = 0;
return 8;
}
const pullMin = this.pullMin;
// set pull progress
this.progress = deltaY / pullMin;
// emit "start" if it hasn't started yet
if (!this.didStart) {
this.didStart = true;
this.ionStart.emit(this);
}
// emit "pulling" on every move
this.ionPull.emit(this);
// do nothing if the delta is less than the pull threshold
if (deltaY < pullMin) {
// ensure it stays in the pulling state, cuz its not ready yet
this.state = RefresherState.Pulling;
return 2;
}
if (deltaY > pullMin + this.pullDelta) {
// they pulled farther than the max, so kick off the refresh
this.beginRefresh();
return 3;
}
// pulled farther than the pull min!!
// it is now in the `ready` state!!
// if they let go then it'll refresh, kerpow!!
this.state = RefresherState.Ready;
return 4;
}
private onEnd() {
// only run in a zone when absolutely necessary
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
// and close the refresher
// set that the refresh is actively cancelling
this.cancel();
}
}
private beginRefresh() {
// assumes we're already back in a zone
// they pulled down far enough, so it's ready to refresh
this.state = RefresherState.Refreshing;
// place the content in a hangout position while it thinks
this.setCss(this.pullMin, this.snapbackDuration, true, '');
// emit "refresh" because it was pulled down far enough
// and they let go to begin refreshing
this.ionRefresh.emit(this);
}
private close(state: RefresherState, delay: string) {
let timer: number;
function close(ev: TransitionEvent) {
// closing is done, return to inactive state
if (ev) {
clearTimeout(timer);
}
this.state = RefresherState.Inactive;
this.progress = 0;
this.didStart = false;
this.setCss(0, '0ms', false, '');
}
// create fallback timer incase something goes wrong with transitionEnd event
timer = setTimeout(close.bind(this), 600);
// create transition end event on the content's scroll element
// TODO: what is this?
// this.scrollEl.onScrollElementTransitionEnd(close.bind(this));
// reset set the styles on the scroll element
// set that the refresh is actively cancelling/completing
this.state = state;
this.setCss(0, '', true, delay);
// TODO: stop gesture
// if (this._pointerEvents) {
// this._pointerEvents.stop();
// }
}
private setCss(y: number, duration: string, overflowVisible: boolean, delay: string) {
this.appliedStyles = (y > 0);
this.dom.write(() => {
const style = this.scrollEl.style;
style.transform = ((y > 0) ? 'translateY(' + y + 'px) translateZ(0px)' : 'translateZ(0px)');
style.transitionDuration = duration;
style.transitionDelay = delay;
style.overflow = (overflowVisible ? 'hidden' : '');
});
}
hostData() {
return {
class: {
'refresher-active': this.state !== RefresherState.Inactive,
'refresher-pulling': this.state === RefresherState.Pulling,
'refresher-ready': this.state === RefresherState.Ready,
'refresher-refreshing': this.state === RefresherState.Refreshing,
'refresher-cancelling': this.state === RefresherState.Cancelling,
'refresher-completing': this.state === RefresherState.Completing
}
};
}
render() {
return <ion-gesture {...this.gestureConfig}
enabled={this.enabled}>
<slot></slot>
</ion-gesture>;
}
}

View File

@@ -0,0 +1,72 @@
<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="UTF-8">
<title>Ionic Item Sliding</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<script src="/dist/ionic.js"></script>
</head>
<body>
<ion-app>
<ion-page>
<ion-header>
<ion-toolbar>
<ion-title>Ionic CDN demo</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-refresher id="refresher" enabled="true" slot="fixed">
<ion-refresher-content></ion-refresher-content>
</ion-refresher>
<ion-list id="list"></ion-list>
</ion-content>
</ion-page>
</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() {
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>${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);
}, 500);
});
}
render();
</script>
</body>
</html>

View File

@@ -37,6 +37,7 @@ exports.config = {
{ components: ['ion-spinner'] },
{ components: ['ion-split-pane'] },
{ components: ['ion-range', 'ion-range-knob']},
{ components: ['ion-refresher', 'ion-refresher-content']},
{ components: ['ion-tabs', 'ion-tab', 'ion-tabbar', 'ion-tab-button'] },
{ components: ['ion-toggle'] },
{ components: ['ion-toast', 'ion-toast-controller'] },