diff --git a/core/src/components/action-sheet-controller/action-sheet-controller.tsx b/core/src/components/action-sheet-controller/action-sheet-controller.tsx index d89477826e..6980da4224 100644 --- a/core/src/components/action-sheet-controller/action-sheet-controller.tsx +++ b/core/src/components/action-sheet-controller/action-sheet-controller.tsx @@ -1,33 +1,15 @@ -import { Component, Listen, Method, Prop } from '@stencil/core'; +import { Component, Method, Prop } from '@stencil/core'; import { ActionSheetOptions, OverlayController } from '../../interface'; -import { createOverlay, dismissOverlay, getTopOverlay, removeLastOverlay } from '../../utils/overlays'; +import { createOverlay, dismissOverlay, getOverlay } from '../../utils/overlays'; @Component({ tag: 'ion-action-sheet-controller' }) export class ActionSheetController implements OverlayController { - private actionSheets = new Map(); - @Prop({ context: 'document' }) doc!: Document; - @Listen('body:ionActionSheetWillPresent') - protected actionSheetWillPresent(ev: any) { - this.actionSheets.set(ev.target.overlayId, ev.target); - } - - @Listen('body:ionActionSheetWillDismiss') - @Listen('body:ionActionSheetDidUnload') - protected actionSheetWillDismiss(ev: any) { - this.actionSheets.delete(ev.target.overlayId); - } - - @Listen('body:keyup.escape') - protected escapeKeyUp() { - removeLastOverlay(this.actionSheets); - } - /** * Create an action sheet overlay with action sheet options. */ @@ -41,7 +23,7 @@ export class ActionSheetController implements OverlayController { */ @Method() dismiss(data?: any, role?: string, actionSheetId = -1) { - return dismissOverlay(data, role, this.actionSheets, actionSheetId); + return dismissOverlay(this.doc, data, role, 'ion-action-sheet', actionSheetId); } /** @@ -49,6 +31,6 @@ export class ActionSheetController implements OverlayController { */ @Method() getTop(): HTMLIonActionSheetElement { - return getTopOverlay(this.actionSheets); + return getOverlay(this.doc, 'ion-action-sheet') as HTMLIonActionSheetElement; } } diff --git a/core/src/components/alert-controller/alert-controller.tsx b/core/src/components/alert-controller/alert-controller.tsx index 0b2ad831d3..ee60bca122 100644 --- a/core/src/components/alert-controller/alert-controller.tsx +++ b/core/src/components/alert-controller/alert-controller.tsx @@ -1,33 +1,15 @@ -import { Component, Listen, Method, Prop } from '@stencil/core'; +import { Component, Method, Prop } from '@stencil/core'; import { AlertOptions, OverlayController } from '../../interface'; -import { createOverlay, dismissOverlay, getTopOverlay, removeLastOverlay } from '../../utils/overlays'; +import { createOverlay, dismissOverlay, getOverlay } from '../../utils/overlays'; @Component({ tag: 'ion-alert-controller' }) export class AlertController implements OverlayController { - private alerts = new Map(); - @Prop({ context: 'document' }) doc!: Document; - @Listen('body:ionAlertWillPresent') - protected alertWillPresent(ev: any) { - this.alerts.set(ev.target.overlayId, ev.target); - } - - @Listen('body:ionAlertWillDismiss') - @Listen('body:ionAlertDidUnload') - protected alertWillDismiss(ev: any) { - this.alerts.delete(ev.target.overlayId); - } - - @Listen('body:keyup.escape') - protected escapeKeyUp() { - removeLastOverlay(this.alerts); - } - /** * Create an alert overlay with alert options */ @@ -41,7 +23,7 @@ export class AlertController implements OverlayController { */ @Method() dismiss(data?: any, role?: string, alertId = -1) { - return dismissOverlay(data, role, this.alerts, alertId); + return dismissOverlay(this.doc, data, role, 'ion-alert', alertId); } /** @@ -49,6 +31,6 @@ export class AlertController implements OverlayController { */ @Method() getTop(): HTMLIonAlertElement { - return getTopOverlay(this.alerts); + return getOverlay(this.doc, 'ion-alert') as HTMLIonAlertElement; } } diff --git a/core/src/components/loading-controller/loading-controller.tsx b/core/src/components/loading-controller/loading-controller.tsx index a2c7d8611c..833db60203 100644 --- a/core/src/components/loading-controller/loading-controller.tsx +++ b/core/src/components/loading-controller/loading-controller.tsx @@ -1,33 +1,15 @@ -import { Component, Listen, Method, Prop } from '@stencil/core'; +import { Component, Method, Prop } from '@stencil/core'; import { LoadingOptions, OverlayController } from '../../interface'; -import { createOverlay, dismissOverlay, getTopOverlay, removeLastOverlay } from '../../utils/overlays'; +import { createOverlay, dismissOverlay, getOverlay } from '../../utils/overlays'; @Component({ tag: 'ion-loading-controller' }) export class LoadingController implements OverlayController { - private loadings = new Map(); - @Prop({ context: 'document' }) doc!: Document; - @Listen('body:ionLoadingWillPresent') - protected loadingWillPresent(ev: any) { - this.loadings.set(ev.target.overlayId, ev.target); - } - - @Listen('body:ionLoadingWillDismiss') - @Listen('body:ionLoadingDidUnload') - protected loadingWillDismiss(ev: any) { - this.loadings.delete(ev.target.overlayId); - } - - @Listen('body:keyup.escape') - protected escapeKeyUp() { - removeLastOverlay(this.loadings); - } - /** * Create a loading overlay with loading options. */ @@ -41,7 +23,7 @@ export class LoadingController implements OverlayController { */ @Method() dismiss(data?: any, role?: string, loadingId = -1) { - return dismissOverlay(data, role, this.loadings, loadingId); + return dismissOverlay(this.doc, data, role, 'ion-loading', loadingId); } /** @@ -49,6 +31,6 @@ export class LoadingController implements OverlayController { */ @Method() getTop(): HTMLIonLoadingElement { - return getTopOverlay(this.loadings); + return getOverlay(this.doc, 'ion-loading') as HTMLIonLoadingElement; } } diff --git a/core/src/components/modal-controller/modal-controller.tsx b/core/src/components/modal-controller/modal-controller.tsx index 196d02b93d..b54774e6be 100644 --- a/core/src/components/modal-controller/modal-controller.tsx +++ b/core/src/components/modal-controller/modal-controller.tsx @@ -1,33 +1,15 @@ -import { Component, Listen, Method, Prop } from '@stencil/core'; +import { Component, Method, Prop } from '@stencil/core'; import { ModalOptions, OverlayController } from '../../interface'; -import { createOverlay, dismissOverlay, getTopOverlay, removeLastOverlay } from '../../utils/overlays'; +import { createOverlay, dismissOverlay, getOverlay } from '../../utils/overlays'; @Component({ tag: 'ion-modal-controller' }) export class ModalController implements OverlayController { - private modals = new Map(); - @Prop({ context: 'document' }) doc!: Document; - @Listen('body:ionModalWillPresent') - protected modalWillPresent(ev: any) { - this.modals.set(ev.target.overlayId, ev.target); - } - - @Listen('body:ionModalWillDismiss') - @Listen('body:ionModalDidUnload') - protected modalWillDismiss(ev: any) { - this.modals.delete(ev.target.overlayId); - } - - @Listen('body:keyup.escape') - protected escapeKeyUp() { - removeLastOverlay(this.modals); - } - /** * Create a modal overlay with modal options. */ @@ -41,7 +23,7 @@ export class ModalController implements OverlayController { */ @Method() dismiss(data?: any, role?: string, modalId = -1) { - return dismissOverlay(data, role, this.modals, modalId); + return dismissOverlay(this.doc, data, role, 'ion-modal', modalId); } /** @@ -49,6 +31,6 @@ export class ModalController implements OverlayController { */ @Method() getTop(): HTMLIonModalElement { - return getTopOverlay(this.modals); + return getOverlay(this.doc, 'ion-modal') as HTMLIonModalElement; } } diff --git a/core/src/components/picker-controller/picker-controller.tsx b/core/src/components/picker-controller/picker-controller.tsx index 936f49f54f..cbe26a713d 100644 --- a/core/src/components/picker-controller/picker-controller.tsx +++ b/core/src/components/picker-controller/picker-controller.tsx @@ -1,7 +1,7 @@ -import { Component, Listen, Method, Prop } from '@stencil/core'; +import { Component, Method, Prop } from '@stencil/core'; import { OverlayController, PickerOptions } from '../../interface'; -import { createOverlay, dismissOverlay, getTopOverlay, removeLastOverlay } from '../../utils/overlays'; +import { createOverlay, dismissOverlay, getOverlay } from '../../utils/overlays'; /** @hidden */ @Component({ @@ -9,26 +9,8 @@ import { createOverlay, dismissOverlay, getTopOverlay, removeLastOverlay } from }) export class PickerController implements OverlayController { - private pickers = new Map(); - @Prop({ context: 'document' }) doc!: Document; - @Listen('body:ionPickerWillPresent') - protected pickerWillPresent(ev: any) { - this.pickers.set(ev.target.overlayId, ev.target); - } - - @Listen('body:ionPickerWillDismiss') - @Listen('body:ionPickerDidUnload') - protected pickerWillDismiss(ev: any) { - this.pickers.delete(ev.target.overlayId); - } - - @Listen('body:keyup.escape') - protected escapeKeyUp() { - removeLastOverlay(this.pickers); - } - /* * Create a picker overlay with picker options. */ @@ -42,7 +24,7 @@ export class PickerController implements OverlayController { */ @Method() dismiss(data?: any, role?: string, pickerId = -1) { - return dismissOverlay(data, role, this.pickers, pickerId); + return dismissOverlay(this.doc, data, role, 'ion-picker', pickerId); } /* @@ -50,6 +32,6 @@ export class PickerController implements OverlayController { */ @Method() getTop(): HTMLIonPickerElement { - return getTopOverlay(this.pickers); + return getOverlay(this.doc, 'ion-picker') as HTMLIonPickerElement; } } diff --git a/core/src/components/popover-controller/popover-controller.tsx b/core/src/components/popover-controller/popover-controller.tsx index e6e9a34a74..d4092aeea8 100644 --- a/core/src/components/popover-controller/popover-controller.tsx +++ b/core/src/components/popover-controller/popover-controller.tsx @@ -1,33 +1,15 @@ -import { Component, Listen, Method, Prop } from '@stencil/core'; +import { Component, Method, Prop } from '@stencil/core'; import { OverlayController, PopoverOptions } from '../../interface'; -import { createOverlay, dismissOverlay, getTopOverlay, removeLastOverlay } from '../../utils/overlays'; +import { createOverlay, dismissOverlay, getOverlay } from '../../utils/overlays'; @Component({ tag: 'ion-popover-controller' }) export class PopoverController implements OverlayController { - private popovers = new Map(); - @Prop({ context: 'document' }) doc!: Document; - @Listen('body:ionPopoverWillPresent') - protected popoverWillPresent(ev: any) { - this.popovers.set(ev.target.overlayId, ev.target); - } - - @Listen('body:ionPopoverWillDismiss') - @Listen('body:ionPopoverDidUnload') - protected popoverWillDismiss(ev: any) { - this.popovers.delete(ev.target.overlayId); - } - - @Listen('body:keyup.escape') - protected escapeKeyUp() { - removeLastOverlay(this.popovers); - } - /** * Create a popover overlay with popover options. */ @@ -41,7 +23,7 @@ export class PopoverController implements OverlayController { */ @Method() dismiss(data?: any, role?: string, popoverId = -1) { - return dismissOverlay(data, role, this.popovers, popoverId); + return dismissOverlay(this.doc, data, role, 'ion-popover', popoverId); } /** @@ -49,6 +31,6 @@ export class PopoverController implements OverlayController { */ @Method() getTop(): HTMLIonPopoverElement { - return getTopOverlay(this.popovers); + return getOverlay(this.doc, 'ion-popover') as HTMLIonPopoverElement; } } diff --git a/core/src/components/toast-controller/toast-controller.tsx b/core/src/components/toast-controller/toast-controller.tsx index 65e19d18d4..1c00880591 100644 --- a/core/src/components/toast-controller/toast-controller.tsx +++ b/core/src/components/toast-controller/toast-controller.tsx @@ -1,33 +1,15 @@ -import { Component, Listen, Method, Prop } from '@stencil/core'; +import { Component, Method, Prop } from '@stencil/core'; import { OverlayController, ToastOptions } from '../../interface'; -import { createOverlay, dismissOverlay, getTopOverlay, removeLastOverlay } from '../../utils/overlays'; +import { createOverlay, dismissOverlay, getOverlay } from '../../utils/overlays'; @Component({ tag: 'ion-toast-controller' }) export class ToastController implements OverlayController { - private toasts = new Map(); - @Prop({ context: 'document' }) doc!: Document; - @Listen('body:ionToastWillPresent') - protected toastWillPresent(ev: any) { - this.toasts.set(ev.target.overlayId, ev.target); - } - - @Listen('body:ionToastWillDismiss') - @Listen('body:ionToastDidUnload') - protected toastWillDismiss(ev: any) { - this.toasts.delete(ev.target.overlayId); - } - - @Listen('body:keyup.escape') - protected escapeKeyUp() { - removeLastOverlay(this.toasts); - } - /** * Create a toast overlay with toast options. */ @@ -41,7 +23,7 @@ export class ToastController implements OverlayController { */ @Method() dismiss(data?: any, role?: string, toastId = -1) { - return dismissOverlay(data, role, this.toasts, toastId); + return dismissOverlay(this.doc, data, role, 'ion-toast', toastId); } /** @@ -49,6 +31,6 @@ export class ToastController implements OverlayController { */ @Method() getTop(): HTMLIonToastElement { - return getTopOverlay(this.toasts); + return getOverlay(this.doc, 'ion-toast') as HTMLIonToastElement; } } diff --git a/core/src/utils/overlays-interface.ts b/core/src/utils/overlays-interface.ts index 8acbd2b73a..364b28bc2e 100644 --- a/core/src/utils/overlays-interface.ts +++ b/core/src/utils/overlays-interface.ts @@ -38,7 +38,7 @@ export interface OverlayController { export interface HTMLIonOverlayElement extends HTMLStencilElement { overlayId: number; + backdropDismiss?: boolean; + dismiss(data?: any, role?: string): Promise; } - -export type OverlayMap = Map; diff --git a/core/src/utils/overlays.ts b/core/src/utils/overlays.ts index eb4edfcdb0..9c24a18f87 100644 --- a/core/src/utils/overlays.ts +++ b/core/src/utils/overlays.ts @@ -1,8 +1,11 @@ -import { AnimationBuilder, HTMLIonOverlayElement, IonicConfig, OverlayInterface, OverlayMap } from '../interface'; +import { AnimationBuilder, HTMLIonOverlayElement, IonicConfig, OverlayInterface } from '../interface'; -let lastId = 1; +let lastId = 0; export function createOverlay, B>(element: T, opts: B): Promise { + const doc = element.ownerDocument; + connectListeners(doc); + // convert the passed in overlay options into props // that get passed down into the new overlay Object.assign(element, opts); @@ -10,39 +13,59 @@ export function createOverlay, B>( element.overlayId = lastId++; // append the overlay element to the document body - const doc = element.ownerDocument; - const appRoot = doc.querySelector('ion-app') || doc.body; - appRoot.appendChild(element); + getAppRoot(doc).appendChild(element); + + doc.body.addEventListener('keyup', ev => { + if (ev.key === 'Escape') { + const lastOverlay = getOverlay(doc); + if (lastOverlay && lastOverlay.backdropDismiss) { + lastOverlay.dismiss(null, BACKDROP); + } + } + }); return element.componentOnReady(); } -export function dismissOverlay(data: any, role: string | undefined, overlays: OverlayMap, id: number): Promise { - id = id >= 0 ? id : getHighestId(overlays); - const overlay = overlays.get(id); +export function connectListeners(doc: Document) { + if (lastId === 0) { + lastId = 1; + doc.body.addEventListener('keyup', ev => { + if (ev.key === 'Escape') { + const lastOverlay = getOverlay(doc); + if (lastOverlay && lastOverlay.backdropDismiss === true) { + lastOverlay.dismiss('backdrop'); + } + } + }); + } +} + +export function dismissOverlay(doc: Document, data: any, role: string | undefined, overlayTag: string, id: number): Promise { + const overlay = getOverlay(doc, overlayTag, id); if (!overlay) { return Promise.reject('overlay does not exist'); } return overlay.dismiss(data, role); } -export function getTopOverlay(overlays: OverlayMap): T { - return overlays.get(getHighestId(overlays)) as T; +export function getOverlays(doc: Document, overlayTag?: string): HTMLIonOverlayElement[] { + const overlays = Array.from(getAppRoot(doc).children) as HTMLIonOverlayElement[]; + if (overlayTag == null) { + return overlays; + } + overlayTag = overlayTag.toUpperCase(); + return overlays.filter(c => c.tagName === overlayTag); } -export function getHighestId(overlays: OverlayMap) { - let minimum = -1; - overlays.forEach((_, id) => { - if (id > minimum) { - minimum = id; - } - }); - return minimum; -} - -export function removeLastOverlay(overlays: OverlayMap) { - const toRemove = getTopOverlay(overlays); - return toRemove ? toRemove.dismiss() : Promise.resolve(); +export function getOverlay(doc: Document, overlayTag?: string, id?: number): HTMLIonOverlayElement | undefined { + const overlays = getOverlays(doc, overlayTag); + if (id != null) { + return overlays.find(o => o.overlayId === id); + } + return (id == null) + ? overlays[overlays.length - 1] + : overlays.find(o => o.overlayId === id); } export async function present( @@ -94,6 +117,10 @@ export async function dismiss( overlay.el.remove(); } +function getAppRoot(doc: Document) { + return doc.querySelector('ion-app') || doc.body; +} + async function overlayAnimation( overlay: OverlayInterface, animationBuilder: AnimationBuilder,