fix(nav): insertPages method correctly inserts multiple pages with props (#21725)

fixes #21724
This commit is contained in:
Liam DeBeasi
2020-07-22 12:06:29 -04:00
committed by GitHub
parent a15cd01bc3
commit eb592b8917
9 changed files with 61 additions and 39 deletions

View File

@ -46,4 +46,4 @@ export { IonicModule } from './ionic-module';
export { IonicSafeString, getPlatforms, isPlatform, createAnimation } from '@ionic/core'; export { IonicSafeString, getPlatforms, isPlatform, createAnimation } from '@ionic/core';
// CORE TYPES // 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';

View File

@ -745,13 +745,13 @@ ion-nav,method,getActive,getActive() => Promise<ViewController | undefined>
ion-nav,method,getByIndex,getByIndex(index: number) => Promise<ViewController | undefined> ion-nav,method,getByIndex,getByIndex(index: number) => Promise<ViewController | undefined>
ion-nav,method,getPrevious,getPrevious(view?: ViewController | undefined) => Promise<ViewController | undefined> ion-nav,method,getPrevious,getPrevious(view?: ViewController | undefined) => Promise<ViewController | undefined>
ion-nav,method,insert,insert<T extends NavComponent>(insertIndex: number, component: T, componentProps?: ComponentProps<T> | null | undefined, opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean> ion-nav,method,insert,insert<T extends NavComponent>(insertIndex: number, component: T, componentProps?: ComponentProps<T> | null | undefined, opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>
ion-nav,method,insertPages,insertPages(insertIndex: number, insertComponents: NavComponent[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean> ion-nav,method,insertPages,insertPages(insertIndex: number, insertComponents: NavComponent[] | NavComponentWithProps[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>
ion-nav,method,pop,pop(opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean> ion-nav,method,pop,pop(opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>
ion-nav,method,popTo,popTo(indexOrViewCtrl: number | ViewController, opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean> ion-nav,method,popTo,popTo(indexOrViewCtrl: number | ViewController, opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>
ion-nav,method,popToRoot,popToRoot(opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean> ion-nav,method,popToRoot,popToRoot(opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>
ion-nav,method,push,push<T extends NavComponent>(component: T, componentProps?: ComponentProps<T> | null | undefined, opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean> ion-nav,method,push,push<T extends NavComponent>(component: T, componentProps?: ComponentProps<T> | null | undefined, opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>
ion-nav,method,removeIndex,removeIndex(startIndex: number, removeCount?: number, opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean> ion-nav,method,removeIndex,removeIndex(startIndex: number, removeCount?: number, opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>
ion-nav,method,setPages,setPages(views: any[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean> ion-nav,method,setPages,setPages(views: NavComponent[] | NavComponentWithProps[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>
ion-nav,method,setRoot,setRoot<T extends NavComponent>(component: T, componentProps?: ComponentProps<T> | null | undefined, opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean> ion-nav,method,setRoot,setRoot<T extends NavComponent>(component: T, componentProps?: ComponentProps<T> | null | undefined, opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>
ion-nav,event,ionNavDidChange,void,false ion-nav,event,ionNavDidChange,void,false
ion-nav,event,ionNavWillChange,void,false ion-nav,event,ionNavWillChange,void,false

View File

@ -5,7 +5,7 @@
* It contains typing information for all components that exist in this project. * It contains typing information for all components that exist in this project.
*/ */
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; 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 { IonicSafeString } from "./utils/sanitization";
import { NavigationHookCallback } from "./components/route/route-interface"; import { NavigationHookCallback } from "./components/route/route-interface";
import { SelectCompareFn } from "./components/select/select-interface"; import { SelectCompareFn } from "./components/select/select-interface";
@ -1413,7 +1413,7 @@ export namespace Components {
* @param opts The navigation options. * @param opts The navigation options.
* @param done The transition complete function. * @param done The transition complete function.
*/ */
"insertPages": (insertIndex: number, insertComponents: NavComponent[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>; "insertPages": (insertIndex: number, insertComponents: NavComponent[] | NavComponentWithProps[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>;
/** /**
* Pop a component off of the navigation stack. Navigates back from the current component. * Pop a component off of the navigation stack. Navigates back from the current component.
* @param opts The navigation options. * @param opts The navigation options.
@ -1463,7 +1463,7 @@ export namespace Components {
* @param opts The navigation options. * @param opts The navigation options.
* @param done The transition complete function. * @param done The transition complete function.
*/ */
"setPages": (views: any[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>; "setPages": (views: NavComponent[] | NavComponentWithProps[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>;
/** /**
* Set the root for the current navigation stack to a component. * Set the root for the current navigation stack to a component.
* @param component The component to set as the root of the navigation stack. * @param component The component to set as the root of the navigation stack.

View File

@ -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'; import { ViewController } from './view-controller';
export type NavDirection = 'back' | 'forward'; export type NavDirection = 'back' | 'forward';
export type NavComponent = ComponentRef | ViewController; export type NavComponent = ComponentRef | ViewController;
export interface NavComponentWithProps<T = any> {
component: NavComponent;
componentProps?: ComponentProps<T> | null;
}
export interface NavResult { export interface NavResult {
hasCompleted: boolean; hasCompleted: boolean;

View File

@ -2,7 +2,7 @@ import { Build, Component, Element, Event, EventEmitter, Method, Prop, Watch, h
import { config } from '../../global/config'; import { config } from '../../global/config';
import { getIonMode } from '../../global/ionic-global'; 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 { getTimeGivenProgression } from '../../utils/animation/cubic-bezier';
import { assert } from '../../utils/helpers'; import { assert } from '../../utils/helpers';
import { TransitionOptions, lifecycle, setPageHidden, transition } from '../../utils/transition'; import { TransitionOptions, lifecycle, setPageHidden, transition } from '../../utils/transition';
@ -156,7 +156,7 @@ export class Nav implements NavOutlet {
return this.queueTrns( return this.queueTrns(
{ {
insertStart: -1, insertStart: -1,
insertViews: [{ page: component, params: componentProps }], insertViews: [{ component, componentProps }],
opts opts
}, },
done done
@ -184,7 +184,7 @@ export class Nav implements NavOutlet {
return this.queueTrns( return this.queueTrns(
{ {
insertStart: insertIndex, insertStart: insertIndex,
insertViews: [{ page: component, params: componentProps }], insertViews: [{ component, componentProps }],
opts opts
}, },
done done
@ -204,7 +204,7 @@ export class Nav implements NavOutlet {
@Method() @Method()
insertPages( insertPages(
insertIndex: number, insertIndex: number,
insertComponents: NavComponent[], insertComponents: NavComponent[] | NavComponentWithProps[],
opts?: NavOptions | null, opts?: NavOptions | null,
done?: TransitionDoneFn done?: TransitionDoneFn
): Promise<boolean> { ): Promise<boolean> {
@ -326,7 +326,7 @@ export class Nav implements NavOutlet {
done?: TransitionDoneFn done?: TransitionDoneFn
): Promise<boolean> { ): Promise<boolean> {
return this.setPages( return this.setPages(
[{ page: component, params: componentProps }], [{ component, componentProps }],
opts, opts,
done done
); );
@ -344,7 +344,7 @@ export class Nav implements NavOutlet {
*/ */
@Method() @Method()
setPages( setPages(
views: any[], views: NavComponent[] | NavComponentWithProps[],
opts?: NavOptions | null, opts?: NavOptions | null,
done?: TransitionDoneFn done?: TransitionDoneFn
): Promise<boolean> { ): Promise<boolean> {
@ -958,16 +958,27 @@ export class Nav implements NavOutlet {
for (let i = views.length - 1; i >= 0; i--) { for (let i = views.length - 1; i >= 0; i--) {
const view = views[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; const element = view.element;
if (i > activeViewIndex) { if (element) {
// this view comes after the active view if (i > activeViewIndex) {
// let's unload it // this view comes after the active view
lifecycle(element, LIFECYCLE_WILL_UNLOAD); // let's unload it
this.destroyView(view); lifecycle(element, LIFECYCLE_WILL_UNLOAD);
} else if (i < activeViewIndex) { this.destroyView(view);
// this view comes before the active view } else if (i < activeViewIndex) {
// and it is not a portal then ensure it is hidden // this view comes before the active view
setPageHidden(element!, true); // and it is not a portal then ensure it is hidden
setPageHidden(element!, true);
}
} }
} }
} }

View File

@ -80,7 +80,7 @@ Type: `Promise<boolean>`
### `insertPages(insertIndex: number, insertComponents: NavComponent[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>` ### `insertPages(insertIndex: number, insertComponents: NavComponent[] | NavComponentWithProps[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>`
Inserts an array of components into the navigation stack at the specified index. 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 The last component in the array will become instantiated as a view, and animate
@ -145,7 +145,7 @@ Type: `Promise<boolean>`
### `setPages(views: any[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>` ### `setPages(views: NavComponent[] | NavComponentWithProps[], opts?: NavOptions | null | undefined, done?: TransitionDoneFn | undefined) => Promise<boolean>`
Set the views of the current navigation stack and navigate to the last view. 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 By default animations are disabled, but they can be enabled by passing options

View File

@ -119,9 +119,9 @@ describe('NavController', () => {
mockViews(nav, [view1]); mockViews(nav, [view1]);
const view2 = mockView(MockView2); const view2 = mockView(MockView2);
await nav.push(view2, null, null, trnsDone); await nav.push(view2, null, null, trnsDone);
const hasCompleted = true; const hasCompleted = true;
const requiresTransition = true; const requiresTransition = true;
expect(trnsDone).toHaveBeenCalledWith( expect(trnsDone).toHaveBeenCalledWith(
@ -818,8 +818,8 @@ describe('NavController', () => {
const view5 = mockView(MockView5); const view5 = mockView(MockView5);
await nav.setPages([ await nav.setPages([
{ page: view4 }, { component: view4 },
{ page: view5 } { component: view5 }
], null, trnsDone); ], null, trnsDone);
expect(instance1.ionViewWillUnload).toHaveBeenCalled(); expect(instance1.ionViewWillUnload).toHaveBeenCalled();
expect(instance2.ionViewWillUnload).toHaveBeenCalled(); expect(instance2.ionViewWillUnload).toHaveBeenCalled();
@ -924,10 +924,10 @@ describe('NavController', () => {
const MockView3 = 'mock-view3'; const MockView3 = 'mock-view3';
const MockView4 = 'mock-view4'; const MockView4 = 'mock-view4';
const MockView5 = 'mock-view5'; const MockView5 = 'mock-view5';
const mockWebAnimation = (el: HTMLElement) => { const mockWebAnimation = (el: HTMLElement) => {
Element.prototype.animate = () => {}; Element.prototype.animate = () => {};
el.animate = () => { el.animate = () => {
const animation = { const animation = {
stop: () => {}, stop: () => {},
@ -935,13 +935,13 @@ describe('NavController', () => {
cancel: () => {}, cancel: () => {},
onfinish: undefined onfinish: undefined
} }
animation.play = () => { animation.play = () => {
if (animation.onfinish) { if (animation.onfinish) {
animation.onfinish(); animation.onfinish();
} }
} }
return animation; return animation;
} }
} }
@ -953,9 +953,9 @@ describe('NavController', () => {
const view = new ViewController(component, params); const view = new ViewController(component, params);
view.element = document.createElement(component) as HTMLElement; view.element = document.createElement(component) as HTMLElement;
mockWebAnimation(view.element); mockWebAnimation(view.element);
return view; return view;
} }

View File

@ -1,4 +1,4 @@
import { AnimationBuilder, ComponentProps, FrameworkDelegate } from '../../interface'; import { AnimationBuilder, ComponentProps, FrameworkDelegate, NavComponentWithProps } from '../../interface';
import { attachComponent } from '../../utils/framework-delegate'; import { attachComponent } from '../../utils/framework-delegate';
import { assert } from '../../utils/helpers'; import { assert } from '../../utils/helpers';
@ -89,13 +89,20 @@ export const convertToView = (page: any, params: ComponentProps | undefined): Vi
return new ViewController(page, params); return new ViewController(page, params);
}; };
export const convertToViews = (pages: any[]): ViewController[] => { export const convertToViews = (pages: NavComponentWithProps[]): ViewController[] => {
return pages.map(page => { return pages.map(page => {
if (page instanceof ViewController) { if (page instanceof ViewController) {
return page; return page;
} }
if ('page' in page) { if ('component' in page) {
return convertToView(page.page, page.params); /**
* 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); return convertToView(page, undefined);
}).filter(v => v !== null) as ViewController[]; }).filter(v => v !== null) as ViewController[];

View File

@ -2,7 +2,7 @@
import { defineCustomElements } from '@ionic/core/loader'; import { defineCustomElements } from '@ionic/core/loader';
import { addIcons } from 'ionicons'; import { addIcons } from 'ionicons';
import { arrowBackSharp, caretBackSharp, chevronBack, chevronForward, close, closeCircle, closeSharp, menuOutline, menuSharp, reorderThreeOutline, reorderTwoSharp, searchOutline, searchSharp } from 'ionicons/icons'; 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'; export * from './proxies';
// createControllerComponent // createControllerComponent