mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
Compare commits
2 Commits
ionic-modu
...
v8.7.14
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7af5d3ca5 | ||
|
|
03fb422bfa |
11
CHANGELOG.md
11
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
|
||||
|
||||
@@ -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
|
||||
|
||||
6
core/package-lock.json
generated
6
core/package-lock.json
generated
@@ -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",
|
||||
@@ -9839,4 +9839,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "8.7.13",
|
||||
"version": "8.7.14",
|
||||
"description": "Base components for Ionic",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
|
||||
@@ -3,5 +3,5 @@
|
||||
"core",
|
||||
"packages/*"
|
||||
],
|
||||
"version": "8.7.13"
|
||||
"version": "8.7.14"
|
||||
}
|
||||
@@ -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
|
||||
|
||||
8
packages/angular-server/package-lock.json
generated
8
packages/angular-server/package-lock.json
generated
@@ -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",
|
||||
@@ -11289,4 +11289,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
8
packages/angular/package-lock.json
generated
8
packages/angular/package-lock.json
generated
@@ -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"
|
||||
@@ -9095,4 +9095,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
6
packages/docs/package-lock.json
generated
6
packages/docs/package-lock.json
generated
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
8
packages/react-router/package-lock.json
generated
8
packages/react-router/package-lock.json
generated
@@ -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": {
|
||||
@@ -6847,4 +6847,4 @@
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
|
||||
8
packages/react/package-lock.json
generated
8
packages/react/package-lock.json
generated
@@ -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": "*"
|
||||
},
|
||||
@@ -11916,4 +11916,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": "*"
|
||||
},
|
||||
|
||||
@@ -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<InternalProps, IonTabBarState> {
|
||||
context!: React.ContextType<typeof NavContext>;
|
||||
|
||||
@@ -79,7 +93,7 @@ class IonTabBarUnwrapped extends React.PureComponent<InternalProps, IonTabBarSta
|
||||
const tabKeys = Object.keys(tabs);
|
||||
const activeTab = tabKeys.find((key) => {
|
||||
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<InternalProps, IonTabBarSta
|
||||
const tabKeys = Object.keys(state.tabs);
|
||||
const activeTab = tabKeys.find((key) => {
|
||||
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
|
||||
|
||||
@@ -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 = () => (
|
||||
<Route path="/tabs" component={Tabs} />
|
||||
<Route path="/tabs-basic" component={TabsBasic} />
|
||||
<Route path="/tabs-direct-navigation" component={TabsDirectNavigation} />
|
||||
<Route path="/tabs-similar-prefixes" component={TabsSimilarPrefixes} />
|
||||
<Route path="/icons" component={Icons} />
|
||||
<Route path="/inputs" component={Inputs} />
|
||||
<Route path="/reorder-group" component={ReorderGroup} />
|
||||
|
||||
@@ -46,6 +46,9 @@ const Main: React.FC<MainProps> = () => {
|
||||
<IonItem routerLink="/tabs-direct-navigation">
|
||||
<IonLabel>Tabs with Direct Navigation</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem routerLink="/tabs-similar-prefixes">
|
||||
<IonLabel>Tabs with Similar Route Prefixes</IonLabel>
|
||||
</IonItem>
|
||||
<IonItem routerLink="/icons">
|
||||
<IonLabel>Icons</IonLabel>
|
||||
</IonItem>
|
||||
|
||||
87
packages/react/test/base/src/pages/TabsSimilarPrefixes.tsx
Normal file
87
packages/react/test/base/src/pages/TabsSimilarPrefixes.tsx
Normal file
@@ -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 = () => (
|
||||
<IonPage data-testid="home-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Home</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<div data-testid="home-content">Home Content</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
|
||||
const Home2Page: React.FC = () => (
|
||||
<IonPage data-testid="home2-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Home 2</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<div data-testid="home2-content">Home 2 Content</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
|
||||
const Home3Page: React.FC = () => (
|
||||
<IonPage data-testid="home3-page">
|
||||
<IonHeader>
|
||||
<IonToolbar>
|
||||
<IonTitle>Home 3</IonTitle>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent>
|
||||
<div data-testid="home3-content">Home 3 Content</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
|
||||
const TabsSimilarPrefixes: React.FC = () => {
|
||||
return (
|
||||
<IonTabs data-testid="tabs-similar-prefixes">
|
||||
<IonRouterOutlet>
|
||||
<Redirect exact path="/tabs-similar-prefixes" to="/tabs-similar-prefixes/home" />
|
||||
<Route path="/tabs-similar-prefixes/home" render={() => <HomePage />} exact={true} />
|
||||
<Route path="/tabs-similar-prefixes/home2" render={() => <Home2Page />} exact={true} />
|
||||
<Route path="/tabs-similar-prefixes/home3" render={() => <Home3Page />} exact={true} />
|
||||
</IonRouterOutlet>
|
||||
|
||||
<IonTabBar slot="bottom" data-testid="tab-bar">
|
||||
<IonTabButton tab="home" href="/tabs-similar-prefixes/home" data-testid="home-tab">
|
||||
<IonIcon icon={homeOutline}></IonIcon>
|
||||
<IonLabel>Home</IonLabel>
|
||||
</IonTabButton>
|
||||
|
||||
<IonTabButton tab="home2" href="/tabs-similar-prefixes/home2" data-testid="home2-tab">
|
||||
<IonIcon icon={radioOutline}></IonIcon>
|
||||
<IonLabel>Home 2</IonLabel>
|
||||
</IonTabButton>
|
||||
|
||||
<IonTabButton tab="home3" href="/tabs-similar-prefixes/home3" data-testid="home3-tab">
|
||||
<IonIcon icon={libraryOutline}></IonIcon>
|
||||
<IonLabel>Home 3</IonLabel>
|
||||
</IonTabButton>
|
||||
</IonTabBar>
|
||||
</IonTabs>
|
||||
);
|
||||
};
|
||||
|
||||
export default TabsSimilarPrefixes;
|
||||
@@ -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');
|
||||
|
||||
@@ -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
|
||||
|
||||
8
packages/vue-router/package-lock.json
generated
8
packages/vue-router/package-lock.json
generated
@@ -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",
|
||||
@@ -12994,4 +12994,4 @@
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
8
packages/vue/package-lock.json
generated
8
packages/vue/package-lock.json
generated
@@ -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"
|
||||
},
|
||||
@@ -4022,4 +4022,4 @@
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -165,6 +165,28 @@ const routes: Array<RouteRecordRaw> = [
|
||||
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({
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
<ion-item router-link="/tabs-basic" id="tab-basic">
|
||||
<ion-label>Tabs with Basic Navigation</ion-label>
|
||||
</ion-item>
|
||||
<ion-item router-link="/tabs-similar-prefixes" id="tabs-similar-prefixes">
|
||||
<ion-label>Tabs with Similar Route Prefixes</ion-label>
|
||||
</ion-item>
|
||||
<ion-item router-link="/lifecycle" id="lifecycle">
|
||||
<ion-label>Lifecycle</ion-label>
|
||||
</ion-item>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<ion-page data-pageid="home">
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Home</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<div data-testid="home-content">Home Content</div>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/vue';
|
||||
</script>
|
||||
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<ion-page data-pageid="home2">
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Home 2</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<div data-testid="home2-content">Home 2 Content</div>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/vue';
|
||||
</script>
|
||||
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<ion-page data-pageid="home3">
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Home 3</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<div data-testid="home3-content">Home 3 Content</div>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/vue';
|
||||
</script>
|
||||
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<ion-page data-pageid="tabs-similar-prefixes">
|
||||
<ion-content>
|
||||
<ion-tabs id="tabs-similar-prefixes">
|
||||
<ion-router-outlet></ion-router-outlet>
|
||||
<ion-tab-bar slot="bottom" data-testid="tab-bar">
|
||||
<ion-tab-button
|
||||
tab="home"
|
||||
href="/tabs-similar-prefixes/home"
|
||||
data-testid="home-tab"
|
||||
id="tab-button-home"
|
||||
>
|
||||
<ion-icon :icon="homeOutline" />
|
||||
<ion-label>Home</ion-label>
|
||||
</ion-tab-button>
|
||||
|
||||
<ion-tab-button
|
||||
tab="home2"
|
||||
href="/tabs-similar-prefixes/home2"
|
||||
data-testid="home2-tab"
|
||||
id="tab-button-home2"
|
||||
>
|
||||
<ion-icon :icon="radioOutline" />
|
||||
<ion-label>Home 2</ion-label>
|
||||
</ion-tab-button>
|
||||
|
||||
<ion-tab-button
|
||||
tab="home3"
|
||||
href="/tabs-similar-prefixes/home3"
|
||||
data-testid="home3-tab"
|
||||
id="tab-button-home3"
|
||||
>
|
||||
<ion-icon :icon="libraryOutline" />
|
||||
<ion-label>Home 3</ion-label>
|
||||
</ion-tab-button>
|
||||
</ion-tab-bar>
|
||||
</ion-tabs>
|
||||
</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
IonContent,
|
||||
IonIcon,
|
||||
IonLabel,
|
||||
IonPage,
|
||||
IonRouterOutlet,
|
||||
IonTabBar,
|
||||
IonTabButton,
|
||||
IonTabs,
|
||||
} from '@ionic/vue';
|
||||
import { homeOutline, radioOutline, libraryOutline } from 'ionicons/icons';
|
||||
</script>
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user