Compare commits

..

1 Commits

Author SHA1 Message Date
Sean Perkins
77bfa7986c fix(react-router): parameterized routes create new instances 2023-07-20 21:24:48 -04:00
64 changed files with 590 additions and 1151 deletions

View File

@@ -3,18 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)
### Bug Fixes
* **item-sliding:** buttons are not interactive on close ([#27829](https://github.com/ionic-team/ionic-framework/issues/27829)) ([6e4919c](https://github.com/ionic-team/ionic-framework/commit/6e4919caff90fc60988e5cc85ad7161844eb5b51)), closes [#22722](https://github.com/ionic-team/ionic-framework/issues/22722)
* **modal:** body background is reset with inline card modals ([#27835](https://github.com/ionic-team/ionic-framework/issues/27835)) ([38626d9](https://github.com/ionic-team/ionic-framework/commit/38626d96809d1c6be523ea62a4fac1dec73ee891)), closes [#27830](https://github.com/ionic-team/ionic-framework/issues/27830)
# [7.2.0](https://github.com/ionic-team/ionic-framework/compare/v7.1.4...v7.2.0) (2023-07-19)

View File

@@ -3,18 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)
### Bug Fixes
* **item-sliding:** buttons are not interactive on close ([#27829](https://github.com/ionic-team/ionic-framework/issues/27829)) ([6e4919c](https://github.com/ionic-team/ionic-framework/commit/6e4919caff90fc60988e5cc85ad7161844eb5b51)), closes [#22722](https://github.com/ionic-team/ionic-framework/issues/22722)
* **modal:** body background is reset with inline card modals ([#27835](https://github.com/ionic-team/ionic-framework/issues/27835)) ([38626d9](https://github.com/ionic-team/ionic-framework/commit/38626d96809d1c6be523ea62a4fac1dec73ee891)), closes [#27830](https://github.com/ionic-team/ionic-framework/issues/27830)
# [7.2.0](https://github.com/ionic-team/ionic-framework/compare/v7.1.4...v7.2.0) (2023-07-19)

52
core/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@ionic/core",
"version": "7.2.1",
"version": "7.2.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/core",
"version": "7.2.1",
"version": "7.2.0",
"license": "MIT",
"dependencies": {
"@stencil/core": "^3.4.0",
@@ -22,12 +22,12 @@
"@ionic/eslint-config": "^0.3.0",
"@ionic/prettier-config": "^2.0.0",
"@jest/core": "^27.5.1",
"@playwright/test": "^1.36.2",
"@playwright/test": "^1.36.1",
"@rollup/plugin-node-resolve": "^8.4.0",
"@rollup/plugin-virtual": "^2.0.3",
"@stencil/angular-output-target": "^0.7.1",
"@stencil/react-output-target": "^0.5.3",
"@stencil/sass": "^3.0.5",
"@stencil/sass": "^3.0.4",
"@stencil/vue-output-target": "^0.8.6",
"@types/jest": "^27.5.2",
"@types/node": "^14.6.0",
@@ -1541,13 +1541,13 @@
}
},
"node_modules/@playwright/test": {
"version": "1.36.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.2.tgz",
"integrity": "sha512-2rVZeyPRjxfPH6J0oGJqE8YxiM1IBRyM8hyrXYK7eSiAqmbNhxwcLa7dZ7fy9Kj26V7FYia5fh9XJRq4Dqme+g==",
"version": "1.36.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.1.tgz",
"integrity": "sha512-YK7yGWK0N3C2QInPU6iaf/L3N95dlGdbsezLya4n0ZCh3IL7VgPGxC6Gnznh9ApWdOmkJeleT2kMTcWPRZvzqg==",
"dev": true,
"dependencies": {
"@types/node": "*",
"playwright-core": "1.36.2"
"playwright-core": "1.36.1"
},
"bin": {
"playwright": "cli.js"
@@ -1655,14 +1655,10 @@
}
},
"node_modules/@stencil/sass": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@stencil/sass/-/sass-3.0.5.tgz",
"integrity": "sha512-9nyllMXOEvHywo6fP2iwXgnq32A+OOUE36Aq7iUjzwT3wdr04qsvupO1JNIyRvmvCDF15hOKXztrZH1/wDu2Zg==",
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@stencil/sass/-/sass-3.0.4.tgz",
"integrity": "sha512-k1dP0A2QBx62m250FATc1hErXxXs6Jnf4TBxdL1C/dc32Kzz2n5aCT4SodBz0ebT5WMnITauZyFqYxzCzDoKag==",
"dev": true,
"engines": {
"node": ">=12.0.0",
"npm": ">=6.0.0"
},
"peerDependencies": {
"@stencil/core": ">=2.0.0 || >=3.0.0-beta.0 || >= 4.0.0-beta.0 || >= 4.0.0"
}
@@ -8194,9 +8190,9 @@
}
},
"node_modules/playwright-core": {
"version": "1.36.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.2.tgz",
"integrity": "sha512-sQYZt31dwkqxOrP7xy2ggDfEzUxM1lodjhsQ3NMMv5uGTRDsLxU0e4xf4wwMkF2gplIxf17QMBCodSFgm6bFVQ==",
"version": "1.36.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.1.tgz",
"integrity": "sha512-7+tmPuMcEW4xeCL9cp9KxmYpQYHKkyjwoXRnoeTowaeNat8PoBMk/HwCYhqkH2fRkshfKEOiVus/IhID2Pg8kg==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
@@ -11455,14 +11451,14 @@
}
},
"@playwright/test": {
"version": "1.36.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.2.tgz",
"integrity": "sha512-2rVZeyPRjxfPH6J0oGJqE8YxiM1IBRyM8hyrXYK7eSiAqmbNhxwcLa7dZ7fy9Kj26V7FYia5fh9XJRq4Dqme+g==",
"version": "1.36.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.1.tgz",
"integrity": "sha512-YK7yGWK0N3C2QInPU6iaf/L3N95dlGdbsezLya4n0ZCh3IL7VgPGxC6Gnznh9ApWdOmkJeleT2kMTcWPRZvzqg==",
"dev": true,
"requires": {
"@types/node": "*",
"fsevents": "2.3.2",
"playwright-core": "1.36.2"
"playwright-core": "1.36.1"
}
},
"@rollup/plugin-node-resolve": {
@@ -11536,9 +11532,9 @@
"requires": {}
},
"@stencil/sass": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@stencil/sass/-/sass-3.0.5.tgz",
"integrity": "sha512-9nyllMXOEvHywo6fP2iwXgnq32A+OOUE36Aq7iUjzwT3wdr04qsvupO1JNIyRvmvCDF15hOKXztrZH1/wDu2Zg==",
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@stencil/sass/-/sass-3.0.4.tgz",
"integrity": "sha512-k1dP0A2QBx62m250FATc1hErXxXs6Jnf4TBxdL1C/dc32Kzz2n5aCT4SodBz0ebT5WMnITauZyFqYxzCzDoKag==",
"dev": true,
"requires": {}
},
@@ -16332,9 +16328,9 @@
}
},
"playwright-core": {
"version": "1.36.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.2.tgz",
"integrity": "sha512-sQYZt31dwkqxOrP7xy2ggDfEzUxM1lodjhsQ3NMMv5uGTRDsLxU0e4xf4wwMkF2gplIxf17QMBCodSFgm6bFVQ==",
"version": "1.36.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.1.tgz",
"integrity": "sha512-7+tmPuMcEW4xeCL9cp9KxmYpQYHKkyjwoXRnoeTowaeNat8PoBMk/HwCYhqkH2fRkshfKEOiVus/IhID2Pg8kg==",
"dev": true
},
"postcss": {

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/core",
"version": "7.2.1",
"version": "7.2.0",
"description": "Base components for Ionic",
"keywords": [
"ionic",
@@ -44,12 +44,12 @@
"@ionic/eslint-config": "^0.3.0",
"@ionic/prettier-config": "^2.0.0",
"@jest/core": "^27.5.1",
"@playwright/test": "^1.36.2",
"@playwright/test": "^1.36.1",
"@rollup/plugin-node-resolve": "^8.4.0",
"@rollup/plugin-virtual": "^2.0.3",
"@stencil/angular-output-target": "^0.7.1",
"@stencil/react-output-target": "^0.5.3",
"@stencil/sass": "^3.0.5",
"@stencil/sass": "^3.0.4",
"@stencil/vue-output-target": "^0.8.6",
"@types/jest": "^27.5.2",
"@types/node": "^14.6.0",

View File

@@ -2176,7 +2176,7 @@ export namespace Components {
*/
"side": PositionSide;
/**
* Describes how to calculate the popover width. If `"cover"`, the popover width will match the width of the trigger. If `"auto"`, the popover width will be set to a static default value.
* Describes how to calculate the popover width. If `"cover"`, the popover width will match the width of the trigger. If `"auto"`, the popover width will be determined by the content in the popover.
*/
"size": PopoverSize;
/**
@@ -6189,7 +6189,7 @@ declare namespace LocalJSX {
*/
"side"?: PositionSide;
/**
* Describes how to calculate the popover width. If `"cover"`, the popover width will match the width of the trigger. If `"auto"`, the popover width will be set to a static default value.
* Describes how to calculate the popover width. If `"cover"`, the popover width will match the width of the trigger. If `"auto"`, the popover width will be determined by the content in the popover.
*/
"size"?: PopoverSize;
/**

View File

@@ -56,17 +56,15 @@ ion-item-options {
}
}
/* stylelint-disable property-disallowed-list */
[dir="ltr"] .item-options-start ion-item-option:first-child,
[dir="rtl"] .item-options-start ion-item-option:last-child {
padding-left: var(--ion-safe-area-left);
.item-options-start ion-item-option:first-child {
@include padding-horizontal(null, var(--ion-safe-area-left));
}
[dir="ltr"] .item-options-end ion-item-option:last-child,
[dir="rtl"] .item-options-end ion-item-option:first-child {
padding-right: var(--ion-safe-area-right);
.item-options-end ion-item-option:last-child {
@include padding-horizontal(null, var(--ion-safe-area-right));
}
/* stylelint-enable property-disallowed-list */
.item-sliding-active-slide {
@include rtl() {

View File

@@ -45,14 +45,6 @@ export class ItemOptions implements ComponentInterface {
// Used internally for styling
[`item-options-${mode}`]: true,
/**
* Note: The "start" and "end" terms refer to the
* direction ion-item-option instances within ion-item-options flow.
* They do not refer to how ion-item-options flows within ion-item-sliding.
* As a result, "item-options-start" means the ion-item-options container
* always appears on the left, and "item-options-end" means the ion-item-options
* container always appears on the right.
*/
'item-options-start': !isEnd,
'item-options-end': isEnd,
}}

View File

@@ -398,7 +398,7 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
});
const ariaDisabled = disabled || childStyles['item-interactive-disabled'] ? 'true' : null;
const fillValue = fill || 'none';
const inList = hostContext('ion-list', this.el) && !hostContext('ion-radio-group', this.el);
const inList = hostContext('ion-list', this.el);
return (
<Host

View File

@@ -1,51 +0,0 @@
import { Radio } from '../../../radio/radio.tsx';
import { RadioGroup } from '../../../radio-group/radio-group.tsx';
import { Item } from '../../item.tsx';
import { List } from '../../../list/list.tsx';
import { newSpecPage } from '@stencil/core/testing';
describe('ion-item', () => {
it('should not have a role when used without list', async () => {
const page = await newSpecPage({
components: [Item],
html: `<ion-item>Hello World</ion-item>`,
});
const item = page.body.querySelector('ion-item');
expect(item.getAttribute('role')).toBe(null);
});
it('should have a listitem role when used inside list', async () => {
const page = await newSpecPage({
components: [Item, List],
html: `
<ion-list>
<ion-item>
Hello World
</ion-item>
</ion-list>
`,
});
const item = page.body.querySelector('ion-item');
expect(item.getAttribute('role')).toBe('listitem');
});
it('should not have a role when used inside radio group and list', async () => {
const page = await newSpecPage({
components: [Radio, RadioGroup, Item, List],
html: `
<ion-list>
<ion-radio-group value="a">
<ion-item>
<ion-radio value="other-value" aria-label="my radio"></ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>
`,
});
const item = page.body.querySelector('ion-item');
expect(item.getAttribute('role')).toBe(null);
});
});

View File

@@ -180,7 +180,8 @@ export class Popover implements ComponentInterface, PopoverInterface {
/**
* Describes how to calculate the popover width.
* If `"cover"`, the popover width will match the width of the trigger.
* If `"auto"`, the popover width will be set to a static default value.
* If `"auto"`, the popover width will be determined by the content in
* the popover.
*/
@Prop() size: PopoverSize = 'auto';

View File

@@ -30,19 +30,23 @@
</ion-list-header>
<ion-item>
<ion-radio value="1">Item 1</ion-radio>
<ion-label>Item 1</ion-label>
<ion-radio value="1" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-radio value="2">Item 2</ion-radio>
<ion-label>Item 2</ion-label>
<ion-radio value="2" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-radio value="3">Item 3</ion-radio>
<ion-label>Item 3</ion-label>
<ion-radio value="3" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-radio value="4">Item 4</ion-radio>
<ion-label>Item 4</ion-label>
<ion-radio value="4" slot="start"></ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>

View File

@@ -1,7 +1,19 @@
import { expect } from '@playwright/test';
import type { Locator } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
import type { E2EPage } from '@utils/test/playwright';
import { RadioFixture } from '../fixtures';
configs().forEach(({ title, screenshot, config }) => {
test.describe(title('radio-group: basic'), () => {
test('should not have visual regressions', async ({ page }) => {
await page.goto(`/src/components/radio-group/test/basic`, config);
const list = page.locator('ion-list');
await expect(list).toHaveScreenshot(screenshot(`radio-group-diff`));
});
});
});
/**
* This behavior does not vary across modes/directions.
@@ -19,7 +31,8 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
`
<ion-radio-group value="one" allow-empty-selection="false">
<ion-item>
<ion-radio id="one" value="one">One</ion-radio>
<ion-label>One</ion-label>
<ion-radio id="one" value="one"></ion-radio>
</ion-item>
</ion-radio-group>
`,
@@ -35,7 +48,8 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
`
<ion-radio-group value="one" allow-empty-selection="true">
<ion-item>
<ion-radio id="one" value="one">One</ion-radio>
<ion-label>One</ion-label>
<ion-radio id="one" value="one"></ion-radio>
</ion-item>
</ion-radio-group>
`,
@@ -51,7 +65,8 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
`
<ion-radio-group value="one" allow-empty-selection="false">
<ion-item>
<ion-radio id="one" value="one">One</ion-radio>
<ion-label>One</ion-label>
<ion-radio id="one" value="one"></ion-radio>
</ion-item>
</ion-radio-group>
`,
@@ -67,7 +82,8 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
`
<ion-radio-group value="one" allow-empty-selection="true">
<ion-item>
<ion-radio id="one" value="one">One</ion-radio>
<ion-label>One</ion-label>
<ion-radio id="one" value="one"></ion-radio>
</ion-item>
</ion-radio-group>
`,
@@ -83,15 +99,18 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
`
<ion-radio-group value="1">
<ion-item>
<ion-radio value="1">Item 1</ion-radio>
<ion-label>Item 1</ion-label>
<ion-radio value="1" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-radio value="2">Item 2</ion-radio>
<ion-label>Item 2</ion-label>
<ion-radio value="2" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-radio value="3">Item 3</ion-radio>
<ion-label>Item 3</ion-label>
<ion-radio value="3" slot="start"></ion-radio>
</ion-item>
</ion-radio-group>
`,
@@ -111,3 +130,34 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
});
});
});
class RadioFixture {
readonly page: E2EPage;
private radio!: Locator;
constructor(page: E2EPage) {
this.page = page;
}
async checkRadio(method: 'keyboard' | 'mouse', selector = 'ion-radio') {
const { page } = this;
const radio = (this.radio = page.locator(selector));
if (method === 'keyboard') {
await radio.focus();
await page.keyboard.press('Space');
} else {
await radio.click();
}
await page.waitForChanges();
return radio;
}
async expectChecked(state: boolean) {
const { radio } = this;
await expect(radio.locator('input')).toHaveJSProperty('checked', state);
}
}

View File

@@ -1,39 +0,0 @@
import type { Locator } from '@playwright/test';
import { expect } from '@playwright/test';
import type { E2EPage } from '@utils/test/playwright';
export class RadioFixture {
readonly page: E2EPage;
private radio!: Locator;
constructor(page: E2EPage) {
this.page = page;
}
async checkRadio(method: 'keyboard' | 'mouse', selector = 'ion-radio') {
const { page } = this;
const radio = (this.radio = page.locator(selector));
if (method === 'keyboard') {
await radio.focus();
await page.keyboard.press('Space');
} else {
await radio.click();
}
await page.waitForChanges();
return radio;
}
async expectChecked(state: boolean) {
const { radio } = this;
if (state) {
await expect(radio).toHaveClass(/radio-checked/);
} else {
await expect(radio).not.toHaveClass(/radio-checked/);
}
}
}

View File

@@ -31,19 +31,32 @@
</ion-list-header>
<ion-item>
<ion-radio value="biff">Biff</ion-radio>
<ion-label
>Biff
<span id="biff"></span>
</ion-label>
<ion-radio value="biff" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-radio value="griff">Griff</ion-radio>
<ion-label
>Griff
<span id="griff"></span>
</ion-label>
<ion-radio value="griff" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-radio value="buford">Buford</ion-radio>
<ion-label
>Buford
<span id="buford"></span>
</ion-label>
<ion-radio value="buford" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-radio value="george" disabled>George</ion-radio>
<ion-label>George</ion-label>
<ion-radio value="george" disabled slot="start"></ion-radio>
</ion-item>
<ion-item>

View File

@@ -1,56 +0,0 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Radio Group - Basic</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Radio Group - Basic</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-radio-group name="items" id="group" value="1">
<ion-list-header>
<ion-label>Radio Group Header</ion-label>
</ion-list-header>
<ion-item>
<ion-label>Item 1</ion-label>
<ion-radio value="1" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Item 2</ion-label>
<ion-radio value="2" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Item 3</ion-label>
<ion-radio value="3" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Item 4</ion-label>
<ion-radio value="4" slot="start"></ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>
</ion-content>
</ion-app>
</body>
</html>

View File

@@ -1,163 +0,0 @@
import { expect } from '@playwright/test';
import type { Locator } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
import type { E2EPage } from '@utils/test/playwright';
configs().forEach(({ title, screenshot, config }) => {
test.describe(title('radio-group: basic'), () => {
test('should not have visual regressions', async ({ page }) => {
await page.goto(`/src/components/radio-group/test/legacy/basic`, config);
const list = page.locator('ion-list');
await expect(list).toHaveScreenshot(screenshot(`radio-group-diff`));
});
});
});
/**
* This behavior does not vary across modes/directions.
*/
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('radio-group: interaction'), () => {
let radioFixture: RadioFixture;
test.beforeEach(({ page }) => {
radioFixture = new RadioFixture(page);
});
test('spacebar should not deselect without allowEmptySelection', async ({ page }) => {
await page.setContent(
`
<ion-radio-group value="one" allow-empty-selection="false">
<ion-item>
<ion-label>One</ion-label>
<ion-radio id="one" value="one"></ion-radio>
</ion-item>
</ion-radio-group>
`,
config
);
await radioFixture.checkRadio('keyboard');
await radioFixture.expectChecked(true);
});
test('spacebar should deselect with allowEmptySelection', async ({ page }) => {
await page.setContent(
`
<ion-radio-group value="one" allow-empty-selection="true">
<ion-item>
<ion-label>One</ion-label>
<ion-radio id="one" value="one"></ion-radio>
</ion-item>
</ion-radio-group>
`,
config
);
await radioFixture.checkRadio('keyboard');
await radioFixture.expectChecked(false);
});
test('click should not deselect without allowEmptySelection', async ({ page }) => {
await page.setContent(
`
<ion-radio-group value="one" allow-empty-selection="false">
<ion-item>
<ion-label>One</ion-label>
<ion-radio id="one" value="one"></ion-radio>
</ion-item>
</ion-radio-group>
`,
config
);
await radioFixture.checkRadio('mouse');
await radioFixture.expectChecked(true);
});
test('click should deselect with allowEmptySelection', async ({ page }) => {
await page.setContent(
`
<ion-radio-group value="one" allow-empty-selection="true">
<ion-item>
<ion-label>One</ion-label>
<ion-radio id="one" value="one"></ion-radio>
</ion-item>
</ion-radio-group>
`,
config
);
await radioFixture.checkRadio('mouse');
await radioFixture.expectChecked(false);
});
test('programmatically assigning a value should update the checked radio', async ({ page }) => {
await page.setContent(
`
<ion-radio-group value="1">
<ion-item>
<ion-label>Item 1</ion-label>
<ion-radio value="1" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Item 2</ion-label>
<ion-radio value="2" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Item 3</ion-label>
<ion-radio value="3" slot="start"></ion-radio>
</ion-item>
</ion-radio-group>
`,
config
);
const radioGroup = page.locator('ion-radio-group');
const radioOne = page.locator('ion-radio[value="1"]');
const radioTwo = page.locator('ion-radio[value="2"]');
await radioGroup.evaluate((el: HTMLIonRadioGroupElement) => (el.value = '2'));
await page.waitForChanges();
await expect(radioOne).not.toHaveClass(/radio-checked/);
await expect(radioTwo).toHaveClass(/radio-checked/);
});
});
});
class RadioFixture {
readonly page: E2EPage;
private radio!: Locator;
constructor(page: E2EPage) {
this.page = page;
}
async checkRadio(method: 'keyboard' | 'mouse', selector = 'ion-radio') {
const { page } = this;
const radio = (this.radio = page.locator(selector));
if (method === 'keyboard') {
await radio.focus();
await page.keyboard.press('Space');
} else {
await radio.click();
}
await page.waitForChanges();
return radio;
}
async expectChecked(state: boolean) {
const { radio } = this;
await expect(radio.locator('input')).toHaveJSProperty('checked', state);
}
}

View File

@@ -1,114 +0,0 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Radio Group - Form</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Radio Group - Form</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="outer-content">
<form>
<ion-list>
<ion-radio-group name="tannen" id="group" value="biff">
<ion-list-header>
<ion-label>Luckiest Man On Earth</ion-label>
</ion-list-header>
<ion-item>
<ion-label
>Biff
<span id="biff"></span>
</ion-label>
<ion-radio value="biff" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-label
>Griff
<span id="griff"></span>
</ion-label>
<ion-radio value="griff" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-label
>Buford
<span id="buford"></span>
</ion-label>
<ion-radio value="buford" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-label>George</ion-label>
<ion-radio value="george" disabled slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-button type="submit">Submit</ion-button>
</ion-item>
</ion-radio-group>
</ion-list>
</form>
<p style="margin: 20px">
Value:
<span id="value"></span>
</p>
<p style="margin: 20px">
Changes:
<span id="changes">0</span>
</p>
</ion-content>
<style>
.outer-content {
--background: #f2f2f2;
}
</style>
<script>
var changes = 0;
document.getElementById('group').addEventListener('ionChange', function (ev) {
document.getElementById('value').textContent = ev.detail.value;
changes++;
document.getElementById('changes').textContent = changes;
});
var biff = 0;
document.querySelector('[value="biff"]').addEventListener('ionSelect', function (ev) {
biff++;
document.getElementById('biff').textContent = biff;
});
var griff = 0;
document.querySelector('[value="griff"]').addEventListener('ionSelect', function (ev) {
griff++;
document.getElementById('griff').textContent = griff;
});
var buford = 0;
document.querySelector('[value="buford"]').addEventListener('ionSelect', function (ev) {
buford++;
document.getElementById('buford').textContent = buford;
});
</script>
</ion-app>
</body>
</html>

View File

@@ -1,35 +0,0 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('radio-group: form'), () => {
test.beforeEach(async ({ page }) => {
await page.goto('/src/components/radio-group/test/legacy/form', config);
});
test('selecting an option should update the value', async ({ page }) => {
const radioGroup = page.locator('ion-radio-group');
const ionChange = await page.spyOnEvent('ionChange');
const griffRadio = page.locator('ion-radio[value="griff"]');
await expect(radioGroup).toHaveAttribute('value', 'biff');
await griffRadio.click();
await page.waitForChanges();
await expect(ionChange).toHaveReceivedEventDetail({ value: 'griff', event: { isTrusted: true } });
});
test('selecting a disabled option should not update the value', async ({ page }) => {
const value = page.locator('#value');
const disabledRadio = page.locator('ion-radio[value="george"]');
await expect(value).toHaveText('');
await expect(disabledRadio).toHaveAttribute('disabled', '');
await disabledRadio.click({ force: true });
await page.waitForChanges();
await expect(value).toHaveText('');
});
});
});

