feat(infinite-scroll): adds infinite-scroll

This commit is contained in:
Manu Mtz.-Almeida
2017-10-22 15:12:30 +02:00
parent fb51b03190
commit a39c56f48d
8 changed files with 725 additions and 58 deletions

View File

@ -596,10 +596,7 @@ declare global {
mode?: string, mode?: string,
color?: string, color?: string,
ionScrollStart?: any, fullscreen?: boolean | "true" | "false"
ionScroll?: any,
ionScrollEnd?: any,
fullscreen?: boolean
} }
} }
} }
@ -629,14 +626,13 @@ declare global {
mode?: string, mode?: string,
color?: string, color?: string,
pickerCtrl?: any, disabled?: boolean | "true" | "false",
disabled?: boolean, min?: any,
min?: string, max?: any,
max?: string, displayFormat?: any,
displayFormat?: string, pickerFormat?: any,
pickerFormat?: string, cancelText?: any,
cancelText?: string, doneText?: any,
doneText?: string,
yearValues?: any, yearValues?: any,
monthValues?: any, monthValues?: any,
dayValues?: any, dayValues?: any,
@ -647,8 +643,7 @@ declare global {
dayNames?: any, dayNames?: any,
dayShortNames?: any, dayShortNames?: any,
pickerOptions?: any, pickerOptions?: any,
placeholder?: string, placeholder?: any
value?: string
} }
} }
} }
@ -997,6 +992,66 @@ declare global {
} }
} }
import { InfiniteScrollContent as IonInfiniteScrollContent } from './components/infinite-scroll/infinite-scroll-content';
interface HTMLIonInfiniteScrollContentElement extends IonInfiniteScrollContent, HTMLElement {
}
declare var HTMLIonInfiniteScrollContentElement: {
prototype: HTMLIonInfiniteScrollContentElement;
new (): HTMLIonInfiniteScrollContentElement;
};
declare global {
interface HTMLElementTagNameMap {
"ion-infinite-scroll-content": HTMLIonInfiniteScrollContentElement;
}
interface ElementTagNameMap {
"ion-infinite-scroll-content": HTMLIonInfiniteScrollContentElement;
}
namespace JSX {
interface IntrinsicElements {
"ion-infinite-scroll-content": JSXElements.IonInfiniteScrollContentAttributes;
}
}
namespace JSXElements {
export interface IonInfiniteScrollContentAttributes extends HTMLAttributes {
loadingSpinner?: any,
loadingText?: any
}
}
}
import { InfiniteScroll as IonInfiniteScroll } from './components/infinite-scroll/infinite-scroll';
interface HTMLIonInfiniteScrollElement extends IonInfiniteScroll, HTMLElement {
}
declare var HTMLIonInfiniteScrollElement: {
prototype: HTMLIonInfiniteScrollElement;
new (): HTMLIonInfiniteScrollElement;
};
declare global {
interface HTMLElementTagNameMap {
"ion-infinite-scroll": HTMLIonInfiniteScrollElement;
}
interface ElementTagNameMap {
"ion-infinite-scroll": HTMLIonInfiniteScrollElement;
}
namespace JSX {
interface IntrinsicElements {
"ion-infinite-scroll": JSXElements.IonInfiniteScrollAttributes;
}
}
namespace JSXElements {
export interface IonInfiniteScrollAttributes extends HTMLAttributes {
complete?: any,
threshold?: any,
enabled?: boolean | "true" | "false",
position?: any
}
}
}
import { Input as IonInput } from './components/input/input'; import { Input as IonInput } from './components/input/input';
interface HTMLIonInputElement extends IonInput, HTMLElement { interface HTMLIonInputElement extends IonInput, HTMLElement {
@ -2346,11 +2401,11 @@ declare global {
mode?: string, mode?: string,
color?: string, color?: string,
enabled?: boolean, enabled?: boolean | "true" | "false",
jsScroll?: boolean, jsScroll?: boolean | "true" | "false",
ionScrollStart?: any, onionScrollStart?: any,
ionScroll?: any, onionScroll?: any,
ionScrollEnd?: any onionScrollEnd?: any
} }
} }
} }

View File

