diff --git a/core/package-lock.json b/core/package-lock.json
index e622f693c6..700354524b 100644
--- a/core/package-lock.json
+++ b/core/package-lock.json
@@ -25,7 +25,7 @@
"@stencil/angular-output-target": "^0.4.0",
"@stencil/react-output-target": "^0.2.1",
"@stencil/sass": "^1.5.2",
- "@stencil/vue-output-target": "^0.6.1",
+ "@stencil/vue-output-target": "^0.6.2",
"@types/jest": "^26.0.20",
"@types/node": "^14.6.0",
"@types/swiper": "5.4.0",
@@ -1839,9 +1839,9 @@
}
},
"node_modules/@stencil/vue-output-target": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.6.1.tgz",
- "integrity": "sha512-JGyl3Bi2NJRDz64c2lFAP6zdRwMD12ruWcbT75VdcLVDmCwo+wqWs/Shj4ZWXlcNhzjxbf9vydtQFwVMld/NrA==",
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.6.2.tgz",
+ "integrity": "sha512-Oh7SLFbOUchCSCbGe/Dqal2xSYPKCFQiVKnvzvS0dsHP/XS7rfHqp3qptW6JCp9lBoo3wmmBurHfldqxhLlnag==",
"dev": true,
"peerDependencies": {
"@stencil/core": "^2.9.0"
@@ -15570,9 +15570,9 @@
"dev": true
},
"@stencil/vue-output-target": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.6.1.tgz",
- "integrity": "sha512-JGyl3Bi2NJRDz64c2lFAP6zdRwMD12ruWcbT75VdcLVDmCwo+wqWs/Shj4ZWXlcNhzjxbf9vydtQFwVMld/NrA==",
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.6.2.tgz",
+ "integrity": "sha512-Oh7SLFbOUchCSCbGe/Dqal2xSYPKCFQiVKnvzvS0dsHP/XS7rfHqp3qptW6JCp9lBoo3wmmBurHfldqxhLlnag==",
"dev": true
},
"@stylelint/postcss-css-in-js": {
diff --git a/core/package.json b/core/package.json
index 3222b2a625..8447400701 100644
--- a/core/package.json
+++ b/core/package.json
@@ -47,7 +47,7 @@
"@stencil/angular-output-target": "^0.4.0",
"@stencil/react-output-target": "^0.2.1",
"@stencil/sass": "^1.5.2",
- "@stencil/vue-output-target": "^0.6.1",
+ "@stencil/vue-output-target": "^0.6.2",
"@types/jest": "^26.0.20",
"@types/node": "^14.6.0",
"@types/swiper": "5.4.0",
diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx
index 5ed5967bc9..2bd214e74d 100644
--- a/core/src/components/datetime/datetime.tsx
+++ b/core/src/components/datetime/datetime.tsx
@@ -1128,6 +1128,13 @@ export class Datetime implements ComponentInterface {
this.initializeListeners();
+ /**
+ * The month/year picker from the date interface
+ * should be closed as it is not available in non-date
+ * interfaces.
+ */
+ this.showMonthAndYear = false;
+
raf(() => {
this.ionRender.emit();
});
diff --git a/core/src/components/datetime/test/presentation/datetime.e2e.ts b/core/src/components/datetime/test/presentation/datetime.e2e.ts
index 8f254747c2..137c625fad 100644
--- a/core/src/components/datetime/test/presentation/datetime.e2e.ts
+++ b/core/src/components/datetime/test/presentation/datetime.e2e.ts
@@ -83,6 +83,27 @@ test.describe('datetime: presentation', () => {
expect(ionChangeSpy.length).toBe(1);
});
+
+ test('switching presentation should close month/year picker', async ({ page }, testInfo) => {
+ await test.skip(testInfo.project.metadata.rtl === true, 'This feature does not have RTL specific behaviors.');
+
+ await page.setContent(`
+
+ `);
+
+ await page.waitForSelector('.datetime-ready');
+
+ const datetime = page.locator('ion-datetime');
+ const monthYearButton = page.locator('ion-datetime .calendar-month-year');
+ await monthYearButton.click();
+
+ await expect(datetime).toHaveClass(/show-month-and-year/);
+
+ await datetime.evaluate((el: HTMLIonDatetimeElement) => (el.presentation = 'time'));
+ await page.waitForChanges();
+
+ await expect(datetime).not.toHaveClass(/show-month-and-year/);
+ });
});
test.describe('datetime: presentation: time', () => {
diff --git a/core/src/components/nav/nav.tsx b/core/src/components/nav/nav.tsx
index bf90a19e35..3f0efdcbfa 100644
--- a/core/src/components/nav/nav.tsx
+++ b/core/src/components/nav/nav.tsx
@@ -131,6 +131,10 @@ export class Nav implements NavOutlet {
this.swipeGestureChanged();
}
+ connectedCallback() {
+ this.destroyed = false;
+ }
+
disconnectedCallback() {
for (const view of this.views) {
lifecycle(view.element!, LIFECYCLE_WILL_UNLOAD);
@@ -879,9 +883,13 @@ export class Nav implements NavOutlet {
leavingView: ViewController | undefined,
opts: NavOptions
): NavResult {
- const cleanupView = hasCompleted ? enteringView : leavingView;
- if (cleanupView) {
- this.cleanup(cleanupView);
+ /**
+ * If the transition did not complete, the leavingView will still be the active
+ * view on the stack. Otherwise unmount all the views after the enteringView.
+ */
+ const activeView = hasCompleted ? enteringView : leavingView;
+ if (activeView) {
+ this.unmountInactiveViews(activeView);
}
return {
@@ -944,9 +952,13 @@ export class Nav implements NavOutlet {
}
/**
+ * Unmounts all inactive views after the specified active view.
+ *
* DOM WRITE
+ *
+ * @param activeView The view that is actively visible in the stack. Used to calculate which views to unmount.
*/
- private cleanup(activeView: ViewController) {
+ private unmountInactiveViews(activeView: ViewController) {
// ok, cleanup time!! Destroy all of the views that are
// INACTIVE and come after the active view
// only do this if the views exist, though
diff --git a/core/src/components/nav/test/modal-navigation/index.html b/core/src/components/nav/test/modal-navigation/index.html
new file mode 100644
index 0000000000..a5dedf3a97
--- /dev/null
+++ b/core/src/components/nav/test/modal-navigation/index.html
@@ -0,0 +1,103 @@
+
+
+
+
+ Nav - Modal Navigation
+
+
+
+
+
+
+
+
+
+
+
+
+ Modal Navigation
+
+
+
+ Open Modal
+
+
+
+ Modal
+
+ Close
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/core/src/components/nav/test/modal-navigation/nav.e2e.ts b/core/src/components/nav/test/modal-navigation/nav.e2e.ts
new file mode 100644
index 0000000000..71a7308c81
--- /dev/null
+++ b/core/src/components/nav/test/modal-navigation/nav.e2e.ts
@@ -0,0 +1,77 @@
+import { expect } from '@playwright/test';
+import type { E2EPage } from '@utils/test/playwright';
+import { test } from '@utils/test/playwright';
+
+test.describe('nav: modal-navigation', () => {
+ test.beforeEach(async ({ page }) => {
+ await page.goto(`/src/components/nav/test/modal-navigation`);
+ await openModal(page);
+ });
+
+ test('should render the root page', async ({ page }) => {
+ const pageOne = page.locator('page-one');
+ const pageOneHeading = page.locator('page-one h1');
+
+ await expect(pageOne).toBeVisible();
+ await expect(pageOneHeading).toHaveText('Page One');
+ });
+
+ test('should push to the next page', async ({ page }) => {
+ await page.click('#goto-page-two');
+
+ const pageTwo = page.locator('page-two');
+ const pageTwoHeading = page.locator('page-two h1');
+
+ await expect(pageTwo).toBeVisible();
+ await expect(pageTwoHeading).toHaveText('Page Two');
+ });
+
+ test('should pop to the previous page', async ({ page }) => {
+ await page.click('#goto-page-two');
+ await page.click('#goto-page-three');
+
+ const pageThree = page.locator('page-three');
+ const pageThreeHeading = page.locator('page-three h1');
+
+ await expect(pageThree).toBeVisible();
+ await expect(pageThreeHeading).toHaveText('Page Three');
+
+ await page.click('#go-back');
+
+ const pageTwo = page.locator('page-two');
+ const pageTwoHeading = page.locator('page-two h1');
+
+ // Verifies the leavingView was unmounted
+ await expect(pageThree).toHaveCount(0);
+ await expect(pageTwo).toBeVisible();
+ await expect(pageTwoHeading).toHaveText('Page Two');
+ });
+
+ test.describe('popping to the root', () => {
+ test('should render the root page', async ({ page }) => {
+ const pageTwo = page.locator('page-two');
+ const pageThree = page.locator('page-three');
+
+ await page.click('#goto-page-two');
+ await page.click('#goto-page-three');
+
+ await page.click('#goto-root');
+
+ const pageOne = page.locator('page-one');
+ const pageOneHeading = page.locator('page-one h1');
+
+ // Verifies all views besides the root were unmounted
+ await expect(pageTwo).toHaveCount(0);
+ await expect(pageThree).toHaveCount(0);
+
+ await expect(pageOne).toBeVisible();
+ await expect(pageOneHeading).toHaveText('Page One');
+ });
+ });
+});
+
+const openModal = async (page: E2EPage) => {
+ const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
+ await page.click('#openModal');
+ await ionModalDidPresent.next();
+};
diff --git a/packages/vue/src/vue-component-lib/utils.ts b/packages/vue/src/vue-component-lib/utils.ts
index c04c013970..e48debacfa 100644
--- a/packages/vue/src/vue-component-lib/utils.ts
+++ b/packages/vue/src/vue-component-lib/utils.ts
@@ -1,7 +1,7 @@
import { VNode, defineComponent, getCurrentInstance, h, inject, ref, Ref } from 'vue';
-export interface InputProps {
- modelValue?: string | boolean;
+export interface InputProps {
+ modelValue?: T;
}
const UPDATE_VALUE_EVENT = 'update:modelValue';
@@ -49,7 +49,7 @@ const getElementClasses = (ref: Ref, componentClasses:
* @prop externalModelUpdateEvent - The external event to fire from your Vue component when modelUpdateEvent fires. This is used for ensuring that v-model references have been
* correctly updated when a user's event callback fires.
*/
-export const defineContainer = (
+export const defineContainer = (
name: string,
defineCustomElement: any,
componentProps: string[] = [],
@@ -67,7 +67,7 @@ export const defineContainer = (
defineCustomElement();
}
- const Container = defineComponent((props: any, { attrs, slots, emit }) => {
+ const Container = defineComponent>((props: any, { attrs, slots, emit }) => {
let modelPropValue = props[modelProp];
const containerRef = ref();
const classes = new Set(getComponentClasses(attrs.class));
@@ -76,7 +76,7 @@ export const defineContainer = (
if (vnode.el) {
const eventsNames = Array.isArray(modelUpdateEvent) ? modelUpdateEvent : [modelUpdateEvent];
eventsNames.forEach((eventName: string) => {
- vnode.el.addEventListener(eventName.toLowerCase(), (e: Event) => {
+ vnode.el!.addEventListener(eventName.toLowerCase(), (e: Event) => {
modelPropValue = (e?.target as any)[modelProp];
emit(UPDATE_VALUE_EVENT, modelPropValue);