View File

@@ -1,82 +0,0 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Radio Group - Search</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Radio Group - Form</ion-title>
</ion-toolbar>
<ion-toolbar>
<ion-searchbar placeholder="Type to filter..." debounce="0"></ion-searchbar>
</ion-toolbar>
<ion-toolbar>
<ion-note id="value">Current value:</ion-note>
</ion-toolbar>
</ion-header>
<ion-content class="outer-content">
<ion-radio-group allow-empty-selection></ion-radio-group>
</ion-content>
<style>
ion-note {
padding: 0 10px;
}
</style>
<script>
const radioGroup = document.querySelector('ion-radio-group');
const searchbar = document.querySelector('ion-searchbar');
const currentValue = document.querySelector('#value');
radioGroup.addEventListener('ionChange', (ev) => {
currentValue.innerText = `Current value: ${ev.detail.value}`;
});
searchbar.addEventListener('ionChange', (ev) => {
filter(ev.detail.value);
});
const filter = (value) => {
const query = value != null ? value.toLowerCase() : '';
const data = [
{ id: 0, value: 'zero' },
{ id: 1, value: 'one' },
{ id: 2, value: 'two' },
{ id: 3, value: 'three' },
];
let html = '';
data.forEach((d) => {
if (d.value.includes(query)) {
html += `
<ion-item>
<ion-label>Item ${d.value}</ion-label>
<ion-radio value="${d.value}"></ion-radio>
</ion-item>
`;
}
});
radioGroup.innerHTML = html;
};
filter();
</script>
</ion-app>
</body>
</html>