@ -24,21 +24,6 @@ export class Content {
$siblingHeader: HTMLElement; $siblingHeader: HTMLElement;
$siblingFooter: HTMLElement; $siblingFooter: HTMLElement;
/**
* @output {ScrollEvent} Emitted when the scrolling first starts.
*/
@Prop() ionScrollStart: Function;
/**
* @output {ScrollEvent} Emitted on every scroll event.
*/
@Prop() ionScroll: Function;
/**
* @output {ScrollEvent} Emitted when scrolling ends.
*/
@Prop() ionScrollEnd: Function;
headerHeight: string; headerHeight: string;
@ -79,7 +64,6 @@ export class Content {
protected render() { protected render() {
const props: any = {};
const scrollStyle: any = {}; const scrollStyle: any = {};
const pageChildren: HTMLElement[] = getParentElement(this.el).children; const pageChildren: HTMLElement[] = getParentElement(this.el).children;
@ -94,16 +78,6 @@ export class Content {
scrollStyle.marginBottom = footerHeight; scrollStyle.marginBottom = footerHeight;
} }
if (this.ionScrollStart) {
props['ionScrollStart'] = this.ionScrollStart.bind(this);
}
if (this.ionScroll) {
props['ionScroll'] = this.ionScroll.bind(this);
}
if (this.ionScrollEnd) {
props['ionScrollEnd'] = this.ionScrollEnd.bind(this);
}
const themedClasses = createThemedClasses(this.mode, this.color, 'content'); const themedClasses = createThemedClasses(this.mode, this.color, 'content');
const hostClasses = getElementClassObject(this.el.classList); const hostClasses = getElementClassObject(this.el.classList);
@ -114,7 +88,7 @@ export class Content {
}; };
return ( return (
<ion-scroll {...props} style={scrollStyle} class={scrollClasses}> <ion-scroll style={scrollStyle} class={scrollClasses}>
<slot></slot> <slot></slot>
</ion-scroll> </ion-scroll>
); );

View File

@ -0,0 +1,45 @@
import { Component, Prop } from '@stencil/core';
import { Config } from '../../index';
/**
* @hidden
*/
@Component({
tag: 'ion-infinite-scroll-content'
})
export class InfiniteScrollContent {
@Prop({ context: 'config' }) config: Config;
/**
* @input {string} An animated SVG spinner that shows while loading.
*/
@Prop({mutable: true}) loadingSpinner: string;
/**
* @input {string} Optional text to display while loading.
*/
@Prop() loadingText: string;
protected ionViewDidLoad() {
if (!this.loadingSpinner) {
this.loadingSpinner = this.config.get('infiniteLoadingSpinner', this.config.get('spinner', 'lines'));
}
}
protected render() {
return (
<div class='infinite-loading'>
{this.loadingSpinner &&
<div class='infinite-loading-spinner'>
<ion-spinner name={this.loadingSpinner}></ion-spinner>
</div>
}
{this.loadingText &&
<div class='infinite-loading-text' innerHTML={this.loadingText}></div>
}
</div>
);
}
}

View File

@ -0,0 +1,106 @@
@import "../../themes/ionic.globals";
// Infinite Scroll
// --------------------------------------------------
// deprecated
$infinite-scroll-loading-margin: null !default;
/// @prop - Minimun height of ion-infinite-scroll-content
$infinite-scroll-content-min-height: 84px !default;
/// @prop - Margin top of the infinite scroll loading icon
$infinite-scroll-loading-margin-top: 0 !default;
/// @prop - Margin end of the infinite scroll loading icon
$infinite-scroll-loading-margin-end: 0 !default;
/// @prop - Margin bottom of the infinite scroll loading icon
$infinite-scroll-loading-margin-bottom: 32px !default;
/// @prop - Margin start of the infinite scroll loading icon
$infinite-scroll-loading-margin-start: 0 !default;
/// @prop - Color of the infinite scroll loading indicator
$infinite-scroll-loading-color: #666 !default;
/// @prop - Text color of the infinite scroll loading indicator
$infinite-scroll-loading-text-color: $infinite-scroll-loading-color !default;
// deprecated
$infinite-scroll-loading-text-margin: null !default;
/// @prop - Margin top of the infinite scroll loading text
$infinite-scroll-loading-text-margin-top: 4px !default;
/// @prop - Margin end of the infinite scroll loading text
$infinite-scroll-loading-text-margin-end: 32px !default;
/// @prop - Margin bottom of the infinite scroll loading text
$infinite-scroll-loading-text-margin-bottom:0 !default;
/// @prop - Margin start of the infinite scroll loading text
$infinite-scroll-loading-text-margin-start: 32px !default;
ion-infinite-scroll {
display: none;
width: 100%;
}
.infinite-scroll-enabled {
display: block;
}
// Infinite Scroll Content
// --------------------------------------------------
ion-infinite-scroll-content {
@include text-align(center);
display: flex;
flex-direction: column;
justify-content: center;
min-height: $infinite-scroll-content-min-height;
}
.infinite-loading {
display: none;
width: 100%;
@include deprecated-variable(margin, $infinite-scroll-loading-margin) {
@include margin($infinite-scroll-loading-margin-top, $infinite-scroll-loading-margin-end, $infinite-scroll-loading-margin-bottom, $infinite-scroll-loading-margin-start);
}
}
.infinite-loading-text {
color: $infinite-scroll-loading-text-color;
@include deprecated-variable(margin, $infinite-scroll-loading-text-margin) {
@include margin($infinite-scroll-loading-text-margin-top, $infinite-scroll-loading-text-margin-end, $infinite-scroll-loading-text-margin-bottom, $infinite-scroll-loading-text-margin-start);
}
}
.infinite-loading-spinner .spinner-ios line,
.infinite-loading-spinner .spinner-ios-small line,
.infinite-loading-spinner .spinner-crescent circle {
stroke: $infinite-scroll-loading-color;
}
.infinite-loading-spinner .spinner-bubbles circle,
.infinite-loading-spinner .spinner-circles circle,
.infinite-loading-spinner .spinner-dots circle {
fill: $infinite-scroll-loading-color;
}
// Infinite Scroll Content States
// --------------------------------------------------
.infinite-scroll-loading ion-infinite-scroll-content > .infinite-loading {
display: block;
}

View File

@ -0,0 +1,386 @@
import { Component, Element, Event, EventEmitter, HostElement, Method, Prop, PropDidChange, State } from '@stencil/core';
import { ScrollDetail } from '../../index';
const enum Position {
Top = 'top',
Bottom = 'bottom',
}
/**
* @name InfiniteScroll
* @description
* The Infinite Scroll allows you to perform an action when the user
* scrolls a specified distance from the bottom or top of the page.
*
* The expression assigned to the `infinite` event is called when
* the user scrolls to the specified distance. When this expression
* has finished its tasks, it should call the `complete()` method
* on the infinite scroll instance.
*
* @usage
* ```html
* <ion-content>
*
* <ion-list>
* <ion-item *ngFor="let i of items">{% raw %}{{i}}{% endraw %}</ion-item>
* </ion-list>
*
* <ion-infinite-scroll (ionInfinite)="doInfinite($event)">
* <ion-infinite-scroll-content></ion-infinite-scroll-content>
* </ion-infinite-scroll>
*
* </ion-content>
* ```
*
* ```ts
* @Component({...})
* export class NewsFeedPage {
* items = [];
*
* constructor() {
* for (let i = 0; i < 30; i++) {
* this.items.push( this.items.length );
* }
* }
*
* doInfinite(infiniteScroll) {
* console.log('Begin async operation');
*
* setTimeout(() => {
* for (let i = 0; i < 30; i++) {
* this.items.push( this.items.length );
* }
*
* console.log('Async operation has ended');
* infiniteScroll.complete();
* }, 500);
* }
*
* }
* ```
*
* ## `waitFor` method of InfiniteScroll
*
* In case if your async operation returns promise you can utilize
* `waitFor` method inside your template.
*
* ```html
* <ion-content>
*
* <ion-list>
* <ion-item *ngFor="let item of items">{{item}}</ion-item>
* </ion-list>
*
* <ion-infinite-scroll (ionInfinite)="$event.waitFor(doInfinite())">
* <ion-infinite-scroll-content></ion-infinite-scroll-content>
* </ion-infinite-scroll>
*
* </ion-content>
* ```
*
* ```ts
* @Component({...})
* export class NewsFeedPage {
* items = [];
*
* constructor() {
* for (var i = 0; i < 30; i++) {
* this.items.push( this.items.length );
* }
* }
*
* doInfinite(): Promise<any> {
* console.log('Begin async operation');
*
* return new Promise((resolve) => {
* setTimeout(() => {
* for (var i = 0; i < 30; i++) {
* this.items.push( this.items.length );
* }
*
* console.log('Async operation has ended');
* resolve();
* }, 500);
* })
* }
* }
* ```
*
* ## Infinite Scroll Content
*
* By default, Ionic uses the infinite scroll spinner that looks
* best for the platform the user is on. However, you can change the
* default spinner or add text by adding properties to the
* `ion-infinite-scroll-content` component.
*
* ```html
* <ion-content>
*
* <ion-infinite-scroll (ionInfinite)="doInfinite($event)">
* <ion-infinite-scroll-content
* loadingSpinner="bubbles"
* loadingText="Loading more data...">
* </ion-infinite-scroll-content>
* </ion-infinite-scroll>
*
* </ion-content>
* ```
*
*
* ## Further Customizing Infinite Scroll Content
*
* The `ion-infinite-scroll` component holds the infinite scroll logic.
* It requires a child component in order to display the content.
* Ionic uses `ion-infinite-scroll-content` by default. This component
* displays the infinite scroll and changes the look depending
* on the infinite scroll's state. Separating these components allows
* developers to create their own infinite scroll content components.
* You could replace our default content with custom SVG or CSS animations.
*
* @demo /docs/demos/src/infinite-scroll/
*
*/
@Component({
tag: 'ion-infinite-scroll',
styleUrl: 'infinite-scroll.scss'
})
export class InfiniteScroll {
private rmListener: Function;
private thrPx: number = 0;
private thrPc: number = 0.15;
private init: boolean = false;
private scrollEl: HTMLElement;
private didFire = false;
private isBusy = false;
@Element() private el: HTMLElement;
@State() isLoading: boolean = false;
/**
* @input {string} The threshold distance from the bottom
* of the content to call the `infinite` output event when scrolled.
* The threshold value can be either a percent, or
* in pixels. For example, use the value of `10%` for the `infinite`
* output event to get called when the user has scrolled 10%
* from the bottom of the page. Use the value `100px` when the
* scroll is within 100 pixels from the bottom of the page.
* Default is `15%`.
*/
@Prop() threshold: string = '15%';
@PropDidChange('threshold')
thresholdChanged(val: string) {
if (val.lastIndexOf('%') > -1) {
this.thrPx = 0;
this.thrPc = (parseFloat(val) / 100);
} else {
this.thrPx = parseFloat(val);
this.thrPc = 0;
}
}
/**
* @input {boolean} If true, Whether or not the infinite scroll should be
* enabled or not. Setting to `false` will remove scroll event listeners
* and hide the display.
*
* Call `enable(false)` to disable the infinite scroll from actively
* trying to receive new data while scrolling. This method is useful
* when it is known that there is no more data that can be added, and
* the infinite scroll is no longer needed.
* @param {boolean} shouldEnable If the infinite scroll should be
* enabled or not. Setting to `false` will remove scroll event listeners
* and hide the display.
*/
@Prop() enabled: boolean = true;
@PropDidChange('enabled')
enabledChanged(val: boolean) {
this.enableScrollEvents(val);
}
/**
* @input {string} The position of the infinite scroll element.
* The value can be either `top` or `bottom`.
* Default is `bottom`.
*/
@Prop() position: Position = Position.Bottom;
/**
* @output {event} Emitted when the scroll reaches
* the threshold distance. From within your infinite handler,
* you must call the infinite scroll's `complete()` method when
* your async operation has completed.
*/
@Event() private ionInfinite: EventEmitter;
ionViewDidLoad() {
const scrollEl = this.scrollEl = this.el.closest('ion-scroll') as HostElement;
if (!scrollEl) {
console.error('ion-infinite-scroll must be used ion-content');
return;
}
this.init = true;
this.enableScrollEvents(this.enabled);
if (this.position === Position.Top) {
// scrollEl.scrollDownOnLoad = true;
}
}
ionViewDidUnload() {
this.enableScrollEvents(false);
this.scrollEl = null;
}
// ******** DOM READ ****************
private onScroll(ev: CustomEvent) {
const detail = ev.detail as ScrollDetail;
if (!this.canStart()) {
return 1;
}
const infiniteHeight = this.el.offsetHeight;
if (!infiniteHeight) {
// if there is no height of this element then do nothing
return 2;
}
const scrollTop = detail.scrollTop;
const scrollHeight = this.scrollEl.scrollHeight;
const height = this.scrollEl.offsetHeight;
const threshold = this.thrPc ? (height * this.thrPc) : this.thrPx;
let distanceFromInfinite: number;
if (this.position === Position.Bottom) {
distanceFromInfinite = scrollHeight - infiniteHeight - scrollTop - threshold - height;
} else {
// assert(this.position === Position.Top, '_position should be top');
distanceFromInfinite = scrollTop - infiniteHeight - threshold;
}
if (distanceFromInfinite < 0) {
if (!this.didFire) {
this.isLoading = true;
this.didFire = true;
this.ionInfinite.emit(this);
return 3;
}
} else {
this.didFire = false;
}
return 4;
}
private canStart(): boolean {
return (
this.enabled &&
!this.isBusy &&
this.scrollEl &&
!this.isLoading);
}
/**
* Call `complete()` within the `infinite` output event handler when
* your async operation has completed. For example, the `loading`
* state is while the app is performing an asynchronous operation,
* such as receiving more data from an AJAX request to add more items
* to a data list. Once the data has been received and UI updated, you
* then call this method to signify that the loading has completed.
* This method will change the infinite scroll's state from `loading`
* to `enabled`.
*/
@Method()
complete() {
if (!this.isLoading) {
return;
}
this.isLoading = false;
if (this.position === Position.Top) {
/** New content is being added at the top, but the scrollTop position stays the same,
* which causes a scroll jump visually. This algorithm makes sure to prevent this.
* (Frame 1)
* - complete() is called, but the UI hasn't had time to update yet.
* - Save the current content dimensions.
* - Wait for the next frame using _dom.read, so the UI will be updated.
* (Frame 2)
* - Read the new content dimensions.
* - Calculate the height difference and the new scroll position.
* - Delay the scroll position change until other possible dom reads are done using _dom.write to be performant.
* (Still frame 2, if I'm correct)
* - Change the scroll position (= visually maintain the scroll position).
* - Change the state to re-enable the InfiniteScroll.
* - This should be after changing the scroll position, or it could
* cause the InfiniteScroll to be triggered again immediately.
* (Frame 3)
* Done.
*/
this.isBusy = true;
// ******** DOM READ ****************
// Save the current content dimensions before the UI updates
const prev = this.scrollEl.scrollHeight - this.scrollEl.scrollTop;
// ******** DOM READ ****************
Context.dom.read(() => {
// UI has updated, save the new content dimensions
const scrollHeight = this.scrollEl.scrollHeight;
// New content was added on top, so the scroll position should be changed immediately to prevent it from jumping around
const newScrollTop = scrollHeight - prev;
// ******** DOM WRITE ****************
Context.dom.write(() => {
this.scrollEl.scrollTop = newScrollTop;
this.isBusy = false;
});
});
}
}
/**
* Pass a promise inside `waitFor()` within the `infinite` output event handler in order to
* change state of infiniteScroll to "complete"
*/
waitFor(action: Promise<any>) {
const enable = this.complete.bind(this);
action.then(enable, enable);
}
/**
* @hidden
*/
private enableScrollEvents(shouldListen: boolean) {
if (!this.init) {
return;
}
if (shouldListen) {
if (!this.rmListener) {
const onScroll = this.onScroll.bind(this);
this.scrollEl.addEventListener('ionScroll', onScroll);
this.rmListener = () => {
this.scrollEl.removeEventListener('ionScroll', onScroll);
};
}
} else {
this.rmListener && this.rmListener();
this.rmListener = null;
}
}
hostData() {
return {
class: {
'infinite-scroll-loading': this.isLoading,
'infinite-scroll-enabled': this.enabled
}
};
}
protected render() {
return <slot></slot>;
}
}

View File

@ -0,0 +1,90 @@
<!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 main class="show-page">
<ion-header>
<ion-toolbar>
<ion-title>Ionic CDN demo</ion-title>
</ion-toolbar>
</ion-header>
<ion-content padding>
<ion-button onclick="toggleInfiniteScroll()" block>
Toggle InfiniteScroll Enabled
</ion-button>
<ion-list id="list">
</ion-list>
<ion-infinite-scroll threshold="100px" id="infinite-scroll">
<ion-infinite-scroll-content
loadingSpinner="bubbles"
loadingText="Loading more data...">
</ion-infinite-scroll-content>
</ion-infinite-scroll>
</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 infiniteScroll = document.getElementById('infinite-scroll');
function toggleInfiniteScroll() {
infiniteScroll.enabled = !infiniteScroll.enabled;
}
infiniteScroll.addEventListener('ionInfinite', async function() {
console.log('Loading data...');
const data = await getAsyncData();
items = items.concat(data);
infiniteScroll.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

@ -1,4 +1,4 @@
import { Component, Element, Listen, Prop } from '@stencil/core'; import { Component, Element, Event, EventEmitter, Listen, Prop } from '@stencil/core';
import { Config, GestureDetail } from '../../index'; import { Config, GestureDetail } from '../../index';
import { GestureController, GestureDelegate } from '../gesture-controller/gesture-controller'; import { GestureController, GestureDelegate } from '../gesture-controller/gesture-controller';
@ -22,9 +22,14 @@ export class Scroll {
@Prop({ context: 'config'}) config: Config; @Prop({ context: 'config'}) config: Config;
@Prop() enabled: boolean = true; @Prop() enabled: boolean = true;
@Prop() jsScroll: boolean = false; @Prop() jsScroll: boolean = false;
@Prop() ionScrollStart: ScrollCallback;
@Prop() ionScroll: ScrollCallback; @Prop() onionScrollStart: ScrollCallback;
@Prop() ionScrollEnd: ScrollCallback; @Prop() onionScroll: ScrollCallback;
@Prop() onionScrollEnd: ScrollCallback;
@Event() ionScrollStart: EventEmitter;
@Event() ionScroll: EventEmitter;
@Event() ionScrollEnd: EventEmitter;
protected ionViewDidLoad() { protected ionViewDidLoad() {
if (Context.isServer) return; if (Context.isServer) return;
@ -77,8 +82,10 @@ export class Scroll {
detail.velocityY = detail.velocityX = detail.deltaY = detail.deltaX = positions.length = 0; detail.velocityY = detail.velocityX = detail.deltaY = detail.deltaX = positions.length = 0;
// emit only on the first scroll event // emit only on the first scroll event
if (self.ionScrollStart) { if (self.onionScrollStart) {
self.ionScrollStart(detail); self.onionScrollStart(detail);
} else {
self.ionScrollStart.emit(detail);
} }
} }
@ -125,21 +132,24 @@ export class Scroll {
}, 80); }, 80);
// emit on each scroll event // emit on each scroll event
if (self.ionScrollStart) { if (self.onionScroll) {
self.ionScroll(detail); self.onionScroll(detail);
} else {
self.ionScroll.emit(detail);
} }
} }
onEnd(timeStamp: number) { onEnd(timeStamp: number) {
const self = this; const detail = this.detail;
const detail = self.detail;
detail.timeStamp = timeStamp || Date.now(); detail.timeStamp = timeStamp || Date.now();
// emit that the scroll has ended // emit that the scroll has ended
if (self.ionScrollEnd) { if (this.onionScrollEnd) {
self.ionScrollEnd(detail); this.onionScrollEnd(detail);
} else {
this.ionScrollEnd.emit(detail);
} }
} }

View File

@ -17,6 +17,7 @@ exports.config = {
{ components: ['ion-gesture', 'ion-scroll'], priority: 'low' }, { components: ['ion-gesture', 'ion-scroll'], priority: 'low' },
{ components: ['ion-grid', 'ion-row', 'ion-col'] }, { components: ['ion-grid', 'ion-row', 'ion-col'] },
{ components: ['ion-item', 'ion-item-divider', 'ion-item-sliding', 'ion-item-options', 'ion-item-option', 'ion-label', 'ion-list', 'ion-list-header', 'ion-skeleton-text'] }, { components: ['ion-item', 'ion-item-divider', 'ion-item-sliding', 'ion-item-options', 'ion-item-option', 'ion-label', 'ion-list', 'ion-list-header', 'ion-skeleton-text'] },
{ components: ['ion-infinite-scroll', 'ion-infinite-scroll-content'] },
{ components: ['ion-input', 'ion-textarea'] }, { components: ['ion-input', 'ion-textarea'] },
{ components: ['ion-loading', 'ion-loading-controller'] }, { components: ['ion-loading', 'ion-loading-controller'] },
{ components: ['ion-menu', 'ion-menu-controller'], priority: 'low' }, { components: ['ion-menu', 'ion-menu-controller'], priority: 'low' },