diff --git a/packages/angular/common/src/directives/navigation/router-link-delegate.ts b/packages/angular/common/src/directives/navigation/router-link-delegate.ts index d996d739428..4391d6ea0d1 100644 --- a/packages/angular/common/src/directives/navigation/router-link-delegate.ts +++ b/packages/angular/common/src/directives/navigation/router-link-delegate.ts @@ -31,12 +31,45 @@ export class RouterLinkDelegateDirective implements OnInit, OnChanges { ngOnInit(): void { this.updateTargetUrlAndHref(); + this.updateTabindex(); } ngOnChanges(): void { this.updateTargetUrlAndHref(); } + /** + * The `tabindex` is set to `0` by default on the host element when + * the `routerLink` directive is used. This causes issues with Ionic + * components that wrap an `a` or `button` element, such as `ion-item`. + * See issue https://github.com/angular/angular/issues/28345 + * + * This method removes the `tabindex` attribute from the host element + * to allow the Ionic component to manage the focus state correctly. + */ + private updateTabindex() { + // Ionic components that render a native anchor or button element + const ionicComponents = [ + 'ION-BACK-BUTTON', + 'ION-BREADCRUMB', + 'ION-BUTTON', + 'ION-CARD', + 'ION-FAB-BUTTON', + 'ION-ITEM', + 'ION-ITEM-OPTION', + 'ION-MENU-BUTTON', + 'ION-SEGMENT-BUTTON', + 'ION-TAB-BUTTON', + ]; + const hostElement = this.elementRef.nativeElement; + + if (ionicComponents.includes(hostElement.tagName)) { + if (hostElement.getAttribute('tabindex') === '0') { + hostElement.removeAttribute('tabindex'); + } + } + } + private updateTargetUrlAndHref() { if (this.routerLink?.urlTree) { const href = this.locationStrategy.prepareExternalUrl(this.router.serializeUrl(this.routerLink.urlTree)); diff --git a/packages/angular/test/base/e2e/src/lazy/router-link.spec.ts b/packages/angular/test/base/e2e/src/lazy/router-link.spec.ts index 5550d6326fc..a4a0d1bf82e 100644 --- a/packages/angular/test/base/e2e/src/lazy/router-link.spec.ts +++ b/packages/angular/test/base/e2e/src/lazy/router-link.spec.ts @@ -123,6 +123,16 @@ describe('Router Link', () => { testBack(); }); }); + + describe('tabindex', () => { + it('should have tabindex="0" with a native span', () => { + cy.get('#span').should('have.attr', 'tabindex', '0'); + }); + + it('should not have tabindex set with an ionic button', () => { + cy.get('#routerLink').should('not.have.attr', 'tabindex'); + }); + }); }); function testForward() { diff --git a/packages/angular/test/base/e2e/src/standalone/router-link.spec.ts b/packages/angular/test/base/e2e/src/standalone/router-link.spec.ts index ce8724876a9..e4abf5768d5 100644 --- a/packages/angular/test/base/e2e/src/standalone/router-link.spec.ts +++ b/packages/angular/test/base/e2e/src/standalone/router-link.spec.ts @@ -2,7 +2,7 @@ describe('RouterLink', () => { beforeEach(() => { cy.visit('/standalone/router-link'); }); - + it('should mount the root component', () => { cy.ionPageVisible('app-router-link'); @@ -21,4 +21,12 @@ describe('RouterLink', () => { cy.url().should('include', '/standalone/popover'); }); + + it('should have tabindex="0" with a native span', () => { + cy.get('span').should('have.attr', 'tabindex', '0'); + }); + + it('should not have tabindex set with an ionic button', () => { + cy.get('ion-button').should('not.have.attr', 'tabindex'); + }); }); diff --git a/packages/angular/test/base/src/app/lazy/router-link/router-link.component.html b/packages/angular/test/base/src/app/lazy/router-link/router-link.component.html index 791b6a54424..6c9f1f00eb3 100644 --- a/packages/angular/test/base/src/app/lazy/router-link/router-link.component.html +++ b/packages/angular/test/base/src/app/lazy/router-link/router-link.component.html @@ -36,4 +36,5 @@
+span[routerLink]
diff --git a/packages/angular/test/base/src/app/standalone/router-link/router-link.component.html b/packages/angular/test/base/src/app/standalone/router-link/router-link.component.html index 078d4b7c3a4..8887626d1cb 100644 --- a/packages/angular/test/base/src/app/standalone/router-link/router-link.component.html +++ b/packages/angular/test/base/src/app/standalone/router-link/router-link.component.html @@ -4,3 +4,9 @@ + + I'm a span + +