From 3324f3cc90108e6db2fc33eb46a213a4e9a689f2 Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Sun, 4 Oct 2015 22:35:54 -0500 Subject: [PATCH] refactor(overlay): actionsheet/popup --- ionic/components/action-sheet/action-sheet.ts | 49 +++-- .../action-sheet/test/basic/main.html | 2 + ionic/components/app/app.ts | 14 -- ionic/components/modal/modal.ts | 20 +- ionic/components/nav/nav-controller.ts | 6 +- ionic/components/nav/view-controller.ts | 2 - .../components/overlay/overlay-controller.ts | 156 ++++++++++++++ ionic/components/overlay/overlay.ts | 199 ++---------------- ionic/components/popup/popup.ts | 60 +++--- ionic/components/popup/test/basic/main.html | 2 + ionic/config/bootstrap.ts | 2 + ionic/config/decorators.ts | 12 +- ionic/config/directives.ts | 2 + 13 files changed, 264 insertions(+), 262 deletions(-) create mode 100644 ionic/components/overlay/overlay-controller.ts diff --git a/ionic/components/action-sheet/action-sheet.ts b/ionic/components/action-sheet/action-sheet.ts index bfed358662..c9f47bd696 100644 --- a/ionic/components/action-sheet/action-sheet.ts +++ b/ionic/components/action-sheet/action-sheet.ts @@ -6,10 +6,11 @@ * The ActionSheet is a modal menu with options to select based on an action. */ -import {View, Injectable, NgFor, NgIf} from 'angular2/angular2'; +import {Component, View, Injectable, NgFor, NgIf} from 'angular2/angular2'; +import {OverlayController} from '../overlay/overlay-controller'; +import {IonicConfig} from '../../config/config'; import {Icon} from '../icon/icon'; -import {Overlay} from '../overlay/overlay'; import {Animation} from '../../animations/animation'; import * as util from 'ionic/util'; @@ -52,6 +53,9 @@ import * as util from 'ionic/util'; * } * ``` */ +@Component({ + selector: 'ion-action-sheet' +}) @View({ template: '' + @@ -76,30 +80,42 @@ import * as util from 'ionic/util'; '', directives: [NgFor, NgIf, Icon] }) -class ActionSheetDirective { +class ActionSheetCmp { _cancel() { this.cancel && this.cancel(); - return this.overlayRef.close(); + return this.close(); } _destructive() { let shouldClose = this.destructiveButtonClicked(); if (shouldClose === true) { - return this.overlayRef.close(); + return this.close(); } } _buttonClicked(index) { let shouldClose = this.buttonClicked(index); if (shouldClose === true) { - return this.overlayRef.close(); + return this.close(); } } } + @Injectable() -export class ActionSheet extends Overlay { +export class ActionSheet { + + constructor(ctrl: OverlayController, config: IonicConfig) { + this.ctrl = ctrl; + this._defaults = { + enterAnimation: config.get('actionSheetEnter'), + leaveAnimation: config.get('actionSheetLeave'), + cancelIcon: config.get('actionSheetCancelIcon'), + destructiveIcon: config.get('actionSheetDestructiveIcon') + }; + } + /** * Create and open a new Action Sheet. This is the * public API, and most often you will only use ActionSheet.open() @@ -108,25 +124,18 @@ export class ActionSheet extends Overlay { * @return {Promise} Promise that resolves when the action sheet is open. */ open(opts={}) { - let config = this.config; - let defaults = { - enterAnimation: config.get('actionSheetEnter'), - leaveAnimation: config.get('actionSheetLeave'), - cancelIcon: config.get('actionSheetCancelIcon'), - destructiveIcon: config.get('actionSheetDestructiveIcon') - }; - - let context = util.extend(defaults, opts); - - return this.create(OVERLAY_TYPE, ActionSheetDirective, context, context); + return this.ctrl.open(OVERLAY_TYPE, ActionSheetCmp, util.extend(this._defaults, opts)); } /** * TODO * @returns {TODO} TODO */ - get() { - return this.getByType(OVERLAY_TYPE); + get(handle) { + if (handle) { + return this.ctrl.getByHandle(handle, OVERLAY_TYPE); + } + return this.ctrl.getByType(OVERLAY_TYPE); } } diff --git a/ionic/components/action-sheet/test/basic/main.html b/ionic/components/action-sheet/test/basic/main.html index e22f9e3ca7..27dcfabc84 100644 --- a/ionic/components/action-sheet/test/basic/main.html +++ b/ionic/components/action-sheet/test/basic/main.html @@ -1,3 +1,5 @@ + + diff --git a/ionic/components/app/app.ts b/ionic/components/app/app.ts index ebf55d1e36..e269277a46 100644 --- a/ionic/components/app/app.ts +++ b/ionic/components/app/app.ts @@ -129,18 +129,4 @@ export class IonicApp { return this.components[id]; } - /** - * Create and append the given component into the root - * element of the app. - * - * @param {TODO} componentType the component to create and insert - * @return {Promise} Promise that resolves with the ContainerRef created - */ - appendOverlay(componentType: Type) { - if (!this.overlayAnchor) { - throw (' must be added to your root component\'s template'); - } - return this.overlayAnchor.append(componentType); - } - } diff --git a/ionic/components/modal/modal.ts b/ionic/components/modal/modal.ts index 3764cecbcf..51bdb32056 100644 --- a/ionic/components/modal/modal.ts +++ b/ionic/components/modal/modal.ts @@ -1,6 +1,8 @@ import {Injectable} from 'angular2/angular2'; -import {Overlay} from '../overlay/overlay'; +import {IonicApp} from '../app/app'; +import {IonicConfig} from '../../config/config'; +import {OverlayController} from '../overlay/overlay-controller'; import {Animation} from '../../animations/animation'; import * as util from 'ionic/util'; @@ -28,7 +30,14 @@ import * as util from 'ionic/util'; * ``` */ @Injectable() -export class Modal extends Overlay { +export class Modal { + + constructor(app: IonicApp, config: IonicConfig) { + // super(app, { + // enterAnimation: config.get('modalEnter') || 'modal-slide-in', + // leaveAnimation: config.get('modalLeave') || 'modal-slide-out', + // }) + } /** * TODO @@ -37,12 +46,7 @@ export class Modal extends Overlay { * @returns {TODO} TODO */ open(ComponentType: Type, opts={}) { - let defaults = { - enterAnimation: this.config.get('modalEnter') || 'modal-slide-in', - leaveAnimation: this.config.get('modalLeave') || 'modal-slide-out', - }; - - return this.create(OVERLAY_TYPE, ComponentType, util.extend(defaults, opts)); + return this.create(OVERLAY_TYPE, ComponentType, opts); } /** diff --git a/ionic/components/nav/nav-controller.ts b/ionic/components/nav/nav-controller.ts index 33f6119a0d..4cfd214674 100644 --- a/ionic/components/nav/nav-controller.ts +++ b/ionic/components/nav/nav-controller.ts @@ -171,7 +171,7 @@ export class NavController extends Ion { // the active view is going to be the leaving one (if one exists) let leavingView = this.getActive() || new ViewController(); - leavingView.shouldCache = (util.isBoolean(opts.cacheleavingView) ? opts.cacheleavingView : true); + leavingView.shouldCache = (util.isBoolean(opts.cacheLeavingView) ? opts.cacheLeavingView : true); leavingView.shouldDestroy = !leavingView.shouldCache; if (leavingView.shouldDestroy) { leavingView.willUnload(); @@ -215,7 +215,7 @@ export class NavController extends Ion { // get the active view and set that it is staged to be leaving // was probably the one popped from the stack let leavingView = this.getActive() || new ViewController(); - leavingView.shouldCache = (util.isBoolean(opts.cacheleavingView) ? opts.cacheleavingView : false); + leavingView.shouldCache = (util.isBoolean(opts.cacheLeavingView) ? opts.cacheLeavingView : false); leavingView.shouldDestroy = !leavingView.shouldCache; if (leavingView.shouldDestroy) { leavingView.willUnload(); @@ -341,7 +341,7 @@ export class NavController extends Ion { opts.animate = opts.animate || false; // ensure leaving views are not cached, and should be destroyed - opts.cacheleavingView = false; + opts.cacheLeavingView = false; // get the views to auto remove without having to do a transiton for each // the last view (the currently active one) will do a normal transition out diff --git a/ionic/components/nav/view-controller.ts b/ionic/components/nav/view-controller.ts index f89d6170fd..042c9ebe6d 100644 --- a/ionic/components/nav/view-controller.ts +++ b/ionic/components/nav/view-controller.ts @@ -14,8 +14,6 @@ export class ViewController { this.state = 0; this.disposals = []; - this._nbItms = []; - this.navbarTemplateRef = null; } diff --git a/ionic/components/overlay/overlay-controller.ts b/ionic/components/overlay/overlay-controller.ts new file mode 100644 index 0000000000..a4e3706df1 --- /dev/null +++ b/ionic/components/overlay/overlay-controller.ts @@ -0,0 +1,156 @@ +import {Component, View, NgZone, Injectable, Renderer} from 'angular2/angular2'; + +import {IonicApp} from '../app/app'; +import {Animation} from '../../animations/animation'; +import * as util from 'ionic/util'; + + +@Injectable() +export class OverlayController { + + constructor(app: IonicApp, zone: NgZone, renderer: Renderer) { + this.app = app; + this.zone = zone; + this.renderer = renderer; + this.refs = []; + } + + open(overlayType, componentType: Type, opts={}) { + let resolve; + let promise = new Promise(res => { resolve = res; }); + + if (!this.anchor) { + console.error(' required in root component template to use: ' + overlayType); + return Promise.reject(); + } + + try { + this.anchor.append(componentType).then(ref => { + let instance = ref.instance; + + ref._z = ROOT_Z_INDEX; + for (let i = 0; i < this.refs.length; i++) { + if (this.refs[i]._z >= ref._z) { + ref._z = this.refs[i]._z + 1; + } + } + this.renderer.setElementStyle(ref.location, 'zIndex', ref._z); + + util.extend(instance, opts); + + ref._type = overlayType; + ref._opts = opts; + ref._handle = opts.handle || (overlayType + instance.zIndex); + + this.add(ref); + + instance.close = (opts={}) => { + this.close(ref, opts); + }; + + instance.onViewLoaded && instance.onViewLoaded(); + instance.onViewWillEnter && instance.onViewWillEnter(); + + let animation = Animation.create(ref.location.nativeElement, opts.enterAnimation); + animation.before.addClass('show-overlay'); + + this.app.setEnabled(false, animation.duration()); + this.app.setTransitioning(true, animation.duration()); + + this.zone.runOutsideAngular(() => { + + animation.play().then(() => { + animation.dispose(); + + this.zone.run(() => { + this.app.setEnabled(true); + this.app.setTransitioning(false); + instance.onViewDidEnter && instance.onViewDidEnter(); + resolve(); + }); + + }); + + }); + + }).catch(err => { + console.error(err); + }); + + } catch (e) { + console.error(e); + } + + return promise; + } + + close(ref, opts) { + let resolve; + let promise = new Promise(res => { resolve = res; }); + + let instance = ref.instance; + instance.onViewWillLeave && instance.onViewWillLeave(); + instance.onViewWillUnload && instance.onViewWillUnload(); + + opts = util.extend(ref._opts, opts); + let animation = Animation.create(ref.location.nativeElement, opts.leaveAnimation); + animation.after.removeClass('show-overlay'); + + this.app.setEnabled(false, animation.duration()); + this.app.setTransitioning(true, animation.duration()); + + this.zone.runOutsideAngular(() => { + + animation.play().then(() => { + animation.dispose(); + + this.zone.run(() => { + instance.onViewDidLeave && instance.onViewDidLeave(); + instance.onViewDidUnload && instance.onViewDidUnload(); + + this.app.setEnabled(true); + this.app.setTransitioning(false); + + this.remove(ref); + + resolve(); + }); + + }); + + }); + + return promise; + } + + add(ref) { + this.refs.push(ref); + } + + remove(ref) { + util.array.remove(this.refs, ref); + ref.dispose && ref.dispose(); + } + + getByType(overlayType) { + for (let i = this.overlays.length - 1; i >= 0; i--) { + if (overlayType === this.overlays[i]._type) { + return this.overlays[i]; + } + } + return null; + } + + getByHandle(handle, overlayType) { + for (let i = this.overlays.length - 1; i >= 0; i--) { + if (handle === this.overlays[i]._handle && overlayType === this.overlays[i]._type) { + return this.overlays[i]; + } + } + return null; + } + +} + + +const ROOT_Z_INDEX = 1000; diff --git a/ionic/components/overlay/overlay.ts b/ionic/components/overlay/overlay.ts index 204cd845a9..c2ad7f8e1b 100644 --- a/ionic/components/overlay/overlay.ts +++ b/ionic/components/overlay/overlay.ts @@ -1,195 +1,27 @@ -import {Component, View, DirectiveBinding} from 'angular2/angular2'; +import {Component, View, ElementRef, DynamicComponentLoader} from 'angular2/angular2'; -import {IonicApp} from '../app/app'; -import {Animation} from '../../animations/animation'; -import * as util from 'ionic/util'; - - -export class Overlay { - - constructor(app: IonicApp) { - this.app = app; - } - - create(overlayType, componentType: Type, opts={}, context=null) { - return new Promise((resolve, reject) => { - let app = this.app; - - let annotation = new Component({ - selector: 'ion-' + overlayType, - host: { - '[style.z-index]': 'zIndex', - 'class': overlayType - } - }); - let overlayComponentType = DirectiveBinding.createFromType(componentType, annotation); - - // create a unique token that works as a cache key - overlayComponentType.token = overlayType + componentType.name; - - app.appendOverlay(overlayComponentType).then(ref => { - let overlayRef = new OverlayRef(app, overlayType, opts, ref, context); - overlayRef._open(opts).then(() => { - resolve(overlayRef); - }); - - }).catch(err => { - console.error('Overlay appendOverlay:', err); - reject(err); - }); - - }).catch(err => { - console.error('Overlay create:', err); - }); - } - - getByType(overlayType) { - if (this.app) { - for (let i = this.app.overlays.length - 1; i >= 0; i--) { - if (overlayType === this.app.overlays[i]._type) { - return this.app.overlays[i]; - } - } - } - return null; - } - - getByHandle(handle, overlayType) { - if (this.app) { - for (let i = this.app.overlays.length - 1; i >= 0; i--) { - if (handle === this.app.overlays[i]._handle && - overlayType === this.app.overlays[i]._type) { - return this.app.overlays[i]; - } - } - } - return null; - } - -} - -export class OverlayRef { - constructor(app, overlayType, opts, ref, context) { - this.app = app; - - let overlayInstance = (ref && ref.instance); - if (!overlayInstance) return; - - if (context) { - util.extend(ref.instance, context); - } - this._instance = overlayInstance; - - overlayInstance.onViewLoaded && overlayInstance.onViewLoaded(); - - this.zIndex = ROOT_Z_INDEX; - for (let i = 0; i < app.overlays.length; i++) { - if (app.overlays[i].zIndex >= this.zIndex) { - this.zIndex = app.overlays[i].zIndex + 1; - } - } - overlayInstance.zIndex = this.zIndex; - overlayInstance.overlayRef = this; - overlayInstance.close = (instanceOpts) => { - this.close(instanceOpts); - }; - - this._elementRef = ref.location; - this._type = overlayType; - this._opts = opts; - this._handle = opts.handle || this.zIndex; - - this._dispose = () => { - this._instance = null; - ref.dispose && ref.dispose(); - util.array.remove(app.overlays, this); - }; - - app.overlays.push(this); - } - - getElementRef() { - return this._elementRef; - } - - _open(opts={}) { - return new Promise(resolve => { - let instance = this._instance || {}; - instance.onViewWillEnter && instance.onViewWillEnter(); - - let animationName = (opts && opts.animation) || this._opts.enterAnimation; - let animation = Animation.create(this._elementRef.nativeElement, animationName); - - animation.before.addClass('show-overlay'); - - this.app.setEnabled(false, animation.duration()); - this.app.setTransitioning(true, animation.duration()); - - this.app.zoneRunOutside(() => { - - animation.play().then(() => { - - this.app.zoneRun(() => { - this.app.setEnabled(true); - this.app.setTransitioning(false); - animation.dispose(); - instance.onViewDidEnter && instance.onViewDidEnter(); - resolve(); - }); - - }); - - }); - - }).catch(err => { - console.error(err); - }); - } - - close(opts={}) { - return new Promise(resolve => { - let instance = this._instance || {}; - instance.onViewWillLeave && instance.onViewWillLeave(); - instance.onViewWillUnload && instance.onViewWillUnload(); - - let animationName = (opts && opts.animation) || this._opts.leaveAnimation; - let animation = Animation.create(this._elementRef.nativeElement, animationName); - - animation.after.removeClass('show-overlay'); - this.app.setEnabled(false, animation.duration()); - this.app.setTransitioning(true, animation.duration()); - - animation.play().then(() => { - instance.onViewDidLeave && instance.onViewDidLeave(); - instance.onViewDidUnload && instance.onViewDidUnload(); - - this._dispose(); - - this.app.setEnabled(true); - this.app.setTransitioning(false); - animation.dispose(); - - resolve(); - }) - }).catch(err => { - console.error(err); - }); - } - -} +import {OverlayController} from './overlay-controller'; @Component({ - selector: 'ion-overlays' + selector: 'ion-overlay' }) @View({ template: '' }) -export class OverlaysContainer { - constructor(app: IonicApp, elementRef: ElementRef, loader: DynamicComponentLoader) { +export class OverlayAnchor { + constructor( + overlayCtrl: OverlayController, + elementRef: ElementRef, + loader: DynamicComponentLoader + ) { + if (overlayCtrl.anchor) { + throw ('An app should only have one '); + } + this.elementRef = elementRef; this.loader = loader; - app.overlayAnchor = this; + overlayCtrl.anchor = this; } append(componentType) { @@ -198,6 +30,3 @@ export class OverlaysContainer { }); } } - - -const ROOT_Z_INDEX = 1000; diff --git a/ionic/components/popup/popup.ts b/ionic/components/popup/popup.ts index e9b1c75b57..bd45911ce7 100644 --- a/ionic/components/popup/popup.ts +++ b/ionic/components/popup/popup.ts @@ -1,9 +1,9 @@ import {FORM_DIRECTIVES, NgControl, NgControlGroup, - Component, View, Injectable, NgClass, NgIf, NgFor} from 'angular2/angular2'; + Component, View, ElementRef, Injectable, NgClass, NgIf, NgFor} from 'angular2/angular2'; -import {Overlay} from '../overlay/overlay'; +import {OverlayController} from '../overlay/overlay-controller'; +import {IonicConfig} from '../../config/config'; import {Animation} from '../../animations/animation'; -import {Ion} from '../ion'; import * as util from 'ionic/util'; @@ -63,25 +63,27 @@ import * as util from 'ionic/util'; * ``` */ @Injectable() -export class Popup extends Overlay { +export class Popup { + + constructor(ctrl: OverlayController, config: IonicConfig) { + this.ctrl = ctrl; + this._defaults = { + enterAnimation: config.get('popupPopIn'), + leaveAnimation: config.get('popupPopOut'), + }; + } /** * TODO * @param {TODO} opts TODO * @returns {object} A promise */ - popup(opts) { + open(opts) { return new Promise((resolve, reject)=> { - let config = this.config; - let defaults = { - enterAnimation: config.get('popupPopIn'), - leaveAnimation: config.get('popupPopOut'), - }; - opts.promiseResolve = resolve; opts.promiseReject = reject; - return this.create(OVERLAY_TYPE, StandardPopup, defaults, opts); + return this.ctrl.open(OVERLAY_TYPE, PopupCmp, util.extend(this._defaults, opts)); }); } @@ -128,7 +130,7 @@ export class Popup extends Overlay { ] }, opts); - return this.popup(opts); + return this.open(opts); } /** @@ -182,7 +184,7 @@ export class Popup extends Overlay { cancelButton, okButton ] }, opts); - return this.popup(opts); + return this.open(opts); } /** @@ -243,7 +245,7 @@ export class Popup extends Overlay { ] }, opts); - return this.popup(opts); + return this.open(opts); } /** @@ -264,7 +266,7 @@ const OVERLAY_TYPE = 'popup'; @Component({ - selector: 'ion-popup-default' + selector: 'ion-popup' }) @View({ template: @@ -285,19 +287,22 @@ const OVERLAY_TYPE = 'popup'; directives: [FORM_DIRECTIVES, NgClass, NgIf, NgFor] }) -class StandardPopup { - constructor(popup: Popup) { - this.popup = popup; +class PopupCmp { + + constructor(elementRef: ElementRef) { + this.elementRef = elementRef; } + onInit() { setTimeout(() => { - this.element = this.overlayRef.getElementRef().nativeElement; - this.promptInput = this.element.querySelector('input'); + // TODO: make more better, no DOM BS + this.promptInput = this.elementRef.nativeElement.querySelector('input'); if (this.promptInput) { this.promptInput.value = ''; } }); } + buttonTapped(button, event) { let promptValue = this.promptInput && this.promptInput.value; @@ -314,20 +319,22 @@ class StandardPopup { // Resolve with the prompt value this.promiseResolve(promptValue); } - return this.overlayRef.close(); + return this.close(); } } + _cancel(event) { this.cancel && this.cancel(event); if (!event.defaultPrevented) { this.promiseReject(); - return this.overlayRef.close(); + return this.close(); } } } + class PopupAnimation extends Animation { constructor(element) { super(element); @@ -342,13 +349,14 @@ class PopupAnimation extends Animation { } } + /** - * Animations for modals + * Animations for popups */ class PopupPopIn extends PopupAnimation { constructor(element) { super(element); - this.wrapper.fromTo('opacity', '0', '1') + this.wrapper.fromTo('opacity', '0.01', '1') this.wrapper.fromTo('scale', '1.1', '1'); this.backdrop.fromTo('opacity', '0', '0.3') @@ -370,7 +378,7 @@ Animation.register('popup-pop-out', PopupPopOut); class PopupMdPopIn extends PopupPopIn { constructor(element) { super(element); - this.backdrop.fromTo('opacity', '0', '0.5') + this.backdrop.fromTo('opacity', '0.01', '0.5') } } Animation.register('popup-md-pop-in', PopupMdPopIn); diff --git a/ionic/components/popup/test/basic/main.html b/ionic/components/popup/test/basic/main.html index adc49714bb..928e705a0e 100644 --- a/ionic/components/popup/test/basic/main.html +++ b/ionic/components/popup/test/basic/main.html @@ -14,3 +14,5 @@ + + diff --git a/ionic/config/bootstrap.ts b/ionic/config/bootstrap.ts index 4dcb53e6f0..f7d7b9bbc6 100644 --- a/ionic/config/bootstrap.ts +++ b/ionic/config/bootstrap.ts @@ -4,6 +4,7 @@ import {ROUTER_BINDINGS, HashLocationStrategy, LocationStrategy} from 'angular2/ import {IonicApp} from '../components/app/app'; import {IonicConfig} from './config'; import {IonicPlatform} from '../platform/platform'; +import {OverlayController} from '../components/overlay/overlay-controller'; import {ActionSheet} from '../components/action-sheet/action-sheet'; import {Modal} from '../components/modal/modal'; import {Popup} from '../components/popup/popup'; @@ -40,6 +41,7 @@ export function ionicBindings(configSettings) { bind(IonicPlatform).toValue(platform), bind(TapClick).toValue(tapClick), bind(Events).toValue(events), + OverlayController, ActionSheet, Modal, Popup, diff --git a/ionic/config/decorators.ts b/ionic/config/decorators.ts index 974ce42a60..64b826f9d6 100644 --- a/ionic/config/decorators.ts +++ b/ionic/config/decorators.ts @@ -49,13 +49,17 @@ export function IonicDirective(config) { */ export function IonicComponent(config) { return function(cls) { - var annotations = Reflect.getMetadata('annotations', cls) || []; - annotations.push(new Component(appendConfig(cls, config))); - Reflect.defineMetadata('annotations', annotations, cls); - return cls; + return makeComponent(cls, appendConfig(cls, config)); } } +export function makeComponent(cls, config) { + var annotations = Reflect.getMetadata('annotations', cls) || []; + annotations.push(new Component(config)); + Reflect.defineMetadata('annotations', annotations, cls); + return cls; +} + function appendConfig(cls, config) { config.host = config.host || {}; diff --git a/ionic/config/directives.ts b/ionic/config/directives.ts index 53081c1257..dda378f4fe 100644 --- a/ionic/config/directives.ts +++ b/ionic/config/directives.ts @@ -1,6 +1,7 @@ import {CORE_DIRECTIVES, FORM_DIRECTIVES, forwardRef} from 'angular2/angular2' import { + OverlayAnchor, Menu, MenuToggle, MenuClose, Button, Content, Scroll, Refresher, Slides, Slide, SlideLazy, @@ -29,6 +30,7 @@ export const IONIC_DIRECTIVES = [ FORM_DIRECTIVES, // Content + forwardRef(() => OverlayAnchor), forwardRef(() => Menu), forwardRef(() => MenuToggle), forwardRef(() => MenuClose),