import { Component, Input, Output, EventEmitter, OnInit, forwardRef, inject, } from "@angular/core"; /** * Minimal shape of `SamAccordionComponent` needed by `SamAccordionSection`. * Kept as an interface (rather than referencing the class type directly) so * `emitDecoratorMetadata` does not emit a value reference to a class that is * declared later in this file, which caused a "Cannot access before * initialization" error when the class type was used directly. */ export interface AccordionParent { sections: SamAccordionSection[]; addSection(section: SamAccordionSection): void; } /** * The component can generates content for a single * accordion item */ @Component({ selector: "sam-accordion-section", templateUrl: "accordion.template.html", standalone: false, }) export class SamAccordionSection implements OnInit { private parent: AccordionParent = inject( forwardRef(() => SamAccordionComponent) ); /** * Accordion header text */ @Input() headerText: string; /** * Defines whether accordion item should be expanded when loaded */ @Input() isExpanded: boolean; /** * Populates the 'name' attribute for the accordion item for 508 compliance */ @Input() name: string; /** * The accordion section that has changed is emitted back to parent */ @Output() isExpandedChange: EventEmitter = new EventEmitter(); constructor() { this.parent.addSection(this); } ngOnInit() { if (!this.name) { throw new Error( "[name] is a required input for the component" ); } } public toggle() { this.isExpanded = !this.isExpanded; this.isExpandedChange.emit(this); } public collapse() { this.isExpanded = false; } public expand() { this.isExpanded = true; } public index() { let index = -1; for (let i = 0; i < this.parent.sections.length; i++) { if (this.parent.sections[i] === this) { index = i; break; } } return index; } } /** * The component can generate accordions component with provided * data */ @Component({ selector: "sam-accordion", template: `
`, standalone: false, }) export class SamAccordionComponent implements OnInit { /** * Control whether the accordion component has a border */ @Input() bordered: boolean = false; /** * Index of an accordion item that should be expanded on load */ @Input() expandIndex = -1; /** * The index of the accordion item that has been opened/closed */ @Output() selectedIndexChange: EventEmitter = new EventEmitter(); public accordionClass: string = "usa-accordion"; public sections: SamAccordionSection[] = []; ngOnInit() { if (this.bordered) { this.accordionClass = "usa-accordion-bordered"; } this.setExpandIndex(this.expandIndex); } addSection(section: SamAccordionSection) { this.sections.push(section); section.isExpandedChange.subscribe((s) => { this.expandedChanged(s); }); } expandedChanged(section: SamAccordionSection) { this.expandIndex = section.isExpanded ? section.index() : -1; this.collapseOthers(section); this.selectedIndexChange.emit(this.expandIndex); } collapseOthers(section: SamAccordionSection) { this.sections.forEach((s) => { if (s !== section) { s.collapse(); } }); } collapseAll() { this.sections.forEach((s) => { s.collapse(); }); } setExpandIndex(index) { this.expandIndex = index; this.sections.forEach((section, i) => { if (i === index) { section.expand(); } else { section.collapse(); } }); } }