chore(): resolve merge conflicts

This commit is contained in:
Liam DeBeasi
2021-04-23 11:41:46 -04:00
120 changed files with 21206 additions and 2546 deletions

View File

@@ -1,12 +1,12 @@
{
"name": "@ionic/angular-server",
"version": "5.6.3",
"version": "5.6.5",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular-server",
"version": "5.6.3",
"version": "5.6.5",
"license": "MIT",
"devDependencies": {
"@angular/animations": "8.2.13",
@@ -16,7 +16,7 @@
"@angular/core": "8.2.13",
"@angular/platform-browser": "8.2.13",
"@angular/platform-server": "8.2.13",
"@ionic/core": "5.6.2",
"@ionic/core": "5.6.4",
"ng-packagr": "5.7.1",
"tslint": "^5.12.1",
"tslint-ionic-rules": "0.0.21",
@@ -137,16 +137,22 @@
}
},
"node_modules/@ionic/core": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.6.2.tgz",
"integrity": "sha512-hnwd6ln0IZUVfFu2ilZK03b6EdQFqEWiTkL5kayq2gjB3BK/u1IEtV3C9fdwc8NJKopGwdbdQnujj6VhYPzV3Q==",
"version": "5.6.4",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.6.4.tgz",
"integrity": "sha512-fxCV/+0ibiaiBn1dsrrOmlLGJlTkqiG6IVXdLpPKimGdFLjy56olDvB5trlz9J5C/nHc7vR5MIiYC0qdTyX7og==",
"dev": true,
"dependencies": {
"@stencil/core": "^2.4.0",
"ionicons": "^5.5.1",
"tslib": "^1.10.0"
"tslib": "^2.1.0"
}
},
"node_modules/@ionic/core/node_modules/tslib": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
"integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
"dev": true
},
"node_modules/@sindresorhus/is": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz",
@@ -5418,14 +5424,22 @@
}
},
"@ionic/core": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.6.2.tgz",
"integrity": "sha512-hnwd6ln0IZUVfFu2ilZK03b6EdQFqEWiTkL5kayq2gjB3BK/u1IEtV3C9fdwc8NJKopGwdbdQnujj6VhYPzV3Q==",
"version": "5.6.4",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.6.4.tgz",
"integrity": "sha512-fxCV/+0ibiaiBn1dsrrOmlLGJlTkqiG6IVXdLpPKimGdFLjy56olDvB5trlz9J5C/nHc7vR5MIiYC0qdTyX7og==",
"dev": true,
"requires": {
"@stencil/core": "^2.4.0",
"ionicons": "^5.5.1",
"tslib": "^1.10.0"
"tslib": "^2.1.0"
},
"dependencies": {
"tslib": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
"integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
"dev": true
}
}
},
"@sindresorhus/is": {

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/angular-server",
"version": "5.6.3",
"version": "5.6.5",
"description": "Angular SSR Module for Ionic",
"keywords": [
"ionic",
@@ -49,7 +49,7 @@
"@angular/core": "8.2.13",
"@angular/platform-browser": "8.2.13",
"@angular/platform-server": "8.2.13",
"@ionic/core": "5.6.3",
"@ionic/core": "5.6.5",
"ng-packagr": "5.7.1",
"tslint": "^5.12.1",
"tslint-ionic-rules": "0.0.21",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/react-router",
"version": "5.6.3",
"version": "5.6.5",
"description": "React Router wrapper for @ionic/react",
"keywords": [
"ionic",
@@ -39,16 +39,16 @@
"tslib": "*"
},
"peerDependencies": {
"@ionic/core": "5.6.3",
"@ionic/react": "5.6.3",
"@ionic/core": "5.6.5",
"@ionic/react": "5.6.5",
"react": ">=16.8.6",
"react-dom": ">=16.8.6",
"react-router": "^5.0.1",
"react-router-dom": "^5.0.1"
},
"devDependencies": {
"@ionic/core": "5.6.3",
"@ionic/react": "5.6.3",
"@ionic/core": "5.6.5",
"@ionic/react": "5.6.5",
"@rollup/plugin-node-resolve": "^8.1.0",
"@testing-library/jest-dom": "^5.11.6",
"@testing-library/react": "^11.2.2",

View File

@@ -243,7 +243,19 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
routeDirection: 'back',
routeAnimation: routeAnimation || routeInfo.routeAnimation,
};
if (routeInfo.lastPathname === routeInfo.pushedByRoute || prevInfo.pathname === routeInfo.pushedByRoute) {
if (
routeInfo.lastPathname === routeInfo.pushedByRoute ||
(
/**
* We need to exclude tab switches/tab
* context changes here because tabbed
* navigation is not linear, but router.back()
* will go back in a linear fashion.
*/
prevInfo.pathname === routeInfo.pushedByRoute &&
routeInfo.tab === '' && prevInfo.tab === ''
)
) {
this.props.history.goBack();
} else {
this.handleNavigate(prevInfo.pathname + (prevInfo.search || ''), 'pop', 'back');

View File

@@ -0,0 +1,46 @@
const port = 3000;
describe('Tabs', () => {
// Verifies fix for https://github.com/ionic-team/ionic-framework/issues/23101
it('should return to previous tab instance when using the ion-back-button', () => {
cy.visit(`http://localhost:${port}/tabs/tab1`);
cy.get('#tabs-secondary').click();
cy.ionPageVisible('tab1-secondary');
cy.get('ion-tab-button#tab-button-tab2-secondary').click();
cy.ionPageHidden('tab1-secondary');
cy.ionPageVisible('tab2-secondary');
cy.get('ion-tab-button#tab-button-tab1-secondary').click();
cy.ionPageHidden('tab2-secondary');
cy.ionPageVisible('tab1-secondary');
cy.ionBackClick('tab1-secondary');
cy.ionPageDoesNotExist('tabs-secondary');
cy.ionPageVisible('tab1');
});
// Verifies fix for https://github.com/ionic-team/ionic-framework/issues/23087
it('should return to correct view and url when going back from child page after switching tabs', () => {
cy.visit(`http://localhost:${port}/tabs/tab1`);
cy.get('#child-one').click();
cy.ionPageHidden('tab1');
cy.ionPageVisible('tab1child1');
cy.get('ion-tab-button#tab-button-tab2').click();
cy.ionPageHidden('tab1child1');
cy.ionPageVisible('tab2');
cy.get('ion-tab-button#tab-button-tab1').click();
cy.ionPageHidden('tab2');
cy.ionPageVisible('tab1child1');
cy.ionBackClick('tab1child1');
cy.ionPageDoesNotExist('tab1child1');
cy.ionPageVisible('tab1');
cy.url().should('include', '/tabs/tab1');
});
});

View File

@@ -47,12 +47,6 @@ Cypress.Commands.add('ionPageVisible', (pageId) => {
// cy.get(`div.ion-page[data-pageid=${pageId}]`).should('have.attr', 'style', 'z-index: 101;')
});
Cypress.Commands.add('ionPageInvisible', (pageId) => {
cy.get(`div.ion-page[data-pageid=${pageId}]`)
.should('have.class', 'ion-page-invisible')
.should('have.length', 1);
});
Cypress.Commands.add('ionPageHidden', (pageId) => {
cy.get(`div.ion-page[data-pageid=${pageId}]`)
.should('have.class', 'ion-page-hidden')

View File

@@ -34,6 +34,8 @@ import { OutletRef } from './pages/outlet-ref/OutletRef';
import { SwipeToGoBack } from './pages/swipe-to-go-back/SwipToGoBack';
import Refs from './pages/refs/Refs';
import DynamicIonpageClassnames from './pages/dynamic-ionpage-classnames/DynamicIonpageClassnames';
import Tabs from './pages/tabs/Tabs';
import TabsSecondary from './pages/tabs/TabsSecondary';
debugger;
const App: React.FC = () => {
return (
@@ -51,6 +53,8 @@ const App: React.FC = () => {
<Route path="/outlet-ref" component={OutletRef} />
<Route path="/swipe-to-go-back" component={SwipeToGoBack} />
<Route path="/dynamic-ionpage-classnames" component={DynamicIonpageClassnames} />
<Route path="/tabs" component={Tabs} />
<Route path="/tabs-secondary" component={TabsSecondary} />
<Route path="/refs" component={Refs} />
</IonReactRouter>
</IonApp>

View File

@@ -0,0 +1,95 @@
import React from 'react';
import {
IonTabs,
IonRouterOutlet,
IonTabBar,
IonTabButton,
IonIcon,
IonLabel,
IonPage,
IonHeader,
IonToolbar,
IonButtons,
IonBackButton,
IonTitle,
IonContent,
IonButton,
} from '@ionic/react';
import { Route, Redirect } from 'react-router';
import { triangle, square } from 'ionicons/icons';
interface Tabs {}
const Tabs: React.FC<Tabs> = () => {
return (
<IonTabs>
<IonRouterOutlet id="tabs">
<Route path="/tabs/tab1" component={Tab1} exact />
<Route path="/tabs/tab2" component={Tab2} exact />
<Route path="/tabs/tab1/child" component={Tab1Child1} exact />
<Redirect from="/tabs" to="/tabs/tab1" exact />
</IonRouterOutlet>
<IonTabBar slot="bottom">
<IonTabButton tab="tab1" href="/tabs/tab1">
<IonIcon icon={triangle} />
<IonLabel>Tab1</IonLabel>
</IonTabButton>
<IonTabButton tab="tab2" href="/tabs/tab2">
<IonIcon icon={square} />
<IonLabel>Tab2</IonLabel>
</IonTabButton>
</IonTabBar>
</IonTabs>
);
};
const Tab1 = () => {
return (
<IonPage data-pageid="tab1">
<IonHeader>
<IonToolbar>
<IonTitle>Tab1</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
<IonButton routerLink="/tabs/tab1/child" id="child-one">Go to Tab1Child1</IonButton>
<IonButton routerLink="/tabs-secondary/tab1" id="tabs-secondary">Go to Secondary Tabs</IonButton>
</IonContent>
</IonPage>
);
};
const Tab1Child1 = () => {
return (
<IonPage data-pageid="tab1child1">
<IonHeader>
<IonToolbar>
<IonButtons slot="start">
<IonBackButton />
</IonButtons>
<IonTitle>Tab1</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
</IonContent>
</IonPage>
);
};
const Tab2 = () => {
return (
<IonPage data-pageid="tab2">
<IonHeader>
<IonToolbar>
<IonTitle>Tab2</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
Tab 2
</IonContent>
</IonPage>
);
};
export default Tabs;

View File

@@ -0,0 +1,78 @@
import React from 'react';
import {
IonTabs,
IonRouterOutlet,
IonTabBar,
IonTabButton,
IonIcon,
IonLabel,
IonPage,
IonHeader,
IonToolbar,
IonButtons,
IonBackButton,
IonTitle,
IonContent,
IonButton,
} from '@ionic/react';
import { Route, Redirect } from 'react-router';
import { triangle, square } from 'ionicons/icons';
interface TabsSecondary {}
const TabsSecondary: React.FC<TabsSecondary> = () => {
return (
<IonTabs>
<IonRouterOutlet id="tabs-secondary">
<Route path="/tabs-secondary/tab1" component={Tab1} exact />
<Route path="/tabs-secondary/tab2" component={Tab2} exact />
<Redirect from="/tabs-secondary" to="/tabs-secondary/tab1" exact />
</IonRouterOutlet>
<IonTabBar slot="bottom">
<IonTabButton tab="tab1-secondary" href="/tabs-secondary/tab1">
<IonIcon icon={triangle} />
<IonLabel>Tab1</IonLabel>
</IonTabButton>
<IonTabButton tab="tab2-secondary" href="/tabs-secondary/tab2">
<IonIcon icon={square} />
<IonLabel>Tab2</IonLabel>
</IonTabButton>
</IonTabBar>
</IonTabs>
);
};
const Tab1 = () => {
return (
<IonPage data-pageid="tab1-secondary">
<IonHeader>
<IonToolbar>
<IonTitle>Tab1</IonTitle>
<IonButtons slot="start">
<IonBackButton />
</IonButtons>
</IonToolbar>
</IonHeader>
<IonContent>
Tab 1
</IonContent>
</IonPage>
);
};
const Tab2 = () => {
return (
<IonPage data-pageid="tab2-secondary">
<IonHeader>
<IonToolbar>
<IonTitle>Tab2</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent>
Tab 2
</IonContent>
</IonPage>
);
};
export default TabsSecondary;

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/react",
"version": "5.6.3",
"version": "5.6.5",
"description": "React specific wrapper for @ionic/core",
"keywords": [
"ionic",
@@ -39,7 +39,7 @@
"css/"
],
"dependencies": {
"@ionic/core": "5.6.3",
"@ionic/core": "5.6.5",
"ionicons": "^5.1.2",
"tslib": "*"
},

View File

@@ -22,7 +22,7 @@ interface IonIconProps {
}
type InternalProps = IonIconProps & {
forwardedRef?: React.RefObject<HTMLIonIconElement>;
forwardedRef?: React.ForwardedRef<HTMLIonIconElement>;
};
class IonIconContainer extends React.PureComponent<InternalProps> {

View File

@@ -9,7 +9,7 @@ import { createForwardRef } from './utils';
interface IonPageProps extends IonicReactProps {}
interface IonPageInternalProps extends IonPageProps {
forwardedRef?: React.RefObject<HTMLDivElement>;
forwardedRef?: React.ForwardedRef<HTMLDivElement>;
}
class IonPageInternal extends React.Component<IonPageInternalProps> {

View File

@@ -10,12 +10,12 @@ import { createForwardRef } from './utils';
type Props = LocalJSX.IonRouterOutlet & {
basePath?: string;
ref?: React.RefObject<any>;
ref?: React.Ref<any>;
ionPage?: boolean;
};
interface InternalProps extends Props {
forwardedRef?: React.RefObject<HTMLIonRouterOutletElement>;
forwardedRef?: React.ForwardedRef<HTMLIonRouterOutletElement>;
}
interface InternalState {}

View File

@@ -27,7 +27,7 @@ describe('createComponent - events', () => {
});
describe('createComponent - ref', () => {
test('should pass ref on to web component instance', () => {
test('should pass ref on to web component instance (RefObject)', () => {
const ionButtonRef: React.RefObject<any> = React.createRef();
const IonButton = createReactComponent<JSX.IonButton, HTMLIonButtonElement>('ion-button');
@@ -35,6 +35,16 @@ describe('createComponent - ref', () => {
const ionButtonItem = getByText('ButtonNameA');
expect(ionButtonRef.current).toEqual(ionButtonItem);
});
test('should pass ref on to web component instance (RefCallback)', () => {
let current
const ionButtonRef: React.RefCallback<any> = value => current = value;
const IonButton = createReactComponent<JSX.IonButton, HTMLIonButtonElement>('ion-button');
const { getByText } = render(<IonButton ref={ionButtonRef}>ButtonNameA</IonButton>);
const ionButtonItem = getByText('ButtonNameA');
expect(current).toEqual(ionButtonItem);
});
});
describe('createComponent - strict mode', () => {

View File

@@ -11,10 +11,11 @@ import {
createForwardRef,
dashToPascalCase,
isCoveredByReact,
mergeRefs,
} from './utils';
interface IonicReactInternalProps<ElementType> extends React.HTMLAttributes<ElementType> {
forwardedRef?: React.RefObject<ElementType>;
forwardedRef?: React.ForwardedRef<ElementType>;
href?: string;
routerLink?: string;
ref?: React.Ref<any>;
@@ -31,12 +32,14 @@ export const createReactComponent = <PropType, ElementType>(
const ReactComponent = class extends React.Component<IonicReactInternalProps<PropType>> {
context!: React.ContextType<typeof NavContext>;
ref: React.RefObject<HTMLElement>;
stableMergedRefs: React.RefCallback<HTMLElement>
constructor(props: IonicReactInternalProps<PropType>) {
super(props);
// If we weren't given a ref to forward, we still need one
// in order to attach props to the wrapped element.
// Create a local ref to to attach props to the wrapped element.
this.ref = React.createRef();
// React refs must be stable (not created inline).
this.stableMergedRefs = mergeRefs(this.ref, this.props.forwardedRef)
}
componentDidMount() {
@@ -44,9 +47,7 @@ export const createReactComponent = <PropType, ElementType>(
}
componentDidUpdate(prevProps: IonicReactInternalProps<PropType>) {
// Try to use the forwarded ref to get the child node.
// Otherwise, use the one we created.
const node = (this.props.forwardedRef?.current || this.ref.current!) as HTMLElement;
const node = this.ref.current! as HTMLElement;
attachProps(node, this.props, prevProps);
}
@@ -81,7 +82,7 @@ export const createReactComponent = <PropType, ElementType>(
const newProps: IonicReactInternalProps<PropType> = {
...propsToPass,
ref: forwardedRef || this.ref,
ref: this.stableMergedRefs,
style,
};

View File

@@ -1,7 +1,7 @@
import { OverlayEventDetail } from '@ionic/core';
import React from 'react';
import { attachProps } from './utils';
import { attachProps, setRef } from './utils';
interface OverlayBase extends HTMLElement {
present: () => Promise<void>;
@@ -30,7 +30,7 @@ export const createControllerComponent = <
type Props = OptionsType &
ReactControllerProps & {
forwardedRef?: React.RefObject<OverlayType>;
forwardedRef?: React.ForwardedRef<OverlayType>;
};
class Overlay extends React.Component<Props> {
@@ -73,9 +73,7 @@ export const createControllerComponent = <
if (this.props.onDidDismiss) {
this.props.onDidDismiss(event);
}
if (this.props.forwardedRef) {
(this.props.forwardedRef as any).current = undefined;
}
setRef(this.props.forwardedRef, null)
}
async present(prevProps?: Props) {
@@ -106,9 +104,7 @@ export const createControllerComponent = <
// Check isOpen again since the value could have changed during the async call to controller.create
// It's also possible for the component to have become unmounted.
if (this.props.isOpen === true && this.isUnmounted === false) {
if (this.props.forwardedRef) {
(this.props.forwardedRef as any).current = this.overlay;
}
setRef(this.props.forwardedRef, this.overlay)
await this.overlay.present();
}
}

View File

@@ -2,7 +2,7 @@ import { OverlayEventDetail } from '@ionic/core';
import React from 'react';
import ReactDOM from 'react-dom';
import { attachProps } from './utils';
import { attachProps, setRef } from './utils';
interface OverlayElement extends HTMLElement {
present: () => Promise<void>;
@@ -32,7 +32,7 @@ export const createOverlayComponent = <
type Props = OverlayComponent &
ReactOverlayProps & {
forwardedRef?: React.RefObject<OverlayType>;
forwardedRef?: React.ForwardedRef<OverlayType>;
};
let isDismissing = false;
@@ -69,9 +69,7 @@ export const createOverlayComponent = <
if (this.props.onDidDismiss) {
this.props.onDidDismiss(event);
}
if (this.props.forwardedRef) {
(this.props.forwardedRef as any).current = undefined;
}
setRef(this.props.forwardedRef, null)
}
shouldComponentUpdate(nextProps: Props) {
@@ -94,6 +92,13 @@ export const createOverlayComponent = <
if (this.overlay && prevProps.isOpen !== this.props.isOpen && this.props.isOpen === false) {
await this.overlay.dismiss();
isDismissing = false;
/**
* Now that the overlay is dismissed
* we need to render again so that any
* inner components will be unmounted
*/
this.forceUpdate();
}
}
@@ -125,10 +130,7 @@ export const createOverlayComponent = <
componentProps: {},
});
if (this.props.forwardedRef) {
(this.props.forwardedRef as any).current = this.overlay;
}
setRef(this.props.forwardedRef, this.overlay);
attachProps(this.overlay, elementProps, prevProps);
await this.overlay.present();

View File

@@ -18,7 +18,7 @@ export const IonBackButtonInner = /*@__PURE__*/ createReactComponent<
export const IonRouterOutletInner = /*@__PURE__*/ createReactComponent<
JSX.IonRouterOutlet & {
setRef?: (val: HTMLIonRouterOutletElement) => void;
forwardedRef?: React.RefObject<HTMLIonRouterOutletElement>;
forwardedRef?: React.ForwardedRef<HTMLIonRouterOutletElement>;
},
HTMLIonRouterOutletElement
>('ion-router-outlet');

View File

@@ -13,7 +13,7 @@ type Props = Omit<LocalJSX.IonBackButton, 'icon'> &
md: string;
}
| string;
ref?: React.RefObject<HTMLIonBackButtonElement>;
ref?: React.Ref<HTMLIonBackButtonElement>;
};
export const IonBackButton = /*@__PURE__*/ (() =>

View File

@@ -18,7 +18,7 @@ type IonTabBarProps = LocalJSX.IonTabBar &
};
interface InternalProps extends IonTabBarProps {
forwardedRef?: React.RefObject<HTMLIonIconElement>;
forwardedRef?: React.ForwardedRef<HTMLIonIconElement>;
onSetCurrentTab: (tab: string, routeInfo: RouteInfo) => void;
routeInfo: RouteInfo;
}

View File

@@ -8,7 +8,7 @@ import { IonTabButtonInner } from '../inner-proxies';
type Props = LocalJSX.IonTabButton &
IonicReactProps & {
routerOptions?: RouterOptions;
ref?: React.RefObject<HTMLIonTabButtonElement>;
ref?: React.Ref<HTMLIonTabButtonElement>;
onClick?: (e: any) => void;
};

View File

@@ -18,7 +18,7 @@ export const createForwardRef = <PropType, ElementType>(
) => {
const forwardRef = (
props: IonicReactExternalProps<PropType, ElementType>,
ref: React.Ref<ElementType>
ref: React.ForwardedRef<ElementType>
) => {
return <ReactComponent {...props} forwardedRef={ref} />;
};
@@ -27,6 +27,25 @@ export const createForwardRef = <PropType, ElementType>(
return React.forwardRef(forwardRef);
};
export const setRef = (ref: React.ForwardedRef<any> | React.Ref<any> | undefined, value: any) => {
if (typeof ref === 'function') {
ref(value)
} else if (ref != null) {
// Cast as a MutableRef so we can assign current
(ref as React.MutableRefObject<any>).current = value
}
};
export const mergeRefs = (
...refs: (React.ForwardedRef<any> | React.Ref<any> | undefined)[]
): React.RefCallback<any> => {
return (value: any) => {
refs.forEach(ref => {
setRef(ref, value)
})
}
};
export * from './attachProps';
export * from './case';

View File

@@ -8,7 +8,7 @@ import { StackContext } from './StackContext';
interface OutletPageManagerProps {
className?: string;
forwardedRef?: React.RefObject<HTMLIonRouterOutletElement>;
forwardedRef?: React.ForwardedRef<HTMLIonRouterOutletElement>;
routeInfo?: RouteInfo;
StackManager: any;
}

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { mergeRefs } from '../components/utils';
import { IonLifeCycleContext } from '../contexts/IonLifeCycleContext';
import { RouteInfo } from '../models';
@@ -7,7 +8,7 @@ import { StackContext } from './StackContext';
interface PageManagerProps {
className?: string;
forwardedRef?: React.RefObject<HTMLDivElement>;
forwardedRef?: React.ForwardedRef<HTMLDivElement>;
routeInfo?: RouteInfo;
}
@@ -15,10 +16,13 @@ export class PageManager extends React.PureComponent<PageManagerProps> {
ionLifeCycleContext!: React.ContextType<typeof IonLifeCycleContext>;
context!: React.ContextType<typeof StackContext>;
ionPageElementRef: React.RefObject<HTMLDivElement>;
stableMergedRefs: React.RefCallback<HTMLDivElement>
constructor(props: PageManagerProps) {
super(props);
this.ionPageElementRef = this.props.forwardedRef || React.createRef();
this.ionPageElementRef = React.createRef();
// React refs must be stable (not created inline).
this.stableMergedRefs = mergeRefs(this.ionPageElementRef, this.props.forwardedRef)
}
componentDidMount() {
@@ -95,7 +99,7 @@ export class PageManager extends React.PureComponent<PageManagerProps> {
className={
className ? `${className} ion-page` : `ion-page`
}
ref={this.ionPageElementRef}
ref={this.stableMergedRefs}
{...props}
>
{children}

View File

@@ -1,12 +1,12 @@
{
"name": "@ionic/vue-router",
"version": "5.6.3",
"version": "5.6.5",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/vue-router",
"version": "5.6.3",
"version": "5.6.5",
"license": "MIT",
"devDependencies": {
"@ionic/vue": "5.4.1",
@@ -23,7 +23,7 @@
},
"../../core": {
"name": "@ionic/core",
"version": "5.6.2",
"version": "5.6.4",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -32,11 +32,12 @@
"tslib": "^2.1.0"
},
"devDependencies": {
"@axe-core/puppeteer": "^4.1.1",
"@jest/core": "^26.6.3",
"@rollup/plugin-node-resolve": "^8.4.0",
"@rollup/plugin-virtual": "^2.0.3",
"@stencil/sass": "1.3.2",
"@stencil/vue-output-target": "^0.4.1",
"@stencil/vue-output-target": "^0.4.2",
"@types/jest": "^26.0.20",
"@types/node": "^14.6.0",
"@types/puppeteer": "5.4.3",
@@ -8241,12 +8242,13 @@
"@ionic/core": {
"version": "file:../../core",
"requires": {
"@axe-core/puppeteer": "^4.1.1",
"@jest/core": "^26.6.3",
"@rollup/plugin-node-resolve": "^8.4.0",
"@rollup/plugin-virtual": "^2.0.3",
"@stencil/core": "^2.4.0",
"@stencil/sass": "1.3.2",
"@stencil/vue-output-target": "^0.4.1",
"@stencil/vue-output-target": "^0.4.2",
"@types/jest": "^26.0.20",
"@types/node": "^14.6.0",
"@types/puppeteer": "5.4.3",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/vue-router",
"version": "5.6.3",
"version": "5.6.5",
"description": "Vue Router integration for @ionic/vue",
"scripts": {
"test.spec": "jest",

View File

@@ -88,7 +88,19 @@ export const createIonRouter = (opts: IonicVueRouterOptions, router: Router) =>
const prevInfo = locationHistory.findLastLocation(routeInfo);
if (prevInfo) {
incomingRouteParams = { ...prevInfo, routerAction: 'pop', routerDirection: 'back', routerAnimation: routerAnimation || routeInfo.routerAnimation };
if (routeInfo.lastPathname === routeInfo.pushedByRoute || prevInfo.pathname === routeInfo.pushedByRoute) {
if (
routeInfo.lastPathname === routeInfo.pushedByRoute ||
(
/**
* We need to exclude tab switches/tab
* context changes here because tabbed
* navigation is not linear, but router.back()
* will go back in a linear fashion.
*/
prevInfo.pathname === routeInfo.pushedByRoute &&
routeInfo.tab === '' && prevInfo.tab === ''
)
) {
router.back();
} else {
router.replace({ path: prevInfo.pathname, query: parseQuery(prevInfo.search) });

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/vue",
"version": "5.6.3",
"version": "5.6.5",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/vue",
"version": "5.6.3",
"version": "5.6.5",
"license": "MIT",
"dependencies": {
"@ionic/core": "5.6.2",
"@ionic/core": "5.6.4",
"ionicons": "^5.1.2"
},
"devDependencies": {
@@ -53,13 +53,13 @@
}
},
"node_modules/@ionic/core": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.6.2.tgz",
"integrity": "sha512-hnwd6ln0IZUVfFu2ilZK03b6EdQFqEWiTkL5kayq2gjB3BK/u1IEtV3C9fdwc8NJKopGwdbdQnujj6VhYPzV3Q==",
"version": "5.6.4",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.6.4.tgz",
"integrity": "sha512-fxCV/+0ibiaiBn1dsrrOmlLGJlTkqiG6IVXdLpPKimGdFLjy56olDvB5trlz9J5C/nHc7vR5MIiYC0qdTyX7og==",
"dependencies": {
"@stencil/core": "^2.4.0",
"ionicons": "^5.5.1",
"tslib": "^1.10.0"
"tslib": "^2.1.0"
}
},
"node_modules/@ionic/core/node_modules/@stencil/core": {
@@ -74,6 +74,11 @@
"npm": ">=6.0.0"
}
},
"node_modules/@ionic/core/node_modules/tslib": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
"integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w=="
},
"node_modules/@stencil/core": {
"version": "1.17.3",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-1.17.3.tgz",
@@ -536,7 +541,8 @@
"node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"dev": true
},
"node_modules/typescript": {
"version": "4.0.5",
@@ -627,19 +633,24 @@
}
},
"@ionic/core": {
"version": "5.6.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.6.2.tgz",
"integrity": "sha512-hnwd6ln0IZUVfFu2ilZK03b6EdQFqEWiTkL5kayq2gjB3BK/u1IEtV3C9fdwc8NJKopGwdbdQnujj6VhYPzV3Q==",
"version": "5.6.4",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-5.6.4.tgz",
"integrity": "sha512-fxCV/+0ibiaiBn1dsrrOmlLGJlTkqiG6IVXdLpPKimGdFLjy56olDvB5trlz9J5C/nHc7vR5MIiYC0qdTyX7og==",
"requires": {
"@stencil/core": "^2.4.0",
"ionicons": "^5.5.1",
"tslib": "^1.10.0"
"tslib": "^2.1.0"
},
"dependencies": {
"@stencil/core": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.4.0.tgz",
"integrity": "sha512-gU6+Yyd6O0KrCSS/O6j8KKqmRo+/Dcs2fI0+APCpbAWK+nqhwDISpdnSEfGDCLMoAC08XOZCycBRk2K1VGnEcg=="
},
"tslib": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
"integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w=="
}
}
},
@@ -1050,7 +1061,8 @@
"tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"dev": true
},
"typescript": {
"version": "4.0.5",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/vue",
"version": "5.6.3",
"version": "5.6.5",
"description": "Vue specific wrapper for @ionic/core",
"scripts": {
"lint": "echo add linter",
@@ -57,7 +57,7 @@
"vue-router": "^4.0.0-rc.4"
},
"dependencies": {
"@ionic/core": "5.6.3",
"@ionic/core": "5.6.5",
"ionicons": "^5.1.2"
},
"vetur": {

View File

@@ -40,16 +40,31 @@ export const IonRouterOutlet = defineComponent({
// The base url for this router outlet
let parentOutletPath: string;
watch(matchedRouteRef, (currentValue, previousValue) => {
/**
* We need to watch the route object
* to listen for navigation changes.
* Previously we had watched matchedRouteRef,
* but if you had a /page/:id route, going from
* page/1 to page/2 would not cause this callback
* to fire since the matchedRouteRef was the same.
*/
watch([route, matchedRouteRef], ([currentRoute, currentMatchedRouteRef], [_, previousMatchedRouteRef]) => {
/**
* We need to make sure that we are not re-rendering
* the same view if navigation changes in a sub-outlet.
* This is mainly for tabs when outlet 1 renders ion-tabs
* and outlet 2 renders the individual tab view. We don't
* want outlet 1 creating a new ion-tabs instance every time
* we switch tabs.
* If the matched route ref has changed,
* then we need to set up a new view item.
* If the matched route ref has not changed,
* it is possible that this is a parameterized URL
* change such as /page/1 to /page/2. In that case,
* we can assume that the `route` object has changed,
* but we should only set up a new view item in this outlet
* if that last matched view item matches our current matched
* view item otherwise if we had this in a nested outlet the
* parent outlet would re-render as well as the child page.
*/
if (currentValue !== previousValue) {
if (
currentMatchedRouteRef !== previousMatchedRouteRef ||
currentRoute.matched[currentRoute.matched.length - 1] === currentMatchedRouteRef
) {
setupViewItem(matchedRouteRef);
}
});

View File

@@ -11,30 +11,53 @@ interface Tab {
ref: VNode
}
const isTabButton = (child: any) => child.type?.name === 'IonTabButton';
const getTabs = (nodes: VNode[]) => {
let tabs: VNode[] = [];
nodes.forEach((node: VNode) => {
if (isTabButton(node)) {
tabs.push(node);
} else if (Array.isArray(node.children) && node.children.length > 1) {
const childTabs = getTabs(node.children as VNode[]);
tabs = [...tabs, ...childTabs];
}
});
return tabs;
}
export const IonTabBar = defineComponent({
name: 'IonTabBar',
props: {
_tabsWillChange: { type: Function, default: () => {} },
_tabsDidChange: { type: Function, default: () => {} }
},
mounted() {
const ionRouter: any = inject('navManager');
const tabState: TabState = {
data() {
return {
tabState: {
activeTab: undefined,
tabs: {}
};
const currentInstance = getCurrentInstance();
const isTabButton = (child: any) => child.type?.name === 'IonTabButton';
/**
* For each tab, we need to keep track of its
* base href as well as any child page that
* is active in its stack so that when we go back
* to a tab from another tab, we can correctly
* show any child pages if necessary.
*/
const children = (currentInstance.subTree.children || []) as VNode[];
children.forEach((child: VNode) => {
if (isTabButton(child)) {
},
tabVnodes: []
}
},
updated() {
this.setupTabState(inject('navManager'));
},
methods: {
setupTabState(ionRouter: any) {
/**
* For each tab, we need to keep track of its
* base href as well as any child page that
* is active in its stack so that when we go back
* to a tab from another tab, we can correctly
* show any child pages if necessary.
*/
const tabState: TabState = this.$data.tabState;
const currentInstance = getCurrentInstance();
const tabs = this.$data.tabVnodes = getTabs((currentInstance.subTree.children || []) as VNode[]);
tabs.forEach(child => {
tabState.tabs[child.props.tab] = {
originalHref: child.props.href,
currentHref: child.props.href,
@@ -47,12 +70,15 @@ export const IonTabBar = defineComponent({
* ion-tab-bar is managing for it.
*/
child.component.props._getTabState = () => tabState;
}
});
});
const checkActiveTab = (currentRoute: any) => {
const childNodes = (currentInstance.subTree.children || []) as VNode[];
const { tabs, activeTab: prevActiveTab } = tabState;
this.checkActiveTab(ionRouter);
},
checkActiveTab(ionRouter: any) {
const currentRoute = ionRouter.getCurrentRouteInfo();
const childNodes = this.$data.tabVnodes;
const { tabs, activeTab: prevActiveTab } = this.$data.tabState;
const tabState = this.$data.tabState;
const tabKeys = Object.keys(tabs);
const activeTab = tabKeys
.find(key => {
@@ -66,21 +92,19 @@ export const IonTabBar = defineComponent({
* it in the tabs state.
*/
childNodes.forEach((child: VNode) => {
if (isTabButton(child)) {
const tab = tabs[child.props.tab];
if (!tab || (tab.originalHref !== child.props.href)) {
const tab = tabs[child.props.tab];
if (!tab || (tab.originalHref !== child.props.href)) {
tabs[child.props.tab] = {
originalHref: child.props.href,
currentHref: child.props.href,
ref: child
}
tabs[child.props.tab] = {
originalHref: child.props.href,
currentHref: child.props.href,
ref: child
}
}
});
if (activeTab && prevActiveTab) {
const prevHref = tabState.tabs[prevActiveTab].currentHref;
const prevHref = this.$data.tabState.tabs[prevActiveTab].currentHref;
/**
* If the tabs change or the url changes,
* update the currentHref for the active tab.
@@ -128,10 +152,14 @@ export const IonTabBar = defineComponent({
tabBar.selectedTab = tabState.activeTab = '';
}
}
};
}
},
mounted() {
const ionRouter: any = inject('navManager');
ionRouter.registerHistoryChangeListener(checkActiveTab.bind(this));
checkActiveTab(ionRouter.getCurrentRouteInfo());
this.setupTabState(ionRouter);
ionRouter.registerHistoryChangeListener(() => this.checkActiveTab(ionRouter));
},
setup(_, { slots }) {
return () => {

View File

@@ -1,37 +1,72 @@
import { defineComponent, h, ref, VNode, getCurrentInstance } from 'vue';
import { defineComponent, h, ref, VNode, getCurrentInstance, ComponentInternalInstance } from 'vue';
import { needsKebabCase } from '../utils';
export interface OverlayProps {
isOpen?: boolean;
}
/**
* Make sure we only
* warn user about each
* event at most once.
*/
let willPresentWarn = false;
let didPresentWarn = false;
let willDismissWarn = false;
let didDismissWarn = false;
const checkForDeprecatedListeners = (instance: ComponentInternalInstance) => {
const props = instance.vnode.props;
if (!willPresentWarn && props.onOnWillPresent !== undefined) {
console.warn('[@ionic/vue Deprecation]: @onWillPresent has been deprecated in favor of @willPresent and will be removed in Ionic Vue v6.0.');
willPresentWarn = true;
}
if (!didPresentWarn && props.onOnDidPresent !== undefined) {
console.warn('[@ionic/vue Deprecation]: @onDidPresent has been deprecated in favor of @didPresent and will be removed in Ionic Vue v6.0.');
didPresentWarn = true;
}
if (!willDismissWarn && props.onOnWillDismiss !== undefined) {
console.warn('[@ionic/vue Deprecation]: @onWillDismiss has been deprecated in favor of @willDismiss and will be removed in Ionic Vue v6.0.');
willDismissWarn = true;
}
if (!didDismissWarn && props.onOnDidDismiss !== undefined) {
console.warn('[@ionic/vue Deprecation]: @onDidDismiss has been deprecated in favor of @didDismiss and will be removed in Ionic Vue v6.0.');
didDismissWarn = true;
}
}
export const defineOverlayContainer = <Props extends object>(name: string, componentProps: string[] = [], controller: any) => {
/**
* Vue 3.0.6 fixed a bug where events on custom elements
* were always converted to lower case, so "ionRefresh"
* became "ionRefresh". We need to account for the old
* became "ionrefresh". We need to account for the old
* issue as well as the new behavior where "ionRefresh"
* is converted to "ion-refresh".
* See https://github.com/vuejs/vue-next/pull/2847
*/
const eventPrefix = name.toLowerCase().split('-').join('');
const lowerCaseListeners = [
{ componentEv: `${eventPrefix}willpresent`, frameworkEv: 'onWillPresent' },
{ componentEv: `${eventPrefix}didpresent`, frameworkEv: 'onDidPresent' },
{ componentEv: `${eventPrefix}willdismiss`, frameworkEv: 'onWillDismiss' },
{ componentEv: `${eventPrefix}diddismiss`, frameworkEv: 'onDidDismiss' },
{ componentEv: `${eventPrefix}willpresent`, frameworkEv: 'willPresent', deprecatedEv: 'onWillPresent' },
{ componentEv: `${eventPrefix}didpresent`, frameworkEv: 'didPresent', deprecatedEv: 'onDidPresent' },
{ componentEv: `${eventPrefix}willdismiss`, frameworkEv: 'willDismiss', deprecatedEv: 'onWillDismiss' },
{ componentEv: `${eventPrefix}diddismiss`, frameworkEv: 'didDismiss', deprecatedEv: 'onDidDismiss' },
];
const kebabCaseListeners = [
{ componentEv: `${name}-will-present`, frameworkEv: 'onWillPresent' },
{ componentEv: `${name}-did-present`, frameworkEv: 'onDidPresent' },
{ componentEv: `${name}-will-dismiss`, frameworkEv: 'onWillDismiss' },
{ componentEv: `${name}-did-dismiss`, frameworkEv: 'onDidDismiss' },
{ componentEv: `${name}-will-present`, frameworkEv: 'willPresent', deprecatedEv: 'onWillPresent' },
{ componentEv: `${name}-did-present`, frameworkEv: 'didPresent', deprecatedEv: 'onDidPresent' },
{ componentEv: `${name}-will-dismiss`, frameworkEv: 'willDismiss', deprecatedEv: 'onWillDismiss' },
{ componentEv: `${name}-did-dismiss`, frameworkEv: 'didDismiss', deprecatedEv: 'onDidDismiss' },
];
const Container = defineComponent<Props & OverlayProps>((props, { slots, emit }) => {
const instance = getCurrentInstance();
const adjustedEventListeners = needsKebabCase(instance.appContext.app.version) ? kebabCaseListeners : lowerCaseListeners;
checkForDeprecatedListeners(instance);
const overlay = ref();
const onVnodeMounted = async () => {
const isOpen = props.isOpen;
@@ -85,9 +120,21 @@ export const defineOverlayContainer = <Props extends object>(name: string, compo
return;
}
/**
* When supporting both the "on" prefixed and non-"on" prefixed
* events, there seems to be an issue where the handlers are
* getting passed as props. This should be resolved when we drop
* support for the "on" prefixed listeners.
*/
const restOfProps = { ...(props as any) };
delete restOfProps.onWillPresent;
delete restOfProps.onDidPresent;
delete restOfProps.onWillDismiss;
delete restOfProps.onDidDismiss;
const component = slots.default && slots.default()[0];
overlay.value = controller.create({
...props,
...restOfProps,
component
});
@@ -96,6 +143,7 @@ export const defineOverlayContainer = <Props extends object>(name: string, compo
adjustedEventListeners.forEach(eventListener => {
overlay.value.addEventListener(eventListener.componentEv, () => {
emit(eventListener.frameworkEv);
emit(eventListener.deprecatedEv);
});
})
@@ -118,7 +166,10 @@ export const defineOverlayContainer = <Props extends object>(name: string, compo
Container.displayName = name;
Container.props = [...componentProps, 'isOpen'];
Container.emits = [...lowerCaseListeners.map(ev => ev.frameworkEv), ...kebabCaseListeners.map(ev => ev.frameworkEv)];
Container.emits = [
'willPresent', 'didPresent', 'willDismiss', 'didDismiss',
'onWillPresent', 'onDidPresent', 'onWillDismiss', 'onDidDismiss'
];
return Container;
}

View File

@@ -2,5 +2,6 @@
"pluginsFile": "tests/e2e/plugins/index.js",
"includeShadowDom": true,
"video": false,
"screenshotOnRunFailure": false
"screenshotOnRunFailure": false,
"defaultCommandTimeout": 10000
}

View File

@@ -46,7 +46,8 @@ const routes: Array<RouteRecordRaw> = [
},
{
path: '/routing/:id',
component: () => import('@/views/RoutingParameter.vue')
component: () => import('@/views/RoutingParameter.vue'),
props: true
},
{
path: '/routing/:id/view',
@@ -71,7 +72,8 @@ const routes: Array<RouteRecordRaw> = [
},
{
path: ':id',
component: () => import('@/views/Folder.vue')
component: () => import('@/views/Folder.vue'),
props: true
}
]
},

View File

@@ -5,19 +5,19 @@
<ion-buttons slot="start">
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title>{{ folder }}</ion-title>
<ion-title>{{ $props.id }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content :fullscreen="true">
<ion-header collapse="condense">
<ion-toolbar>
<ion-title size="large">{{ folder }}</ion-title>
<ion-title size="large">{{ $props.id }}</ion-title>
</ion-toolbar>
</ion-header>
<div id="container">
<strong class="capitalize">{{ folder }}</strong>
<strong class="capitalize">{{ $props.id }}</strong>
<p>Explore <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p>
</div>
</ion-content>
@@ -26,8 +26,6 @@
<script lang="ts">
import { IonButtons, IonContent, IonHeader, IonMenuButton, IonPage, IonTitle, IonToolbar } from '@ionic/vue';
import { useRoute } from 'vue-router';
import { ref, computed, watch } from 'vue';
export default {
name: 'Folder',
@@ -40,16 +38,8 @@ export default {
IonTitle,
IonToolbar,
},
setup() {
const route = useRoute();
const folder = ref(route.params.id || 'Inbox');
const matchedFolder = computed(() => route.params.id);
watch(matchedFolder, () => {
folder.value = matchedFolder.value as string;
})
return { folder }
props: {
id: { type: String, default: 'Inbox' }
}
}
</script>

View File

@@ -76,7 +76,7 @@
<ion-action-sheet
:is-open="isActionSheetOpen"
:buttons="actionSheetButtons"
@onDidDismiss="setActionSheetRef(false)"
@didDismiss="setActionSheetRef(false)"
>
</ion-action-sheet>
@@ -84,7 +84,7 @@
:is-open="isAlertOpen"
header="Alert!"
:buttons="alertButtons"
@onDidDismiss="setAlertRef(false)"
@didDismiss="setAlertRef(false)"
>
</ion-alert>
@@ -93,17 +93,17 @@
:duration="2000"
message="Loading"
:backdrop-dismiss="true"
@onDidDismiss="setLoadingRef(false)"
@didDismiss="setLoadingRef(false)"
>
</ion-loading>
<ion-modal
:is-open="isModalOpen"
:componentProps="overlayProps"
@onWillPresent="onModalWillPresent"
@onDidPresent="onModalDidPresent"
@onWillDismiss="onModalWillDismiss"
@onDidDismiss="onModalDidDismiss"
@willPresent="onModalWillPresent"
@didPresent="onModalDidPresent"
@willDismiss="onModalWillDismiss"
@didDismiss="onModalDidDismiss"
>
<ModalContent></ModalContent>
</ion-modal>
@@ -112,7 +112,7 @@
:is-open="isPopoverOpen"
:componentProps="overlayProps"
:event="popoverEvent"
@onDidDismiss="setPopoverRef(false)"
@didDismiss="setPopoverRef(false)"
>
<PopoverContent></PopoverContent>
</ion-popover>
@@ -122,7 +122,7 @@
:duration="2000"
message="Toast"
:buttons="toastButtons"
@onDidDismiss="setToastRef(false)"
@didDismiss="setToastRef(false)"
>
</ion-toast>
</ion-content>

View File

@@ -18,8 +18,11 @@
<ion-button id="parameter-view" :router-link="'/routing/' + $route.params.id + '/view'">Go to Single View</ion-button>
<ion-button router-link="/routing/abc">Go to Parameter Page ABC</ion-button>
<ion-button router-link="/routing/xyz">Go to Parameter Page XYZ</ion-button>
<div class="ion-padding" id="parameter-value">
{{ $route.params.id }}
{{ $props.id }}
</div>
</ion-content>
</ion-page>
@@ -39,6 +42,9 @@ import {
import { defineComponent } from 'vue';
export default defineComponent({
props: {
id: { type: String, default: 'my default' }
},
components: {
IonButton,
IonBackButton,

View File

@@ -3,20 +3,17 @@
<ion-content>
<ion-tabs id="tabs">
<ion-tab-bar slot="bottom">
<ion-tab-button tab="tab1" href="/tabs/tab1">
<ion-icon :icon="triangle" />
<ion-label>Tab 1</ion-label>
<ion-tab-button
v-for="tab in tabs"
:tab="'tab' + tab.id"
:href="'/tabs/tab' + tab.id"
:key="tab.id"
>
<ion-icon :icon="tab.icon" />
<ion-label>Tab {{ tab.id }}</ion-label>
</ion-tab-button>
<ion-tab-button tab="tab2" href="/tabs/tab2">
<ion-icon :icon="ellipse" />
<ion-label>Tab 2</ion-label>
</ion-tab-button>
<ion-tab-button tab="tab3" href="/tabs/tab3">
<ion-icon :icon="square" />
<ion-label>Tab 3</ion-label>
</ion-tab-button>
<ion-button id="add-tab" @click="addTab()">Add Tab</ion-button>
</ion-tab-bar>
</ion-tabs>
</ion-content>
@@ -24,18 +21,33 @@
</template>
<script lang="ts">
import { IonTabBar, IonTabButton, IonTabs, IonContent, IonLabel, IonIcon, IonPage } from '@ionic/vue';
import { ellipse, square, triangle } from 'ionicons/icons';
import { IonButton, IonTabBar, IonTabButton, IonTabs, IonContent, IonLabel, IonIcon, IonPage } from '@ionic/vue';
import { ellipse, square, triangle, shield } from 'ionicons/icons';
import { useRouter } from 'vue-router';
import { ref, defineComponent } from 'vue';
export default {
export default defineComponent({
name: 'Tabs',
components: { IonContent, IonLabel, IonTabs, IonTabBar, IonTabButton, IonIcon, IonPage },
components: { IonButton, IonContent, IonLabel, IonTabs, IonTabBar, IonTabButton, IonIcon, IonPage },
setup() {
return {
ellipse,
square,
triangle,
const tabs = ref([
{ id: 1, icon: triangle },
{ id: 2, icon: ellipse },
{ id: 3, icon: square }
])
const router = useRouter();
const addTab = () => {
router.addRoute({ path: '/tabs/tab4', component: () => import('@/views/Tab4.vue') });
tabs.value = [
...tabs.value,
{
id: 4,
icon: shield
}
]
}
return { tabs, addTab }
}
}
});
</script>

View File

@@ -1,6 +1,7 @@
describe('Nested', () => {
beforeEach(() => {
cy.visit('http://localhost:8080/nested');
cy.ionPageVisible('nestedchild');
});
it('should show first page', () => {

View File

@@ -203,6 +203,71 @@ describe('Tabs', () => {
cy.get('ion-tab-button#tab-button-tab1').should('not.have.class', 'tab-selected');
});
// Verifies fix for https://github.com/ionic-team/ionic-framework/issues/23101
it('should return to previous tab instance when using the ion-back-button', () => {
cy.visit('http://localhost:8080/tabs/tab1');
cy.get('#tabs-secondary').click();
cy.ionPageHidden('tabs');
cy.ionPageVisible('tab1-secondary');
cy.get('ion-tab-button#tab-button-tab2-secondary').click();
cy.ionPageHidden('tab1-secondary');
cy.ionPageVisible('tab2-secondary');
cy.get('ion-tab-button#tab-button-tab1-secondary').click();
cy.ionPageHidden('tab2-secondary');
cy.ionPageVisible('tab1-secondary');
cy.ionBackClick('tab1-secondary');
cy.ionPageDoesNotExist('tabs-secondary');
cy.ionPageVisible('tab1');
});
// Verifies fix for https://github.com/ionic-team/ionic-framework/issues/23087
it('should return to correct view and url when going back from child page after switching tabs', () => {
cy.visit('http://localhost:8080/tabs/tab1');
cy.get('#child-one').click();
cy.ionPageHidden('tab1');
cy.ionPageVisible('tab1childone');
cy.get('ion-tab-button#tab-button-tab2').click();
cy.ionPageHidden('tab1childone');
cy.ionPageVisible('tab2');
cy.get('ion-tab-button#tab-button-tab1').click();
cy.ionPageHidden('tab2');
cy.ionPageVisible('tab1childone');
cy.ionBackClick('tab1childone');
cy.ionPageDoesNotExist('tab1childone');
cy.ionPageVisible('tab1');
cy.url().should('include', '/tabs/tab1');
});
// Verifies fix for https://github.com/ionic-team/ionic-framework/issues/22847
it('should support dynamic tabs', () => {
cy.visit('http://localhost:8080/tabs/tab1');
cy.ionPageVisible('tab1');
cy.get('ion-tab-button').its('length').should('equal', 3);
cy.get('ion-tab-button#tab-button-tab1').should('have.class', 'tab-selected');
cy.get('#add-tab').click();
cy.get('ion-tab-button').its('length').should('equal', 4);
cy.get('ion-tab-button#tab-button-tab4').click();
cy.ionPageVisible('tab4');
cy.ionPageHidden('tab1');
cy.get('ion-tab-button#tab-button-tab1').should('not.have.class', 'tab-selected');
cy.get('ion-tab-button#tab-button-tab4').should('have.class', 'tab-selected');
});
})
describe('Tabs - Swipe to Go Back', () => {
@@ -214,11 +279,15 @@ describe('Tabs - Swipe to Go Back', () => {
cy.ionPageVisible('tab1')
});
it('should swipe and abort', () => {
// TODO: Flaky if test runner is slow
// Delays between gesture movements
// cause swipe back gesture to think
// velocity is higher than it actually is
/*it('should swipe and abort', () => {
cy.ionSwipeToGoBack();
cy.ionPageHidden('home');
cy.ionPageVisible('tab1');
});
});*/
it('should swipe and go back to home', () => {
cy.ionSwipeToGoBack(true);

View File

@@ -47,12 +47,6 @@ Cypress.Commands.add('ionPageVisible', (pageId) => {
.should('have.length', 1)
})
Cypress.Commands.add('ionPageInvisible', (pageId) => {
cy.get(`div.ion-page[data-pageid=${pageId}]`)
.should('have.class', 'ion-page-invisible')
.should('have.length', 1)
})
Cypress.Commands.add('ionPageHidden', (pageId) => {
cy.get(`div.ion-page[data-pageid=${pageId}]`)
.should('have.class', 'ion-page-hidden')

View File

@@ -358,4 +358,39 @@ describe('Routing', () => {
const cmpAgain = wrapper.findComponent(Page1);
expect(cmpAgain.props()).toEqual({ title: 'abc Title' });
});
// Verifies fix for https://github.com/ionic-team/ionic-framework/pull/23189
it('should update props on a parameterized url', async () => {
const Page = {
props: {
id: { type: String, default: 'Default ID' }
},
components: { IonPage },
template: `<ion-page>{{ $props.id }}</ion-page>`
}
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes: [
{ path: '/page/:id', component: Page, props: true },
{ path: '/', redirect: '/page/1' }
]
});
router.push('/');
await router.isReady();
const wrapper = mount(App, {
global: {
plugins: [router, IonicVue]
}
});
const page = wrapper.findComponent(Page);
expect(page.props()).toEqual({ id: '1' });
router.push('/page/2');
await waitForRouter();
expect(page.props()).toEqual({ id: '2' });
});
});