View File

@@ -1,42 +0,0 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
/**
* This behavior does not var across modes/directions.
*/
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('radio-group'), () => {
test.beforeEach(async ({ page }) => {
await page.goto('/src/components/radio-group/test/legacy/search', config);
});
test.describe('radio-group: state', () => {
test('radio should remain checked after being removed/readded to the dom', async ({ page }) => {
const radioGroup = page.locator('ion-radio-group');
const radio = page.locator('ion-radio[value=two]');
const searchbarInput = page.locator('ion-searchbar input');
// select radio
await radio.click();
await expect(radio.locator('input')).toHaveJSProperty('checked', true);
// filter radio so it is not in DOM
await page.fill('ion-searchbar input', 'zero');
await searchbarInput.evaluate((el) => el.blur());
await page.waitForChanges();
await expect(radio).toBeHidden();
// ensure radio group has the same value
await expect(radioGroup).toHaveJSProperty('value', 'two');
// clear the search so the radio appears
await page.fill('ion-searchbar input', '');
await searchbarInput.evaluate((el) => el.blur());
await page.waitForChanges();
// ensure that the new radio instance is still checked
await expect(radio.locator('input')).toHaveJSProperty('checked', true);
});
});
});
});

View File

@@ -65,7 +65,8 @@
if (d.value.includes(query)) {
html += `
<ion-item>
<ion-radio value="${d.value}">Item ${d.value}</ion-radio>
<ion-label>Item ${d.value}</ion-label>
<ion-radio value="${d.value}"></ion-radio>
</ion-item>
`;
}

View File

