diff --git a/src/aria/accordion/accordion.spec.ts b/src/aria/accordion/accordion.spec.ts index 12d0515b8ad9..bfd02dc690e4 100644 --- a/src/aria/accordion/accordion.spec.ts +++ b/src/aria/accordion/accordion.spec.ts @@ -487,6 +487,57 @@ describe('AccordionGroup', () => { }); }); + describe('with interactive content inside a panel', () => { + let link: HTMLElement; + let button: HTMLElement; + let input: HTMLElement; + + const dispatchKeydown = async (key: string, target: HTMLElement) => { + const event = new KeyboardEvent('keydown', {bubbles: true, cancelable: true, key}); + target.dispatchEvent(event); + await fixture.whenStable(); + return event; + }; + + beforeEach(async () => { + fixture = TestBed.createComponent(AccordionWithInteractiveContent); + testComponent = fixture.componentInstance; + groupElement = fixture.nativeElement.querySelector('[ngAccordionGroup]') as HTMLElement; + testComponent.expandItem('item-1'); + await setupTriggerAndPanels(); + + link = panelElements[0].querySelector('a')!; + button = panelElements[0].querySelector('button')!; + input = panelElements[0].querySelector('input')!; + triggerElements[1].focus(); + await fixture.whenStable(); + expect(isTriggerExpanded(0)).toBeTrue(); + }); + + it('should not toggle the panel or prevent default on Enter from a link', async () => { + const event = await dispatchKeydown('Enter', link); + + expect(event.defaultPrevented).toBeFalse(); + expect(isTriggerExpanded(0)).toBeTrue(); + }); + + it('should not toggle the panel or prevent default on Space from a button', async () => { + const event = await dispatchKeydown(' ', button); + + expect(event.defaultPrevented).toBeFalse(); + expect(isTriggerExpanded(0)).toBeTrue(); + }); + + it('should not move focus or prevent default on Home and End from an input', async () => { + const homeEvent = await dispatchKeydown('Home', input); + const endEvent = await dispatchKeydown('End', input); + + expect(homeEvent.defaultPrevented).toBeFalse(); + expect(endEvent.defaultPrevented).toBeFalse(); + expect(isTriggerActive(1)).toBeTrue(); + }); + }); + describe('structural validations', () => { let consoleSpy: jasmine.Spy; @@ -696,6 +747,30 @@ class AccordionGroupWithIfs extends AccordionGroupWithLoop { includeThird = signal(true); } +@Component({ + template: ` +
+ @for (item of items(); track item.panelId) { +
+ +
+ + Link inside panel + + + +
+
+ } +
+ `, + imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent], + changeDetection: ChangeDetectionStrategy.Eager, +}) +class AccordionWithInteractiveContent extends AccordionGroupWithLoop {} + @Component({ template: `
diff --git a/src/aria/private/accordion/accordion.ts b/src/aria/private/accordion/accordion.ts index 9cd9fc8935e4..72ebed476e50 100644 --- a/src/aria/private/accordion/accordion.ts +++ b/src/aria/private/accordion/accordion.ts @@ -93,6 +93,9 @@ export class AccordionGroupPattern { /** Handles keydown events on the trigger, delegating to the group if not disabled. */ onKeydown(event: KeyboardEvent): void { + const target = _getEventTarget(event); + if (target !== this.inputs.element() && !this._findTriggerPattern(target)) return; + this.keydown().handle(event); }