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,7 +958,17 @@ 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 (element) {
if (i > activeViewIndex) { if (i > activeViewIndex) {
// this view comes after the active view // this view comes after the active view
// let's unload it // let's unload it
@ -971,6 +981,7 @@ export class Nav implements NavOutlet {
} }
} }
} }
}
private canStart(): boolean { private canStart(): boolean {
return ( return (

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

@ -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();

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