@@ -1,41 +1,42 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
import { RadioFixture } from '../fixtures';
/**
* This behavior does not var across modes/directions.
*/
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('radio-group: search'), () => {
test('radio should remain checked after being removed/readded to the dom', async ({ page }) => {
test.describe(title('radio-group'), () => {
test.beforeEach(async ({ page }) => {
await page.goto('/src/components/radio-group/test/search', config);
});
const radioFixture = new RadioFixture(page);
test.describe('radio-group: state', () => {
test('radio should remain checked after being removed/readded to the dom', async ({ page }) => {
const radioGroup = page.locator('ion-radio-group');
const radio = page.locator('ion-radio[value=two]');
const searchbarInput = page.locator('ion-searchbar input');
const radioGroup = page.locator('ion-radio-group');
const searchbarInput = page.locator('ion-searchbar input');
// select radio
await radio.click();
await expect(radio.locator('input')).toHaveJSProperty('checked', true);
// select radio
const radio = await radioFixture.checkRadio('mouse', 'ion-radio[value=two]');
await radioFixture.expectChecked(true);
// filter radio so it is not in DOM
await page.fill('ion-searchbar input', 'zero');
await searchbarInput.evaluate((el) => el.blur());
await page.waitForChanges();
await expect(radio).toBeHidden();
// filter radio so it is not in DOM
await page.fill('ion-searchbar input', 'zero');
await searchbarInput.evaluate((el) => el.blur());
await page.waitForChanges();
await expect(radio).toBeHidden();
// ensure radio group has the same value
await expect(radioGroup).toHaveJSProperty('value', 'two');
// ensure radio group has the same value
await expect(radioGroup).toHaveJSProperty('value', 'two');
// clear the search so the radio appears
await page.fill('ion-searchbar input', '');
await searchbarInput.evaluate((el) => el.blur());
await page.waitForChanges();
// clear the search so the radio appears
await page.fill('ion-searchbar input', '');
await searchbarInput.evaluate((el) => el.blur());
await page.waitForChanges();
// ensure that the new radio instance is still checked
await radioFixture.expectChecked(true);
// ensure that the new radio instance is still checked
await expect(radio.locator('input')).toHaveJSProperty('checked', true);
});
});
});
});

View File

@@ -2,7 +2,8 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Method, Prop, State, Watch, h } from '@stencil/core';
import type { LegacyFormController } from '@utils/forms';
import { createLegacyFormController } from '@utils/forms';
import { addEventListener, getAriaLabel, removeEventListener } from '@utils/helpers';
import type { Attributes } from '@utils/helpers';
import { addEventListener, getAriaLabel, inheritAriaAttributes, removeEventListener } from '@utils/helpers';
import { printIonWarning } from '@utils/logging';
import { createColorClasses, hostContext } from '@utils/theme';
@@ -30,6 +31,7 @@ export class Radio implements ComponentInterface {
private radioGroup: HTMLIonRadioGroupElement | null = null;
private nativeInput!: HTMLInputElement;
private legacyFormController!: LegacyFormController;
private inheritedAttributes: Attributes = {};
// This flag ensures we log the deprecation warning at most once.
private hasLoggedDeprecationWarning = false;
@@ -133,7 +135,8 @@ export class Radio implements ComponentInterface {
ev.stopPropagation();
ev.preventDefault();
this.el.focus();
const element = this.legacyFormController.hasLegacyControl() ? this.el : this.nativeInput;
element.focus();
}
/** @internal */
@@ -164,6 +167,12 @@ export class Radio implements ComponentInterface {
componentWillLoad() {
this.emitStyle();
if (!this.legacyFormController.hasLegacyControl()) {
this.inheritedAttributes = {
...inheritAriaAttributes(this.el),
};
}
}
@Watch('checked')
@@ -192,34 +201,7 @@ export class Radio implements ComponentInterface {
};
private onClick = () => {
const { radioGroup, checked } = this;
/**
* The legacy control uses a native input inside
* of the radio host, so we can set this.checked
* to the state of the nativeInput. RadioGroup
* will prevent the native input from checking if
* allowEmptySelection="false" by calling ev.preventDefault().
*/
if (this.legacyFormController.hasLegacyControl()) {
this.checked = this.nativeInput.checked;
return;
}
/**
* The modern control does not use a native input
* inside of the radio host, so we cannot rely on the
* ev.preventDefault() behavior above. If the radio
* is checked and the parent radio group allows for empty
* selection, then we can set the checked state to false.
* Otherwise, the checked state should always be set
* to true because the checked state cannot be toggled.
*/
if (checked && radioGroup?.allowEmptySelection) {
this.checked = false;
} else {
this.checked = true;
}
this.checked = this.nativeInput.checked;
};
private onFocus = () => {
@@ -250,14 +232,23 @@ export class Radio implements ComponentInterface {
}
private renderRadio() {
const { checked, disabled, color, el, justify, labelPlacement, hasLabel, buttonTabindex } = this;
const {
checked,
disabled,
inputId,
color,
el,
justify,
labelPlacement,
inheritedAttributes,
hasLabel,
buttonTabindex,
} = this;
const mode = getIonMode(this);
const inItem = hostContext('ion-item', el);
return (
<Host
onFocus={this.onFocus}
onBlur={this.onBlur}
onClick={this.onClick}
class={createColorClasses(color, {
[mode]: true,
@@ -270,12 +261,21 @@ export class Radio implements ComponentInterface {
'ion-activatable': !inItem,
'ion-focusable': !inItem,
})}
role="radio"
aria-checked={checked ? 'true' : 'false'}
aria-disabled={disabled ? 'true' : null}
tabindex={buttonTabindex}
>
<label class="radio-wrapper">
{/*
The native control must be rendered
before the visible label text due to https://bugs.webkit.org/show_bug.cgi?id=251951
*/}
<input
type="radio"
checked={checked}
disabled={disabled}
id={inputId}
tabindex={buttonTabindex}
ref={(nativeEl) => (this.nativeInput = nativeEl as HTMLInputElement)}
{...inheritedAttributes}
/>
<div
class={{
'label-text-wrapper': true,

View File

@@ -16,7 +16,10 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
});
test.describe(title('radio: keyboard navigation'), () => {
test.beforeEach(async ({ page }) => {
test.beforeEach(async ({ page, skip }) => {
// TODO (FW-2979)
skip.browser('webkit', 'Safari 16 only allows text fields and pop-up menus to be focused.');
await page.setContent(
`
<ion-app>

View File

@@ -95,7 +95,7 @@ configs().forEach(({ title, screenshot, config }) => {
test('should apply color correctly', async ({ page }) => {
await page.setContent(
`
<ion-radio legacy="true" value="pepperoni" color="success"></ion-radio>
<ion-radio slot="start" value="pepperoni" color="success"></ion-radio>
`,
config
);
@@ -138,7 +138,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
test('radio should be checked when activated even without a radio group', async ({ page }) => {
await page.setContent(
`
<ion-radio legacy="true" value="pepperoni"></ion-radio>
<ion-radio slot="start" value="pepperoni"></ion-radio>
`,
config
);

View File

@@ -3,14 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)
**Note:** Version bump only for package @ionic/docs
# [7.2.0](https://github.com/ionic-team/ionic-framework/compare/v7.1.4...v7.2.0) (2023-07-19)
**Note:** Version bump only for package @ionic/docs

View File

@@ -1,12 +1,12 @@
{
"name": "@ionic/docs",
"version": "7.2.1",
"version": "7.2.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/docs",
"version": "7.2.1",
"version": "7.2.0",
"license": "MIT"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/docs",
"version": "7.2.1",
"version": "7.2.0",
"description": "Pre-packaged API documentation for the Ionic docs.",
"main": "core.json",
"types": "core.d.ts",

View File

@@ -4,5 +4,5 @@
"docs",
"packages/*"
],
"version": "7.2.1"
"version": "7.2.0"
}

View File

@@ -3,14 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)
**Note:** Version bump only for package @ionic/angular-server
# [7.2.0](https://github.com/ionic-team/ionic-framework/compare/v7.1.4...v7.2.0) (2023-07-19)
**Note:** Version bump only for package @ionic/angular-server

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/angular-server",
"version": "7.2.1",
"version": "7.2.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular-server",
"version": "7.2.1",
"version": "7.2.0",
"license": "MIT",
"dependencies": {
"@ionic/core": "^7.2.1"
"@ionic/core": "^7.2.0"
},
"devDependencies": {
"@angular-eslint/eslint-plugin": "^14.0.0",
@@ -1060,9 +1060,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"dependencies": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
@@ -7342,9 +7342,9 @@
"dev": true
},
"@ionic/core": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"requires": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/angular-server",
"version": "7.2.1",
"version": "7.2.0",
"description": "Angular SSR Module for Ionic",
"keywords": [
"ionic",
@@ -61,6 +61,6 @@
},
"prettier": "@ionic/prettier-config",
"dependencies": {
"@ionic/core": "^7.2.1"
"@ionic/core": "^7.2.0"
}
}

View File

@@ -3,14 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)
**Note:** Version bump only for package @ionic/angular
# [7.2.0](https://github.com/ionic-team/ionic-framework/compare/v7.1.4...v7.2.0) (2023-07-19)

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/angular",
"version": "7.2.1",
"version": "7.2.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular",
"version": "7.2.1",
"version": "7.2.0",
"license": "MIT",
"dependencies": {
"@ionic/core": "^7.2.1",
"@ionic/core": "^7.2.0",
"ionicons": "^7.0.0",
"jsonc-parser": "^3.0.0",
"tslib": "^2.3.0"
@@ -1227,9 +1227,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"dependencies": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
@@ -8104,9 +8104,9 @@
"dev": true
},
"@ionic/core": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"requires": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/angular",
"version": "7.2.1",
"version": "7.2.0",
"description": "Angular specific wrappers for @ionic/core",
"keywords": [
"ionic",
@@ -47,7 +47,7 @@
}
},
"dependencies": {
"@ionic/core": "^7.2.1",
"@ionic/core": "^7.2.0",
"ionicons": "^7.0.0",
"jsonc-parser": "^3.0.0",
"tslib": "^2.3.0"

View File

@@ -3,14 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)
**Note:** Version bump only for package @ionic/react-router
# [7.2.0](https://github.com/ionic-team/ionic-framework/compare/v7.1.4...v7.2.0) (2023-07-19)
**Note:** Version bump only for package @ionic/react-router

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/react-router",
"version": "7.2.1",
"version": "7.2.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/react-router",
"version": "7.2.1",
"version": "7.2.0",
"license": "MIT",
"dependencies": {
"@ionic/react": "^7.2.1",
"@ionic/react": "^7.2.0",
"tslib": "*"
},
"devDependencies": {
@@ -205,9 +205,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"dependencies": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
@@ -401,11 +401,11 @@
}
},
"node_modules/@ionic/react": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-7.2.1.tgz",
"integrity": "sha512-z9PSPoBiHsdwotdywGcnln1y01wjLoS09vmCUboc+2qSadVMczcjEuBxfRIvjVbaQt3u8RGHif2+K3AgGNKMeA==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-7.2.0.tgz",
"integrity": "sha512-90evXKqkpptvSVYbxyyY/D9hE20ZbRV1dyZhxL5PzeLt4eIuvz6G11gTfH8YCyEAhBFZpCsg+mpcIcoP45ITvA==",
"dependencies": {
"@ionic/core": "7.2.1",
"@ionic/core": "7.2.0",
"ionicons": "^7.0.0",
"tslib": "*"
},
@@ -486,9 +486,9 @@
}
},
"node_modules/@stencil/core": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-3.4.2.tgz",
"integrity": "sha512-FAUhUVaakCy29nU2GwO/HQBRV1ihPRvncz3PUc8oR+UJLAxGabTmP8PLY7wvHfbw+Cvi4VXfJFTBvdfDu6iKPQ==",
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-3.4.1.tgz",
"integrity": "sha512-7rjOmM0W9K5op2gtOQRLERGH1155rv2fm6ppxOzYqqG8ISct4m9skp5XgUBYPu+GSPsJFdRuCIQs0IuVsG/7+g==",
"bin": {
"stencil": "bin/stencil"
},
@@ -3663,9 +3663,9 @@
"dev": true
},
"@ionic/core": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"requires": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
@@ -3786,11 +3786,11 @@
"requires": {}
},
"@ionic/react": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-7.2.1.tgz",
"integrity": "sha512-z9PSPoBiHsdwotdywGcnln1y01wjLoS09vmCUboc+2qSadVMczcjEuBxfRIvjVbaQt3u8RGHif2+K3AgGNKMeA==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-7.2.0.tgz",
"integrity": "sha512-90evXKqkpptvSVYbxyyY/D9hE20ZbRV1dyZhxL5PzeLt4eIuvz6G11gTfH8YCyEAhBFZpCsg+mpcIcoP45ITvA==",
"requires": {
"@ionic/core": "7.2.1",
"@ionic/core": "7.2.0",
"ionicons": "^7.0.0",
"tslib": "*"
}
@@ -3844,9 +3844,9 @@
}
},
"@stencil/core": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-3.4.2.tgz",
"integrity": "sha512-FAUhUVaakCy29nU2GwO/HQBRV1ihPRvncz3PUc8oR+UJLAxGabTmP8PLY7wvHfbw+Cvi4VXfJFTBvdfDu6iKPQ=="
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-3.4.1.tgz",
"integrity": "sha512-7rjOmM0W9K5op2gtOQRLERGH1155rv2fm6ppxOzYqqG8ISct4m9skp5XgUBYPu+GSPsJFdRuCIQs0IuVsG/7+g=="
},
"@types/estree": {
"version": "0.0.39",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/react-router",
"version": "7.2.1",
"version": "7.2.0",
"description": "React Router wrapper for @ionic/react",
"keywords": [
"ionic",
@@ -37,7 +37,7 @@
"dist/"
],
"dependencies": {
"@ionic/react": "^7.2.1",
"@ionic/react": "^7.2.0",
"tslib": "*"
},
"peerDependencies": {

View File

@@ -44,6 +44,7 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
createViewItem: this.viewStack.createViewItem,
findViewItemByRouteInfo: this.viewStack.findViewItemByRouteInfo,
findLeavingViewItemByRouteInfo: this.viewStack.findLeavingViewItemByRouteInfo,
findRouteMatchByRouteInfo: this.viewStack.findRouteMatchByRouteInfo,
addViewItem: this.viewStack.add,
unMountViewItem: this.viewStack.remove,
};
@@ -270,13 +271,13 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
const goBack = history.goBack || history.back;
goBack();
} else {
this.handleNavigate(prevInfo.pathname + (prevInfo.search || ''), 'pop', 'back', routeAnimation);
this.handleNavigate(prevInfo.pathname + (prevInfo.search || ''), 'pop', 'back');
}
} else {
this.handleNavigate(defaultHref as string, 'pop', 'back', routeAnimation);
this.handleNavigate(defaultHref as string, 'pop', 'back');
}
} else {
this.handleNavigate(defaultHref as string, 'pop', 'back', routeAnimation);
this.handleNavigate(defaultHref as string, 'pop', 'back');
}
}

View File

@@ -11,6 +11,7 @@ export class ReactRouterViewStack extends ViewStacks {
this.findLeavingViewItemByRouteInfo = this.findLeavingViewItemByRouteInfo.bind(this);
this.getChildrenToRender = this.getChildrenToRender.bind(this);
this.findViewItemByPathname = this.findViewItemByPathname.bind(this);
this.findRouteMatchByRouteInfo = this.findRouteMatchByRouteInfo.bind(this);
}
createViewItem(outletId: string, reactElement: React.ReactElement, routeInfo: RouteInfo, page?: HTMLElement) {
@@ -96,12 +97,8 @@ export class ReactRouterViewStack extends ViewStacks {
return children;
}
findViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string, updateMatch?: boolean) {
const { viewItem, match } = this.findViewItemByPath(routeInfo.pathname, outletId);
const shouldUpdateMatch = updateMatch === undefined || updateMatch === true;
if (shouldUpdateMatch && viewItem && match) {
viewItem.routeData.match = match;
}
findViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string) {
const { viewItem } = this.findViewItemByPath(routeInfo.pathname, outletId);
return viewItem;
}
@@ -115,6 +112,11 @@ export class ReactRouterViewStack extends ViewStacks {
return viewItem;
}
findRouteMatchByRouteInfo(routeInfo: RouteInfo, outletId?: string) {
const { match } = this.findViewItemByPath(routeInfo.pathname, outletId);
return match;
}
private findViewItemByPath(pathname: string, outletId?: string, forceExact?: boolean, mustBeIonRoute?: boolean) {
let viewItem: ViewItem | undefined;
let match: ReturnType<typeof matchPath> | undefined;

View File

@@ -1,7 +1,6 @@
import type { RouteInfo, StackContextState, ViewItem } from '@ionic/react';
import { RouteManagerContext, StackContext, generateId, getConfig } from '@ionic/react';
import type { PropsWithChildren } from 'react';
import React, { cloneElement, useContext, useEffect, useMemo, useReducer, useRef, useState } from 'react';
import React from 'react';
import { matchPath } from 'react-router-dom';
import { clonePageElement } from './clonePageElement';
@@ -12,93 +11,76 @@ interface StackManagerProps {
routeInfo: RouteInfo;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface StackManagerState {}
const isViewVisible = (el: HTMLElement) =>
!el.classList.contains('ion-page-invisible') && !el.classList.contains('ion-page-hidden');
export const StackManager = ({ children, ...props }: PropsWithChildren<StackManagerProps>) => {
const { routeInfo } = props;
const {
findViewItemByRouteInfo,
findLeavingViewItemByRouteInfo,
findViewItemByPathname,
createViewItem,
addViewItem,
goBack,
getChildrenToRender,
clearOutlet,
} = useContext(RouteManagerContext);
export class StackManager extends React.PureComponent<StackManagerProps, StackManagerState> {
id: string;
context!: React.ContextType<typeof RouteManagerContext>;
ionRouterOutlet?: React.ReactElement;
routerOutletElement: HTMLIonRouterOutletElement | undefined;
prevProps?: StackManagerProps;
skipTransition: boolean;
const routerOutletRef = useRef<HTMLIonRouterOutletElement>();
const ionRouterOutletRef = useRef<React.ReactElement>();
const skipTransitionRef = useRef(false);
const clearOutletTimeout = useRef(null);
const pendingPageTransitionRef = useRef(false);
const prevProps = useRef<{ routeInfo: RouteInfo }>();
stackContextValue: StackContextState = {
registerIonPage: this.registerIonPage.bind(this),
isInOutlet: () => true,
};
const forceUpdate = useReducer((x) => x + 1, 0)[1];
private clearOutletTimeout: any;
private pendingPageTransition = false;
const [id] = useState(generateId('routerOutlet'));
constructor(props: StackManagerProps) {
super(props);
this.registerIonPage = this.registerIonPage.bind(this);
this.transitionPage = this.transitionPage.bind(this);
this.handlePageTransition = this.handlePageTransition.bind(this);
this.id = generateId('routerOutlet');
this.prevProps = undefined;
this.skipTransition = false;
}
const stackContextValue: StackContextState = useMemo(
() => ({
isInOutlet: () => true,
registerIonPage: (page: HTMLElement, routeInfo: RouteInfo) => {
const foundView = findViewItemByRouteInfo(routeInfo, id);
if (foundView) {
const oldPageElement = foundView.ionPageElement;
foundView.ionPageElement = page;
foundView.ionRoute = true;
/**
* React 18 will unmount and remount IonPage
* elements in development mode when using createRoot.
* This can cause duplicate page transitions to occur.
*/
if (oldPageElement === page) {
return;
}
}
handlePageTransition(routeInfo);
},
}),
[routerOutletRef.current]
);
useEffect(() => {
const routerOutletElement = routerOutletRef.current;
if (routerOutletElement) {
// Mount behavior for the initial route
setupRouterOutlet(routerOutletElement);
handlePageTransition(routeInfo);
componentDidMount() {
if (this.clearOutletTimeout) {
/**
* The clearOutlet integration with React Router is a bit hacky.
* It uses a timeout to clear the outlet after a transition.
* In React v18, components are mounted and unmounted in development mode
* to check for side effects.
*
* This clearTimeout prevents the outlet from being cleared when the component is re-mounted,
* which should only happen in development mode and as a result of a hot reload.
*/
clearTimeout(this.clearOutletTimeout);
}
}, []);
useEffect(() => {
const { pathname } = routeInfo;
if (pathname !== prevProps.current?.routeInfo.pathname) {
prevProps.current = props;
handlePageTransition(routeInfo);
} else if (pendingPageTransitionRef.current) {
handlePageTransition(routeInfo);
pendingPageTransitionRef.current = false;
if (this.routerOutletElement) {
this.setupRouterOutlet(this.routerOutletElement);
this.handlePageTransition(this.props.routeInfo);
}
}, [routeInfo]);
}
useEffect(() => {
return () => {
clearOutlet(id);
if (clearOutletTimeout.current) {
clearTimeout(clearOutletTimeout.current);
clearOutletTimeout.current = null;
}
};
}, []);
componentDidUpdate(prevProps: StackManagerProps) {
const { pathname } = this.props.routeInfo;
const { pathname: prevPathname } = prevProps.routeInfo;
const handlePageTransition = (routeInfo: RouteInfo) => {
const routerOutletElement = routerOutletRef.current;
if (!routerOutletElement || !routerOutletElement.commit) {
if (pathname !== prevPathname) {
this.prevProps = prevProps;
this.handlePageTransition(this.props.routeInfo);
} else if (this.pendingPageTransition) {
this.handlePageTransition(this.props.routeInfo);
this.pendingPageTransition = false;
}
}
componentWillUnmount() {
this.clearOutletTimeout = this.context.clearOutlet(this.id);
}
async handlePageTransition(routeInfo: RouteInfo) {
if (!this.routerOutletElement || !this.routerOutletElement.commit) {
/**
* The route outlet has not mounted yet. We need to wait for it to render
* before we can transition the page.
@@ -106,126 +88,175 @@ export const StackManager = ({ children, ...props }: PropsWithChildren<StackMana
* Set a flag to indicate that we should transition the page after
* the component has updated.
*/
pendingPageTransitionRef.current = true;
return;
}
this.pendingPageTransition = true;
} else {
let enteringViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id);
let leavingViewItem = this.context.findLeavingViewItemByRouteInfo(routeInfo, this.id);
let enteringViewItem = findViewItemByRouteInfo(routeInfo, id);
let leavingViewItem = findLeavingViewItemByRouteInfo(routeInfo, id);
let recreateEnteringView = false;
if (!leavingViewItem && routeInfo.prevRouteLastPathname) {
leavingViewItem = findViewItemByPathname(routeInfo.prevRouteLastPathname, id);
}
if (routeInfo.prevRouteLastPathname) {
let prevRouteViewItem = this.context.findViewItemByPathname(routeInfo.prevRouteLastPathname, this.id);
if (prevRouteViewItem) {
if (prevRouteViewItem === enteringViewItem && prevRouteViewItem.routeData.match.url !== routeInfo.pathname) {
recreateEnteringView = true;
}
}
}
// Check if the leavingViewItem should be unmounted
if (leavingViewItem) {
if (routeInfo.routeAction === 'replace') {
leavingViewItem.mount = false;
} else if (!(routeInfo.routeAction === 'push' && routeInfo.routeDirection === 'forward')) {
if (routeInfo.routeDirection !== 'none' && enteringViewItem !== leavingViewItem) {
const match = this.context.findRouteMatchByRouteInfo(routeInfo, this.id);
if (enteringViewItem && match) {
enteringViewItem.routeData.match = match;
}
if (!leavingViewItem && routeInfo.prevRouteLastPathname) {
leavingViewItem = this.context.findViewItemByPathname(routeInfo.prevRouteLastPathname, this.id);
}
// Check if leavingViewItem should be unmounted
if (leavingViewItem) {
if (routeInfo.routeAction === 'replace') {
leavingViewItem.mount = false;
} else if (!(routeInfo.routeAction === 'push' && routeInfo.routeDirection === 'forward')) {
if (routeInfo.routeDirection !== 'none' && enteringViewItem !== leavingViewItem) {
leavingViewItem.mount = false;
}
} else if (routeInfo.routeOptions?.unmount) {
leavingViewItem.mount = false;
}
} else if (routeInfo.routeOptions?.unmount) {
leavingViewItem.mount = false;
}
}
const enteringRoute = matchRoute(ionRouterOutletRef.current?.props.children, routeInfo) as React.ReactElement;
const enteringRoute = matchRoute(this.ionRouterOutlet?.props.children, routeInfo) as React.ReactElement;
if (enteringViewItem) {
// If the entering view is already in the stack, then we need to clone it
enteringViewItem.reactElement = enteringRoute;
} else if (enteringRoute) {
// Otherwise we need to create a new view item
enteringViewItem = createViewItem(id, enteringRoute, routeInfo);
addViewItem(enteringViewItem);
}
if (enteringViewItem) {
const match = this.context.findRouteMatchByRouteInfo(routeInfo, this.id);
if (enteringViewItem && enteringViewItem.ionPageElement) {
/**
* If the entering view item is the same as the leaving view item,
* then we don't need to transition.
*/
if (enteringViewItem === leavingViewItem) {
if (match) {
enteringViewItem.routeData.match = match;
}
/**
* If we are re-entering a view item, then we need to validate that the
* view item is valid for the current route. For example, if the user navigates
* to a parametrized route: /foo/:id, then navigates to /example, then navigates
* back to /foo/:id, we only want to re-use the view item if the id param is the same.
* Otherwise we need to construct a new view item.
*/
if (recreateEnteringView) {
enteringViewItem = this.context.createViewItem(this.id, enteringRoute, routeInfo);
this.context.addViewItem(enteringViewItem);
}
enteringViewItem.reactElement = enteringRoute;
} else if (enteringRoute) {
enteringViewItem = this.context.createViewItem(this.id, enteringRoute, routeInfo);
this.context.addViewItem(enteringViewItem);
}
if (enteringViewItem && enteringViewItem.ionPageElement) {
/**
* If the entering view item is the same as the leaving view item,
* we are either transitioning using parameterized routes to the same view
* or a parent router outlet is re-rendering as a result of React props changing.
*
* If the route data does not match the current path, the parent router outlet
* is attempting to transition and we cancel the operation.
* then we don't need to transition.
*/
if (enteringViewItem.routeData.match.url !== routeInfo.pathname) {
if (enteringViewItem === leavingViewItem) {
/**
* If the entering view item is the same as the leaving view item,
* we are either transitioning using parameterized routes to the same view
* or a parent router outlet is re-rendering as a result of React props changing.
*
* If the route data does not match the current path, the parent router outlet
* is attempting to transition and we cancel the operation.
*/
if (enteringViewItem.routeData.match.url !== routeInfo.pathname) {
return;
}
}
/**
* If there isn't a leaving view item, but the route info indicates
* that the user has routed from a previous path, then we need
* to find the leaving view item to transition between.
*/
if (!leavingViewItem && this.props.routeInfo.prevRouteLastPathname) {
leavingViewItem = this.context.findViewItemByPathname(this.props.routeInfo.prevRouteLastPathname, this.id);
}
/**
* If the entering view is already visible and the leaving view is not, the transition does not need to occur.
*/
if (
isViewVisible(enteringViewItem.ionPageElement) &&
leavingViewItem !== undefined &&
!isViewVisible(leavingViewItem.ionPageElement!)
) {
return;
}
/**
* The view should only be transitioned in the following cases:
* 1. Performing a replace or pop action, such as a swipe to go back gesture
* to animation the leaving view off the screen.
*
* 2. Navigating between top-level router outlets, such as /page-1 to /page-2;
* or navigating within a nested outlet, such as /tabs/tab-1 to /tabs/tab-2.
*
* 3. The entering view is an ion-router-outlet containing a page
* matching the current route and that hasn't already transitioned in.
*
* This should only happen when navigating directly to a nested router outlet
* route or on an initial page load (i.e. refreshing). In cases when loading
* /tabs/tab-1, we need to transition the /tabs page element into the view.
*/
this.transitionPage(routeInfo, enteringViewItem, leavingViewItem);
} else if (leavingViewItem && !enteringRoute && !enteringViewItem) {
// If we have a leavingView but no entering view/route, we are probably leaving to
// another outlet, so hide this leavingView. We do it in a timeout to give time for a
// transition to finish.
// setTimeout(() => {
if (leavingViewItem.ionPageElement) {
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
}
// }, 250);
}
/**
* If there isn't a leaving view item, but the route info indicates
* that the user has routed from a previous path, then we need
* to find the leaving view item to transition between.
*/
if (!leavingViewItem && routeInfo.prevRouteLastPathname) {
leavingViewItem = findViewItemByPathname(routeInfo.prevRouteLastPathname, id);
}
this.forceUpdate();
}
}
registerIonPage(page: HTMLElement, routeInfo: RouteInfo) {
const foundView = this.context.findViewItemByRouteInfo(routeInfo, this.id);
if (foundView) {
const oldPageElement = foundView.ionPageElement;
foundView.ionPageElement = page;
foundView.ionRoute = true;
/**
* If the entering view is already visible and the leaving view is not, the transition does not need to occur.
* React 18 will unmount and remount IonPage
* elements in development mode when using createRoot.
* This can cause duplicate page transitions to occur.
*/
if (
isViewVisible(enteringViewItem.ionPageElement) &&
leavingViewItem !== undefined &&
!isViewVisible(leavingViewItem.ionPageElement!)
) {
if (oldPageElement === page) {
return;
}
/**
* The view should only be transitioned in the following cases:
* 1. Performing a replace or pop action, such as a swipe to go back gesture
* to animation the leaving view off the screen.
*
* 2. Navigating between top-level router outlets, such as /page-1 to /page-2;
* or navigating within a nested outlet, such as /tabs/tab-1 to /tabs/tab-2.
*
* 3. The entering view is an ion-router-outlet containing a page
* matching the current route and that hasn't already transitioned in.
*
* This should only happen when navigating directly to a nested router outlet
* route or on an initial page load (i.e. refreshing). In cases when loading
* /tabs/tab-1, we need to transition the /tabs page element into the view.
*/
transitionPage(routeInfo, enteringViewItem, leavingViewItem!);
} else if (leavingViewItem && !enteringRoute && !enteringViewItem) {
// If we have a leavingView but no entering view/route, we are probably leaving to
// another outlet, so hide this leavingView.
if (leavingViewItem.ionPageElement) {
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
}
}
this.handlePageTransition(routeInfo);
}
// This causes the router outlet to re-render with the updated view items.
// Without it, a push navigation will remove the previous route's view item,
// but will not render the new route's view item.
forceUpdate();
};
const setupRouterOutlet = (routerOutlet: HTMLIonRouterOutletElement) => {
const canStart = (): boolean => {
async setupRouterOutlet(routerOutlet: HTMLIonRouterOutletElement) {
const canStart = () => {
const config = getConfig();
const swipeEnabled = config?.getBoolean('swipeBackEnabled', routerOutlet.mode === 'ios');
const swipeEnabled = config && config.get('swipeBackEnabled', routerOutlet.mode === 'ios');
if (!swipeEnabled) {
return false;
}
const propsToUse =
prevProps.current?.routeInfo.pathname === routeInfo.pushedByRoute
? prevProps.current!.routeInfo
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
const { routeInfo } = this.props;
const enteringViewItem = findViewItemByRouteInfo(propsToUse, id, false);
const propsToUse =
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
? this.prevProps.routeInfo
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id);
return (
!!enteringViewItem &&
@@ -249,12 +280,14 @@ export const StackManager = ({ children, ...props }: PropsWithChildren<StackMana
};
const onStart = async () => {
const { routeInfo } = this.props;
const propsToUse =
prevProps.current?.routeInfo.pathname === routeInfo.pushedByRoute
? prevProps.current!.routeInfo
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
? this.prevProps.routeInfo
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
const enteringViewItem = findViewItemByRouteInfo(propsToUse, id, false);
const leavingViewItem = findViewItemByRouteInfo(routeInfo, id, false);
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id);
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id);
/**
* When the gesture starts, kick off
@@ -262,28 +295,30 @@ export const StackManager = ({ children, ...props }: PropsWithChildren<StackMana
* via a swipe gesture.
*/
if (enteringViewItem && leavingViewItem) {
await transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back', true);
await this.transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back', true);
}
return Promise.resolve();
};
const onEnd = (shouldContinue: boolean) => {
if (shouldContinue) {
skipTransitionRef.current = true;
goBack();
this.skipTransition = true;
this.context.goBack();
} else {
/**
* In the event that the swipe
* gesture was aborted, we should
* re-hide the page that was going to enter.
*/
const { routeInfo } = this.props;
const propsToUse =
prevProps.current?.routeInfo.pathname === routeInfo.pushedByRoute
? prevProps.current!.routeInfo
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
? this.prevProps.routeInfo
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
const enteringViewItem = findViewItemByRouteInfo(propsToUse, id, false);
const leavingViewItem = findViewItemByRouteInfo(routeInfo, id, false);
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id);
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id);
/**
* Ionic React has a design defect where it
@@ -307,17 +342,17 @@ export const StackManager = ({ children, ...props }: PropsWithChildren<StackMana
onStart,
onEnd,
};
};
}
const transitionPage = async (
async transitionPage(
routeInfo: RouteInfo,
enteringViewItem: ViewItem,
leavingViewItem: ViewItem,
leavingViewItem?: ViewItem,
direction?: 'forward' | 'back',
progressAnimation = false
) => {
) {
const runCommit = async (enteringEl: HTMLElement, leavingEl?: HTMLElement) => {
const skipTransition = skipTransitionRef.current;
const skipTransition = this.skipTransition;
/**
* If the transition was handled
@@ -340,99 +375,102 @@ export const StackManager = ({ children, ...props }: PropsWithChildren<StackMana
* transition triggered by handlePageTransition
* in componentDidUpdate.
*/
skipTransitionRef.current = false;
this.skipTransition = false;
} else {
enteringEl.classList.add('ion-page', 'ion-page-invisible');
enteringEl.classList.add('ion-page');
enteringEl.classList.add('ion-page-invisible');
}
const routerOutletElement = routerOutletRef.current;
if (routerOutletElement) {
await routerOutletElement.commit(enteringEl, leavingEl, {
duration: skipTransitionRef.current || directionToUse === undefined ? 0 : undefined,
direction: directionToUse,
showGoBack: !!routeInfo.pushedByRoute,
progressAnimation,
animationBuilder: routeInfo.routeAnimation,
});
}
await routerOutlet.commit(enteringEl, leavingEl, {
duration: skipTransition || directionToUse === undefined ? 0 : undefined,
direction: directionToUse,
showGoBack: !!routeInfo.pushedByRoute,
progressAnimation,
animationBuilder: routeInfo.routeAnimation,
});
};
const routerInfoFallbackDirection =
const routerOutlet = this.routerOutletElement!;
const routeInfoFallbackDirection =
routeInfo.routeDirection === 'none' || routeInfo.routeDirection === 'root' ? undefined : routeInfo.routeDirection;
const directionToUse = direction ?? routerInfoFallbackDirection;
const directionToUse = direction ?? routeInfoFallbackDirection;
const routerOutletElement = routerOutletRef.current;
if (enteringViewItem?.ionPageElement && routerOutletElement) {
if (leavingViewItem?.ionPageElement && enteringViewItem === leavingViewItem) {
if (enteringViewItem && enteringViewItem.ionPageElement && this.routerOutletElement) {
if (leavingViewItem && leavingViewItem.ionPageElement && enteringViewItem === leavingViewItem) {
// If a page is transitioning to another version of itself
// we clone it so we can have an animation to show
const match = matchComponent(leavingViewItem.reactElement, routeInfo.pathname, true);
if (match) {
const newLeavingElement = clonePageElement(leavingViewItem.ionPageElement.outerHTML);
if (newLeavingElement) {
routerOutletElement.appendChild(newLeavingElement);
this.routerOutletElement.appendChild(newLeavingElement);
await runCommit(enteringViewItem.ionPageElement, newLeavingElement);
routerOutletElement.removeChild(newLeavingElement);
this.routerOutletElement.removeChild(newLeavingElement);
}
} else {
await runCommit(enteringViewItem.ionPageElement, undefined);
}
} else {
await runCommit(enteringViewItem.ionPageElement, leavingViewItem?.ionPageElement);
if (leavingViewItem?.ionPageElement && !progressAnimation) {
const { ionPageElement } = leavingViewItem;
ionPageElement.setAttribute('aria-hidden', 'true');
ionPageElement.classList.add('ion-page-hidden');
if (leavingViewItem && leavingViewItem.ionPageElement && !progressAnimation) {
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
}
}
}
};
}
const renderComponents = () => {
const ionRouterOutlet = React.Children.only<React.ReactElement>(children as React.ReactElement);
ionRouterOutletRef.current = ionRouterOutlet;
render() {
const { children } = this.props;
const ionRouterOutlet = React.Children.only(children) as React.ReactElement;
this.ionRouterOutlet = ionRouterOutlet;
const components = getChildrenToRender(id, ionRouterOutlet, routeInfo, () => {
forceUpdate();
const components = this.context.getChildrenToRender(this.id, this.ionRouterOutlet, this.props.routeInfo, () => {
this.forceUpdate();
});
return cloneElement(ionRouterOutlet, {
ref: (node: HTMLIonRouterOutletElement) => {
if (ionRouterOutlet.props.setRef) {
ionRouterOutlet.props.setRef(node);
}
if (ionRouterOutlet.props.forwardedRef) {
ionRouterOutlet.props.forwardedRef.current = node;
}
return (
<StackContext.Provider value={this.stackContextValue}>
{React.cloneElement(
ionRouterOutlet as any,
{
ref: (node: HTMLIonRouterOutletElement) => {
if (ionRouterOutlet.props.setRef) {
ionRouterOutlet.props.setRef(node);
}
if (ionRouterOutlet.props.forwardedRef) {
ionRouterOutlet.props.forwardedRef.current = node;
}
this.routerOutletElement = node;
const { ref } = ionRouterOutlet as any;
if (typeof ref === 'function') {
ref(node);
}
},
},
components
)}
</StackContext.Provider>
);
}
routerOutletRef.current = node;
const { ref } = ionRouterOutlet as any;
if (typeof ref === 'function') {
ref(node);
}
},
components,
});
};
return <StackContext.Provider value={stackContextValue}>{renderComponents()}</StackContext.Provider>;
};
static get contextType() {
return RouteManagerContext;
}
}
export default StackManager;
function matchRoute(node: React.ReactNode, routeInfo: RouteInfo) {
let matchedNode: React.ReactNode;
React.Children.forEach(node as React.ReactElement, (child: React.ReactElement) => {
const matchProps = {
exact: child.props.exact,
path: child.props.path || child.props.from,
component: child.props.component,
};
// In React Router v6 the prop arguments are in a different order
const match = matchPath(routeInfo.pathname, matchProps);
if (match) {
matchedNode = child;
@@ -459,7 +497,6 @@ function matchComponent(node: React.ReactElement, pathname: string, forceExact?:
path: node.props.path || node.props.from,
component: node.props.component,
};
// In React Router v6 the prop arguments are in a different order
const match = matchPath(pathname, matchProps);
return match;

View File

@@ -3,14 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)
**Note:** Version bump only for package @ionic/react
# [7.2.0](https://github.com/ionic-team/ionic-framework/compare/v7.1.4...v7.2.0) (2023-07-19)
**Note:** Version bump only for package @ionic/react

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/react",
"version": "7.2.1",
"version": "7.2.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/react",
"version": "7.2.1",
"version": "7.2.0",
"license": "MIT",
"dependencies": {
"@ionic/core": "^7.2.1",
"@ionic/core": "^7.2.0",
"ionicons": "^7.0.0",
"tslib": "*"
},
@@ -697,9 +697,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"dependencies": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
@@ -11778,9 +11778,9 @@
"dev": true
},
"@ionic/core": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"requires": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/react",
"version": "7.2.1",
"version": "7.2.0",
"description": "React specific wrapper for @ionic/core",
"keywords": [
"ionic",
@@ -41,7 +41,7 @@
"css/"
],
"dependencies": {
"@ionic/core": "^7.2.1",
"@ionic/core": "^7.2.0",
"ionicons": "^7.0.0",
"tslib": "*"
},

View File

@@ -16,7 +16,8 @@ export interface RouteManagerContextState {
) => ViewItem;
findViewItemByPathname(pathname: string, outletId?: string): ViewItem | undefined;
findLeavingViewItemByRouteInfo: (routeInfo: RouteInfo, outletId?: string) => ViewItem | undefined;
findViewItemByRouteInfo: (routeInfo: RouteInfo, outletId?: string, updateMatch?: boolean) => ViewItem | undefined;
findViewItemByRouteInfo: (routeInfo: RouteInfo, outletId?: string) => ViewItem | undefined;
findRouteMatchByRouteInfo: (routeInfo: RouteInfo, outletId?: string) => any | undefined;
getChildrenToRender: (
outletId: string,
ionRouterOutlet: React.ReactElement,
@@ -36,6 +37,7 @@ export const RouteManagerContext = /*@__PURE__*/ React.createContext<RouteManage
findViewItemByPathname: () => undefined,
findLeavingViewItemByRouteInfo: () => undefined,
findViewItemByRouteInfo: () => undefined,
findRouteMatchByRouteInfo: () => undefined,
getChildrenToRender: () => undefined as any,
goBack: () => undefined,
unMountViewItem: () => undefined,

View File

@@ -3,14 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)
**Note:** Version bump only for package @ionic/vue-router
# [7.2.0](https://github.com/ionic-team/ionic-framework/compare/v7.1.4...v7.2.0) (2023-07-19)
**Note:** Version bump only for package @ionic/vue-router

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/vue-router",
"version": "7.2.1",
"version": "7.2.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/vue-router",
"version": "7.2.1",
"version": "7.2.0",
"license": "MIT",
"dependencies": {
"@ionic/vue": "^7.2.1"
"@ionic/vue": "^7.2.0"
},
"devDependencies": {
"@ionic/eslint-config": "^0.3.0",
@@ -660,9 +660,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"dependencies": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
@@ -871,11 +871,11 @@
}
},
"node_modules/@ionic/vue": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-7.2.1.tgz",
"integrity": "sha512-l9ucLk1NrAZHXdAT8qrJYArklS7cCok6/Qlq52Kg0+C912sfV3IIEMxjfQw8e6MX0aZGkSYMG/BGLEBqxp6gOw==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-7.2.0.tgz",
"integrity": "sha512-KcfQ00pwevAhaRh5izlKnOEt9y38896WB6Qp8q8wpLux/+Ax4o+XPWNkSt5NxtEPHesdop5qv/oFTENn2diKnw==",
"dependencies": {
"@ionic/core": "7.2.1",
"@ionic/core": "7.2.0",
"ionicons": "^7.0.0"
}
},
@@ -1323,9 +1323,9 @@
}
},
"node_modules/@stencil/core": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-3.4.2.tgz",
"integrity": "sha512-FAUhUVaakCy29nU2GwO/HQBRV1ihPRvncz3PUc8oR+UJLAxGabTmP8PLY7wvHfbw+Cvi4VXfJFTBvdfDu6iKPQ==",
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-3.4.1.tgz",
"integrity": "sha512-7rjOmM0W9K5op2gtOQRLERGH1155rv2fm6ppxOzYqqG8ISct4m9skp5XgUBYPu+GSPsJFdRuCIQs0IuVsG/7+g==",
"bin": {
"stencil": "bin/stencil"
},
@@ -6807,9 +6807,9 @@
}
},
"node_modules/tslib": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz",
"integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig=="
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz",
"integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA=="
},
"node_modules/tsutils": {
"version": "3.21.0",
@@ -7697,9 +7697,9 @@
"dev": true
},
"@ionic/core": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"requires": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
@@ -7829,11 +7829,11 @@
"requires": {}
},
"@ionic/vue": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-7.2.1.tgz",
"integrity": "sha512-l9ucLk1NrAZHXdAT8qrJYArklS7cCok6/Qlq52Kg0+C912sfV3IIEMxjfQw8e6MX0aZGkSYMG/BGLEBqxp6gOw==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-7.2.0.tgz",
"integrity": "sha512-KcfQ00pwevAhaRh5izlKnOEt9y38896WB6Qp8q8wpLux/+Ax4o+XPWNkSt5NxtEPHesdop5qv/oFTENn2diKnw==",
"requires": {
"@ionic/core": "7.2.1",
"@ionic/core": "7.2.0",
"ionicons": "^7.0.0"
}
},
@@ -8192,9 +8192,9 @@
}
},
"@stencil/core": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-3.4.2.tgz",
"integrity": "sha512-FAUhUVaakCy29nU2GwO/HQBRV1ihPRvncz3PUc8oR+UJLAxGabTmP8PLY7wvHfbw+Cvi4VXfJFTBvdfDu6iKPQ=="
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-3.4.1.tgz",
"integrity": "sha512-7rjOmM0W9K5op2gtOQRLERGH1155rv2fm6ppxOzYqqG8ISct4m9skp5XgUBYPu+GSPsJFdRuCIQs0IuVsG/7+g=="
},
"@tootallnate/once": {
"version": "2.0.0",
@@ -12220,9 +12220,9 @@
}
},
"tslib": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz",
"integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig=="
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz",
"integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA=="
},
"tsutils": {
"version": "3.21.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/vue-router",
"version": "7.2.1",
"version": "7.2.0",
"description": "Vue Router integration for @ionic/vue",
"scripts": {
"test.spec": "jest",
@@ -45,7 +45,7 @@
},
"homepage": "https://github.com/ionic-team/ionic#readme",
"dependencies": {
"@ionic/vue": "^7.2.1"
"@ionic/vue": "^7.2.0"
},
"devDependencies": {
"@ionic/eslint-config": "^0.3.0",

View File

@@ -186,10 +186,10 @@ export const createIonRouter = (
router.go(prevInfo.position - routeInfo.position);
}
} else {
handleNavigate(defaultHref, "pop", "back", routerAnimation);
handleNavigate(defaultHref, "pop", "back");
}
} else {
handleNavigate(defaultHref, "pop", "back", routerAnimation);
handleNavigate(defaultHref, "pop", "back");
}
};

View File

@@ -3,14 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)
**Note:** Version bump only for package @ionic/vue
# [7.2.0](https://github.com/ionic-team/ionic-framework/compare/v7.1.4...v7.2.0) (2023-07-19)

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/vue",
"version": "7.2.1",
"version": "7.2.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/vue",
"version": "7.2.1",
"version": "7.2.0",
"license": "MIT",
"dependencies": {
"@ionic/core": "^7.2.1",
"@ionic/core": "^7.2.0",
"ionicons": "^7.0.0"
},
"devDependencies": {
@@ -207,9 +207,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"dependencies": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
@@ -3746,9 +3746,9 @@
"dev": true
},
"@ionic/core": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"requires": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/vue",
"version": "7.2.1",
"version": "7.2.0",
"description": "Vue specific wrapper for @ionic/core",
"scripts": {
"eslint": "eslint src",
@@ -66,7 +66,7 @@
"vue-router": "^4.0.16"
},
"dependencies": {
"@ionic/core": "^7.2.1",
"@ionic/core": "^7.2.0",
"ionicons": "^7.0.0"
},
"vetur": {