diff --git a/angular/src/index.ts b/angular/src/index.ts index bc2495e7f9..fa4cb21abe 100644 --- a/angular/src/index.ts +++ b/angular/src/index.ts @@ -46,4 +46,4 @@ export { IonicModule } from './ionic-module'; export { IonicSafeString, getPlatforms, isPlatform, createAnimation } from '@ionic/core'; // CORE TYPES -export { Animation, AnimationBuilder, AnimationCallbackOptions, AnimationDirection, AnimationFill, AnimationKeyFrames, AnimationLifecycle, Gesture, GestureConfig, GestureDetail, mdTransitionAnimation, iosTransitionAnimation } from '@ionic/core'; +export { Animation, AnimationBuilder, AnimationCallbackOptions, AnimationDirection, AnimationFill, AnimationKeyFrames, AnimationLifecycle, Gesture, GestureConfig, GestureDetail, mdTransitionAnimation, iosTransitionAnimation, NavComponentWithProps } from '@ionic/core'; diff --git a/core/api.txt b/core/api.txt index db0f03daf2..8c559cd3da 100644 --- a/core/api.txt +++ b/core/api.txt @@ -745,13 +745,13 @@ ion-nav,method,getActive,getActive() => Promise ion-nav,method,getByIndex,getByIndex(index: number) => Promise ion-nav,method,getPrevious,getPrevious(view?: ViewController | undefined) => Promise ion-nav,method,insert,insert(insertIndex: number, component: T, componentProps?: ComponentProps | null | undefined, opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise -ion-nav,method,insertPages,insertPages(insertIndex: number, insertComponents: NavComponent[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise +ion-nav,method,insertPages,insertPages(insertIndex: number, insertComponents: NavComponent[] | NavComponentWithProps[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise ion-nav,method,pop,pop(opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise ion-nav,method,popTo,popTo(indexOrViewCtrl: number | ViewController, opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise ion-nav,method,popToRoot,popToRoot(opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise ion-nav,method,push,push(component: T, componentProps?: ComponentProps | null | undefined, opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise ion-nav,method,removeIndex,removeIndex(startIndex: number, removeCount?: number, opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise -ion-nav,method,setPages,setPages(views: any[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise +ion-nav,method,setPages,setPages(views: NavComponent[] | NavComponentWithProps[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise ion-nav,method,setRoot,setRoot(component: T, componentProps?: ComponentProps | null | undefined, opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise ion-nav,event,ionNavDidChange,void,false ion-nav,event,ionNavWillChange,void,false diff --git a/core/src/components.d.ts b/core/src/components.d.ts index a586bb0549..045812b6c7 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -5,7 +5,7 @@ * It contains typing information for all components that exist in this project. */ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; -import { ActionSheetButton, AlertButton, AlertInput, AnimationBuilder, AutocompleteTypes, CheckboxChangeEventDetail, Color, ComponentProps, ComponentRef, DatetimeChangeEventDetail, DatetimeOptions, DomRenderFn, FooterHeightFn, FrameworkDelegate, HeaderFn, HeaderHeightFn, InputChangeEventDetail, ItemHeightFn, ItemRenderFn, ItemReorderEventDetail, MenuChangeEventDetail, NavComponent, NavOptions, OverlayEventDetail, PickerButton, PickerColumn, RadioGroupChangeEventDetail, RangeChangeEventDetail, RangeValue, RefresherEventDetail, RouteID, RouterDirection, RouterEventDetail, RouterOutletOptions, RouteWrite, ScrollBaseDetail, ScrollDetail, SearchbarChangeEventDetail, SegmentButtonLayout, SegmentChangeEventDetail, SelectChangeEventDetail, SelectInterface, SelectPopoverOption, Side, SpinnerTypes, StyleEventDetail, SwipeGestureHandler, TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout, TextareaChangeEventDetail, TextFieldTypes, ToastButton, ToggleChangeEventDetail, TransitionDoneFn, TransitionInstruction, ViewController } from "./interface"; +import { ActionSheetButton, AlertButton, AlertInput, AnimationBuilder, AutocompleteTypes, CheckboxChangeEventDetail, Color, ComponentProps, ComponentRef, DatetimeChangeEventDetail, DatetimeOptions, DomRenderFn, FooterHeightFn, FrameworkDelegate, HeaderFn, HeaderHeightFn, InputChangeEventDetail, ItemHeightFn, ItemRenderFn, ItemReorderEventDetail, MenuChangeEventDetail, NavComponent, NavComponentWithProps, NavOptions, OverlayEventDetail, PickerButton, PickerColumn, RadioGroupChangeEventDetail, RangeChangeEventDetail, RangeValue, RefresherEventDetail, RouteID, RouterDirection, RouterEventDetail, RouterOutletOptions, RouteWrite, ScrollBaseDetail, ScrollDetail, SearchbarChangeEventDetail, SegmentButtonLayout, SegmentChangeEventDetail, SelectChangeEventDetail, SelectInterface, SelectPopoverOption, Side, SpinnerTypes, StyleEventDetail, SwipeGestureHandler, TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout, TextareaChangeEventDetail, TextFieldTypes, ToastButton, ToggleChangeEventDetail, TransitionDoneFn, TransitionInstruction, ViewController } from "./interface"; import { IonicSafeString } from "./utils/sanitization"; import { NavigationHookCallback } from "./components/route/route-interface"; import { SelectCompareFn } from "./components/select/select-interface"; @@ -1413,7 +1413,7 @@ export namespace Components { * @param opts The navigation options. * @param done The transition complete function. */ - "insertPages": (insertIndex: number, insertComponents: NavComponent[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise; + "insertPages": (insertIndex: number, insertComponents: NavComponent[] | NavComponentWithProps[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise; /** * Pop a component off of the navigation stack. Navigates back from the current component. * @param opts The navigation options. @@ -1463,7 +1463,7 @@ export namespace Components { * @param opts The navigation options. * @param done The transition complete function. */ - "setPages": (views: any[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise; + "setPages": (views: NavComponent[] | NavComponentWithProps[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise; /** * Set the root for the current navigation stack to a component. * @param component The component to set as the root of the navigation stack. diff --git a/core/src/components/nav/nav-interface.ts b/core/src/components/nav/nav-interface.ts index b0a4f2ea33..720be984eb 100644 --- a/core/src/components/nav/nav-interface.ts +++ b/core/src/components/nav/nav-interface.ts @@ -1,10 +1,14 @@ -import { Animation, AnimationBuilder, ComponentRef, FrameworkDelegate, Mode } from '../../interface'; +import { Animation, AnimationBuilder, ComponentProps, ComponentRef, FrameworkDelegate, Mode } from '../../interface'; import { ViewController } from './view-controller'; export type NavDirection = 'back' | 'forward'; export type NavComponent = ComponentRef | ViewController; +export interface NavComponentWithProps { + component: NavComponent; + componentProps?: ComponentProps | null; +} export interface NavResult { hasCompleted: boolean; diff --git a/core/src/components/nav/nav.tsx b/core/src/components/nav/nav.tsx index a637e997c8..b5527dc0c7 100644 --- a/core/src/components/nav/nav.tsx +++ b/core/src/components/nav/nav.tsx @@ -2,7 +2,7 @@ import { Build, Component, Element, Event, EventEmitter, Method, Prop, Watch, h import { config } from '../../global/config'; import { getIonMode } from '../../global/ionic-global'; -import { Animation, AnimationBuilder, ComponentProps, FrameworkDelegate, Gesture, NavComponent, NavOptions, NavOutlet, NavResult, RouteID, RouteWrite, RouterDirection, TransitionDoneFn, TransitionInstruction, ViewController } from '../../interface'; +import { Animation, AnimationBuilder, ComponentProps, FrameworkDelegate, Gesture, NavComponent, NavComponentWithProps, NavOptions, NavOutlet, NavResult, RouteID, RouteWrite, RouterDirection, TransitionDoneFn, TransitionInstruction, ViewController } from '../../interface'; import { getTimeGivenProgression } from '../../utils/animation/cubic-bezier'; import { assert } from '../../utils/helpers'; import { TransitionOptions, lifecycle, setPageHidden, transition } from '../../utils/transition'; @@ -156,7 +156,7 @@ export class Nav implements NavOutlet { return this.queueTrns( { insertStart: -1, - insertViews: [{ page: component, params: componentProps }], + insertViews: [{ component, componentProps }], opts }, done @@ -184,7 +184,7 @@ export class Nav implements NavOutlet { return this.queueTrns( { insertStart: insertIndex, - insertViews: [{ page: component, params: componentProps }], + insertViews: [{ component, componentProps }], opts }, done @@ -204,7 +204,7 @@ export class Nav implements NavOutlet { @Method() insertPages( insertIndex: number, - insertComponents: NavComponent[], + insertComponents: NavComponent[] | NavComponentWithProps[], opts?: NavOptions | null, done?: TransitionDoneFn ): Promise { @@ -326,7 +326,7 @@ export class Nav implements NavOutlet { done?: TransitionDoneFn ): Promise { return this.setPages( - [{ page: component, params: componentProps }], + [{ component, componentProps }], opts, done ); @@ -344,7 +344,7 @@ export class Nav implements NavOutlet { */ @Method() setPages( - views: any[], + views: NavComponent[] | NavComponentWithProps[], opts?: NavOptions | null, done?: TransitionDoneFn ): Promise { @@ -958,16 +958,27 @@ export class Nav implements NavOutlet { for (let i = views.length - 1; i >= 0; i--) { const view = views[i]; + + /** + * When inserting multiple views via insertPages + * the last page will be transitioned to, but the + * others will not be. As a result, a DOM element + * will only be created for the last page inserted. + * As a result, it is possible to have views in the + * stack that do not have `view.element` yet. + */ const element = view.element; - if (i > activeViewIndex) { - // this view comes after the active view - // let's unload it - lifecycle(element, LIFECYCLE_WILL_UNLOAD); - this.destroyView(view); - } else if (i < activeViewIndex) { - // this view comes before the active view - // and it is not a portal then ensure it is hidden - setPageHidden(element!, true); + if (element) { + if (i > activeViewIndex) { + // this view comes after the active view + // let's unload it + lifecycle(element, LIFECYCLE_WILL_UNLOAD); + this.destroyView(view); + } else if (i < activeViewIndex) { + // this view comes before the active view + // and it is not a portal then ensure it is hidden + setPageHidden(element!, true); + } } } } diff --git a/core/src/components/nav/readme.md b/core/src/components/nav/readme.md index ed0464e5e1..aabc3fd97a 100644 --- a/core/src/components/nav/readme.md +++ b/core/src/components/nav/readme.md @@ -80,7 +80,7 @@ Type: `Promise` -### `insertPages(insertIndex: number, insertComponents: NavComponent[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise` +### `insertPages(insertIndex: number, insertComponents: NavComponent[] | NavComponentWithProps[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise` Inserts an array of components into the navigation stack at the specified index. The last component in the array will become instantiated as a view, and animate @@ -145,7 +145,7 @@ Type: `Promise` -### `setPages(views: any[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise` +### `setPages(views: NavComponent[] | NavComponentWithProps[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise` Set the views of the current navigation stack and navigate to the last view. By default animations are disabled, but they can be enabled by passing options diff --git a/core/src/components/nav/test/nav-controller.spec.ts b/core/src/components/nav/test/nav-controller.spec.ts index 168f1a0e29..e6a6eab9ce 100644 --- a/core/src/components/nav/test/nav-controller.spec.ts +++ b/core/src/components/nav/test/nav-controller.spec.ts @@ -119,9 +119,9 @@ describe('NavController', () => { mockViews(nav, [view1]); const view2 = mockView(MockView2); - + await nav.push(view2, null, null, trnsDone); - + const hasCompleted = true; const requiresTransition = true; expect(trnsDone).toHaveBeenCalledWith( @@ -818,8 +818,8 @@ describe('NavController', () => { const view5 = mockView(MockView5); await nav.setPages([ - { page: view4 }, - { page: view5 } + { component: view4 }, + { component: view5 } ], null, trnsDone); expect(instance1.ionViewWillUnload).toHaveBeenCalled(); expect(instance2.ionViewWillUnload).toHaveBeenCalled(); @@ -924,10 +924,10 @@ describe('NavController', () => { const MockView3 = 'mock-view3'; const MockView4 = 'mock-view4'; const MockView5 = 'mock-view5'; - + const mockWebAnimation = (el: HTMLElement) => { Element.prototype.animate = () => {}; - + el.animate = () => { const animation = { stop: () => {}, @@ -935,13 +935,13 @@ describe('NavController', () => { cancel: () => {}, onfinish: undefined } - + animation.play = () => { if (animation.onfinish) { animation.onfinish(); } } - + return animation; } } @@ -953,9 +953,9 @@ describe('NavController', () => { const view = new ViewController(component, params); view.element = document.createElement(component) as HTMLElement; - + mockWebAnimation(view.element); - + return view; } diff --git a/core/src/components/nav/view-controller.ts b/core/src/components/nav/view-controller.ts index a8942b04e8..004eebbf11 100644 --- a/core/src/components/nav/view-controller.ts +++ b/core/src/components/nav/view-controller.ts @@ -1,4 +1,4 @@ -import { AnimationBuilder, ComponentProps, FrameworkDelegate } from '../../interface'; +import { AnimationBuilder, ComponentProps, FrameworkDelegate, NavComponentWithProps } from '../../interface'; import { attachComponent } from '../../utils/framework-delegate'; import { assert } from '../../utils/helpers'; @@ -89,13 +89,20 @@ export const convertToView = (page: any, params: ComponentProps | undefined): Vi return new ViewController(page, params); }; -export const convertToViews = (pages: any[]): ViewController[] => { +export const convertToViews = (pages: NavComponentWithProps[]): ViewController[] => { return pages.map(page => { if (page instanceof ViewController) { return page; } - if ('page' in page) { - return convertToView(page.page, page.params); + if ('component' in page) { + /** + * TODO Ionic 6: + * Consider switching to just using `undefined` here + * as well as on the public interfaces and on + * `NavComponentWithProps`. Previously `pages` was + * of type `any[]` so TypeScript did not catch this. + */ + return convertToView(page.component, (page.componentProps === null) ? undefined : page.componentProps); } return convertToView(page, undefined); }).filter(v => v !== null) as ViewController[]; diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts index 724f908c15..d2c7b50234 100644 --- a/packages/react/src/components/index.ts +++ b/packages/react/src/components/index.ts @@ -2,7 +2,7 @@ import { defineCustomElements } from '@ionic/core/loader'; import { addIcons } from 'ionicons'; import { arrowBackSharp, caretBackSharp, chevronBack, chevronForward, close, closeCircle, closeSharp, menuOutline, menuSharp, reorderThreeOutline, reorderTwoSharp, searchOutline, searchSharp } from 'ionicons/icons'; -export { createAnimation, createGesture, AlertButton, AlertInput, Gesture, GestureConfig, GestureDetail, iosTransitionAnimation, IonicSafeString, mdTransitionAnimation, setupConfig } from '@ionic/core'; +export { createAnimation, createGesture, AlertButton, AlertInput, Gesture, GestureConfig, GestureDetail, iosTransitionAnimation, IonicSafeString, mdTransitionAnimation, NavComponentWithProps, setupConfig } from '@ionic/core'; export * from './proxies'; // createControllerComponent