diff --git a/.github/workflows/actions/download-archive/action.yml b/.github/workflows/actions/download-archive/action.yml index 70f201e3d67..e61c44a0910 100644 --- a/.github/workflows/actions/download-archive/action.yml +++ b/.github/workflows/actions/download-archive/action.yml @@ -10,7 +10,7 @@ inputs: runs: using: 'composite' steps: - - uses: actions/download-artifact@v6 + - uses: actions/download-artifact@v7 with: name: ${{ inputs.name }} path: ${{ inputs.path }} diff --git a/.github/workflows/actions/test-core-screenshot/action.yml b/.github/workflows/actions/test-core-screenshot/action.yml index aef3b3620c6..e3c7d8771d5 100644 --- a/.github/workflows/actions/test-core-screenshot/action.yml +++ b/.github/workflows/actions/test-core-screenshot/action.yml @@ -62,7 +62,7 @@ runs: working-directory: ./core - name: 📦 Archive Updated Screenshots if: inputs.update == 'true' && steps.test-and-update.outputs.hasUpdatedScreenshots == 'true' - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v6 with: name: updated-screenshots-${{ inputs.shard }}-${{ inputs.totalShards }} path: UpdatedScreenshots-${{ inputs.shard }}-${{ inputs.totalShards }}.zip diff --git a/.github/workflows/actions/update-reference-screenshots/action.yml b/.github/workflows/actions/update-reference-screenshots/action.yml index 991913477bc..ad41d5e724d 100644 --- a/.github/workflows/actions/update-reference-screenshots/action.yml +++ b/.github/workflows/actions/update-reference-screenshots/action.yml @@ -10,7 +10,7 @@ runs: - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version: 24.x - - uses: actions/download-artifact@v6 + - uses: actions/download-artifact@v7 with: path: ./artifacts - name: 🔎 Extract Archives diff --git a/.github/workflows/actions/upload-archive/action.yml b/.github/workflows/actions/upload-archive/action.yml index e836e84c82d..67465651c88 100644 --- a/.github/workflows/actions/upload-archive/action.yml +++ b/.github/workflows/actions/upload-archive/action.yml @@ -13,7 +13,7 @@ runs: - name: 🗄️ Create Archive run: zip -q -r ${{ inputs.output }} ${{ inputs.paths }} shell: bash - - uses: actions/upload-artifact@v5 + - uses: actions/upload-artifact@v6 with: name: ${{ inputs.name }} path: ${{ inputs.output }} diff --git a/CHANGELOG.md b/CHANGELOG.md index f5d8b1ae591..d04b799720e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.7.14](https://github.com/ionic-team/ionic-framework/compare/v8.7.13...v8.7.14) (2025-12-17) + + +### Bug Fixes + +* **tabs:** select correct tab when routes have similar prefixes ([#30863](https://github.com/ionic-team/ionic-framework/issues/30863)) ([03fb422](https://github.com/ionic-team/ionic-framework/commit/03fb422bfa775e3e9dd695ea1857fa88d4245ecd)), closes [#30448](https://github.com/ionic-team/ionic-framework/issues/30448) + + + + + ## [8.7.13](https://github.com/ionic-team/ionic-framework/compare/v8.7.12...v8.7.13) (2025-12-13) **Note:** Version bump only for package ionic-framework diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index d71c543621e..b1138a8c984 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.7.14](https://github.com/ionic-team/ionic-framework/compare/v8.7.13...v8.7.14) (2025-12-17) + +**Note:** Version bump only for package @ionic/core + + + + + ## [8.7.13](https://github.com/ionic-team/ionic-framework/compare/v8.7.12...v8.7.13) (2025-12-13) **Note:** Version bump only for package @ionic/core diff --git a/core/package-lock.json b/core/package-lock.json index 0449661c83d..da06cc940e0 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ionic/core", - "version": "8.7.13", + "version": "8.7.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ionic/core", - "version": "8.7.13", + "version": "8.7.14", "license": "MIT", "dependencies": { "@stencil/core": "4.38.0", diff --git a/core/package.json b/core/package.json index fc207767f13..a61a961d33a 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/core", - "version": "8.7.13", + "version": "8.7.14", "description": "Base components for Ionic", "engines": { "node": ">= 16" diff --git a/lerna.json b/lerna.json index 7d016233960..f1a42eb9adb 100644 --- a/lerna.json +++ b/lerna.json @@ -3,5 +3,5 @@ "core", "packages/*" ], - "version": "8.7.13" + "version": "8.7.14" } \ No newline at end of file diff --git a/packages/angular-server/CHANGELOG.md b/packages/angular-server/CHANGELOG.md index 7b4178b5aec..b85cf7b4af2 100644 --- a/packages/angular-server/CHANGELOG.md +++ b/packages/angular-server/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.7.14](https://github.com/ionic-team/ionic-framework/compare/v8.7.13...v8.7.14) (2025-12-17) + +**Note:** Version bump only for package @ionic/angular-server + + + + + ## [8.7.13](https://github.com/ionic-team/ionic-framework/compare/v8.7.12...v8.7.13) (2025-12-13) **Note:** Version bump only for package @ionic/angular-server diff --git a/packages/angular-server/package-lock.json b/packages/angular-server/package-lock.json index b8ca7679bf5..3d9097f3bca 100644 --- a/packages/angular-server/package-lock.json +++ b/packages/angular-server/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/angular-server", - "version": "8.7.13", + "version": "8.7.14", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/angular-server", - "version": "8.7.13", + "version": "8.7.14", "license": "MIT", "dependencies": { - "@ionic/core": "^8.7.13" + "@ionic/core": "^8.7.14" }, "devDependencies": { "@angular-eslint/eslint-plugin": "^16.0.0", @@ -1031,9 +1031,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.7.13", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.13.tgz", - "integrity": "sha512-72sbep6UOiGn+KYKtVSPZhKuq0o68X6mWi5sCyXYE/V1nzUknew9RGohcxbtt5iMVgjuny/m4liIUwVgvvQ5mw==", + "version": "8.7.14", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.14.tgz", + "integrity": "sha512-9CHfow8n19tmfSllUHj3NITqKQGrZHHEF/iyG7xPOlQ9SQ2V4bzd3rHIZ3CKVet1Z/y4HLe0uRShZnmFkSujng==", "license": "MIT", "dependencies": { "@stencil/core": "4.38.0", @@ -7309,9 +7309,9 @@ "dev": true }, "@ionic/core": { - "version": "8.7.13", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.13.tgz", - "integrity": "sha512-72sbep6UOiGn+KYKtVSPZhKuq0o68X6mWi5sCyXYE/V1nzUknew9RGohcxbtt5iMVgjuny/m4liIUwVgvvQ5mw==", + "version": "8.7.14", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.14.tgz", + "integrity": "sha512-9CHfow8n19tmfSllUHj3NITqKQGrZHHEF/iyG7xPOlQ9SQ2V4bzd3rHIZ3CKVet1Z/y4HLe0uRShZnmFkSujng==", "requires": { "@stencil/core": "4.38.0", "ionicons": "^8.0.13", diff --git a/packages/angular-server/package.json b/packages/angular-server/package.json index ec624d15062..7cd4bc6cdc5 100644 --- a/packages/angular-server/package.json +++ b/packages/angular-server/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/angular-server", - "version": "8.7.13", + "version": "8.7.14", "description": "Angular SSR Module for Ionic", "keywords": [ "ionic", @@ -62,6 +62,6 @@ }, "prettier": "@ionic/prettier-config", "dependencies": { - "@ionic/core": "^8.7.13" + "@ionic/core": "^8.7.14" } } diff --git a/packages/angular/CHANGELOG.md b/packages/angular/CHANGELOG.md index bc4beba98ed..012bd397c46 100644 --- a/packages/angular/CHANGELOG.md +++ b/packages/angular/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.7.14](https://github.com/ionic-team/ionic-framework/compare/v8.7.13...v8.7.14) (2025-12-17) + +**Note:** Version bump only for package @ionic/angular + + + + + ## [8.7.13](https://github.com/ionic-team/ionic-framework/compare/v8.7.12...v8.7.13) (2025-12-13) **Note:** Version bump only for package @ionic/angular diff --git a/packages/angular/package-lock.json b/packages/angular/package-lock.json index 5f97715748d..43082656525 100644 --- a/packages/angular/package-lock.json +++ b/packages/angular/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/angular", - "version": "8.7.13", + "version": "8.7.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ionic/angular", - "version": "8.7.13", + "version": "8.7.14", "license": "MIT", "dependencies": { - "@ionic/core": "^8.7.13", + "@ionic/core": "^8.7.14", "ionicons": "^8.0.13", "jsonc-parser": "^3.0.0", "tslib": "^2.3.0" @@ -1398,9 +1398,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.7.13", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.13.tgz", - "integrity": "sha512-72sbep6UOiGn+KYKtVSPZhKuq0o68X6mWi5sCyXYE/V1nzUknew9RGohcxbtt5iMVgjuny/m4liIUwVgvvQ5mw==", + "version": "8.7.14", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.14.tgz", + "integrity": "sha512-9CHfow8n19tmfSllUHj3NITqKQGrZHHEF/iyG7xPOlQ9SQ2V4bzd3rHIZ3CKVet1Z/y4HLe0uRShZnmFkSujng==", "license": "MIT", "dependencies": { "@stencil/core": "4.38.0", diff --git a/packages/angular/package.json b/packages/angular/package.json index e500c50064e..a5b9322d46b 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/angular", - "version": "8.7.13", + "version": "8.7.14", "description": "Angular specific wrappers for @ionic/core", "keywords": [ "ionic", @@ -48,7 +48,7 @@ } }, "dependencies": { - "@ionic/core": "^8.7.13", + "@ionic/core": "^8.7.14", "ionicons": "^8.0.13", "jsonc-parser": "^3.0.0", "tslib": "^2.3.0" diff --git a/packages/docs/CHANGELOG.md b/packages/docs/CHANGELOG.md index 075fa803db3..31769f43b2b 100644 --- a/packages/docs/CHANGELOG.md +++ b/packages/docs/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.7.14](https://github.com/ionic-team/ionic-framework/compare/v8.7.13...v8.7.14) (2025-12-17) + +**Note:** Version bump only for package @ionic/docs + + + + + ## [8.7.13](https://github.com/ionic-team/ionic-framework/compare/v8.7.12...v8.7.13) (2025-12-13) **Note:** Version bump only for package @ionic/docs diff --git a/packages/docs/package-lock.json b/packages/docs/package-lock.json index bbc96abd474..696bf80792b 100644 --- a/packages/docs/package-lock.json +++ b/packages/docs/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ionic/docs", - "version": "8.7.13", + "version": "8.7.14", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/docs", - "version": "8.7.13", + "version": "8.7.14", "license": "MIT" } } diff --git a/packages/docs/package.json b/packages/docs/package.json index 7acd7ed7bd2..bb42b1bff7f 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/docs", - "version": "8.7.13", + "version": "8.7.14", "description": "Pre-packaged API documentation for the Ionic docs.", "main": "core.json", "types": "core.d.ts", diff --git a/packages/react-router/CHANGELOG.md b/packages/react-router/CHANGELOG.md index c5ed4776e11..0fa577225a1 100644 --- a/packages/react-router/CHANGELOG.md +++ b/packages/react-router/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.7.14](https://github.com/ionic-team/ionic-framework/compare/v8.7.13...v8.7.14) (2025-12-17) + +**Note:** Version bump only for package @ionic/react-router + + + + + ## [8.7.13](https://github.com/ionic-team/ionic-framework/compare/v8.7.12...v8.7.13) (2025-12-13) **Note:** Version bump only for package @ionic/react-router diff --git a/packages/react-router/package-lock.json b/packages/react-router/package-lock.json index 4764e963749..22ea0283288 100644 --- a/packages/react-router/package-lock.json +++ b/packages/react-router/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/react-router", - "version": "8.7.13", + "version": "8.7.14", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/react-router", - "version": "8.7.13", + "version": "8.7.14", "license": "MIT", "dependencies": { - "@ionic/react": "^8.7.13", + "@ionic/react": "^8.7.14", "tslib": "*" }, "devDependencies": { @@ -238,9 +238,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.7.13", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.13.tgz", - "integrity": "sha512-72sbep6UOiGn+KYKtVSPZhKuq0o68X6mWi5sCyXYE/V1nzUknew9RGohcxbtt5iMVgjuny/m4liIUwVgvvQ5mw==", + "version": "8.7.14", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.14.tgz", + "integrity": "sha512-9CHfow8n19tmfSllUHj3NITqKQGrZHHEF/iyG7xPOlQ9SQ2V4bzd3rHIZ3CKVet1Z/y4HLe0uRShZnmFkSujng==", "license": "MIT", "dependencies": { "@stencil/core": "4.38.0", @@ -418,12 +418,12 @@ } }, "node_modules/@ionic/react": { - "version": "8.7.13", - "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.7.13.tgz", - "integrity": "sha512-PCuIpaurVYxYZ/CoUN3gP56Fwdx+bx78Qy7V5Ac61nGGW7XpVlV4vM9328Kv7OPs5fkmIvKI6LoY78BnjF0PkA==", + "version": "8.7.14", + "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.7.14.tgz", + "integrity": "sha512-8lJZz+GbwDs0HEZuSjFQkg2+fGNdVrISh9N+SAlKRajtea4wva3hBjDUl21iE/u32W7xOslTGdZ9zIdAPYjHlg==", "license": "MIT", "dependencies": { - "@ionic/core": "8.7.13", + "@ionic/core": "8.7.14", "ionicons": "^8.0.13", "tslib": "*" }, @@ -4178,9 +4178,9 @@ "dev": true }, "@ionic/core": { - "version": "8.7.13", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.13.tgz", - "integrity": "sha512-72sbep6UOiGn+KYKtVSPZhKuq0o68X6mWi5sCyXYE/V1nzUknew9RGohcxbtt5iMVgjuny/m4liIUwVgvvQ5mw==", + "version": "8.7.14", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.14.tgz", + "integrity": "sha512-9CHfow8n19tmfSllUHj3NITqKQGrZHHEF/iyG7xPOlQ9SQ2V4bzd3rHIZ3CKVet1Z/y4HLe0uRShZnmFkSujng==", "requires": { "@stencil/core": "4.38.0", "ionicons": "^8.0.13", @@ -4284,11 +4284,11 @@ "requires": {} }, "@ionic/react": { - "version": "8.7.13", - "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.7.13.tgz", - "integrity": "sha512-PCuIpaurVYxYZ/CoUN3gP56Fwdx+bx78Qy7V5Ac61nGGW7XpVlV4vM9328Kv7OPs5fkmIvKI6LoY78BnjF0PkA==", + "version": "8.7.14", + "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.7.14.tgz", + "integrity": "sha512-8lJZz+GbwDs0HEZuSjFQkg2+fGNdVrISh9N+SAlKRajtea4wva3hBjDUl21iE/u32W7xOslTGdZ9zIdAPYjHlg==", "requires": { - "@ionic/core": "8.7.13", + "@ionic/core": "8.7.14", "ionicons": "^8.0.13", "tslib": "*" } diff --git a/packages/react-router/package.json b/packages/react-router/package.json index f9010b14e91..d52d5364f02 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/react-router", - "version": "8.7.13", + "version": "8.7.14", "description": "React Router wrapper for @ionic/react", "keywords": [ "ionic", @@ -36,7 +36,7 @@ "dist/" ], "dependencies": { - "@ionic/react": "^8.7.13", + "@ionic/react": "^8.7.14", "tslib": "*" }, "peerDependencies": { diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index d196c993b47..7916238f80f 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.7.14](https://github.com/ionic-team/ionic-framework/compare/v8.7.13...v8.7.14) (2025-12-17) + + +### Bug Fixes + +* **tabs:** select correct tab when routes have similar prefixes ([#30863](https://github.com/ionic-team/ionic-framework/issues/30863)) ([03fb422](https://github.com/ionic-team/ionic-framework/commit/03fb422bfa775e3e9dd695ea1857fa88d4245ecd)), closes [#30448](https://github.com/ionic-team/ionic-framework/issues/30448) + + + + + ## [8.7.13](https://github.com/ionic-team/ionic-framework/compare/v8.7.12...v8.7.13) (2025-12-13) **Note:** Version bump only for package @ionic/react diff --git a/packages/react/package-lock.json b/packages/react/package-lock.json index ea43a49781a..39d3a60c664 100644 --- a/packages/react/package-lock.json +++ b/packages/react/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/react", - "version": "8.7.13", + "version": "8.7.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ionic/react", - "version": "8.7.13", + "version": "8.7.14", "license": "MIT", "dependencies": { - "@ionic/core": "^8.7.13", + "@ionic/core": "^8.7.14", "ionicons": "^8.0.13", "tslib": "*" }, @@ -736,9 +736,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.7.13", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.13.tgz", - "integrity": "sha512-72sbep6UOiGn+KYKtVSPZhKuq0o68X6mWi5sCyXYE/V1nzUknew9RGohcxbtt5iMVgjuny/m4liIUwVgvvQ5mw==", + "version": "8.7.14", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.14.tgz", + "integrity": "sha512-9CHfow8n19tmfSllUHj3NITqKQGrZHHEF/iyG7xPOlQ9SQ2V4bzd3rHIZ3CKVet1Z/y4HLe0uRShZnmFkSujng==", "license": "MIT", "dependencies": { "@stencil/core": "4.38.0", diff --git a/packages/react/package.json b/packages/react/package.json index 1e559e7880f..1404de8d4a5 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/react", - "version": "8.7.13", + "version": "8.7.14", "description": "React specific wrapper for @ionic/core", "keywords": [ "ionic", @@ -40,7 +40,7 @@ "css/" ], "dependencies": { - "@ionic/core": "^8.7.13", + "@ionic/core": "^8.7.14", "ionicons": "^8.0.13", "tslib": "*" }, diff --git a/packages/react/src/components/navigation/IonTabBar.tsx b/packages/react/src/components/navigation/IonTabBar.tsx index 92fde774ddd..124b00f6990 100644 --- a/packages/react/src/components/navigation/IonTabBar.tsx +++ b/packages/react/src/components/navigation/IonTabBar.tsx @@ -40,6 +40,20 @@ interface IonTabBarState { // TODO(FW-2959): types +/** + * Checks if pathname matches the tab's href using path segment matching. + * Avoids false matches like /home2 matching /home by requiring exact match + * or a path segment boundary (/). + */ +const matchesTab = (pathname: string, href: string | undefined): boolean => { + if (href === undefined) { + return false; + } + + const normalizedHref = href.endsWith('/') && href !== '/' ? href.slice(0, -1) : href; + return pathname === normalizedHref || pathname.startsWith(normalizedHref + '/'); +}; + class IonTabBarUnwrapped extends React.PureComponent { context!: React.ContextType; @@ -79,7 +93,7 @@ class IonTabBarUnwrapped extends React.PureComponent { const href = tabs[key].originalHref; - return this.props.routeInfo!.pathname.startsWith(href); + return matchesTab(this.props.routeInfo!.pathname, href); }); if (activeTab) { @@ -121,7 +135,7 @@ class IonTabBarUnwrapped extends React.PureComponent { const href = state.tabs[key].originalHref; - return props.routeInfo!.pathname.startsWith(href); + return matchesTab(props.routeInfo!.pathname, href); }); // Check to see if the tab button href has changed, and if so, update it in the tabs state diff --git a/packages/react/test/base/src/App.tsx b/packages/react/test/base/src/App.tsx index 634af89f075..c8ea117f60e 100644 --- a/packages/react/test/base/src/App.tsx +++ b/packages/react/test/base/src/App.tsx @@ -28,6 +28,7 @@ import Tabs from './pages/Tabs'; import TabsBasic from './pages/TabsBasic'; import NavComponent from './pages/navigation/NavComponent'; import TabsDirectNavigation from './pages/TabsDirectNavigation'; +import TabsSimilarPrefixes from './pages/TabsSimilarPrefixes'; import IonModalConditional from './pages/overlay-components/IonModalConditional'; import IonModalConditionalSibling from './pages/overlay-components/IonModalConditionalSibling'; import IonModalDatetimeButton from './pages/overlay-components/IonModalDatetimeButton'; @@ -67,6 +68,7 @@ const App: React.FC = () => ( + diff --git a/packages/react/test/base/src/pages/Main.tsx b/packages/react/test/base/src/pages/Main.tsx index 3873cd3d5b5..e0dbdffcf6a 100644 --- a/packages/react/test/base/src/pages/Main.tsx +++ b/packages/react/test/base/src/pages/Main.tsx @@ -46,6 +46,9 @@ const Main: React.FC = () => { Tabs with Direct Navigation + + Tabs with Similar Route Prefixes + Icons diff --git a/packages/react/test/base/src/pages/TabsSimilarPrefixes.tsx b/packages/react/test/base/src/pages/TabsSimilarPrefixes.tsx new file mode 100644 index 00000000000..78672dc075d --- /dev/null +++ b/packages/react/test/base/src/pages/TabsSimilarPrefixes.tsx @@ -0,0 +1,87 @@ +import { + IonContent, + IonHeader, + IonIcon, + IonLabel, + IonPage, + IonRouterOutlet, + IonTabBar, + IonTabButton, + IonTabs, + IonTitle, + IonToolbar, +} from '@ionic/react'; +import { homeOutline, radioOutline, libraryOutline } from 'ionicons/icons'; +import React from 'react'; +import { Route, Redirect } from 'react-router-dom'; + +const HomePage: React.FC = () => ( + + + + Home + + + +
Home Content
+
+
+); + +const Home2Page: React.FC = () => ( + + + + Home 2 + + + +
Home 2 Content
+
+
+); + +const Home3Page: React.FC = () => ( + + + + Home 3 + + + +
Home 3 Content
+
+
+); + +const TabsSimilarPrefixes: React.FC = () => { + return ( + + + + } exact={true} /> + } exact={true} /> + } exact={true} /> + + + + + + Home + + + + + Home 2 + + + + + Home 3 + + + + ); +}; + +export default TabsSimilarPrefixes; diff --git a/packages/react/test/base/tests/e2e/specs/tabs/tabs.cy.ts b/packages/react/test/base/tests/e2e/specs/tabs/tabs.cy.ts index 544fdc47c41..34b4ee4f1e6 100644 --- a/packages/react/test/base/tests/e2e/specs/tabs/tabs.cy.ts +++ b/packages/react/test/base/tests/e2e/specs/tabs/tabs.cy.ts @@ -1,4 +1,45 @@ describe('IonTabs', () => { + /** + * Verifies that tabs with similar route prefixes (e.g., /home, /home2, /home3) + * correctly select the matching tab instead of the first prefix match. + * + * @see https://github.com/ionic-team/ionic-framework/issues/30448 + */ + describe('Similar Route Prefixes', () => { + it('should select the correct tab when routes have similar prefixes', () => { + cy.visit('/tabs-similar-prefixes/home2'); + + cy.get('[data-testid="home2-content"]').should('be.visible'); + cy.get('[data-testid="home2-tab"]').should('have.class', 'tab-selected'); + cy.get('[data-testid="home-tab"]').should('not.have.class', 'tab-selected'); + }); + + it('should select the correct tab when navigating via tab buttons', () => { + cy.visit('/tabs-similar-prefixes/home'); + + cy.get('[data-testid="home-tab"]').should('have.class', 'tab-selected'); + cy.get('[data-testid="home2-tab"]').should('not.have.class', 'tab-selected'); + + cy.get('[data-testid="home2-tab"]').click(); + cy.get('[data-testid="home2-tab"]').should('have.class', 'tab-selected'); + cy.get('[data-testid="home-tab"]').should('not.have.class', 'tab-selected'); + + cy.get('[data-testid="home3-tab"]').click(); + cy.get('[data-testid="home3-tab"]').should('have.class', 'tab-selected'); + cy.get('[data-testid="home-tab"]').should('not.have.class', 'tab-selected'); + cy.get('[data-testid="home2-tab"]').should('not.have.class', 'tab-selected'); + }); + + it('should select the correct tab when directly navigating to home3', () => { + cy.visit('/tabs-similar-prefixes/home3'); + + cy.get('[data-testid="home3-content"]').should('be.visible'); + cy.get('[data-testid="home3-tab"]').should('have.class', 'tab-selected'); + cy.get('[data-testid="home-tab"]').should('not.have.class', 'tab-selected'); + cy.get('[data-testid="home2-tab"]').should('not.have.class', 'tab-selected'); + }); + }); + describe('With IonRouterOutlet', () => { beforeEach(() => { cy.visit('/tabs/tab1'); diff --git a/packages/vue-router/CHANGELOG.md b/packages/vue-router/CHANGELOG.md index 0e409c4d520..38c7bd11d99 100644 --- a/packages/vue-router/CHANGELOG.md +++ b/packages/vue-router/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.7.14](https://github.com/ionic-team/ionic-framework/compare/v8.7.13...v8.7.14) (2025-12-17) + +**Note:** Version bump only for package @ionic/vue-router + + + + + ## [8.7.13](https://github.com/ionic-team/ionic-framework/compare/v8.7.12...v8.7.13) (2025-12-13) **Note:** Version bump only for package @ionic/vue-router diff --git a/packages/vue-router/package-lock.json b/packages/vue-router/package-lock.json index 7b67f77d65a..a6d92ecc54c 100644 --- a/packages/vue-router/package-lock.json +++ b/packages/vue-router/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/vue-router", - "version": "8.7.13", + "version": "8.7.14", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/vue-router", - "version": "8.7.13", + "version": "8.7.14", "license": "MIT", "dependencies": { - "@ionic/vue": "^8.7.13" + "@ionic/vue": "^8.7.14" }, "devDependencies": { "@ionic/eslint-config": "^0.3.0", @@ -673,9 +673,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.7.13", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.13.tgz", - "integrity": "sha512-72sbep6UOiGn+KYKtVSPZhKuq0o68X6mWi5sCyXYE/V1nzUknew9RGohcxbtt5iMVgjuny/m4liIUwVgvvQ5mw==", + "version": "8.7.14", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.14.tgz", + "integrity": "sha512-9CHfow8n19tmfSllUHj3NITqKQGrZHHEF/iyG7xPOlQ9SQ2V4bzd3rHIZ3CKVet1Z/y4HLe0uRShZnmFkSujng==", "license": "MIT", "dependencies": { "@stencil/core": "4.38.0", @@ -868,12 +868,12 @@ } }, "node_modules/@ionic/vue": { - "version": "8.7.13", - "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.7.13.tgz", - "integrity": "sha512-hflvGaNPABYP0Qt68YgrauVaXyjKeHODOkYzJhk36kcr+VexwTWm1FGJG1/nKKgdh6fwDIsubJvlhoZaRhtVtg==", + "version": "8.7.14", + "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.7.14.tgz", + "integrity": "sha512-4KYsOr55N8B95zMayS036EsExD6r3i9gwXrWKa6zwKH1QowIde3oQBVzaVyQvMoebaZP5LskpeAJKgHYDTKGrg==", "license": "MIT", "dependencies": { - "@ionic/core": "8.7.13", + "@ionic/core": "8.7.14", "@stencil/vue-output-target": "0.10.7", "ionicons": "^8.0.13" } @@ -8044,9 +8044,9 @@ "dev": true }, "@ionic/core": { - "version": "8.7.13", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.13.tgz", - "integrity": "sha512-72sbep6UOiGn+KYKtVSPZhKuq0o68X6mWi5sCyXYE/V1nzUknew9RGohcxbtt5iMVgjuny/m4liIUwVgvvQ5mw==", + "version": "8.7.14", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.14.tgz", + "integrity": "sha512-9CHfow8n19tmfSllUHj3NITqKQGrZHHEF/iyG7xPOlQ9SQ2V4bzd3rHIZ3CKVet1Z/y4HLe0uRShZnmFkSujng==", "requires": { "@stencil/core": "4.38.0", "ionicons": "^8.0.13", @@ -8159,11 +8159,11 @@ "requires": {} }, "@ionic/vue": { - "version": "8.7.13", - "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.7.13.tgz", - "integrity": "sha512-hflvGaNPABYP0Qt68YgrauVaXyjKeHODOkYzJhk36kcr+VexwTWm1FGJG1/nKKgdh6fwDIsubJvlhoZaRhtVtg==", + "version": "8.7.14", + "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.7.14.tgz", + "integrity": "sha512-4KYsOr55N8B95zMayS036EsExD6r3i9gwXrWKa6zwKH1QowIde3oQBVzaVyQvMoebaZP5LskpeAJKgHYDTKGrg==", "requires": { - "@ionic/core": "8.7.13", + "@ionic/core": "8.7.14", "@stencil/vue-output-target": "0.10.7", "ionicons": "^8.0.13" } diff --git a/packages/vue-router/package.json b/packages/vue-router/package.json index f4865db2a67..fd9211fe98d 100644 --- a/packages/vue-router/package.json +++ b/packages/vue-router/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/vue-router", - "version": "8.7.13", + "version": "8.7.14", "description": "Vue Router integration for @ionic/vue", "scripts": { "test.spec": "jest", @@ -44,7 +44,7 @@ }, "homepage": "https://github.com/ionic-team/ionic-framework#readme", "dependencies": { - "@ionic/vue": "^8.7.13" + "@ionic/vue": "^8.7.14" }, "devDependencies": { "@ionic/eslint-config": "^0.3.0", diff --git a/packages/vue/CHANGELOG.md b/packages/vue/CHANGELOG.md index 167449662f6..8ec7d30f7e5 100644 --- a/packages/vue/CHANGELOG.md +++ b/packages/vue/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.7.14](https://github.com/ionic-team/ionic-framework/compare/v8.7.13...v8.7.14) (2025-12-17) + + +### Bug Fixes + +* **tabs:** select correct tab when routes have similar prefixes ([#30863](https://github.com/ionic-team/ionic-framework/issues/30863)) ([03fb422](https://github.com/ionic-team/ionic-framework/commit/03fb422bfa775e3e9dd695ea1857fa88d4245ecd)), closes [#30448](https://github.com/ionic-team/ionic-framework/issues/30448) + + + + + ## [8.7.13](https://github.com/ionic-team/ionic-framework/compare/v8.7.12...v8.7.13) (2025-12-13) **Note:** Version bump only for package @ionic/vue diff --git a/packages/vue/package-lock.json b/packages/vue/package-lock.json index f4e71335228..db7070b2a80 100644 --- a/packages/vue/package-lock.json +++ b/packages/vue/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/vue", - "version": "8.7.13", + "version": "8.7.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ionic/vue", - "version": "8.7.13", + "version": "8.7.14", "license": "MIT", "dependencies": { - "@ionic/core": "^8.7.13", + "@ionic/core": "^8.7.14", "@stencil/vue-output-target": "0.10.7", "ionicons": "^8.0.13" }, @@ -222,9 +222,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.7.13", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.13.tgz", - "integrity": "sha512-72sbep6UOiGn+KYKtVSPZhKuq0o68X6mWi5sCyXYE/V1nzUknew9RGohcxbtt5iMVgjuny/m4liIUwVgvvQ5mw==", + "version": "8.7.14", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.14.tgz", + "integrity": "sha512-9CHfow8n19tmfSllUHj3NITqKQGrZHHEF/iyG7xPOlQ9SQ2V4bzd3rHIZ3CKVet1Z/y4HLe0uRShZnmFkSujng==", "license": "MIT", "dependencies": { "@stencil/core": "4.38.0", diff --git a/packages/vue/package.json b/packages/vue/package.json index 990152c0ef6..45f3764c4f1 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/vue", - "version": "8.7.13", + "version": "8.7.14", "description": "Vue specific wrapper for @ionic/core", "scripts": { "eslint": "eslint src", @@ -68,7 +68,7 @@ "vue-router": "^4.0.16" }, "dependencies": { - "@ionic/core": "^8.7.13", + "@ionic/core": "^8.7.14", "@stencil/vue-output-target": "0.10.7", "ionicons": "^8.0.13" }, diff --git a/packages/vue/src/components/IonTabBar.ts b/packages/vue/src/components/IonTabBar.ts index 4da54f9eee0..30002f4d858 100644 --- a/packages/vue/src/components/IonTabBar.ts +++ b/packages/vue/src/components/IonTabBar.ts @@ -24,6 +24,23 @@ interface TabBarData { const isTabButton = (child: any) => child.type?.name === "IonTabButton"; +/** + * Checks if pathname matches the tab's href using path segment matching. + * Avoids false matches like /home2 matching /home by requiring exact match + * or a path segment boundary (/). + */ +const matchesTab = (pathname: string, href: string | undefined): boolean => { + if (href === undefined) { + return false; + } + + const normalizedHref = + href.endsWith("/") && href !== "/" ? href.slice(0, -1) : href; + return ( + pathname === normalizedHref || pathname.startsWith(normalizedHref + "/") + ); +}; + const getTabs = (nodes: VNode[]) => { let tabs: VNode[] = []; nodes.forEach((node: VNode) => { @@ -135,7 +152,9 @@ export const IonTabBar = defineComponent({ const tabKeys = Object.keys(tabs); let activeTab = tabKeys.find((key) => { const href = tabs[key].originalHref; - return currentRoute?.pathname.startsWith(href); + return ( + currentRoute?.pathname && matchesTab(currentRoute.pathname, href) + ); }); /** diff --git a/packages/vue/test/base/src/router/index.ts b/packages/vue/test/base/src/router/index.ts index ab5850e33c0..e518550fd01 100644 --- a/packages/vue/test/base/src/router/index.ts +++ b/packages/vue/test/base/src/router/index.ts @@ -165,6 +165,28 @@ const routes: Array = [ path: '/tabs-basic', component: () => import('@/views/TabsBasic.vue') }, + { + path: '/tabs-similar-prefixes/', + component: () => import('@/views/tabs-similar-prefixes/TabsSimilarPrefixes.vue'), + children: [ + { + path: '', + redirect: '/tabs-similar-prefixes/home' + }, + { + path: 'home', + component: () => import('@/views/tabs-similar-prefixes/Home.vue'), + }, + { + path: 'home2', + component: () => import('@/views/tabs-similar-prefixes/Home2.vue'), + }, + { + path: 'home3', + component: () => import('@/views/tabs-similar-prefixes/Home3.vue'), + } + ] + }, ] const router = createRouter({ diff --git a/packages/vue/test/base/src/views/Home.vue b/packages/vue/test/base/src/views/Home.vue index a3ddbdf99c2..d37ab45bbea 100644 --- a/packages/vue/test/base/src/views/Home.vue +++ b/packages/vue/test/base/src/views/Home.vue @@ -50,6 +50,9 @@ Tabs with Basic Navigation + + Tabs with Similar Route Prefixes + Lifecycle diff --git a/packages/vue/test/base/src/views/tabs-similar-prefixes/Home.vue b/packages/vue/test/base/src/views/tabs-similar-prefixes/Home.vue new file mode 100644 index 00000000000..0954fac9d4f --- /dev/null +++ b/packages/vue/test/base/src/views/tabs-similar-prefixes/Home.vue @@ -0,0 +1,16 @@ + + + diff --git a/packages/vue/test/base/src/views/tabs-similar-prefixes/Home2.vue b/packages/vue/test/base/src/views/tabs-similar-prefixes/Home2.vue new file mode 100644 index 00000000000..4e190d99e98 --- /dev/null +++ b/packages/vue/test/base/src/views/tabs-similar-prefixes/Home2.vue @@ -0,0 +1,16 @@ + + + diff --git a/packages/vue/test/base/src/views/tabs-similar-prefixes/Home3.vue b/packages/vue/test/base/src/views/tabs-similar-prefixes/Home3.vue new file mode 100644 index 00000000000..78099959b06 --- /dev/null +++ b/packages/vue/test/base/src/views/tabs-similar-prefixes/Home3.vue @@ -0,0 +1,16 @@ + + + diff --git a/packages/vue/test/base/src/views/tabs-similar-prefixes/TabsSimilarPrefixes.vue b/packages/vue/test/base/src/views/tabs-similar-prefixes/TabsSimilarPrefixes.vue new file mode 100644 index 00000000000..8ec968edbd2 --- /dev/null +++ b/packages/vue/test/base/src/views/tabs-similar-prefixes/TabsSimilarPrefixes.vue @@ -0,0 +1,54 @@ + + + diff --git a/packages/vue/test/base/tests/e2e/specs/tabs.cy.js b/packages/vue/test/base/tests/e2e/specs/tabs.cy.js index 2b6cb15790f..1fdc83cf720 100644 --- a/packages/vue/test/base/tests/e2e/specs/tabs.cy.js +++ b/packages/vue/test/base/tests/e2e/specs/tabs.cy.js @@ -1,4 +1,45 @@ describe('Tabs', () => { + /** + * Verifies that tabs with similar route prefixes (e.g., /home, /home2, /home3) + * correctly select the matching tab instead of the first prefix match. + * + * @see https://github.com/ionic-team/ionic-framework/issues/30448 + */ + describe('Similar Route Prefixes', () => { + it('should select the correct tab when routes have similar prefixes', () => { + cy.visit('/tabs-similar-prefixes/home2'); + + cy.get('[data-testid="home2-content"]').should('be.visible'); + cy.get('[data-testid="home2-tab"]').should('have.class', 'tab-selected'); + cy.get('[data-testid="home-tab"]').should('not.have.class', 'tab-selected'); + }); + + it('should select the correct tab when navigating via tab buttons', () => { + cy.visit('/tabs-similar-prefixes/home'); + + cy.get('[data-testid="home-tab"]').should('have.class', 'tab-selected'); + cy.get('[data-testid="home2-tab"]').should('not.have.class', 'tab-selected'); + + cy.get('[data-testid="home2-tab"]').click(); + cy.get('[data-testid="home2-tab"]').should('have.class', 'tab-selected'); + cy.get('[data-testid="home-tab"]').should('not.have.class', 'tab-selected'); + + cy.get('[data-testid="home3-tab"]').click(); + cy.get('[data-testid="home3-tab"]').should('have.class', 'tab-selected'); + cy.get('[data-testid="home-tab"]').should('not.have.class', 'tab-selected'); + cy.get('[data-testid="home2-tab"]').should('not.have.class', 'tab-selected'); + }); + + it('should select the correct tab when directly navigating to home3', () => { + cy.visit('/tabs-similar-prefixes/home3'); + + cy.get('[data-testid="home3-content"]').should('be.visible'); + cy.get('[data-testid="home3-tab"]').should('have.class', 'tab-selected'); + cy.get('[data-testid="home-tab"]').should('not.have.class', 'tab-selected'); + cy.get('[data-testid="home2-tab"]').should('not.have.class', 'tab-selected'); + }); + }); + describe('With IonRouterOutlet', () => { it('should go back from child pages', () => { cy.visit('/tabs');