mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-16 01:52:19 +08:00
refactor(modal): use framework delegate for mounting the user's component
This commit is contained in:
@ -1,65 +1,33 @@
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ComponentFactoryResolver,
|
||||
ComponentRef,
|
||||
Directive,
|
||||
ElementRef,
|
||||
Injector,
|
||||
ReflectiveInjector,
|
||||
Type,
|
||||
ViewContainerRef,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
|
||||
import { FrameworkDelegate } from '@ionic/core';
|
||||
|
||||
import { getProviders } from '../di/di';
|
||||
import { AngularComponentMounter } from '../providers/angular-component-mounter';
|
||||
import { AngularMountingData } from '../types/interfaces';
|
||||
|
||||
const elementToComponentRefMap = new Map<HTMLElement, ComponentRef<any>>();
|
||||
|
||||
@Component({
|
||||
@Directive({
|
||||
selector: 'ion-nav',
|
||||
template: `
|
||||
<div #viewport class="ng-nav-viewport"></div>
|
||||
`
|
||||
})
|
||||
export class IonNavDelegate implements FrameworkDelegate {
|
||||
|
||||
@ViewChild('viewport', { read: ViewContainerRef}) viewport: ViewContainerRef;
|
||||
|
||||
constructor(private elementRef: ElementRef, private changeDetection: ChangeDetectorRef, private angularComponentMounter: AngularComponentMounter, private injector: Injector, private componentResolveFactory: ComponentFactoryResolver) {
|
||||
constructor(private elementRef: ElementRef, private angularComponentMounter: AngularComponentMounter, private componentResolveFactory: ComponentFactoryResolver, private injector: Injector) {
|
||||
this.elementRef.nativeElement.delegate = this;
|
||||
|
||||
}
|
||||
|
||||
async attachViewToDom(elementOrContainerToMountTo: HTMLIonNavElement, elementOrComponentToMount: Type<any>,
|
||||
_propsOrDataObj?: any, _classesToAdd?: string[]): Promise<AngularMountingData> {
|
||||
attachViewToDom(elementOrContainerToMountTo: HTMLIonNavElement, elementOrComponentToMount: Type<any>, _propsOrDataObj?: any, classesToAdd?: string[]): Promise<AngularMountingData> {
|
||||
|
||||
const componentProviders = ReflectiveInjector.resolve(getProviders(elementOrContainerToMountTo));
|
||||
console.log('componentProviders: ', componentProviders);
|
||||
|
||||
const element = document.createElement('ion-page');
|
||||
for (const clazz of _classesToAdd) {
|
||||
element.classList.add(clazz);
|
||||
}
|
||||
|
||||
elementOrContainerToMountTo.appendChild(element);
|
||||
const mountingData = await this.angularComponentMounter.attachViewToDom(element, elementOrComponentToMount, [], this.changeDetection, this.componentResolveFactory, this.injector);
|
||||
mountingData.element = element;
|
||||
|
||||
elementToComponentRefMap.set(mountingData.angularHostElement, mountingData.componentRef);
|
||||
|
||||
return mountingData;
|
||||
const hostElement = document.createElement('div');
|
||||
return this.angularComponentMounter.attachViewToDom(elementOrContainerToMountTo, hostElement, elementOrComponentToMount, this.componentResolveFactory, this.injector, classesToAdd);
|
||||
}
|
||||
|
||||
async removeViewFromDom(_parentElement: HTMLElement, childElement: HTMLElement) {
|
||||
const componentRef = elementToComponentRefMap.get(childElement);
|
||||
if (componentRef) {
|
||||
return this.angularComponentMounter.removeViewFromDom(componentRef);
|
||||
}
|
||||
return Promise.resolve();
|
||||
removeViewFromDom(_parentElement: HTMLElement, childElement: HTMLElement) {
|
||||
return this.angularComponentMounter.removeViewFromDom(childElement);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,10 +8,16 @@ export const NavControllerToken = new InjectionToken<any>('NavControllerToken');
|
||||
export const ViewControllerToken = new InjectionToken<any>('ViewControllerToken');
|
||||
export const AppToken = new InjectionToken<any>('AppToken');
|
||||
|
||||
export function getProviders(element: HTMLIonNavElement) {
|
||||
export function getProviders(element: HTMLElement) {
|
||||
if (element.tagName !== 'ion-nav') {
|
||||
element.closest('ion-nav');
|
||||
}
|
||||
|
||||
const nearestNavElement = (element.tagName.toLowerCase() === 'ion-nav' ? element : element.closest('ion-nav')) as HTMLIonNavElement;
|
||||
|
||||
return [
|
||||
{
|
||||
provide: NavControllerToken, useValue: element
|
||||
provide: NavControllerToken, useValue: nearestNavElement
|
||||
},
|
||||
|
||||
{
|
||||
|
@ -1,10 +1,13 @@
|
||||
export { IonNavDelegate } from './components/ion-nav';
|
||||
export { IonicAngularModule } from './module';
|
||||
|
||||
/* Directives/Components */
|
||||
export { IonNavDelegate } from './components/ion-nav';
|
||||
|
||||
/* Providers */
|
||||
export { ActionSheetController, ActionSheetProxy } from './providers/action-sheet-controller';
|
||||
export { AlertController, AlertProxy } from './providers/alert-controller';
|
||||
export { App } from './providers/app';
|
||||
export { LoadingController, LoadingProxy } from './providers/loading-controller';
|
||||
export { ModalController, ModalProxy } from './providers/modal-controller';
|
||||
export { NavController } from './providers/nav-controller';
|
||||
export { ToastController, ToastProxy } from './providers/toast-controller';
|
@ -1,3 +1,4 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import {
|
||||
ModuleWithProviders,
|
||||
NgModule,
|
||||
@ -11,6 +12,7 @@ import { SelectValueAccessor } from './control-value-accessors/select-value-acce
|
||||
import { TextValueAccessor } from './control-value-accessors/text-value-accessor';
|
||||
|
||||
|
||||
/* Components */
|
||||
import { IonNavDelegate } from './components/ion-nav';
|
||||
|
||||
/* Providers */
|
||||
@ -18,6 +20,7 @@ import { ActionSheetController } from './providers/action-sheet-controller';
|
||||
import { AlertController } from './providers/alert-controller';
|
||||
import { AngularComponentMounter } from './providers/angular-component-mounter';
|
||||
import { LoadingController } from './providers/loading-controller';
|
||||
import { ModalController } from './providers/modal-controller';
|
||||
import { ToastController } from './providers/toast-controller';
|
||||
|
||||
@NgModule({
|
||||
@ -37,6 +40,9 @@ import { ToastController } from './providers/toast-controller';
|
||||
SelectValueAccessor,
|
||||
TextValueAccessor
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
],
|
||||
@ -50,6 +56,7 @@ export class IonicAngularModule {
|
||||
ActionSheetController,
|
||||
AngularComponentMounter,
|
||||
LoadingController,
|
||||
ModalController,
|
||||
ToastController
|
||||
]
|
||||
};
|
||||
|
@ -1,51 +1,70 @@
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
ApplicationRef,
|
||||
ComponentFactoryResolver,
|
||||
ComponentRef,
|
||||
Injectable,
|
||||
Injector,
|
||||
NgZone,
|
||||
ReflectiveInjector,
|
||||
Type,
|
||||
Type
|
||||
} from '@angular/core';
|
||||
|
||||
import { getProviders } from '../di/di';
|
||||
import { AngularMountingData } from '../types/interfaces';
|
||||
|
||||
const elementToComponentRefMap = new Map<HTMLElement, ComponentRef<any>>();
|
||||
|
||||
@Injectable()
|
||||
export class AngularComponentMounter {
|
||||
|
||||
constructor(private defaultCfr: ComponentFactoryResolver, private zone: NgZone) {
|
||||
constructor(private defaultCfr: ComponentFactoryResolver, private zone: NgZone, private appRef: ApplicationRef) {
|
||||
}
|
||||
|
||||
attachViewToDom(parentElement: HTMLElement, componentToMount: Type<any>, providers: any[], changeDetection: ChangeDetectorRef, componentResolveFactory: ComponentFactoryResolver, injector: Injector): Promise<AngularMountingData> {
|
||||
attachViewToDom(parentElement: HTMLElement, hostElement: HTMLElement, componentToMount: Type<any>, componentResolveFactory: ComponentFactoryResolver, injector: Injector, classesToAdd: string[]): Promise<AngularMountingData> {
|
||||
|
||||
return new Promise((resolve) => {
|
||||
this.zone.run(() => {
|
||||
console.log('parentElement: ', parentElement);
|
||||
|
||||
const crf = componentResolveFactory ? componentResolveFactory : this.defaultCfr;
|
||||
const mountingData = attachViewToDom(crf, componentToMount, parentElement, providers, changeDetection, injector);
|
||||
|
||||
const mountingData = attachViewToDom(crf, parentElement, hostElement, componentToMount, injector, this.appRef, classesToAdd);
|
||||
resolve(mountingData);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
removeViewFromDom(componentRef: ComponentRef<any>): Promise<any> {
|
||||
removeViewFromDom(childElement: HTMLElement): Promise<any> {
|
||||
return new Promise((resolve) => {
|
||||
this.zone.run(() => {
|
||||
componentRef.destroy();
|
||||
removeViewFromDom(childElement);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function attachViewToDom(crf: ComponentFactoryResolver, componentToMount: Type<any>, element: HTMLElement, providers: any, changeDetection: ChangeDetectorRef, injector: Injector): AngularMountingData {
|
||||
const componentFactory = crf.resolveComponentFactory(componentToMount);
|
||||
const componentProviders = ReflectiveInjector.resolve(providers);
|
||||
const childInjector = ReflectiveInjector.fromResolvedProviders(componentProviders, injector);
|
||||
const componentRef = componentFactory.create(childInjector, [], element);
|
||||
export function removeViewFromDom(childElement: HTMLElement) {
|
||||
const componentRef = elementToComponentRefMap.get(childElement);
|
||||
if (componentRef) {
|
||||
componentRef.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
changeDetection.detectChanges();
|
||||
export function attachViewToDom(crf: ComponentFactoryResolver, parentElement: HTMLElement, hostElement: HTMLElement, componentToMount: Type<any>, injector: Injector, appRef: ApplicationRef, classesToAdd: string[]): AngularMountingData {
|
||||
|
||||
const componentProviders = ReflectiveInjector.resolve(getProviders(parentElement));
|
||||
const componentFactory = crf.resolveComponentFactory(componentToMount);
|
||||
const childInjector = ReflectiveInjector.fromResolvedProviders(componentProviders, injector);
|
||||
const componentRef = componentFactory.create(childInjector, [], hostElement);
|
||||
for (const clazz of classesToAdd) {
|
||||
hostElement.classList.add(clazz);
|
||||
}
|
||||
|
||||
parentElement.appendChild(hostElement);
|
||||
|
||||
appRef.attachView(componentRef.hostView);
|
||||
|
||||
elementToComponentRefMap.set(hostElement, componentRef);
|
||||
|
||||
return {
|
||||
componentFactory,
|
||||
@ -53,7 +72,7 @@ export function attachViewToDom(crf: ComponentFactoryResolver, componentToMount:
|
||||
componentRef: componentRef,
|
||||
instance: componentRef.instance,
|
||||
angularHostElement: componentRef.location.nativeElement,
|
||||
element: componentRef.location.nativeElement,
|
||||
element: hostElement,
|
||||
};
|
||||
}
|
||||
|
||||
|
134
packages/angular/src/providers/modal-controller.ts
Normal file
134
packages/angular/src/providers/modal-controller.ts
Normal file
@ -0,0 +1,134 @@
|
||||
import {
|
||||
ComponentFactoryResolver,
|
||||
Injectable,
|
||||
Injector,
|
||||
Type,
|
||||
} from '@angular/core';
|
||||
|
||||
import {
|
||||
FrameworkDelegate,
|
||||
ModalDismissEvent,
|
||||
ModalOptions
|
||||
} from '@ionic/core';
|
||||
|
||||
import { AngularComponentMounter } from '../providers/angular-component-mounter';
|
||||
import { AngularMountingData } from '../types/interfaces';
|
||||
|
||||
import { ensureElementInBody, hydrateElement } from '../util/util';
|
||||
|
||||
let modalId = 0;
|
||||
|
||||
@Injectable()
|
||||
export class ModalController implements FrameworkDelegate {
|
||||
|
||||
constructor(private angularComponentMounter: AngularComponentMounter, private componentResolveFactory: ComponentFactoryResolver, private injector: Injector) {
|
||||
}
|
||||
|
||||
create(opts?: ModalOptions): ModalProxy {
|
||||
opts.delegate = this;
|
||||
return getModalProxy(opts);
|
||||
}
|
||||
|
||||
attachViewToDom(elementOrContainerToMountTo: HTMLElement, elementOrComponentToMount: Type<any>, _propsOrDataObj?: any, classesToAdd?: string[]): Promise<AngularMountingData> {
|
||||
|
||||
const hostElement = document.createElement('div');
|
||||
return this.angularComponentMounter.attachViewToDom(elementOrContainerToMountTo, hostElement, elementOrComponentToMount, this.componentResolveFactory, this.injector, classesToAdd);
|
||||
}
|
||||
|
||||
removeViewFromDom(_parentElement: HTMLElement, childElement: HTMLElement) {
|
||||
return this.angularComponentMounter.removeViewFromDom(childElement);
|
||||
}
|
||||
}
|
||||
|
||||
export function getModalProxy(opts: ModalOptions) {
|
||||
return {
|
||||
id: modalId++,
|
||||
state: PRESENTING,
|
||||
opts: opts,
|
||||
present: function() { return present(this); },
|
||||
dismiss: function() { return dismiss(this); },
|
||||
onDidDismiss: function(callback: (data: any, role: string) => void) {
|
||||
(this as ModalProxyInternal).onDidDismissHandler = callback;
|
||||
},
|
||||
onWillDismiss: function(callback: (data: any, role: string) => void) {
|
||||
(this as ModalProxyInternal).onWillDismissHandler = callback;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function present(modalProxy: ModalProxyInternal): Promise<any> {
|
||||
modalProxy.state = PRESENTING;
|
||||
return loadOverlay(modalProxy.opts).then((modalElement: HTMLIonModalElement) => {
|
||||
Object.assign(modalElement, modalProxy.opts);
|
||||
modalProxy.element = modalElement;
|
||||
|
||||
const onDidDismissHandler = (event: ModalDismissEvent) => {
|
||||
modalElement.removeEventListener(ION_MODAL_DID_DISMISS_EVENT, onDidDismissHandler);
|
||||
if (modalProxy.onDidDismissHandler) {
|
||||
modalProxy.onDidDismissHandler(event.detail.data, event.detail.role);
|
||||
}
|
||||
};
|
||||
|
||||
const onWillDismissHandler = (event: ModalDismissEvent) => {
|
||||
modalElement.removeEventListener(ION_MODAL_WILL_DISMISS_EVENT, onWillDismissHandler);
|
||||
if (modalProxy.onWillDismissHandler) {
|
||||
modalProxy.onWillDismissHandler(event.detail.data, event.detail.role);
|
||||
}
|
||||
};
|
||||
|
||||
modalElement.addEventListener(ION_MODAL_DID_DISMISS_EVENT, onDidDismissHandler);
|
||||
modalElement.addEventListener(ION_MODAL_WILL_DISMISS_EVENT, onWillDismissHandler);
|
||||
|
||||
if (modalProxy.state === PRESENTING) {
|
||||
return modalElement.present();
|
||||
}
|
||||
|
||||
// we'll only ever get here if someone tried to dismiss the overlay or mess with it's internal state
|
||||
// attribute before it could async load and present itself.
|
||||
// with that in mind, just return null to make the TS compiler happy
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
export function dismiss(modalProxy: ModalProxyInternal): Promise<any> {
|
||||
modalProxy.state = DISMISSING;
|
||||
if (modalProxy.element) {
|
||||
if (modalProxy.state === DISMISSING) {
|
||||
return modalProxy.element.dismiss();
|
||||
}
|
||||
}
|
||||
// either we're not in the dismissing state
|
||||
// or we're calling this before the element is created
|
||||
// so just return a resolved promise
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
export function loadOverlay(opts: ModalOptions): Promise<HTMLIonModalElement> {
|
||||
const element = ensureElementInBody('ion-modal-controller') as HTMLIonModalControllerElement;
|
||||
return hydrateElement(element).then(() => {
|
||||
return element.create(opts);
|
||||
});
|
||||
}
|
||||
|
||||
export interface ModalProxy {
|
||||
present(): Promise<void>;
|
||||
dismiss(): Promise<void>;
|
||||
onDidDismiss(callback: (data: any, role: string) => void): void;
|
||||
onWillDismiss(callback: (data: any, role: string) => void): void;
|
||||
}
|
||||
|
||||
export interface ModalProxyInternal extends ModalProxy {
|
||||
id: number;
|
||||
opts: ModalOptions;
|
||||
state: number;
|
||||
element: HTMLIonModalElement;
|
||||
onDidDismissHandler?: (data: any, role: string) => void;
|
||||
onWillDismissHandler?: (data: any, role: string) => void;
|
||||
}
|
||||
|
||||
export const PRESENTING = 1;
|
||||
export const DISMISSING = 2;
|
||||
|
||||
const ION_MODAL_DID_DISMISS_EVENT = 'ionModalDidDismiss';
|
||||
const ION_MODAL_WILL_DISMISS_EVENT = 'ionModalWillDismiss';
|
||||
|
@ -12,4 +12,4 @@ export interface AngularMountingData extends FrameworkMountingData {
|
||||
componentRef?: ComponentRef<any>;
|
||||
instance?: any;
|
||||
angularHostElement?: HTMLElement;
|
||||
}
|
||||
}
|
||||
|
1
packages/core/src/components.d.ts
vendored
1
packages/core/src/components.d.ts
vendored
@ -1630,6 +1630,7 @@ declare global {
|
||||
enterAnimation?: AnimationBuilder;
|
||||
leaveAnimation?: AnimationBuilder;
|
||||
animate?: boolean;
|
||||
delegate?: FrameworkDelegate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ export class ActionSheetController {
|
||||
|
||||
// give this action sheet a unique id
|
||||
actionSheet.actionSheetId = `action-sheet-${id}`;
|
||||
actionSheet.style.zIndex = (20000 + id).toString();
|
||||
|
||||
// convert the passed in action sheet options into props
|
||||
// that get passed down into the new action sheet
|
||||
|
@ -125,6 +125,8 @@ export class ActionSheet {
|
||||
}
|
||||
this.ionActionSheetWillPresent.emit();
|
||||
|
||||
this.el.style.zIndex = `${20000 + this.actionSheetId}`;
|
||||
|
||||
// get the user's animation fn if one was provided
|
||||
const animationBuilder = this.enterAnimation || this.config.get('actionSheetEnter', this.mode === 'ios' ? iosEnterAnimation : mdEnterAnimation);
|
||||
|
||||
|
@ -23,7 +23,6 @@ export class AlertController {
|
||||
|
||||
// give this action sheet a unique id
|
||||
alert.alertId = `alert-${id}`;
|
||||
alert.style.zIndex = (20000 + id).toString();
|
||||
|
||||
// convert the passed in action sheet options into props
|
||||
// that get passed down into the new action sheet
|
||||
|
@ -131,6 +131,8 @@ export class Alert {
|
||||
}
|
||||
this.ionAlertWillPresent.emit();
|
||||
|
||||
this.el.style.zIndex = `${20000 + this.alertId}`;
|
||||
|
||||
// get the user's animation fn if one was provided
|
||||
const animationBuilder = this.enterAnimation || this.config.get('alertEnter', this.mode === 'ios' ? iosEnterAnimation : mdEnterAnimation);
|
||||
|
||||
|
@ -22,7 +22,6 @@ export class LoadingController {
|
||||
|
||||
// give this loading a unique id
|
||||
loading.loadingId = `loading-${id}`;
|
||||
loading.style.zIndex = (20000 + id).toString();
|
||||
|
||||
// convert the passed in loading options into props
|
||||
// that get passed down into the new loading
|
||||
|
@ -129,6 +129,8 @@ export class Loading {
|
||||
|
||||
this.ionLoadingWillPresent.emit();
|
||||
|
||||
this.el.style.zIndex = `${20000 + this.loadingId}`;
|
||||
|
||||
// get the user's animation fn if one was provided
|
||||
const animationBuilder = this.enterAnimation || this.config.get('loadingEnter', this.mode === 'ios' ? iosEnterAnimation : mdEnterAnimation);
|
||||
|
||||
|
@ -20,7 +20,6 @@ export class ModalController {
|
||||
|
||||
// give this modal a unique id
|
||||
modal.modalId = `modal-${id}`;
|
||||
modal.style.zIndex = (10000 + id).toString();
|
||||
|
||||
// convert the passed in modal options into props
|
||||
// that get passed down into the new modal
|
||||
|
@ -4,9 +4,12 @@ import {
|
||||
AnimationBuilder,
|
||||
AnimationController,
|
||||
Config,
|
||||
FrameworkDelegate,
|
||||
OverlayDismissEvent,
|
||||
OverlayDismissEventDetail
|
||||
} from '../../index';
|
||||
|
||||
import { DomFrameworkDelegate } from '../../utils/dom-framework-delegate';
|
||||
import { domControllerAsync, playAnimationAsync } from '../../utils/helpers';
|
||||
import { createThemedClasses } from '../../utils/theme';
|
||||
|
||||
@ -73,12 +76,13 @@ export class Modal {
|
||||
@Prop() enterAnimation: AnimationBuilder;
|
||||
@Prop() leaveAnimation: AnimationBuilder;
|
||||
@Prop() animate: boolean;
|
||||
@Prop({ mutable: true }) delegate: FrameworkDelegate;
|
||||
|
||||
private animation: Animation;
|
||||
|
||||
private usersComponentElement: HTMLElement;
|
||||
|
||||
@Method()
|
||||
present() {
|
||||
async present() {
|
||||
if (this.animation) {
|
||||
this.animation.destroy();
|
||||
this.animation = null;
|
||||
@ -86,27 +90,39 @@ export class Modal {
|
||||
|
||||
this.ionModalWillPresent.emit();
|
||||
|
||||
this.el.style.zIndex = `${20000 + this.modalId}`;
|
||||
|
||||
// get the user's animation fn if one was provided
|
||||
const animationBuilder = this.enterAnimation || this.config.get('modalEnter', this.mode === 'ios' ? iosEnterAnimation : mdEnterAnimation);
|
||||
|
||||
// build the animation and kick it off
|
||||
// build the animation and kick it off
|
||||
return this.animationCtrl.create(animationBuilder, this.el).then(animation => {
|
||||
this.animation = animation;
|
||||
if (!this.animate) {
|
||||
// if the duration is 0, it won't actually animate I don't think
|
||||
// TODO - validate this
|
||||
this.animation = animation.duration(0);
|
||||
}
|
||||
return playAnimationAsync(animation);
|
||||
}).then((animation) => {
|
||||
animation.destroy();
|
||||
this.ionModalDidPresent.emit();
|
||||
});
|
||||
const userComponentParent = this.el.querySelector(`.${USER_COMPONENT_MODAL_CONTAINER_CLASS}`);
|
||||
if (!this.delegate) {
|
||||
this.delegate = new DomFrameworkDelegate();
|
||||
}
|
||||
|
||||
const cssClasses = ['ion-page'];
|
||||
if (this.cssClass && this.cssClass.length) {
|
||||
cssClasses.push(this.cssClass);
|
||||
}
|
||||
|
||||
// add the modal by default to the data being passed
|
||||
this.data = this.data || {};
|
||||
this.data.modal = this.el;
|
||||
const mountingData = await this.delegate.attachViewToDom(userComponentParent, this.component, this.data, cssClasses);
|
||||
this.usersComponentElement = mountingData.element;
|
||||
this.animation = await this.animationCtrl.create(animationBuilder, this.el);
|
||||
if (!this.animate) {
|
||||
// if the duration is 0, it won't actually animate I don't think
|
||||
// TODO - validate this
|
||||
this.animation = this.animation.duration(0);
|
||||
}
|
||||
await playAnimationAsync(this.animation);
|
||||
this.animation.destroy();
|
||||
this.ionModalDidPresent.emit();
|
||||
}
|
||||
|
||||
@Method()
|
||||
dismiss(data?: any, role?: string) {
|
||||
async dismiss(data?: any, role?: string) {
|
||||
if (this.animation) {
|
||||
this.animation.destroy();
|
||||
this.animation = null;
|
||||
@ -116,25 +132,37 @@ export class Modal {
|
||||
role
|
||||
});
|
||||
|
||||
if (!this.delegate) {
|
||||
this.delegate = new DomFrameworkDelegate();
|
||||
}
|
||||
|
||||
// get the user's animation fn if one was provided
|
||||
const animationBuilder = this.leaveAnimation || this.config.get('modalLeave', this.mode === 'ios' ? iosLeaveAnimation : mdLeaveAnimation);
|
||||
|
||||
return this.animationCtrl.create(animationBuilder, this.el).then(animation => {
|
||||
this.animation = animation;
|
||||
return playAnimationAsync(animation);
|
||||
}).then((animation) => {
|
||||
animation.destroy();
|
||||
return domControllerAsync(Context.dom.write, () => {
|
||||
this.el.parentNode.removeChild(this.el);
|
||||
});
|
||||
}).then(() => {
|
||||
this.ionModalDidDismiss.emit({
|
||||
data,
|
||||
role
|
||||
});
|
||||
this.animation = await this.animationCtrl.create(animationBuilder, this.el);
|
||||
await playAnimationAsync(this.animation);
|
||||
this.animation.destroy();
|
||||
|
||||
|
||||
await domControllerAsync(Context.dom.write, () => {});
|
||||
|
||||
// TODO - Figure out how to make DOM controller work with callbacks that return a promise or are async
|
||||
const userComponentParent = this.el.querySelector(`.${USER_COMPONENT_MODAL_CONTAINER_CLASS}`);
|
||||
await this.delegate.removeViewFromDom(userComponentParent, this.usersComponentElement);
|
||||
|
||||
this.el.parentElement.removeChild(this.el);
|
||||
|
||||
this.ionModalDidDismiss.emit({
|
||||
data,
|
||||
role
|
||||
});
|
||||
}
|
||||
|
||||
@Method()
|
||||
getUserComponentContainer(): HTMLElement {
|
||||
return this.el.querySelector(`.${USER_COMPONENT_MODAL_CONTAINER_CLASS}`);
|
||||
}
|
||||
|
||||
@Listen('ionDismiss')
|
||||
protected onDismiss(ev: UIEvent) {
|
||||
ev.stopPropagation();
|
||||
@ -188,6 +216,7 @@ export interface ModalOptions {
|
||||
enterAnimation?: AnimationBuilder;
|
||||
exitAnimation?: AnimationBuilder;
|
||||
cssClass?: string;
|
||||
delegate?: FrameworkDelegate;
|
||||
}
|
||||
|
||||
|
||||
@ -213,3 +242,5 @@ export {
|
||||
mdEnterAnimation as mdModalEnterAnimation,
|
||||
mdLeaveAnimation as mdModalLeaveAnimation
|
||||
};
|
||||
|
||||
export const USER_COMPONENT_MODAL_CONTAINER_CLASS = 'modal-wrapper';
|
||||
|
@ -32,6 +32,11 @@ string
|
||||
any
|
||||
|
||||
|
||||
#### delegate
|
||||
|
||||
any
|
||||
|
||||
|
||||
#### enableBackdropDismiss
|
||||
|
||||
boolean
|
||||
@ -89,6 +94,11 @@ string
|
||||
any
|
||||
|
||||
|
||||
#### delegate
|
||||
|
||||
any
|
||||
|
||||
|
||||
#### enableBackdropDismiss
|
||||
|
||||
boolean
|
||||
@ -144,6 +154,9 @@ boolean
|
||||
#### dismiss()
|
||||
|
||||
|
||||
#### getUserComponentContainer()
|
||||
|
||||
|
||||
#### present()
|
||||
|
||||
|
||||
|
@ -28,10 +28,21 @@
|
||||
|
||||
<script>
|
||||
async function presentModal() {
|
||||
const element = document.createElement('div');
|
||||
element.innerHTML = `
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Cool Modal</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<div>Sure, I like dogs. I like caravans moar.</div>
|
||||
</ion-content>
|
||||
`;
|
||||
const modalController = document.querySelector('ion-modal-controller');
|
||||
await modalController.componentOnReady();
|
||||
const modalElement = await modalController.create({
|
||||
component: 'page-one'
|
||||
component: element
|
||||
});
|
||||
modalElement.present();
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ export function buildIOSTransition(rootTransition: Transition, enteringView: Vie
|
||||
|
||||
if (enteringView) {
|
||||
const enteringContent = rootTransition.create();
|
||||
enteringContent.addElement(enteringView.element.querySelectorAll('ion-header > *:not(ion-navbar),ion-footer > *'));
|
||||
enteringContent.addElement(enteringView.element.querySelectorAll('ion-header > *:not(ion-toolbar),ion-footer > *'));
|
||||
|
||||
rootTransition.add(enteringContent);
|
||||
|
||||
@ -42,30 +42,30 @@ export function buildIOSTransition(rootTransition: Transition, enteringView: Vie
|
||||
enteringContent.beforeClearStyles([OPACITY]).fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true);
|
||||
}
|
||||
|
||||
const enteringNavbarEle = enteringView.element.querySelector('ion-navbar');
|
||||
if (enteringNavbarEle) {
|
||||
const enteringNavBar = rootTransition.create();
|
||||
enteringNavBar.addElement(enteringNavbarEle);
|
||||
const enteringToolBarEle = enteringView.element.querySelector('ion-toolbar');
|
||||
if (enteringToolBarEle) {
|
||||
const enteringToolBar = rootTransition.create();
|
||||
enteringToolBar.addElement(enteringToolBarEle);
|
||||
|
||||
rootTransition.add(enteringNavBar);
|
||||
rootTransition.add(enteringToolBar);
|
||||
|
||||
const enteringTitle = rootTransition.create();
|
||||
enteringTitle.addElement(enteringNavbarEle.querySelector('ion-title'));
|
||||
const enteringNavbarItems = rootTransition.create();
|
||||
enteringNavbarItems.addElement(enteringNavbarEle.querySelectorAll('ion-buttons,[menuToggle]'));
|
||||
const enteringNavbarBg = rootTransition.create();
|
||||
enteringNavbarBg.addElement(enteringNavbarEle.querySelector('.toolbar-background'));
|
||||
enteringTitle.addElement(enteringToolBarEle.querySelector('ion-title'));
|
||||
const enteringToolBarItems = rootTransition.create();
|
||||
enteringToolBarItems.addElement(enteringToolBarEle.querySelectorAll('ion-buttons,[menuToggle]'));
|
||||
const enteringToolBarBg = rootTransition.create();
|
||||
enteringToolBarBg.addElement(enteringToolBarEle.querySelector('.toolbar-background'));
|
||||
const enteringBackButton = rootTransition.create();
|
||||
enteringBackButton.addElement(enteringNavbarEle.querySelector('.back-button'));
|
||||
enteringBackButton.addElement(enteringToolBarEle.querySelector('.back-button'));
|
||||
|
||||
enteringNavBar
|
||||
enteringToolBar
|
||||
.add(enteringTitle)
|
||||
.add(enteringNavbarItems)
|
||||
.add(enteringNavbarBg)
|
||||
.add(enteringToolBarItems)
|
||||
.add(enteringToolBarBg)
|
||||
.add(enteringBackButton);
|
||||
|
||||
enteringTitle.fromTo(OPACITY, 0.01, 1, true);
|
||||
enteringNavbarItems.fromTo(OPACITY, 0.01, 1, true);
|
||||
enteringToolBarItems.fromTo(OPACITY, 0.01, 1, true);
|
||||
|
||||
if (backDirection) {
|
||||
enteringTitle.fromTo(TRANSLATEX, OFF_LEFT, CENTER, true);
|
||||
@ -75,10 +75,10 @@ export function buildIOSTransition(rootTransition: Transition, enteringView: Vie
|
||||
enteringBackButton.beforeAddClass(SHOW_BACK_BTN_CSS).fromTo(OPACITY, 0.01, 1, true);
|
||||
}
|
||||
} else {
|
||||
// entering navbar, forward direction
|
||||
// entering toolbar, forward direction
|
||||
enteringTitle.fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true);
|
||||
|
||||
enteringNavbarBg.beforeClearStyles([OPACITY]).fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true);
|
||||
enteringToolBarBg.beforeClearStyles([OPACITY]).fromTo(TRANSLATEX, OFF_RIGHT, CENTER, true);
|
||||
|
||||
if (canNavGoBack(enteringView.nav)) {
|
||||
// forward direction, entering page has a back button
|
||||
@ -86,10 +86,10 @@ export function buildIOSTransition(rootTransition: Transition, enteringView: Vie
|
||||
|
||||
|
||||
const enteringBackBtnText = rootTransition.create();
|
||||
enteringBackBtnText.addElement(enteringNavbarEle.querySelector('.back-button-text'));
|
||||
enteringBackBtnText.addElement(enteringToolBarEle.querySelector('.back-button-text'));
|
||||
|
||||
enteringBackBtnText.fromTo(TRANSLATEX, (isRTL ? '-100px' : '100px'), '0px');
|
||||
enteringNavBar.add(enteringBackBtnText);
|
||||
enteringToolBar.add(enteringBackBtnText);
|
||||
|
||||
} else {
|
||||
enteringBackButton.beforeRemoveClass(SHOW_BACK_BTN_CSS);
|
||||
@ -103,7 +103,7 @@ export function buildIOSTransition(rootTransition: Transition, enteringView: Vie
|
||||
|
||||
const leavingContent = rootTransition.create();
|
||||
leavingContent.addElement(leavingView.element);
|
||||
leavingContent.addElement(leavingView.element.querySelectorAll('ion-header > *:not(ion-navbar),ion-footer > *'));
|
||||
leavingContent.addElement(leavingView.element.querySelectorAll('ion-header > *:not(ion-toolbar),ion-footer > *'));
|
||||
|
||||
rootTransition.add(leavingContent);
|
||||
|
||||
@ -119,59 +119,59 @@ export function buildIOSTransition(rootTransition: Transition, enteringView: Vie
|
||||
.afterClearStyles([TRANSFORM, OPACITY]);
|
||||
}
|
||||
|
||||
const leavingNavbarEle = leavingView.element.querySelector('ion-navbar');
|
||||
if (leavingNavbarEle) {
|
||||
const leavingNavBar = rootTransition.create();
|
||||
leavingNavBar.addElement(leavingNavbarEle);
|
||||
const leavingToolBarEle = leavingView.element.querySelector('ion-toolbar');
|
||||
if (leavingToolBarEle) {
|
||||
const leavingToolBar = rootTransition.create();
|
||||
leavingToolBar.addElement(leavingToolBarEle);
|
||||
|
||||
const leavingTitle = rootTransition.create();
|
||||
leavingTitle.addElement(leavingNavbarEle.querySelector('ion-title'));
|
||||
leavingTitle.addElement(leavingToolBarEle.querySelector('ion-title'));
|
||||
|
||||
const leavingNavbarItems = rootTransition.create();
|
||||
leavingNavbarItems.addElement(leavingNavbarEle.querySelectorAll('ion-buttons,[menuToggle]'));
|
||||
const leavingToolBarItems = rootTransition.create();
|
||||
leavingToolBarItems.addElement(leavingToolBarEle.querySelectorAll('ion-buttons,[menuToggle]'));
|
||||
|
||||
const leavingNavbarBg = rootTransition.create();
|
||||
leavingNavbarBg.addElement(leavingNavbarEle.querySelector('.toolbar-background'));
|
||||
const leavingToolBarBg = rootTransition.create();
|
||||
leavingToolBarBg.addElement(leavingToolBarEle.querySelector('.toolbar-background'));
|
||||
|
||||
const leavingBackButton = rootTransition.create();
|
||||
leavingBackButton.addElement(leavingNavbarEle.querySelector('.back-button'));
|
||||
leavingBackButton.addElement(leavingToolBarEle.querySelector('.back-button'));
|
||||
|
||||
leavingNavBar
|
||||
leavingToolBar
|
||||
.add(leavingTitle)
|
||||
.add(leavingNavbarItems)
|
||||
.add(leavingToolBarItems)
|
||||
.add(leavingBackButton)
|
||||
.add(leavingNavbarBg);
|
||||
this.add(leavingNavBar);
|
||||
.add(leavingToolBarBg);
|
||||
this.add(leavingToolBar);
|
||||
|
||||
// fade out leaving navbar items
|
||||
// fade out leaving toolbar items
|
||||
leavingBackButton.fromTo(OPACITY, 0.99, 0);
|
||||
leavingTitle.fromTo(OPACITY, 0.99, 0);
|
||||
leavingNavbarItems.fromTo(OPACITY, 0.99, 0);
|
||||
leavingToolBarItems.fromTo(OPACITY, 0.99, 0);
|
||||
|
||||
if (backDirection) {
|
||||
// leaving navbar, back direction
|
||||
// leaving toolbar, back direction
|
||||
leavingTitle.fromTo(TRANSLATEX, CENTER, (isRTL ? '-100%' : '100%'));
|
||||
|
||||
// leaving navbar, back direction, and there's no entering navbar
|
||||
// leaving toolbar, back direction, and there's no entering toolbar
|
||||
// should just slide out, no fading out
|
||||
leavingNavbarBg
|
||||
leavingToolBarBg
|
||||
.beforeClearStyles([OPACITY])
|
||||
.fromTo(TRANSLATEX, CENTER, (isRTL ? '-100%' : '100%'));
|
||||
|
||||
const leavingBackBtnText = rootTransition.create();
|
||||
leavingBackBtnText.addElement(leavingNavbarEle.querySelector('.back-button-text'));
|
||||
leavingBackBtnText.addElement(leavingToolBarEle.querySelector('.back-button-text'));
|
||||
leavingBackBtnText.fromTo(TRANSLATEX, CENTER, (isRTL ? -300 : 300) + 'px');
|
||||
leavingNavBar.add(leavingBackBtnText);
|
||||
leavingToolBar.add(leavingBackBtnText);
|
||||
|
||||
} else {
|
||||
// leaving navbar, forward direction
|
||||
// leaving toolbar, forward direction
|
||||
leavingTitle
|
||||
.fromTo(TRANSLATEX, CENTER, OFF_LEFT)
|
||||
.afterClearStyles([TRANSFORM]);
|
||||
|
||||
leavingBackButton.afterClearStyles([OPACITY]);
|
||||
leavingTitle.afterClearStyles([OPACITY]);
|
||||
leavingNavbarItems.afterClearStyles([OPACITY]);
|
||||
leavingToolBarItems.afterClearStyles([OPACITY]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,14 +27,14 @@ export function buildMdTransition(rootTransition: Transition, enteringView: View
|
||||
.fromTo('opacity', 0.01, 1, true);
|
||||
}
|
||||
|
||||
const enteringNavbarEle = enteringView.element.querySelector('ion-navbar');
|
||||
if (enteringNavbarEle) {
|
||||
const enteringNavBar = rootTransition.create();
|
||||
enteringNavBar.addElement(enteringNavbarEle);
|
||||
rootTransition.add(enteringNavBar);
|
||||
const enteringToolbarEle = enteringView.element.querySelector('ion-toolbar');
|
||||
if (enteringToolbarEle) {
|
||||
const enteringToolBar = rootTransition.create();
|
||||
enteringToolBar.addElement(enteringToolbarEle);
|
||||
rootTransition.add(enteringToolBar);
|
||||
|
||||
const enteringBackButton = rootTransition.create();
|
||||
enteringBackButton.addElement(enteringNavbarEle.querySelector('.back-button'));
|
||||
enteringBackButton.addElement(enteringToolbarEle.querySelector('.back-button'));
|
||||
rootTransition.add(enteringBackButton);
|
||||
|
||||
if (canNavGoBack(enteringView.nav)) {
|
||||
|
@ -19,7 +19,6 @@ export class PickerController {
|
||||
|
||||
// give this picker a unique id
|
||||
picker.pickerId = `picker-${id}`;
|
||||
picker.style.zIndex = (20000 + id).toString();
|
||||
|
||||
// convert the passed in picker options into props
|
||||
// that get passed down into the new picker
|
||||
|
@ -89,6 +89,8 @@ export class Picker {
|
||||
|
||||
this.ionPickerWillPresent.emit();
|
||||
|
||||
this.el.style.zIndex = `${20000 + this.pickerId}`;
|
||||
|
||||
// get the user's animation fn if one was provided
|
||||
const animationBuilder = this.enterAnimation || this.config.get('pickerEnter', iosEnterAnimation);
|
||||
|
||||
|
@ -21,7 +21,6 @@ export class PopoverController {
|
||||
|
||||
// give this popover a unique id
|
||||
popover.popoverId = `popover-${id}`;
|
||||
popover.style.zIndex = (10000 + id).toString();
|
||||
|
||||
// convert the passed in popover options into props
|
||||
// that get passed down into the new popover
|
||||
|
@ -86,6 +86,8 @@ export class Popover {
|
||||
}
|
||||
this.ionPopoverWillPresent.emit();
|
||||
|
||||
this.el.style.zIndex = `${10000 + this.popoverId}`;
|
||||
|
||||
// get the user's animation fn if one was provided
|
||||
const animationBuilder = this.enterAnimation || this.config.get('popoverEnter', this.mode === 'ios' ? iosEnterAnimation : mdEnterAnimation);
|
||||
|
||||
@ -249,4 +251,3 @@ export {
|
||||
mdEnterAnimation as mdPopoverEnterAnimation,
|
||||
mdLeaveAnimation as mdPopoverLeaveAnimation
|
||||
};
|
||||
|
||||
|
@ -10,7 +10,8 @@ const routes: Routes = [
|
||||
{ path: 'actionSheet', loadChildren: 'app/action-sheet/action-sheet.module#ActionSheetModule' },
|
||||
{ path: 'toast', loadChildren: 'app/toast/toast.module#ToastModule' },
|
||||
{ path: 'loading', loadChildren: 'app/loading/loading.module#LoadingModule' },
|
||||
{ path: 'nav', loadChildren: 'app/nav/nav.module#NavModule' }
|
||||
{ path: 'nav', loadChildren: 'app/nav/nav.module#NavModule' },
|
||||
{ path: 'modal', loadChildren: 'app/modal/modal.module#ModalModule' }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@ -24,4 +24,7 @@
|
||||
<li>
|
||||
<a href='nav'>Nav Page</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href='modal'>Modal Page</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -0,0 +1,35 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'page-one',
|
||||
template: `
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Page One</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
Page One
|
||||
<ul>
|
||||
<li>ngOnInit - {{ngOnInitDetection}}</li>
|
||||
</ul>
|
||||
</ion-content>
|
||||
`
|
||||
})
|
||||
export class ModalPageToPresent {
|
||||
|
||||
ngOnInitDetection = 'initial';
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
ngOnInit() {
|
||||
console.log('page one ngOnInit');
|
||||
setInterval(() => {
|
||||
this.ngOnInitDetection = '' + Date.now();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
}
|
35
packages/demos/angular/src/app/modal/modal-page.component.ts
Normal file
35
packages/demos/angular/src/app/modal/modal-page.component.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
import { ModalController } from '@ionic/angular';
|
||||
import { ModalPageToPresent } from './modal-page-to-present';
|
||||
|
||||
@Component({
|
||||
selector: 'app-modal-page',
|
||||
template: `
|
||||
<ion-app>
|
||||
<ion-page class="show-page">
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Test</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content padding>
|
||||
<ion-button (click)="clickMe()">Open Basic Modal</ion-button>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</ion-app>
|
||||
`
|
||||
})
|
||||
export class ModalPageComponent {
|
||||
|
||||
constructor(private modalController: ModalController) {
|
||||
}
|
||||
|
||||
clickMe() {
|
||||
const modal = this.modalController.create({
|
||||
component: ModalPageToPresent
|
||||
});
|
||||
return modal.present();
|
||||
}
|
||||
|
||||
}
|
14
packages/demos/angular/src/app/modal/modal-routing.module.ts
Normal file
14
packages/demos/angular/src/app/modal/modal-routing.module.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
|
||||
import { ModalPageComponent } from './modal-page.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', component: ModalPageComponent }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class ModalRoutingModule { }
|
27
packages/demos/angular/src/app/modal/modal.module.ts
Normal file
27
packages/demos/angular/src/app/modal/modal.module.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { IonicAngularModule } from '@ionic/angular';
|
||||
|
||||
import { ModalPageComponent } from './modal-page.component';
|
||||
import { ModalRoutingModule } from './modal-routing.module';
|
||||
|
||||
import { ModalPageToPresent } from './modal-page-to-present';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicAngularModule.forRoot(),
|
||||
ModalRoutingModule
|
||||
],
|
||||
declarations: [
|
||||
ModalPageComponent,
|
||||
ModalPageToPresent
|
||||
],
|
||||
providers: [
|
||||
],
|
||||
entryComponents: [
|
||||
ModalPageToPresent
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class ModalModule { }
|
49
packages/demos/react/src/pages/ModalPage.js
Normal file
49
packages/demos/react/src/pages/ModalPage.js
Normal file
@ -0,0 +1,49 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import PageThree from './PageThree';
|
||||
|
||||
export default class ModalPage extends Component {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.style = {
|
||||
height: '100%'
|
||||
};
|
||||
this.state = {
|
||||
content: 'modal page - ' + 50
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
console.log('componentDidMount');
|
||||
setInterval(() => {
|
||||
this.setState({ content: 'Modal page - ' + Math.random() * 1000});
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
dismiss() {
|
||||
return this.props.modal.dismiss();
|
||||
}
|
||||
|
||||
render() {
|
||||
return [
|
||||
<ion-header ref={(element) => this.element = element}>
|
||||
<ion-toolbar>
|
||||
<ion-title>Modal Page</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>,
|
||||
<ion-content>
|
||||
Modal Page
|
||||
<div>
|
||||
<ion-button onClick={() => this.dismiss()}>Dismiss</ion-button>
|
||||
</div>
|
||||
<div>
|
||||
Some random content: {this.state.content}
|
||||
</div>
|
||||
<div>
|
||||
Props : {this.props.paramOne}
|
||||
</div>
|
||||
</ion-content>
|
||||
];
|
||||
}
|
||||
}
|
@ -1,6 +1,9 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { createModal } from '@ionic/react';
|
||||
|
||||
import PageTwo from './PageTwo';
|
||||
import ModalPage from './ModalPage';
|
||||
|
||||
export default class PageOne extends Component {
|
||||
|
||||
@ -35,6 +38,14 @@ export default class PageOne extends Component {
|
||||
nav.push(PageTwo, { paramOne: 'Tobey Flenderson'});
|
||||
}
|
||||
|
||||
openModal() {
|
||||
return createModal({
|
||||
component: ModalPage
|
||||
}).then((modal) => {
|
||||
return modal.present();
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
setInterval(() => {
|
||||
this.setState({ content: Math.random() * 1000});
|
||||
@ -54,6 +65,9 @@ export default class PageOne extends Component {
|
||||
<div>
|
||||
<ion-button onClick={() => this.goToPageTwo()}>Go to Page Two</ion-button>
|
||||
</div>
|
||||
<div>
|
||||
<ion-button onClick={() => this.openModal()}>OpenModal</ion-button>
|
||||
</div>
|
||||
<div>
|
||||
Some random content: {this.state.content}
|
||||
</div>
|
||||
|
13
packages/react/src/apis/modal.ts
Normal file
13
packages/react/src/apis/modal.ts
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
import { ModalOptions } from '@ionic/core';
|
||||
|
||||
import { Delegate } from '../react-framework-delegate';
|
||||
import { getOrAppendElement } from '../utils/helpers';
|
||||
|
||||
export function createModal(opts: ModalOptions): Promise<HTMLIonModalElement> {
|
||||
opts.delegate = Delegate;
|
||||
const element = getOrAppendElement('ion-modal-controller') as HTMLIonModalControllerElement;
|
||||
return (element as any).componentOnReady().then(() => {
|
||||
return element.create(opts);
|
||||
});
|
||||
}
|
@ -1,2 +1,3 @@
|
||||
export { Delegate } from './react-framework-delegate';
|
||||
export { createModal } from './apis/modal';
|
||||
export * from './utils/wc-shim';
|
10
packages/react/src/utils/helpers.ts
Normal file
10
packages/react/src/utils/helpers.ts
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
export function getOrAppendElement(tagName: string): Element {
|
||||
const element = document.querySelector(tagName);
|
||||
if (element) {
|
||||
return element;
|
||||
}
|
||||
const tmp = document.createElement(tagName);
|
||||
document.body.appendChild(tmp);
|
||||
return tmp;
|
||||
}
|
Reference in New Issue
Block a user