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

View File

@ -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<boolean> {
@ -326,7 +326,7 @@ export class Nav implements NavOutlet {
done?: TransitionDoneFn
): Promise<boolean> {
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<boolean> {
@ -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);
}
}
}
}

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.
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.
By default animations are disabled, but they can be enabled by passing options

View File

@ -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;
}

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 { 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[];

View File

@ -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