Skip to content

feat/bug(aria/accordion): custom trigger click #33810

Description

@mauriziocescon

Hey!

I'm not sure if this is intended behaviour, but I think I may have hit an edge case with AccordionTrigger when it's applied via hostDirectives rather than directly on an element. In that setup, clicks on nested children inside the trigger don't seem to toggle the panel (clicking its direct text works fine), whereas using ngAccordionTrigger directly works in all cases.

From what I can tell, it may be related to _findTriggerPattern resolving the target via .closest('[ngAccordionTrigger]') — since host-directive usage doesn't stamp that attribute onto the host element, the lookup appears to fail for nested targets.

Adding an explicit host: { ngAccordionTrigger: '' } seems to fix the problem, but I haven't found any reference to such practice. I believe it would be nice to make it working out of the box.

Cheers!

@Component({
  selector: '[myTrigger]',
  hostDirectives: [
    {
      directive: AccordionTrigger,
      inputs: ['panel', 'disabled', 'expanded'],
      outputs: ['expandedChange'],
    },
  ],
  // uncomment to make it working
  // host: { ngAccordionTrigger: '' },
  template: ` <ng-content /> `,
  styles: `
    :host {
      font-size: 16px;
    }
  `,
})
export class MyTrigger {}

@Component({
  selector: 'CustomAccordion',
  imports: [
    NgTemplateOutlet,
    AccordionGroup,
    AccordionTrigger,
    AccordionPanel,
    AccordionContent,
  ],
  template: `
    <div ngAccordionGroup [multiExpandable]="false">
      @if (trigger()) {
        <ng-container
          [ngTemplateOutlet]="trigger()"
          [ngTemplateOutletContext]="{
            panel: panel,
            expanded: expanded(),
            expandedChange: () => expanded.update(v=> !v),
            disabled: disabled(),
          }"
          [ngTemplateOutletInjector]="'outlet'" />
      } @else {
        <button 
          ngAccordionTrigger 
          [panel]="panel" 
          [(expanded)]="expanded">
          Click works! ✅
        <span>Text inside span: working ✅</span>
        </button>
      }
      <div ngAccordionPanel #panel="ngAccordionPanel">
        <ng-template ngAccordionContent>
          Content  
        </ng-template>
      </div>
    </div>
  `,
})
export class CustomAccordion {
  readonly disabled = model(false);
  readonly expanded = model(false);
  readonly trigger = input<TemplateRef<unknown> | undefined>(undefined);
}

@Component({
  selector: 'Consumer',
  imports: [MyTrigger, CustomAccordion],
  template: `
    <CustomAccordion />
    <hr />
    <CustomAccordion [trigger]="customTrigger" />

    <ng-template
      #customTrigger
      let-panel="panel"
      let-disabled="disabled"
      let-expanded="expanded"
      let-expandedChange="expandedChange">

      <button
        myTrigger
        class="trigger"
        [panel]="panel"
        [disabled]="disabled"
        [expanded]="expanded"
        (expandedChange)="expandedChange()">
        Click works! ✅
        <span>Text inside span: not working ❌</span>
      </button>
    </ng-template>
  `,
  styles: `
    .trigger {
      color: red;
    }
  `,
})
export class Consumer {}

https://stackblitz.com/edit/stackblitz-starters-gpx1tbtc?file=src%2Fmain.ts

Use Case

Custom trigger

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: aria/accordionfeatureLabel used to distinguish feature request from other issuesgemini-triagedLabel noting that an issue has been triaged by geminineeds triageThis issue needs to be triaged by the team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions