diff --git a/angular/src/providers/menu-controller.ts b/angular/src/providers/menu-controller.ts index aab98b1592..7b76abe6fe 100644 --- a/angular/src/providers/menu-controller.ts +++ b/angular/src/providers/menu-controller.ts @@ -1,24 +1,18 @@ -import { DOCUMENT } from '@angular/common'; -import { Inject, Injectable } from '@angular/core'; +import { Injectable } from '@angular/core'; +import { menuController } from '@ionic/core'; -import { proxyMethod } from '../util/util'; - -const CTRL = 'ion-menu-controller'; @Injectable({ providedIn: 'root', }) export class MenuController { - constructor(@Inject(DOCUMENT) private doc: any) { - } - /** * Programmatically open the Menu. * @param [menuId] Optionally get the menu by its id, or side. * @return returns a promise when the menu is fully opened */ - open(menuId?: string): Promise { - return proxyMethod(CTRL, this.doc, 'open', menuId); + open(menuId?: string) { + return menuController.open(menuId); } /** @@ -28,8 +22,8 @@ export class MenuController { * @param [menuId] Optionally get the menu by its id, or side. * @return returns a promise when the menu is fully closed */ - close(menuId?: string): Promise { - return proxyMethod(CTRL, this.doc, 'close', menuId); + close(menuId?: string) { + return menuController.close(menuId); } /** @@ -38,8 +32,8 @@ export class MenuController { * @param [menuId] Optionally get the menu by its id, or side. * @return returns a promise when the menu has been toggled */ - toggle(menuId?: string): Promise { - return proxyMethod(CTRL, this.doc, 'toggle', menuId); + toggle(menuId?: string) { + return menuController.toggle(menuId); } /** @@ -50,8 +44,8 @@ export class MenuController { * @param [menuId] Optionally get the menu by its id, or side. * @return Returns the instance of the menu, which is useful for chaining. */ - enable(shouldEnable: boolean, menuId?: string): Promise { - return proxyMethod(CTRL, this.doc, 'enable', shouldEnable, menuId); + enable(shouldEnable: boolean, menuId?: string) { + return menuController.enable(shouldEnable, menuId); } /** @@ -61,7 +55,7 @@ export class MenuController { * @return Returns the instance of the menu, which is useful for chaining. * @deprecated Use swipeGesture() instead */ - swipeEnable(shouldEnable: boolean, menuId?: string): Promise { + swipeEnable(shouldEnable: boolean, menuId?: string) { console.warn('MenuController.swipeEnable is deprecated. Use MenuController.swipeGesture() instead'); return this.swipeGesture(shouldEnable, menuId); } @@ -72,8 +66,8 @@ export class MenuController { * @param [menuId] Optionally get the menu by its id, or side. * @return Returns the instance of the menu, which is useful for chaining. */ - swipeGesture(shouldEnable: boolean, menuId?: string): Promise { - return proxyMethod(CTRL, this.doc, 'swipeGesture', shouldEnable, menuId); + swipeGesture(shouldEnable: boolean, menuId?: string) { + return menuController.swipeGesture(shouldEnable, menuId); } /** @@ -81,16 +75,16 @@ export class MenuController { * @return Returns true if the specified menu is currently open, otherwise false. * If the menuId is not specified, it returns true if ANY menu is currenly open. */ - isOpen(menuId?: string): Promise { - return proxyMethod(CTRL, this.doc, 'isOpen', menuId); + isOpen(menuId?: string) { + return menuController.isOpen(menuId); } /** * @param [menuId] Optionally get the menu by its id, or side. * @return Returns true if the menu is currently enabled, otherwise false. */ - isEnabled(menuId?: string): Promise { - return proxyMethod(CTRL, this.doc, 'isEnabled', menuId); + isEnabled(menuId?: string) { + return menuController.isEnabled(menuId); } /** @@ -102,21 +96,21 @@ export class MenuController { * @param [menuId] Optionally get the menu by its id, or side. * @return Returns the instance of the menu if found, otherwise `null`. */ - get(menuId?: string): Promise { - return proxyMethod(CTRL, this.doc, 'get', menuId); + get(menuId?: string) { + return menuController.get(menuId); } /** * @return Returns the instance of the menu already opened, otherwise `null`. */ - getOpen(): Promise { - return proxyMethod(CTRL, this.doc, 'getOpen'); + getOpen() { + return menuController.getOpen(); } /** * @return Returns an array of all menu instances. */ - getMenus(): Promise { - return proxyMethod(CTRL, this.doc, 'getMenus'); + getMenus() { + return menuController.getMenus(); } } diff --git a/angular/src/util/util.ts b/angular/src/util/util.ts index 8173970708..37bbe613f5 100644 --- a/angular/src/util/util.ts +++ b/angular/src/util/util.ts @@ -1,4 +1,3 @@ -import { HTMLStencilElement } from '../types/interfaces'; declare const __zone_symbol__requestAnimationFrame: any; declare const requestAnimationFrame: any; @@ -12,18 +11,3 @@ export const raf = (h: any) => { } return setTimeout(h); }; - -export const proxyMethod = (ctrlName: string, doc: Document, methodName: string, ...args: any[]) => { - const controller = ensureElementInBody(ctrlName, doc); - return controller.componentOnReady() - .then(() => (controller as any)[methodName].apply(controller, args)); -}; - -export const ensureElementInBody = (elementName: string, doc: Document) => { - let element = doc.querySelector(elementName); - if (!element) { - element = doc.createElement(elementName); - doc.body.appendChild(element); - } - return element as HTMLStencilElement; -}; diff --git a/core/package.json b/core/package.json index 5529541410..4d1cc894df 100644 --- a/core/package.json +++ b/core/package.json @@ -34,7 +34,7 @@ "tslib": "^1.10.0" }, "devDependencies": { - "@stencil/core": "1.3.1", + "@stencil/core": "1.3.2", "@stencil/sass": "1.0.1", "@types/jest": "24.0.17", "@types/node": "12.7.1", diff --git a/core/src/components.d.ts b/core/src/components.d.ts index b3e89ed348..33562ca43d 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -31,7 +31,6 @@ import { ItemReorderEventDetail, LoadingOptions, MenuChangeEventDetail, - MenuControllerI, MenuI, ModalOptions, NavComponent, @@ -1378,7 +1377,6 @@ export namespace Components { 'type': 'submit' | 'reset' | 'button'; } interface IonMenuController { - '_getInstance': () => Promise; /** * Close the menu. If a menu is specified, it will close that menu. If no menu is specified, then it will close any menu that is open. If it does not find any open menus, it will return `false`. * @param menu The menuId or side of the menu to close. 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 1ece5497b0..7264b099dc 100644 --- a/core/src/components/action-sheet-controller/action-sheet-controller.tsx +++ b/core/src/components/action-sheet-controller/action-sheet-controller.tsx @@ -1,4 +1,4 @@ -import { Component, ComponentInterface, Method } from '@stencil/core'; +import { Build, Component, ComponentInterface, Method } from '@stencil/core'; import { ActionSheetOptions, OverlayController } from '../../interface'; import { createOverlay, dismissOverlay, getOverlay } from '../../utils/overlays'; @@ -8,6 +8,14 @@ import { createOverlay, dismissOverlay, getOverlay } from '../../utils/overlays' }) export class ActionSheetController implements ComponentInterface, OverlayController { + constructor() { + if (Build.isDev) { + console.warn(`[DEPRECATED][ion-action-sheet-controller] Use the actionSheetController export from @ionic/core: + import { actionSheetController } from '@ionic/core'; + const actionSheet = await actionSheetController.create({...});`); + } + } + /** * Create an action sheet overlay with action sheet options. * diff --git a/core/src/components/action-sheet/action-sheet.tsx b/core/src/components/action-sheet/action-sheet.tsx index 1835de4033..d2365739e7 100644 --- a/core/src/components/action-sheet/action-sheet.tsx +++ b/core/src/components/action-sheet/action-sheet.tsx @@ -2,7 +2,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Meth import { getIonMode } from '../../global/ionic-global'; import { ActionSheetButton, AnimationBuilder, CssClassMap, OverlayEventDetail, OverlayInterface } from '../../interface'; -import { BACKDROP, dismiss, eventMethod, isCancel, present, safeCall } from '../../utils/overlays'; +import { BACKDROP, dismiss, eventMethod, isCancel, prepareOverlay, present, safeCall } from '../../utils/overlays'; import { getClassMap } from '../../utils/theme'; import { iosEnterAnimation } from './animations/ios.enter'; @@ -27,7 +27,7 @@ export class ActionSheet implements ComponentInterface, OverlayInterface { animation?: any; mode = getIonMode(this); - @Element() el!: HTMLElement; + @Element() el!: HTMLIonActionSheetElement; /** @internal */ @Prop() overlayIndex!: number; @@ -113,6 +113,10 @@ export class ActionSheet implements ComponentInterface, OverlayInterface { return present(this, 'actionSheetEnter', iosEnterAnimation, mdEnterAnimation); } + constructor() { + prepareOverlay(this.el); + } + /** * Dismiss the action sheet overlay after it has been presented. * diff --git a/core/src/components/action-sheet/readme.md b/core/src/components/action-sheet/readme.md index c7a4b569ac..708b67cc30 100644 --- a/core/src/components/action-sheet/readme.md +++ b/core/src/components/action-sheet/readme.md @@ -79,45 +79,45 @@ export class ActionSheetExample { ```javascript async function presentActionSheet() { - const actionSheetController = document.querySelector('ion-action-sheet-controller'); - const actionSheet = await actionSheetController.create({ - header: "Albums", - buttons: [{ - text: 'Delete', - role: 'destructive', - icon: 'trash', - handler: () => { - console.log('Delete clicked'); - } - }, { - text: 'Share', - icon: 'share', - handler: () => { - console.log('Share clicked'); - } - }, { - text: 'Play (open modal)', - icon: 'arrow-dropright-circle', - handler: () => { - console.log('Play clicked'); - } - }, { - text: 'Favorite', - icon: 'heart', - handler: () => { - console.log('Favorite clicked'); - } - }, { - text: 'Cancel', - icon: 'close', - role: 'cancel', - handler: () => { - console.log('Cancel clicked'); - } - }] - }); - await actionSheet.present(); + const actionSheet = document.createElement('ion-action-sheet'); + + actionSheet.header = "Albums"; + actionSheet.buttons = [{ + text: 'Delete', + role: 'destructive', + icon: 'trash', + handler: () => { + console.log('Delete clicked'); + } + }, { + text: 'Share', + icon: 'share', + handler: () => { + console.log('Share clicked'); + } + }, { + text: 'Play (open modal)', + icon: 'arrow-dropright-circle', + handler: () => { + console.log('Play clicked'); + } + }, { + text: 'Favorite', + icon: 'heart', + handler: () => { + console.log('Favorite clicked'); + } + }, { + text: 'Cancel', + icon: 'close', + role: 'cancel', + handler: () => { + console.log('Cancel clicked'); + } + }]; + document.body.appendChild(actionSheet); + return actionSheet.present(); } ``` diff --git a/core/src/components/action-sheet/test/basic/index.html b/core/src/components/action-sheet/test/basic/index.html index 06f96f49f1..52bd855f5c 100644 --- a/core/src/components/action-sheet/test/basic/index.html +++ b/core/src/components/action-sheet/test/basic/index.html @@ -13,9 +13,6 @@ - - - Action Sheet - Basic @@ -59,8 +56,7 @@ async function presentBasic() { const mode = Ionic.mode; - const actionSheetController = document.querySelector('ion-action-sheet-controller'); - const actionSheetElement = await actionSheetController.create({ + const actionSheetElement = Object.assign(document.createElement('ion-action-sheet'), { header: "Albums", buttons: [{ text: 'Delete', @@ -97,17 +93,16 @@ } }] }); + document.body.append(actionSheetElement); await actionSheetElement.present(); } async function presentAlert() { - const actionSheetController = document.querySelector('ion-action-sheet-controller'); - const actionSheetElement = await actionSheetController.create({ + const actionSheetElement = Object.assign(document.createElement('ion-action-sheet'), { buttons: [{ text: 'Open Alert', handler: async () => { - const alertController = document.querySelector('ion-alert-controller'); - const alert = await alertController.create({ + const alert = Object.assign(document.createElement('ion-alert'), { header: 'Alert from Action Sheet', subHeader: 'Subtitle', message: 'This is an alert message.', @@ -119,6 +114,7 @@ } }] }); + document.body.appendChild(alert); await alert.present(); return false; } @@ -130,12 +126,12 @@ } }] }); + document.body.append(actionSheetElement); await actionSheetElement.present(); } async function presentCancelOnly() { - const actionSheetController = document.querySelector('ion-action-sheet-controller'); - const actionSheetElement = await actionSheetController.create({ + const actionSheetElement = Object.assign(document.createElement('ion-action-sheet'), { buttons: [ { text: 'Cancel', @@ -146,12 +142,12 @@ } ] }); + document.body.append(actionSheetElement); await actionSheetElement.present(); } async function presentWithCssClass() { - const actionSheetController = document.querySelector('ion-action-sheet-controller'); - const actionSheetElement = await actionSheetController.create({ + const actionSheetElement = Object.assign(document.createElement('ion-action-sheet'), { header: "Custom Css Class", cssClass: "my-color-class my-custom-class", buttons: [ @@ -194,14 +190,14 @@ } ] }); + document.body.append(actionSheetElement); await actionSheetElement.present(); } async function presentIcons() { const mode = Ionic.mode; - const actionSheetController = document.querySelector('ion-action-sheet-controller'); - const actionSheetElement = await actionSheetController.create({ + const actionSheetElement = Object.assign(document.createElement('ion-action-sheet'), { header: "Albums", buttons: [{ text: 'Delete', @@ -237,13 +233,13 @@ console.log('Cancel clicked'); } }] - }) + }); + document.body.append(actionSheetElement); await actionSheetElement.present(); } async function presentNoBackdropDismiss() { - const actionSheetController = document.querySelector('ion-action-sheet-controller'); - const actionSheetElement = await actionSheetController.create({ + const actionSheetElement = Object.assign(document.createElement('ion-action-sheet'), { backdropDismiss: false, buttons: [{ text: 'Archive', @@ -264,12 +260,12 @@ } }] }); + document.body.append(actionSheetElement); await actionSheetElement.present(); } async function presentScroll() { - const actionSheetController = document.querySelector('ion-action-sheet-controller'); - const actionSheetElement = await actionSheetController.create({ + const actionSheetElement = Object.assign(document.createElement('ion-action-sheet'), { buttons: [ { text: 'Add Reaction', @@ -357,12 +353,12 @@ } ] }); + document.body.append(actionSheetElement); await actionSheetElement.present(); } async function presentScrollNoCancel() { - const actionSheetController = document.querySelector('ion-action-sheet-controller'); - const actionSheetElement = await actionSheetController.create({ + const actionSheetElement = Object.assign(document.createElement('ion-action-sheet'), { buttons: [ { text: 'Add Reaction', @@ -428,6 +424,7 @@ } ] }); + document.body.append(actionSheetElement); await actionSheetElement.present(); } diff --git a/core/src/components/action-sheet/test/spec/index.html b/core/src/components/action-sheet/test/spec/index.html index df0f80b4f4..1077ae3f87 100644 --- a/core/src/components/action-sheet/test/spec/index.html +++ b/core/src/components/action-sheet/test/spec/index.html @@ -21,8 +21,6 @@ - - Spec @@ -34,8 +32,7 @@ async function presentSpec() { const mode = Ionic.mode; - const actionSheetController = document.querySelector('ion-action-sheet-controller'); - const actionSheetElement = await actionSheetController.create({ + const actionSheetElement = Object.assign(document.createElement('ion-action-sheet'), { header: "Open in", buttons: [{ text: 'Item 1', @@ -69,6 +66,7 @@ } }] }); + document.body.appendChild(actionSheetElement); await actionSheetElement.present(); } diff --git a/core/src/components/action-sheet/test/standalone/index.html b/core/src/components/action-sheet/test/standalone/index.html index c9a6a6fca2..f51abea605 100644 --- a/core/src/components/action-sheet/test/standalone/index.html +++ b/core/src/components/action-sheet/test/standalone/index.html @@ -21,8 +21,6 @@ - - - + @@ -142,7 +142,6 @@ - diff --git a/core/src/components/loading-controller/loading-controller.tsx b/core/src/components/loading-controller/loading-controller.tsx index e091eba9ac..eefa66eb52 100644 --- a/core/src/components/loading-controller/loading-controller.tsx +++ b/core/src/components/loading-controller/loading-controller.tsx @@ -1,4 +1,4 @@ -import { Component, ComponentInterface, Method } from '@stencil/core'; +import { Build, Component, ComponentInterface, Method } from '@stencil/core'; import { LoadingOptions, OverlayController } from '../../interface'; import { createOverlay, dismissOverlay, getOverlay } from '../../utils/overlays'; @@ -8,6 +8,14 @@ import { createOverlay, dismissOverlay, getOverlay } from '../../utils/overlays' }) export class LoadingController implements ComponentInterface, OverlayController { + constructor() { + if (Build.isDev) { + console.warn(`[DEPRECATED][ion-loading-controller] Use the loadingController export from @ionic/core: + import { loadingController } from '@ionic/core'; + const modal = await loadingController.create({...});`); + } + } + /** * Create a loading overlay with loading options. * diff --git a/core/src/components/loading-controller/readme.md b/core/src/components/loading-controller/readme.md index 56c1041301..99ba90b4cd 100644 --- a/core/src/components/loading-controller/readme.md +++ b/core/src/components/loading-controller/readme.md @@ -8,25 +8,6 @@ Loading controllers programmatically control the loading component. Loadings can -## Usage - -### Javascript - -```javascript -async function presentLoading() { - const loadingController = document.querySelector('ion-loading-controller'); - - const loadingElement = await loadingController.create({ - message: 'Please wait...', - spinner: 'crescent', - duration: 2000 - }); - return await loadingElement.present(); -} -``` - - - ## Methods ### `create(options?: LoadingOptions | undefined) => Promise` diff --git a/core/src/components/loading-controller/usage/javascript.md b/core/src/components/loading-controller/usage/javascript.md deleted file mode 100644 index 386157826d..0000000000 --- a/core/src/components/loading-controller/usage/javascript.md +++ /dev/null @@ -1,12 +0,0 @@ -```javascript -async function presentLoading() { - const loadingController = document.querySelector('ion-loading-controller'); - - const loadingElement = await loadingController.create({ - message: 'Please wait...', - spinner: 'crescent', - duration: 2000 - }); - return await loadingElement.present(); -} -``` diff --git a/core/src/components/loading/loading.tsx b/core/src/components/loading/loading.tsx index ca02be14b9..63a1c69e97 100644 --- a/core/src/components/loading/loading.tsx +++ b/core/src/components/loading/loading.tsx @@ -3,7 +3,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Meth import { config } from '../../global/config'; import { getIonMode } from '../../global/ionic-global'; import { Animation, AnimationBuilder, OverlayEventDetail, OverlayInterface, SpinnerTypes } from '../../interface'; -import { BACKDROP, dismiss, eventMethod, present } from '../../utils/overlays'; +import { BACKDROP, dismiss, eventMethod, prepareOverlay, present } from '../../utils/overlays'; import { sanitizeDOMString } from '../../utils/sanitization'; import { getClassMap } from '../../utils/theme'; @@ -30,7 +30,7 @@ export class Loading implements ComponentInterface, OverlayInterface { animation?: Animation; mode = getIonMode(this); - @Element() el!: HTMLElement; + @Element() el!: HTMLIonLoadingElement; /** @internal */ @Prop() overlayIndex!: number; @@ -113,6 +113,10 @@ export class Loading implements ComponentInterface, OverlayInterface { */ @Event({ eventName: 'ionLoadingDidDismiss' }) didDismiss!: EventEmitter; + constructor() { + prepareOverlay(this.el); + } + componentWillLoad() { if (this.spinner === undefined) { const mode = getIonMode(this); diff --git a/core/src/components/loading/readme.md b/core/src/components/loading/readme.md index 91d7fcb482..2bbf9f09e0 100644 --- a/core/src/components/loading/readme.md +++ b/core/src/components/loading/readme.md @@ -62,13 +62,11 @@ export class LoadingExample { ```javascript async function presentLoading() { - const loadingController = document.querySelector('ion-loading-controller'); - - const loading = await loadingController.create({ - message: 'Hellooo', - duration: 2000 - }); + const loading = document.createElement('ion-loading'); + loading.message: 'Hellooo', + loading.duration: 2000; + document.body.appendChild(loading); await loading.present(); const { role, data } = await loading.onDidDismiss(); @@ -76,17 +74,16 @@ async function presentLoading() { console.log('Loading dismissed!'); } -async function presentLoadingWithOptions() { - const loadingController = document.querySelector('ion-loading-controller'); +function presentLoadingWithOptions() { + const loading = document.createElement('ion-loading'); + loading.spinner = null; + loading.duration = 5000; + loading.message = 'Please wait...'; + loading.translucent = true; + loading.cssClass = 'custom-class custom-loading'; - const loading = await loadingController.create({ - spinner: null, - duration: 5000, - message: 'Please wait...', - translucent: true, - cssClass: 'custom-class custom-loading' - }); - return await loading.present(); + document.body.appendChild(loading); + return loading.present(); } ``` diff --git a/core/src/components/loading/test/basic/index.html b/core/src/components/loading/test/basic/index.html index c082ae1c43..13fec93364 100644 --- a/core/src/components/loading/test/basic/index.html +++ b/core/src/components/loading/test/basic/index.html @@ -30,8 +30,6 @@ Show Backdrop Click Loading Show Loading with HTML content - - @@ -64,19 +62,19 @@ diff --git a/core/src/components/loading/test/standalone/index.html b/core/src/components/loading/test/standalone/index.html index a6d75156b8..c1ebb92f0d 100644 --- a/core/src/components/loading/test/standalone/index.html +++ b/core/src/components/loading/test/standalone/index.html @@ -18,8 +18,6 @@ - - - diff --git a/core/src/utils/animation/test/animationbuilder/e2e.ts b/core/src/utils/animation/test/animationbuilder/e2e.ts index 39b16dfbe7..9571799e65 100644 --- a/core/src/utils/animation/test/animationbuilder/e2e.ts +++ b/core/src/utils/animation/test/animationbuilder/e2e.ts @@ -1,9 +1,4 @@ -import { newE2EPage } from '@stencil/core/testing'; - -import { listenForEvent, waitForFunctionTestContext } from '../../../test/utils'; - -const navChanged = () => new Promise(resolve => window.addEventListener('ionRouteDidChange', resolve)); -const ROUTE_CHANGED = 'onRouteChanged'; +import { E2EPage, newE2EPage } from '@stencil/core/testing'; test('animation:backwards-compatibility animationbuilder', async () => { const page = await newE2EPage({ url: '/src/utils/animation/test/animationbuilder?_forceAnimationBuilder=true' }); @@ -25,41 +20,28 @@ test('animation:ios-transition css', async () => { await testNavigation(page); }); -const testNavigation = async page => { +const testNavigation = async (page: E2EPage) => { const screenshotCompares = []; - const body = await page.$('body'); - await listenForEvent(page, 'ionRouteDidChange', body, ROUTE_CHANGED); - const routeChangedCount: any = { count: 0 }; - await page.exposeFunction(ROUTE_CHANGED, () => { - routeChangedCount.count += 1; - }); + const ionRouteDidChange = await page.spyOnEvent('ionRouteDidChange'); screenshotCompares.push(await page.compareScreenshot()); - page.click('page-root ion-button.next'); - await waitForNavChange(page, routeChangedCount); + await page.click('page-root ion-button.next'); + await ionRouteDidChange.next(); page.click('page-one ion-button.next'); - await waitForNavChange(page, routeChangedCount); + await ionRouteDidChange.next(); page.click('page-two ion-button.next'); - await waitForNavChange(page, routeChangedCount); + await ionRouteDidChange.next(); page.click('page-three ion-back-button'); - await waitForNavChange(page, routeChangedCount); + await ionRouteDidChange.next(); page.click('page-two ion-back-button'); - await waitForNavChange(page, routeChangedCount); + await ionRouteDidChange.next(); page.click('page-one ion-back-button'); - await waitForNavChange(page, routeChangedCount); + await ionRouteDidChange.next(); - screenshotCompares.push(await page.compareScreenshot()); + screenshotCompares.push(await page.compareScreenshot('end navigation')); for (const screenshotCompare of screenshotCompares) { expect(screenshotCompare).toMatchScreenshot(); } }; - -const waitForNavChange = async (page, routeChangedCount) => { - await waitForFunctionTestContext((payload: any) => { - return payload.routeChangedCount.count === 1; - }, { routeChangedCount }); - - routeChangedCount.count = 0; -}; diff --git a/core/src/utils/animation/test/basic/e2e.ts b/core/src/utils/animation/test/basic/e2e.ts index 30f43d7c4c..e97649cdff 100644 --- a/core/src/utils/animation/test/basic/e2e.ts +++ b/core/src/utils/animation/test/basic/e2e.ts @@ -24,7 +24,7 @@ test(`animation:web: basic`, async () => { return payload.animationFinishedCount.count === 1; }, { animationFinishedCount }); - screenshotCompares.push(await page.compareScreenshot()); + screenshotCompares.push(await page.compareScreenshot('end animation')); }); test(`animation:css: basic`, async () => { @@ -49,5 +49,5 @@ test(`animation:css: basic`, async () => { return payload.animationFinishedCount.count === 1; }, { animationFinishedCount }); - screenshotCompares.push(await page.compareScreenshot()); + screenshotCompares.push(await page.compareScreenshot('end animation')); }); diff --git a/core/src/utils/animation/test/display/e2e.ts b/core/src/utils/animation/test/display/e2e.ts index 1b98bb8a48..526f212aa5 100644 --- a/core/src/utils/animation/test/display/e2e.ts +++ b/core/src/utils/animation/test/display/e2e.ts @@ -33,5 +33,5 @@ const runTest = async (page: any) => { return payload.animationStatus.join(', ') === ['AnimationBFinished', 'AnimationAFinished', 'AnimationRootFinished'].join(', '); }, { animationStatus }); - screenshotCompares.push(await page.compareScreenshot()); + screenshotCompares.push(await page.compareScreenshot('end animation')); }; diff --git a/core/src/utils/animation/test/hooks/e2e.ts b/core/src/utils/animation/test/hooks/e2e.ts index e4bec98179..96bf639068 100644 --- a/core/src/utils/animation/test/hooks/e2e.ts +++ b/core/src/utils/animation/test/hooks/e2e.ts @@ -51,7 +51,7 @@ test(`animation:web: hooks`, async () => { expect(stylesAgain.paddingBottom).toEqual('20px'); expect(stylesAgain.color).toEqual('rgb(0, 0, 0)'); - screenshotCompares.push(await page.compareScreenshot()); + screenshotCompares.push(await page.compareScreenshot('end animation')); }); test(`animation:css: hooks`, async () => { @@ -103,7 +103,7 @@ test(`animation:css: hooks`, async () => { expect(stylesAgain.paddingBottom).toEqual('20px'); expect(stylesAgain.color).toEqual('rgb(0, 0, 0)'); - screenshotCompares.push(await page.compareScreenshot()); + screenshotCompares.push(await page.compareScreenshot('end animation')); }); const waitForEventToBeCalled = (eventName: string, page: any, el: HTMLElement, fn: any, num = 1) => { diff --git a/core/src/utils/animation/test/multiple/e2e.ts b/core/src/utils/animation/test/multiple/e2e.ts index dbaee98ce5..b44796fa6d 100644 --- a/core/src/utils/animation/test/multiple/e2e.ts +++ b/core/src/utils/animation/test/multiple/e2e.ts @@ -24,7 +24,7 @@ test(`animation:web: multiple`, async () => { return payload.animationStatus.join(', ') === ['AnimationCSubBFinished', 'AnimationBFinished', 'AnimationCSubAFinished', 'AnimationCFinished', 'AnimationAFinished', 'AnimationRootFinished'].join(', '); }, { animationStatus }); - screenshotCompares.push(await page.compareScreenshot()); + screenshotCompares.push(await page.compareScreenshot('end animation')); }); test(`animation:css: multiple`, async () => { @@ -49,5 +49,5 @@ test(`animation:css: multiple`, async () => { return payload.animationStatus.join(', ') === ['AnimationCSubBFinished', 'AnimationBFinished', 'AnimationCSubAFinished', 'AnimationCFinished', 'AnimationAFinished', 'AnimationRootFinished'].join(', '); }, { animationStatus }); - screenshotCompares.push(await page.compareScreenshot()); + screenshotCompares.push(await page.compareScreenshot('end animation')); }); diff --git a/core/src/components/menu-controller/animations/base.ts b/core/src/utils/menu-controller/animations/base.ts similarity index 92% rename from core/src/components/menu-controller/animations/base.ts rename to core/src/utils/menu-controller/animations/base.ts index b8f90a94a7..cd92b4f8d6 100644 --- a/core/src/components/menu-controller/animations/base.ts +++ b/core/src/utils/menu-controller/animations/base.ts @@ -1,5 +1,5 @@ import { IonicAnimation } from '../../../interface'; -import { createAnimation } from '../../../utils/animation/animation'; +import { createAnimation } from '../../animation/animation'; /** * baseAnimation diff --git a/core/src/components/menu-controller/animations/overlay.ts b/core/src/utils/menu-controller/animations/overlay.ts similarity index 93% rename from core/src/components/menu-controller/animations/overlay.ts rename to core/src/utils/menu-controller/animations/overlay.ts index dc8e4ab234..61760e5714 100644 --- a/core/src/components/menu-controller/animations/overlay.ts +++ b/core/src/utils/menu-controller/animations/overlay.ts @@ -1,5 +1,5 @@ import { IonicAnimation, MenuI } from '../../../interface'; -import { createAnimation } from '../../../utils/animation/animation'; +import { createAnimation } from '../../animation/animation'; import { baseAnimation } from './base'; diff --git a/core/src/components/menu-controller/animations/push.ts b/core/src/utils/menu-controller/animations/push.ts similarity index 94% rename from core/src/components/menu-controller/animations/push.ts rename to core/src/utils/menu-controller/animations/push.ts index 6dfcd5d21a..2118a1c3c4 100644 --- a/core/src/components/menu-controller/animations/push.ts +++ b/core/src/utils/menu-controller/animations/push.ts @@ -1,5 +1,5 @@ import { IonicAnimation, MenuI } from '../../../interface'; -import { createAnimation } from '../../../utils/animation/animation'; +import { createAnimation } from '../../animation/animation'; import { baseAnimation } from './base'; diff --git a/core/src/components/menu-controller/animations/reveal.ts b/core/src/utils/menu-controller/animations/reveal.ts similarity index 89% rename from core/src/components/menu-controller/animations/reveal.ts rename to core/src/utils/menu-controller/animations/reveal.ts index 493b3d70b3..2e13d0ac66 100644 --- a/core/src/components/menu-controller/animations/reveal.ts +++ b/core/src/utils/menu-controller/animations/reveal.ts @@ -1,5 +1,5 @@ import { IonicAnimation, MenuI } from '../../../interface'; -import { createAnimation } from '../../../utils/animation/animation'; +import { createAnimation } from '../../animation/animation'; import { baseAnimation } from './base'; diff --git a/core/src/utils/menu-controller/index.ts b/core/src/utils/menu-controller/index.ts new file mode 100644 index 0000000000..96fa34e058 --- /dev/null +++ b/core/src/utils/menu-controller/index.ts @@ -0,0 +1,231 @@ +import { AnimationBuilder, IonicAnimation, MenuI } from '../../interface'; + +import { menuOverlayAnimation } from './animations/overlay'; +import { menuPushAnimation } from './animations/push'; +import { menuRevealAnimation } from './animations/reveal'; + +const createMenuController = () => { + const menuAnimations = new Map IonicAnimation) | AnimationBuilder>(); + const menus: MenuI[] = []; + + const open = async (menu?: string | null): Promise => { + const menuEl = await get(menu); + if (menuEl) { + return menuEl.open(); + } + return false; + }; + + const close = async (menu?: string | null): Promise => { + const menuEl = await (menu !== undefined ? get(menu) : getOpen()); + if (menuEl !== undefined) { + return menuEl.close(); + } + return false; + }; + + const toggle = async (menu?: string | null): Promise => { + const menuEl = await get(menu); + if (menuEl) { + return menuEl.toggle(); + } + return false; + }; + + const enable = async (shouldEnable: boolean, menu?: string | null): Promise => { + const menuEl = await get(menu); + if (menuEl) { + menuEl.disabled = !shouldEnable; + } + return menuEl; + }; + + const swipeGesture = async (shouldEnable: boolean, menu?: string | null): Promise => { + const menuEl = await get(menu); + if (menuEl) { + menuEl.swipeGesture = shouldEnable; + } + return menuEl; + }; + + const isOpen = async (menu?: string | null): Promise => { + if (menu != null) { + const menuEl = await get(menu); + return (menuEl !== undefined && menuEl.isOpen()); + } else { + const menuEl = await getOpen(); + return menuEl !== undefined; + } + }; + + const isEnabled = async (menu?: string | null): Promise => { + const menuEl = await get(menu); + if (menuEl) { + return !menuEl.disabled; + } + return false; + }; + + const get = async (menu?: string | null): Promise => { + await waitUntilReady(); + + if (menu === 'start' || menu === 'end') { + // there could be more than one menu on the same side + // so first try to get the enabled one + const menuRef = find(m => m.side === menu && !m.disabled); + if (menuRef) { + return menuRef; + } + + // didn't find a menu side that is enabled + // so try to get the first menu side found + return find(m => m.side === menu); + + } else if (menu != null) { + // the menuId was not left or right + // so try to get the menu by its "id" + return find(m => m.menuId === menu); + } + + // return the first enabled menu + const menuEl = find(m => !m.disabled); + if (menuEl) { + return menuEl; + } + + // get the first menu in the array, if one exists + return menus.length > 0 ? menus[0].el : undefined; + }; + + /** + * Get the instance of the opened menu. Returns `null` if a menu is not found. + */ + const getOpen = async (): Promise => { + await waitUntilReady(); + return _getOpenSync(); + }; + + /** + * Get all menu instances. + */ + const getMenus = async (): Promise => { + await waitUntilReady(); + return getMenusSync(); + }; + + /** + * Get whether or not a menu is animating. Returns `true` if any + * menu is currently animating. + */ + const isAnimating = async (): Promise => { + await waitUntilReady(); + return isAnimatingSync(); + }; + + const registerAnimation = (name: string, animation: AnimationBuilder | ((menu: MenuI) => IonicAnimation)) => { + menuAnimations.set(name, animation); + }; + + const _register = (menu: MenuI) => { + if (menus.indexOf(menu) < 0) { + if (!menu.disabled) { + _setActiveMenu(menu); + } + menus.push(menu); + } + }; + + const _unregister = (menu: MenuI) => { + const index = menus.indexOf(menu); + if (index > -1) { + menus.splice(index, 1); + } + }; + + const _setActiveMenu = (menu: MenuI) => { + // if this menu should be enabled + // then find all the other menus on this same side + // and automatically disable other same side menus + const side = menu.side; + menus + .filter(m => m.side === side && m !== menu) + .forEach(m => m.disabled = true); + }; + + const _setOpen = async (menu: MenuI, shouldOpen: boolean, animated: boolean): Promise => { + if (isAnimatingSync()) { + return false; + } + if (shouldOpen) { + const openedMenu = await getOpen(); + if (openedMenu && menu.el !== openedMenu) { + await openedMenu.setOpen(false, false); + } + } + return menu._setOpen(shouldOpen, animated); + }; + + const _createAnimation = (type: string, menuCmp: MenuI): Promise => { + const animationBuilder = menuAnimations.get(type) as any; + if (!animationBuilder) { + throw new Error('animation not registered'); + } + + const animation = animationBuilder(menuCmp); + return animation; + }; + + const _getOpenSync = (): HTMLIonMenuElement | undefined => { + return find(m => m._isOpen); + }; + + const getMenusSync = (): HTMLIonMenuElement[] => { + return menus.map(menu => menu.el); + }; + + const isAnimatingSync = (): boolean => { + return menus.some(menu => menu.isAnimating); + }; + + const find = (predicate: (menu: MenuI) => boolean): HTMLIonMenuElement | undefined => { + const instance = menus.find(predicate); + if (instance !== undefined) { + return instance.el; + } + return undefined; + }; + + const waitUntilReady = () => { + return Promise.all( + Array.from(document.querySelectorAll('ion-menu')) + .map(menu => menu.componentOnReady()) + ); + }; + + registerAnimation('reveal', menuRevealAnimation); + registerAnimation('push', menuPushAnimation); + registerAnimation('overlay', menuOverlayAnimation); + + return { + registerAnimation, + get, + getMenus, + getOpen, + isEnabled, + swipeGesture, + isAnimating, + isOpen, + enable, + toggle, + close, + open, + _getOpenSync, + _createAnimation, + _register, + _unregister, + _setOpen, + _setActiveMenu, + }; +}; + +export const menuController = /*@__PURE__*/createMenuController(); diff --git a/core/src/utils/overlays.ts b/core/src/utils/overlays.ts index 9477528bae..079e0c1686 100644 --- a/core/src/utils/overlays.ts +++ b/core/src/utils/overlays.ts @@ -28,21 +28,25 @@ export const pickerController = /*@__PURE__*/createController('ion-popover'); export const toastController = /*@__PURE__*/createController('ion-toast'); +export const prepareOverlay = (el: T) => { + const doc = document; + connectListeners(doc); + const overlayIndex = lastId++; + el.overlayIndex = overlayIndex; + if (!el.hasAttribute('id')) { + el.id = `ion-overlay-${overlayIndex}`; + } +}; + export const createOverlay = (tagName: string, opts: object | undefined): Promise => { return customElements.whenDefined(tagName).then(() => { const doc = document; const element = doc.createElement(tagName) as HTMLIonOverlayElement; - connectListeners(doc); + element.classList.add('overlay-hidden'); // convert the passed in overlay options into props // that get passed down into the new overlay Object.assign(element, opts); - element.classList.add('overlay-hidden'); - const overlayIndex = lastId++; - element.overlayIndex = overlayIndex; - if (!element.hasAttribute('id')) { - element.id = `ion-overlay-${overlayIndex}`; - } // append the overlay element to the document body getAppRoot(doc).appendChild(element); @@ -95,13 +99,12 @@ export const dismissOverlay = (doc: Document, data: any, role: string | undefine return overlay.dismiss(data, role); }; -export const getOverlays = (doc: Document, overlayTag?: string): HTMLIonOverlayElement[] => { - const overlays = (Array.from(getAppRoot(doc).children) as HTMLIonOverlayElement[]).filter(c => c.overlayIndex > 0); - if (overlayTag === undefined) { - return overlays; +export const getOverlays = (doc: Document, selector?: string): HTMLIonOverlayElement[] => { + if (selector === undefined) { + selector = 'ion-alert,ion-action-sheet,ion-loading,ion-modal,ion-picker,ion-popover,ion-toast'; } - overlayTag = overlayTag.toUpperCase(); - return overlays.filter(c => c.tagName === overlayTag); + return (Array.from(doc.querySelectorAll(selector)) as HTMLIonOverlayElement[]) + .filter(c => c.overlayIndex > 0); }; export const getOverlay = (doc: Document, overlayTag?: string, id?: string): HTMLIonOverlayElement | undefined => { diff --git a/core/src/utils/platform.ts b/core/src/utils/platform.ts index fa971e58d3..3134d8e28f 100644 --- a/core/src/utils/platform.ts +++ b/core/src/utils/platform.ts @@ -6,7 +6,7 @@ interface IsPlatformSignature { (win: Window, plt: Platforms): boolean; } -export const getPlatforms = (win?: any) => setupPlatforms(win); +export const getPlatforms = (win: any) => setupPlatforms(win); export const isPlatform: IsPlatformSignature = (winOrPlatform: Window | Platforms | undefined, platform?: Platforms) => { if (typeof winOrPlatform === 'string') { @@ -96,15 +96,15 @@ const isElectron = (win: Window): boolean => testUserAgent(win, /electron/i); const isPWA = (win: Window): boolean => - (win as any).matchMedia ? (win.matchMedia('(display-mode: standalone)').matches || (win.navigator as any).standalone) : false; + !!(win.matchMedia('(display-mode: standalone)').matches || (win.navigator as any).standalone); export const testUserAgent = (win: Window, expr: RegExp) => - (win as any).navigator && (win.navigator as any).userAgent ? expr.test(win.navigator.userAgent) : false; + expr.test(win.navigator.userAgent); -const matchMedia = (win: any, query: string): boolean => - (win as any).matchMedia ? win.matchMedia(query).matches : false; +const matchMedia = (win: Window, query: string): boolean => + win.matchMedia(query).matches; -export const PLATFORMS_MAP = { +const PLATFORMS_MAP = { 'ipad': isIpad, 'iphone': isIphone, 'ios': isIOS,