refactor(all): data -> componentProps

This commit is contained in:
Manu Mtz.-Almeida
2018-03-29 18:18:02 +02:00
parent ce500858fe
commit a36913e9db
17 changed files with 115 additions and 119 deletions

View File

@ -53,7 +53,7 @@ export class ActionSheet implements OverlayInterface {
* Additional classes to apply for custom CSS. If multiple classes are
* provided they should be separated by spaces.
*/
@Prop() cssClass: string;
@Prop() cssClass: string | string[];
/**
* If true, the action sheet will be dismissed when the backdrop is clicked. Defaults to `true`.

View File

@ -51,7 +51,7 @@ export class Alert implements OverlayInterface {
* Additional classes to apply for custom CSS. If multiple classes are
* provided they should be separated by spaces.
*/
@Prop() cssClass: string;
@Prop() cssClass: string | string[];
/**
* The main title in the heading of the alert.
@ -455,7 +455,7 @@ export interface AlertOptions {
title?: string;
subTitle?: string;
message?: string;
cssClass?: string;
cssClass?: string | string[];
mode?: string;
inputs?: AlertInput[];
buttons?: (AlertButton|string)[];
@ -480,6 +480,6 @@ export interface AlertInput {
export interface AlertButton {
text: string;
role?: string;
cssClass?: string;
cssClass?: string | string[];
handler?: (value: any) => boolean|void;
}

View File

@ -54,7 +54,7 @@ export class Loading implements OverlayInterface {
* Additional classes to apply for custom CSS. If multiple classes are
* provided they should be separated by spaces.
*/
@Prop() cssClass: string;
@Prop() cssClass: string | string[];
/**
* If true, the loading indicator will dismiss when the page changes. Defaults to `false`.
@ -222,7 +222,7 @@ export class Loading implements OverlayInterface {
export interface LoadingOptions {
spinner?: string;
content?: string;
cssClass?: string;
cssClass?: string | string[];
showBackdrop?: boolean;
dismissOnPageChange?: boolean;
duration?: number;

View File

@ -1,5 +1,5 @@
import { Component, Element, Event, EventEmitter, Listen, Method, Prop } from '@stencil/core';
import { Animation, AnimationBuilder, Config, FrameworkDelegate } from '../../index';
import { Animation, AnimationBuilder, ComponentProps, ComponentRef, Config, FrameworkDelegate } from '../../index';
import { createThemedClasses, getClassList } from '../../utils/theme';
import { BACKDROP, OverlayEventDetail, OverlayInterface, dismiss, eventMethod, present } from '../../utils/overlays';
@ -64,18 +64,18 @@ export class Modal implements OverlayInterface {
/**
* The component to display inside of the modal.
*/
@Prop() component: any;
@Prop() component: ComponentRef;
/**
* The data to pass to the modal component.
*/
@Prop() data: any = {};
@Prop() componentProps: ComponentProps;
/**
* Additional classes to apply for custom CSS. If multiple classes are
* provided they should be separated by spaces.
*/
@Prop() cssClass: string;
@Prop() cssClass: string | string[];
/**
* If true, the modal will be dismissed when the backdrop is clicked. Defaults to `true`.
@ -169,15 +169,15 @@ export class Modal implements OverlayInterface {
return Promise.resolve();
}
const container = this.el.querySelector(`.modal-wrapper`);
const data = {
...this.data,
const componentProps = {
...this.componentProps,
modal: this.el
};
const classes = [
...getClassList(this.cssClass),
'ion-page'
];
this.usersElement = await attachComponent(this.delegate, container, this.component, classes, data);
this.usersElement = await attachComponent(this.delegate, container, this.component, classes, componentProps);
return present(this, 'modalEnter', iosEnterAnimation, mdEnterAnimation);
}
@ -243,12 +243,12 @@ const LIFECYCLE_MAP: any = {
};
export interface ModalOptions {
component: any;
data?: any;
component: ComponentRef;
componentProps?: ComponentProps;
showBackdrop?: boolean;
enableBackdropDismiss?: boolean;
enterAnimation?: AnimationBuilder;
leaveAnimation?: AnimationBuilder;
cssClass?: string;
cssClass?: string | string[];
delegate?: FrameworkDelegate;
}

View File

@ -1,5 +1,6 @@
import { Component, Element, Listen, Prop } from '@stencil/core';
// import { NavResult } from '../../index';
import { ComponentProps } from '../..';
import { NavComponent } from '../nav/nav-util';
@Component({
tag: 'ion-nav-push',
@ -7,16 +8,16 @@ import { Component, Element, Listen, Prop } from '@stencil/core';
export class NavPush {
@Element() el: HTMLElement;
@Prop() component: any;
@Prop() component: NavComponent;
@Prop() componentProps: ComponentProps;
@Prop() url: string;
@Prop() data: any;
@Listen('child:click')
push(): Promise<any> {
const nav = this.el.closest('ion-nav');
const toPush = this.url || this.component;
if (nav && toPush) {
return nav.push(toPush, this.data);
return nav.push(toPush, this.componentProps);
}
return Promise.resolve(null);
}

View File

@ -1,4 +1,6 @@
import { Component, Element, Listen, Prop } from '@stencil/core';
import { ComponentProps } from '../..';
import { NavComponent } from '../nav/nav-util';
@Component({
tag: 'ion-nav-set-root',
@ -6,16 +8,16 @@ import { Component, Element, Listen, Prop } from '@stencil/core';
export class NavSetRoot {
@Element() el: HTMLElement;
@Prop() component: any;
@Prop() component: NavComponent;
@Prop() componentProps: ComponentProps;
@Prop() url: string;
@Prop() data: any;
@Listen('child:click')
push(): Promise<any> {
const nav = this.el.closest('ion-nav');
if (nav) {
const toPush = this.url || this.component;
return nav.setRoot(toPush, this.data);
return nav.setRoot(toPush, this.componentProps);
}
return Promise.resolve(null);
}

View File

@ -1,5 +1,5 @@
import { ViewController, isViewController } from './view-controller';
import { Animation, FrameworkDelegate } from '../..';
import { Animation, ComponentRef, FrameworkDelegate } from '../..';
export function convertToView(page: any, params: any): ViewController|null {
if (!page) {
@ -38,7 +38,7 @@ export const enum NavDirection {
Forward = 'forward'
}
export type NavParams = {[key: string]: any};
export type NavComponent = ComponentRef | ViewController | Function;
export interface NavResult {
hasCompleted: boolean;

View File

@ -1,8 +1,8 @@
import { Build, Component, Element, Event, EventEmitter, Method, Prop, Watch } from '@stencil/core';
import {
NavComponent,
NavDirection,
NavOptions,
NavParams,
NavResult,
TransitionDoneFn,
TransitionInstruction,
@ -12,7 +12,7 @@ import {
} from './nav-util';
import { ViewController, isViewController } from './view-controller';
import { Animation, Config, DomController, FrameworkDelegate, GestureDetail, NavOutlet } from '../..';
import { Animation, ComponentProps, Config, DomController, FrameworkDelegate, GestureDetail, NavOutlet } from '../..';
import { RouteID, RouteWrite, RouterDirection } from '../router/utils/interfaces';
import { AnimationOptions, ViewLifecycle, lifecycle, transition } from '../../utils/transition';
import { assert } from '../../utils/helpers';
@ -25,22 +25,16 @@ import mdTransitionAnimation from './animations/md.transition';
})
export class NavControllerBase implements NavOutlet {
private _ids = -1;
private _init = false;
private _queue: TransitionInstruction[] = [];
private _sbTrns: Animation;
private useRouter = false;
isTransitioning = false;
private _destroyed = false;
private _views: ViewController[] = [];
_views: ViewController[] = [];
id: string;
name: string;
mode: string;
parent: any;
@Element() el: HTMLElement;
@Prop({context: 'dom'}) dom: DomController;
@ -50,8 +44,8 @@ export class NavControllerBase implements NavOutlet {
@Prop({ mutable: true }) swipeBackEnabled: boolean;
@Prop({ mutable: true }) animated: boolean;
@Prop() delegate: FrameworkDelegate;
@Prop() rootParams: any;
@Prop() root: any;
@Prop() rootParams: ComponentProps;
@Prop() root: NavComponent;
@Watch('root')
rootChanged() {
if (this.root) {
@ -66,7 +60,6 @@ export class NavControllerBase implements NavOutlet {
@Event() ionNavChanged: EventEmitter<void>;
componentWillLoad() {
this.id = 'n' + (++ctrlIds);
this.useRouter = !!document.querySelector('ion-router') && !this.el.closest('[no-router]');
if (this.swipeBackEnabled === undefined) {
this.swipeBackEnabled = this.config.getBoolean('swipeBackEnabled', this.mode === 'ios');
@ -81,32 +74,44 @@ export class NavControllerBase implements NavOutlet {
}
componentDidUnload() {
this.destroy();
const views = this._views;
let view: ViewController;
for (let i = 0; i < views.length; i++) {
view = views[i];
lifecycle(view.element, ViewLifecycle.WillUnload);
view._destroy();
}
// release swipe back gesture and transition
this._sbTrns && this._sbTrns.destroy();
this._queue = this._views = this._sbTrns = null;
this._destroyed = true;
}
@Method()
push(page: any, params?: NavParams, opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
push(component: NavComponent, componentProps?: ComponentProps, opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
return this._queueTrns({
insertStart: -1,
insertViews: [{ page: page, params: params }],
insertViews: [{ page: component, params: componentProps }],
opts: opts,
}, done);
}
@Method()
insert(insertIndex: number, page: any, params?: NavParams, opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
insert(insertIndex: number, component: NavComponent, componentProps?: ComponentProps, opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
return this._queueTrns({
insertStart: insertIndex,
insertViews: [{ page: page, params: params }],
insertViews: [{ page: component, params: componentProps }],
opts: opts,
}, done);
}
@Method()
insertPages(insertIndex: number, insertPages: any[], opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
insertPages(insertIndex: number, insertComponents: NavComponent[], opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
return this._queueTrns({
insertStart: insertIndex,
insertViews: insertPages,
insertViews: insertComponents,
opts: opts,
}, done);
}
@ -121,7 +126,7 @@ export class NavControllerBase implements NavOutlet {
}
@Method()
popTo(indexOrViewCtrl: any, opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
popTo(indexOrViewCtrl: number | ViewController, opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
const config: TransitionInstruction = {
removeStart: -1,
removeCount: -1,
@ -174,12 +179,12 @@ export class NavControllerBase implements NavOutlet {
}
@Method()
setRoot(pageOrViewCtrl: any, params?: any, opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
return this.setPages([{ page: pageOrViewCtrl, params: params }], opts, done);
setRoot(component: NavComponent, componentProps?: ComponentProps, opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
return this.setPages([{ page: component, params: componentProps }], opts, done);
}
@Method()
setPages(pages: any[], opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
setPages(views: any[], opts?: NavOptions, done?: TransitionDoneFn): Promise<boolean> {
if (!opts) {
opts = {};
}
@ -189,7 +194,7 @@ export class NavControllerBase implements NavOutlet {
}
return this._queueTrns({
insertStart: 0,
insertViews: pages,
insertViews: views,
removeStart: 0,
removeCount: -1,
opts: opts
@ -252,12 +257,12 @@ export class NavControllerBase implements NavOutlet {
}
@Method()
getActive(): ViewController {
getActive(): ViewController|undefined {
return this._views[this._views.length - 1];
}
@Method()
getByIndex(index: number): ViewController {
getByIndex(index: number): ViewController|undefined {
return this._views[index];
}
@ -269,18 +274,10 @@ export class NavControllerBase implements NavOutlet {
}
@Method()
getViews(): Array<ViewController> {
getViews(): ViewController[] {
return this._views.slice();
}
/**
* Return a view controller
*/
@Method()
getViewById(id: string): ViewController|undefined {
return this._views.find(vc => vc.id === id);
}
indexOf(viewController: ViewController) {
return this._views.indexOf(viewController);
}
@ -674,12 +671,6 @@ export class NavControllerBase implements NavOutlet {
// create the new entering view
view._setNav(this);
// give this inserted view an ID
this._ids++;
if (!view.id) {
view.id = `${this.id}-${this._ids}`;
}
// insert the entering view into the correct index in the stack
this._views.splice(index, 0, view);
}
@ -729,28 +720,6 @@ export class NavControllerBase implements NavOutlet {
}
}
destroy() {
const views = this._views;
let view: ViewController;
for (let i = 0; i < views.length; i++) {
view = views[i];
lifecycle(view.element, ViewLifecycle.WillUnload);
view._destroy();
}
// release swipe back gesture and transition
this._sbTrns && this._sbTrns.destroy();
this._queue = this._views = this._sbTrns = null;
// Unregister navcontroller
if (this.parent && this.parent.unregisterChildNav) {
// TODO: event
this.parent.unregisterChildNav(this);
}
this._destroyed = true;
}
private swipeBackStart() {
if (this.isTransitioning || this._queue.length > 0) {
return;
@ -837,5 +806,3 @@ export class NavControllerBase implements NavOutlet {
return dom;
}
}
let ctrlIds = -1;

View File

@ -207,8 +207,8 @@ describe('NavController', () => {
hasCompleted, requiresTransition, undefined, undefined, undefined
);
expect(nav.length()).toEqual(4);
expect(nav._views[0].component).toEqual(MockView4);
expect(nav._views[nav._views.length - 1].component).toEqual(MockView3);
expect(nav.getByIndex(0).component).toEqual(MockView4);
expect(nav.getByIndex(nav.length() - 1).component).toEqual(MockView3);
}, 10000);
@ -226,7 +226,7 @@ describe('NavController', () => {
hasCompleted, requiresTransition, view2, view1, NavDirection.Forward
);
expect(nav.length()).toEqual(2);
expect(nav._views[nav._views.length - 1].component).toEqual(MockView2);
expect(nav.getByIndex(nav.length() - 1).component).toEqual(MockView2);
}, 10000);
@ -242,7 +242,7 @@ describe('NavController', () => {
hasCompleted, requiresTransition, view2, view1, NavDirection.Forward
);
expect(nav.length()).toEqual(2);
expect(nav._views[nav._views.length - 1].component).toEqual(MockView2);
expect(nav.getByIndex(nav.length() - 1).component).toEqual(MockView2);
}, 10000);
@ -260,7 +260,7 @@ describe('NavController', () => {
expect(err).toEqual(rejectReason);
expect(trnsDone).toHaveBeenCalledWith(hasCompleted, requiresTransition, rejectReason);
expect(nav.length()).toEqual(1);
expect(nav._views[nav._views.length - 1].component).toEqual(MockView1);
expect(nav.getByIndex(nav.length() - 1).component).toEqual(MockView1);
done();
});
}, 10000);
@ -980,7 +980,7 @@ describe('NavController', () => {
});
describe('destroy', () => {
describe('componentDidUnload', () => {
it('should not crash when destroyed while transitioning', (done) => {
const view1 = mockView(MockView1);
@ -991,7 +991,7 @@ describe('NavController', () => {
fail('should never get here');
done();
});
nav.destroy();
nav.componentDidUnload();
}, 10000);
});
@ -1092,7 +1092,7 @@ function mockView(component ?: any, data ?: any) {
}
function mockViews(nav: NavControllerBase, views: ViewController[]) {
nav._views = views;
nav['_views'] = views;
views.forEach(v => {
v._setNav(nav);
});

View File

@ -59,7 +59,7 @@ export class Picker implements OverlayInterface {
* Additional classes to apply for custom CSS. If multiple classes are
* provided they should be separated by spaces.
*/
@Prop() cssClass: string;
@Prop() cssClass: string | string[];
/**
* Number of milliseconds to wait before dismissing the picker.
@ -341,14 +341,14 @@ function buttonClass(button: PickerButton): CssClassMap {
export interface PickerButton {
text?: string;
role?: string;
cssClass?: string;
cssClass?: string | string[];
handler?: (value: any) => boolean|void;
}
export interface PickerOptions {
buttons?: PickerButton[];
columns?: PickerColumn[];
cssClass?: string;
cssClass?: string | string[];
enableBackdropDismiss?: boolean;
}
@ -360,7 +360,7 @@ export interface PickerColumn {
prefix?: string;
suffix?: string;
options: PickerColumnOption[];
cssClass?: string;
cssClass?: string | string[];
columnWidth?: string;
prefixWidth?: string;
suffixWidth?: string;

View File

@ -1,5 +1,5 @@
import { Component, Element, Event, EventEmitter, Listen, Method, Prop } from '@stencil/core';
import { Animation, AnimationBuilder, Config, FrameworkDelegate } from '../../index';
import { Animation, AnimationBuilder, ComponentProps, ComponentRef, Config, FrameworkDelegate } from '../../index';
import { createThemedClasses, getClassList } from '../../utils/theme';
import { BACKDROP, OverlayEventDetail, OverlayInterface, dismiss, eventMethod, present } from '../../utils/overlays';
@ -63,18 +63,18 @@ export class Popover implements OverlayInterface {
/**
* The component to display inside of the popover.
*/
@Prop() component: string;
@Prop() component: ComponentRef;
/**
* The data to pass to the popover component.
*/
@Prop() data: any = {};
@Prop() componentProps: ComponentProps;
/**
* Additional classes to apply for custom CSS. If multiple classes are
* provided they should be separated by spaces.
*/
@Prop() cssClass: string;
@Prop() cssClass: string | string[];
/**
* If true, the popover will be dismissed when the backdrop is clicked. Defaults to `true`.
@ -180,7 +180,7 @@ export class Popover implements OverlayInterface {
}
const container = this.el.querySelector('.popover-content');
const data = {
...this.data,
...this.componentProps,
popover: this.el
};
const classes = [
@ -254,14 +254,14 @@ export class Popover implements OverlayInterface {
}
export interface PopoverOptions {
component: any;
data?: any;
component: ComponentRef;
componentProps?: ComponentProps;
showBackdrop?: boolean;
enableBackdropDismiss?: boolean;
translucent?: boolean;
enterAnimation?: AnimationBuilder;
leaveAnimation?: AnimationBuilder;
cssClass?: string;
cssClass?: string | string[];
ev: Event;
delegate?: FrameworkDelegate;
}

View File

@ -310,7 +310,7 @@ export class Select {
const popoverOpts: PopoverOptions = Object.assign(interfaceOptions, {
component: 'ion-select-popover',
data: {
componentProps: {
title: interfaceOptions.title,
subTitle: interfaceOptions.subTitle,
message: interfaceOptions.message,

View File

@ -56,7 +56,7 @@ export class Toast implements OverlayInterface {
* Additional classes to apply for custom CSS. If multiple classes are
* provided they should be separated by spaces.
*/
@Prop() cssClass: string;
@Prop() cssClass: string | string[];
/**
* If true, the toast will dismiss when the page changes. Defaults to `false`.
@ -226,7 +226,7 @@ export class Toast implements OverlayInterface {
export interface ToastOptions {
message?: string;
cssClass?: string;
cssClass?: string | string[];
duration?: number;
showCloseButton?: boolean;
closeButtonText?: string;

5
core/src/index.d.ts vendored
View File

@ -59,7 +59,7 @@ export * from './components/modal/modal';
export { ModalController } from './components/modal-controller/modal-controller';
export * from './components/nav/nav';
export { ViewController } from './components/nav/view-controller';
export { NavParams, NavOptions, TransitionDoneFn} from './components/nav/nav-util';
export { NavOptions, TransitionDoneFn} from './components/nav/nav-util';
export { Note } from './components/note/note';
export { PickerColumnCmp } from './components/picker-column/picker-column';
export * from './components/picker/picker';
@ -110,6 +110,9 @@ export { DomController, RafCallback } from './global/dom-controller';
export { FrameworkDelegate } from './utils/framework-delegate';
export { OverlayEventDetail } from './utils/overlays';
export type ComponentRef = HTMLElement | string;
export type ComponentProps = {[key: string]: any};
export interface Config {
get: (key: string, fallback?: any) => any;
getBoolean: (key: string, fallback?: boolean) => boolean;

View File

@ -1,17 +1,18 @@
import { ComponentRef } from '..';
export interface FrameworkDelegate {
attachViewToDom(container: any, component: any, propsOrDataObj?: any, cssClasses?: string[]): Promise<HTMLElement>;
removeViewFromDom(container: any, component: any): Promise<void>;
}
export function attachComponent(delegate: FrameworkDelegate, container: Element, component: string|HTMLElement, cssClasses?: string[], params?: {[key: string]: any}): Promise<HTMLElement> {
export function attachComponent(delegate: FrameworkDelegate, container: Element, component: ComponentRef, cssClasses?: string[], componentProps?: {[key: string]: any}): Promise<HTMLElement> {
if (delegate) {
return delegate.attachViewToDom(container, component, params, cssClasses);
return delegate.attachViewToDom(container, component, componentProps, cssClasses);
}
const el = (typeof component === 'string') ? document.createElement(component) : component;
cssClasses && cssClasses.forEach(c => el.classList.add(c));
params && Object.assign(el, params);
componentProps && Object.assign(el, componentProps);
container.appendChild(el);
if ((el as any).componentOnReady) {

19
core/src/utils/lazy.ts Normal file
View File

@ -0,0 +1,19 @@
export function waitUntilVisible(el: HTMLElement, callback?: Function) {
return new Promise((resolve) => {
if ('IntersectionObserver' in window) {
const io = new IntersectionObserver(data => {
if (data[0].isIntersecting) {
resolve();
io.disconnect();
}
});
io.observe(el);
} else {
// fall back to setTimeout for Safari and IE
setTimeout(() => resolve(), 300);
}
}).then(() => {
callback && callback();
});
}

View File

@ -50,8 +50,11 @@ export function getButtonClassMap(buttonType: string, mode: string): CssClassMap
};
}
export function getClassList(classes: string | undefined): string[] {
export function getClassList(classes: string | string[] | undefined): string[] {
if (classes) {
if (Array.isArray(classes)) {
return classes;
}
return classes
.split(' ')
.filter(c => c.trim() !== '');
@ -59,7 +62,7 @@ export function getClassList(classes: string | undefined): string[] {
return [];
}
export function getClassMap(classes: string | undefined): CssClassMap {
export function getClassMap(classes: string | string[] | undefined): CssClassMap {
const map: CssClassMap = {};
getClassList(classes).forEach(c => map[c] = true);
return map;