primeng
Version:
PrimeNG is an open source UI library for Angular featuring a rich set of 80+ components, a theme designer, various theme alternatives such as Material, Bootstrap, Tailwind, premium templates and professional support. In addition, it integrates with PrimeB
1,159 lines (1,138 loc) • 56 kB
JavaScript
import { trigger, state, transition, style, animate } from '@angular/animations';
import * as i2 from '@angular/common';
import { CommonModule } from '@angular/common';
import * as i0 from '@angular/core';
import { Injectable, inject, forwardRef, model, input, computed, ViewEncapsulation, ChangeDetectionStrategy, Component, HostListener, ContentChild, EventEmitter, numberAttribute, booleanAttribute, ContentChildren, Output, Input, HostBinding, signal, NgModule } from '@angular/core';
import { findSingle, getAttribute, focus, uuid } from '@primeuix/utils';
import { Header, PrimeTemplate, SharedModule } from 'primeng/api';
import { BaseComponent } from 'primeng/basecomponent';
import { ChevronDownIcon, ChevronUpIcon } from 'primeng/icons';
import * as i1 from 'primeng/ripple';
import { Ripple } from 'primeng/ripple';
import { transformToBoolean } from 'primeng/utils';
import { BaseStyle } from 'primeng/base';
const theme = ({ dt }) => `
.p-accordionpanel {
display: flex;
flex-direction: column;
border-style: solid;
border-width: ${dt('accordion.panel.border.width')};
border-color: ${dt('accordion.panel.border.color')};
}
.p-accordionheader {
all: unset;
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
padding: ${dt('accordion.header.padding')};
color: ${dt('accordion.header.color')};
background: ${dt('accordion.header.background')};
border-style: solid;
border-width: ${dt('accordion.header.border.width')};
border-color: ${dt('accordion.header.border.color')};
font-weight: ${dt('accordion.header.font.weight')};
border-radius: ${dt('accordion.header.border.radius')};
transition: background ${dt('accordion.transition.duration')}; color ${dt('accordion.transition.duration')}color ${dt('accordion.transition.duration')}, outline-color ${dt('accordion.transition.duration')}, box-shadow ${dt('accordion.transition.duration')};
outline-color: transparent;
position: relative;
overflow: hidden;
}
.p-accordionpanel:first-child > .p-accordionheader {
border-width: ${dt('accordion.header.first.border.width')};
border-start-start-radius: ${dt('accordion.header.first.top.border.radius')};
border-start-end-radius: ${dt('accordion.header.first.top.border.radius')};
}
.p-accordionpanel:last-child > .p-accordionheader {
border-end-start-radius: ${dt('accordion.header.last.bottom.border.radius')};
border-end-end-radius: ${dt('accordion.header.last.bottom.border.radius')};
}
.p-accordionpanel:last-child.p-accordionpanel-active > .p-accordionheader {
border-end-start-radius: ${dt('accordion.header.last.active.bottom.border.radius')};
border-end-end-radius:${dt('accordion.header.last.active.bottom.border.radius')};
}
.p-accordionheader-toggle-icon {
color: ${dt('accordion.header.toggle.icon.color')};
}
.p-accordionpanel:not(.p-disabled) .p-accordionheader:focus-visible {
box-shadow: ${dt('accordion.header.focus.ring.shadow')};
outline: ${dt('accordion.header.focus.ring.width')} ${dt('accordion.header.focus.ring.style')} ${dt('accordion.header.focus.ring.color')};
outline-offset: ${dt('accordion.header.focus.ring.offset')};
}
.p-accordionpanel:not(.p-accordionpanel-active):not(.p-disabled) > .p-accordionheader:hover {
background: ${dt('accordion.header.hover.background')};
color: ${dt('accordion.header.hover.color')}
}
.p-accordionpanel:not(.p-accordionpanel-active):not(.p-disabled) .p-accordionheader:hover .p-accordionheader-toggle-icon {
color: ${dt('accordion.header.toggle.icon.hover.color')};
}
.p-accordionpanel:not(.p-disabled).p-accordionpanel-active > .p-accordionheader {
background: ${dt('accordion.header.active.background')};
color: ${dt('accordion.header.active.color')}
}
.p-accordionpanel:not(.p-disabled).p-accordionpanel-active > .p-accordionheader .p-accordionheader-toggle-icon {
color: ${dt('accordion.header.toggle.icon.active.color')};
}
.p-accordionpanel:not(.p-disabled).p-accordionpanel-active > .p-accordionheader:hover {
background: ${dt('accordion.header.active.hover.background')};
color: ${dt('accordion.header.active.hover.color')}
}
.p-accordionpanel:not(.p-disabled).p-accordionpanel-active > .p-accordionheader:hover .p-accordionheader-toggle-icon {
color: ${dt('accordion.header.toggle.icon.active.hover.color')};
}
.p-accordioncontent-content {
border-style: solid;
border-width: ${dt('accordion.content.border.width')};
border-color: ${dt('accordion.content.border.color')};
background-color: ${dt('accordion.content.background')};
color: ${dt('accordion.content.color')};
padding: ${dt('accordion.content.padding')}
}
/*For PrimeNG*/
.p-accordion .p-accordioncontent {
overflow: hidden;
}
.p-accordionpanel.p-accordioncontent:not(.ng-animating) {
overflow: inherit;
}
.p-accordionheader-toggle-icon.icon-start {
order: -1;
}
.p-accordionheader:has(.p-accordionheader-toggle-icon.icon-start) {
justify-content: flex-start;
gap: ${dt('accordion.header.padding')};
}
`;
const classes = {
root: 'p-accordion p-component'
};
class AccordionStyle extends BaseStyle {
name = 'accordion';
theme = theme;
classes = classes;
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: AccordionStyle, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: AccordionStyle });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: AccordionStyle, decorators: [{
type: Injectable
}] });
/**
*
* Accordion groups a collection of contents in tabs.
*
* [Live Demo](https://www.primeng.org/accordion/)
*
* @module accordionstyle
*
*/
var AccordionClasses;
(function (AccordionClasses) {
/**
* Class name of the root element
*/
AccordionClasses["root"] = "p-accordion";
/**
* Class name of the content wrapper
*/
AccordionClasses["contentwrapper"] = "p-accordioncontent";
/**
* Class name of the content
*/
AccordionClasses["content"] = "p-accordioncontent-content";
/**
* Class name of the header
*/
AccordionClasses["header"] = "p-accordionheader";
/**
* Class name of the toggle icon
*/
AccordionClasses["toggleicon"] = "p-accordionheader-toggle-icon";
/**
* Class name of the panel
*/
AccordionClasses["panel"] = "p-accordionpanel";
})(AccordionClasses || (AccordionClasses = {}));
/**
* AccordionPanel is a helper component for Accordion component.
* @group Components
*/
class AccordionPanel extends BaseComponent {
pcAccordion = inject(forwardRef(() => Accordion));
/**
* Value of the active tab.
* @defaultValue undefined
* @group Props
*/
value = model(undefined);
/**
* Disables the tab when enabled.
* @defaultValue false
* @group Props
*/
disabled = input(false, { transform: (v) => transformToBoolean(v) });
active = computed(() => (this.pcAccordion.multiple() ? this.valueEquals(this.pcAccordion.value(), this.value()) : this.pcAccordion.value() === this.value()));
valueEquals(currentValue, value) {
if (Array.isArray(currentValue)) {
return currentValue.includes(value);
}
return currentValue === value;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: AccordionPanel, deps: null, target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.2", type: AccordionPanel, isStandalone: true, selector: "p-accordion-panel, p-accordionpanel", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { properties: { "class.p-accordionpanel": "true", "class.p-accordionpanel-active": "active()", "class.p-disabled": "disabled()", "attr.data-pc-name": "\"accordionpanel\"", "attr.data-p-disabled": "disabled()", "attr.data-p-active": "active()" } }, usesInheritance: true, ngImport: i0, template: `<ng-content />`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: AccordionPanel, decorators: [{
type: Component,
args: [{
selector: 'p-accordion-panel, p-accordionpanel',
imports: [CommonModule],
standalone: true,
template: `<ng-content />`,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
host: {
'[class.p-accordionpanel]': 'true',
'[class.p-accordionpanel-active]': 'active()',
'[class.p-disabled]': 'disabled()',
'[attr.data-pc-name]': '"accordionpanel"',
'[attr.data-p-disabled]': 'disabled()',
'[attr.data-p-active]': 'active()'
}
}]
}] });
/**
* AccordionHeader is a helper component for Accordion component.
* @group Components
*/
class AccordionHeader extends BaseComponent {
pcAccordion = inject(forwardRef(() => Accordion));
pcAccordionPanel = inject(forwardRef(() => AccordionPanel));
id = computed(() => `${this.pcAccordion.id()}_accordionheader_${this.pcAccordionPanel.value()}`);
active = computed(() => this.pcAccordionPanel.active());
disabled = computed(() => this.pcAccordionPanel.disabled());
ariaControls = computed(() => `${this.pcAccordion.id()}_accordioncontent_${this.pcAccordionPanel.value()}`);
/**
* Toggle icon template.
* @type {TemplateRef<AccordionToggleIconTemplateContext>} context - Context of the template
* @example
* ```html
* <ng-template #toggleicon let-active="active"> </ng-template>
* ```
* @see {@link AccordionToggleIconTemplateContext}
* @group Templates
*/
toggleicon;
onClick(event) {
const wasActive = this.active();
this.changeActiveValue();
const isActive = this.active();
const index = this.pcAccordionPanel.value();
if (!wasActive && isActive) {
this.pcAccordion.onOpen.emit({ originalEvent: event, index });
}
else if (wasActive && !isActive) {
this.pcAccordion.onClose.emit({ originalEvent: event, index });
}
}
onFocus() {
this.pcAccordion.selectOnFocus() && this.changeActiveValue();
}
onKeydown(event) {
switch (event.code) {
case 'ArrowDown':
this.arrowDownKey(event);
break;
case 'ArrowUp':
this.arrowUpKey(event);
break;
case 'Home':
this.onHomeKey(event);
break;
case 'End':
this.onEndKey(event);
break;
case 'Enter':
case 'Space':
case 'NumpadEnter':
this.onEnterKey(event);
break;
default:
break;
}
}
changeActiveValue() {
this.pcAccordion.updateValue(this.pcAccordionPanel.value());
}
findPanel(headerElement) {
return headerElement?.closest('[data-pc-name="accordionpanel"]');
}
findHeader(panelElement) {
return findSingle(panelElement, '[data-pc-name="accordionheader"]');
}
findNextPanel(panelElement, selfCheck = false) {
const element = selfCheck ? panelElement : panelElement.nextElementSibling;
return element ? (getAttribute(element, 'data-p-disabled') ? this.findNextPanel(element) : this.findHeader(element)) : null;
}
findPrevPanel(panelElement, selfCheck = false) {
const element = selfCheck ? panelElement : panelElement.previousElementSibling;
return element ? (getAttribute(element, 'data-p-disabled') ? this.findPrevPanel(element) : this.findHeader(element)) : null;
}
findFirstPanel() {
return this.findNextPanel(this.pcAccordion.el.nativeElement.firstElementChild, true);
}
findLastPanel() {
return this.findPrevPanel(this.pcAccordion.el.nativeElement.lastElementChild, true);
}
changeFocusedPanel(event, element) {
focus(element);
}
arrowDownKey(event) {
const nextPanel = this.findNextPanel(this.findPanel(event.currentTarget));
nextPanel ? this.changeFocusedPanel(event, nextPanel) : this.onHomeKey(event);
event.preventDefault();
}
arrowUpKey(event) {
const prevPanel = this.findPrevPanel(this.findPanel(event.currentTarget));
prevPanel ? this.changeFocusedPanel(event, prevPanel) : this.onEndKey(event);
event.preventDefault();
}
onHomeKey(event) {
const firstPanel = this.findFirstPanel();
this.changeFocusedPanel(event, firstPanel);
event.preventDefault();
}
onEndKey(event) {
const lastPanel = this.findLastPanel();
this.changeFocusedPanel(event, lastPanel);
event.preventDefault();
}
onEnterKey(event) {
this.changeActiveValue();
event.preventDefault();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: AccordionHeader, deps: null, target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.2", type: AccordionHeader, isStandalone: true, selector: "p-accordion-header, p-accordionheader", host: { listeners: { "click": "onClick($event)", "focus": "onFocus($event)", "keydown": "onKeydown($event)" }, properties: { "class.p-accordionheader": "true", "attr.id": "id()", "attr.aria-expanded": "active()", "attr.aria-controls": "ariaControls()", "attr.role": "\"button\"", "attr.tabindex": "\"0\"", "attr.data-p-active": "active()", "attr.data-p-disabled": "disabled()", "attr.data-pc-name": "\"accordionheader\"", "style.user-select": "\"none\"" } }, queries: [{ propertyName: "toggleicon", first: true, predicate: ["toggleicon"], descendants: true }], usesInheritance: true, hostDirectives: [{ directive: i1.Ripple }], ngImport: i0, template: `
<ng-content />
@if (toggleicon) {
<ng-template *ngTemplateOutlet="toggleicon; context: { active: active() }"></ng-template>
} @else {
<ng-container *ngIf="active()">
<span *ngIf="pcAccordion.collapseIcon" [class]="pcAccordion.collapseIcon" [ngClass]="pcAccordion.iconClass" [attr.aria-hidden]="true"></span>
<ChevronDownIcon *ngIf="!pcAccordion.collapseIcon" [ngClass]="pcAccordion.iconClass" [attr.aria-hidden]="true" />
</ng-container>
<ng-container *ngIf="!active()">
<span *ngIf="pcAccordion.expandIcon" [class]="pcAccordion.expandIcon" [ngClass]="pcAccordion.iconClass" [attr.aria-hidden]="true"></span>
<ChevronUpIcon *ngIf="!pcAccordion.expandIcon" [ngClass]="pcAccordion.iconClass" [attr.aria-hidden]="true" />
</ng-container>
}
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: ChevronDownIcon, selector: "ChevronDownIcon" }, { kind: "component", type: ChevronUpIcon, selector: "ChevronUpIcon" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: AccordionHeader, decorators: [{
type: Component,
args: [{
selector: 'p-accordion-header, p-accordionheader',
imports: [CommonModule, ChevronDownIcon, ChevronUpIcon],
standalone: true,
template: `
<ng-content />
@if (toggleicon) {
<ng-template *ngTemplateOutlet="toggleicon; context: { active: active() }"></ng-template>
} @else {
<ng-container *ngIf="active()">
<span *ngIf="pcAccordion.collapseIcon" [class]="pcAccordion.collapseIcon" [ngClass]="pcAccordion.iconClass" [attr.aria-hidden]="true"></span>
<ChevronDownIcon *ngIf="!pcAccordion.collapseIcon" [ngClass]="pcAccordion.iconClass" [attr.aria-hidden]="true" />
</ng-container>
<ng-container *ngIf="!active()">
<span *ngIf="pcAccordion.expandIcon" [class]="pcAccordion.expandIcon" [ngClass]="pcAccordion.iconClass" [attr.aria-hidden]="true"></span>
<ChevronUpIcon *ngIf="!pcAccordion.expandIcon" [ngClass]="pcAccordion.iconClass" [attr.aria-hidden]="true" />
</ng-container>
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
host: {
'[class.p-accordionheader]': 'true',
'[attr.id]': 'id()',
'[attr.aria-expanded]': 'active()',
'[attr.aria-controls]': 'ariaControls()',
'[attr.role]': '"button"',
'[attr.tabindex]': '"0"',
'[attr.data-p-active]': 'active()',
'[attr.data-p-disabled]': 'disabled()',
'[attr.data-pc-name]': '"accordionheader"',
'[style.user-select]': '"none"'
},
hostDirectives: [Ripple]
}]
}], propDecorators: { toggleicon: [{
type: ContentChild,
args: ['toggleicon']
}], onClick: [{
type: HostListener,
args: ['click', ['$event']]
}], onFocus: [{
type: HostListener,
args: ['focus', ['$event']]
}], onKeydown: [{
type: HostListener,
args: ['keydown', ['$event']]
}] } });
class AccordionContent extends BaseComponent {
pcAccordion = inject(forwardRef(() => Accordion));
pcAccordionPanel = inject(forwardRef(() => AccordionPanel));
active = computed(() => this.pcAccordionPanel.active());
ariaLabelledby = computed(() => `${this.pcAccordion.id()}_accordionheader_${this.pcAccordionPanel.value()}`);
id = computed(() => `${this.pcAccordion.id()}_accordioncontent_${this.pcAccordionPanel.value()}`);
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: AccordionContent, deps: null, target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.2", type: AccordionContent, isStandalone: true, selector: "p-accordion-content, p-accordioncontent", host: { properties: { "class.p-accordioncontent": "true", "attr.id": "id()", "attr.role": "\"region\"", "attr.data-pc-name": "\"accordioncontent\"", "attr.data-p-active": "active()", "attr.aria-labelledby": "ariaLabelledby()", "@content": "active()\n ? { value: 'visible', params: { transitionParams: pcAccordion.transitionOptions } }\n : { value: 'hidden', params: { transitionParams: pcAccordion.transitionOptions } }" } }, usesInheritance: true, ngImport: i0, template: ` <div class="p-accordioncontent-content">
<ng-content />
</div>`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }], animations: [
trigger('content', [
state('hidden', style({
height: '0',
visibility: 'hidden'
})),
state('visible', style({
height: '*',
visibility: 'visible'
})),
transition('visible <=> hidden', [animate('{{transitionParams}}')]),
transition('void => *', animate(0))
])
], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: AccordionContent, decorators: [{
type: Component,
args: [{
selector: 'p-accordion-content, p-accordioncontent',
imports: [CommonModule],
standalone: true,
template: ` <div class="p-accordioncontent-content">
<ng-content />
</div>`,
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
host: {
'[class.p-accordioncontent]': 'true',
'[attr.id]': 'id()',
'[attr.role]': '"region"',
'[attr.data-pc-name]': '"accordioncontent"',
'[attr.data-p-active]': 'active()',
'[attr.aria-labelledby]': 'ariaLabelledby()',
'[@content]': `active()
? { value: 'visible', params: { transitionParams: pcAccordion.transitionOptions } }
: { value: 'hidden', params: { transitionParams: pcAccordion.transitionOptions } }`
},
animations: [
trigger('content', [
state('hidden', style({
height: '0',
visibility: 'hidden'
})),
state('visible', style({
height: '*',
visibility: 'visible'
})),
transition('visible <=> hidden', [animate('{{transitionParams}}')]),
transition('void => *', animate(0))
])
]
}]
}] });
/**
* AccordionTab is a helper component for Accordion.
* @deprecated Use AccordionPanel, AccordionHeader, AccordionContent instead.
* @group Components
*/
class AccordionTab extends BaseComponent {
get hostClass() {
return this.tabStyleClass;
}
get hostStyle() {
return this.tabStyle;
}
/**
* Current id state as a string.
* @group Props
*/
id = uuid('pn_id_');
/**
* Used to define the header of the tab.
* @group Props
*/
header;
/**
* Inline style of the tab header.
* @group Props
*/
headerStyle;
/**
* Inline style of the tab.
* @group Props
*/
tabStyle;
/**
* Inline style of the tab content.
* @group Props
*/
contentStyle;
/**
* Style class of the tab.
* @group Props
*/
tabStyleClass;
/**
* Style class of the tab header.
* @group Props
*/
headerStyleClass;
/**
* Style class of the tab content.
* @group Props
*/
contentStyleClass;
/**
* Whether the tab is disabled.
* @group Props
*/
disabled;
/**
* Whether a lazy loaded panel should avoid getting loaded again on reselection.
* @group Props
*/
cache = true;
/**
* Transition options of the animation.
* @group Props
*/
transitionOptions = '400ms cubic-bezier(0.86, 0, 0.07, 1)';
/**
* Position of the icon.
* @group Props
*/
iconPos = 'start';
/**
* The value that returns the selection.
* @group Props
*/
get selected() {
return this._selected;
}
set selected(val) {
this._selected = val;
if (!this.loaded) {
if (this._selected && this.cache) {
this.loaded = true;
}
this.cd.detectChanges();
}
}
/**
* The aria-level that each accordion header will have. The default value is 2 as per W3C specifications
* @group Props
*/
headerAriaLevel = 2;
/**
* Event triggered by changing the choice.
* @param {boolean} value - Boolean value indicates that the option is changed.
* @group Emits
*/
selectedChange = new EventEmitter();
headerFacet;
_selected = false;
get iconClass() {
if (this.iconPos === 'end') {
return 'p-accordionheader-toggle-icon icon-end';
}
else {
return 'p-accordionheader-toggle-icon icon-start';
}
}
/**
* Content template for the content of the drawer.
* @group Templates
*/
headerTemplate;
/**
* Template for the header icon.
* @group Templates
*/
iconTemplate;
/**
* Content template for the footer of the drawer.
* @group Templates
*/
contentTemplate;
templates;
_headerTemplate;
_iconTemplate;
_contentTemplate;
loaded = false;
accordion = inject(forwardRef(() => Accordion));
_componentStyle = inject(AccordionStyle);
ngOnInit() {
super.ngOnInit();
console.log('AccordionTab is deprecated as of v18, please use the new structure instead.');
}
ngAfterContentInit() {
this.templates.forEach((item) => {
switch (item.getType()) {
case 'content':
this._contentTemplate = item.template;
break;
case 'header':
this._headerTemplate = item.template;
break;
case 'icon':
this._iconTemplate = item.template;
break;
default:
this._contentTemplate = item.template;
break;
}
});
}
toggle(event) {
if (this.disabled) {
return false;
}
let index = this.findTabIndex();
if (this.selected) {
this.selected = false;
this.accordion.onClose.emit({ originalEvent: event, index: index });
}
else {
if (!this.accordion.multiple()) {
for (var i = 0; i < this.accordion.tabs.length; i++) {
if (this.accordion.tabs[i].selected) {
this.accordion.tabs[i].selected = false;
this.accordion.tabs[i].selectedChange.emit(false);
this.accordion.tabs[i].cd.markForCheck();
}
}
}
this.selected = true;
this.loaded = true;
this.accordion.onOpen.emit({ originalEvent: event, index: index });
}
this.selectedChange.emit(this.selected);
this.accordion.updateActiveIndex();
this.cd.markForCheck();
event?.preventDefault();
}
findTabIndex() {
let index = -1;
for (var i = 0; i < this.accordion.tabs.length; i++) {
if (this.accordion.tabs[i] == this) {
index = i;
break;
}
}
return index;
}
onKeydown(event) {
switch (event.code) {
case 'Enter':
case 'Space':
this.toggle(event);
event.preventDefault();
break;
default:
break;
}
}
getTabHeaderActionId(tabId) {
return `${tabId}_header_action`;
}
getTabContentId(tabId) {
return `${tabId}_content`;
}
ngOnDestroy() {
this.accordion.tabs.splice(this.findTabIndex(), 1);
super.ngOnDestroy();
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: AccordionTab, deps: null, target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.2", type: AccordionTab, isStandalone: true, selector: "p-accordionTab, p-accordion-tab, p-accordiontab", inputs: { id: "id", header: "header", headerStyle: "headerStyle", tabStyle: "tabStyle", contentStyle: "contentStyle", tabStyleClass: "tabStyleClass", headerStyleClass: "headerStyleClass", contentStyleClass: "contentStyleClass", disabled: ["disabled", "disabled", booleanAttribute], cache: ["cache", "cache", booleanAttribute], transitionOptions: "transitionOptions", iconPos: "iconPos", selected: "selected", headerAriaLevel: ["headerAriaLevel", "headerAriaLevel", numberAttribute] }, outputs: { selectedChange: "selectedChange" }, host: { properties: { "class.p-accordionpanel": "true", "class.p-accordionpanel-active": "selected", "attr.data-pc-name": "\"accordiontab\"", "class": "this.hostClass", "style": "this.hostStyle" } }, providers: [AccordionStyle], queries: [{ propertyName: "headerTemplate", first: true, predicate: ["header"] }, { propertyName: "iconTemplate", first: true, predicate: ["icon"] }, { propertyName: "contentTemplate", first: true, predicate: ["content"] }, { propertyName: "headerFacet", predicate: Header }, { propertyName: "templates", predicate: PrimeTemplate }], usesInheritance: true, ngImport: i0, template: `
<button
class="p-accordionheader"
type="button"
[disabled]="disabled"
[attr.aria-expanded]="selected"
[attr.aria-level]="headerAriaLevel"
[class.p-disabled]="disabled"
[attr.data-p-disabled]="disabled"
[attr.data-pc-section]="'accordionheader'"
(click)="toggle($event)"
(keydown)="onKeydown($event)"
[ngClass]="headerStyleClass"
[ngStyle]="headerStyle"
[attr.tabindex]="disabled ? null : 0"
[attr.id]="getTabHeaderActionId(id)"
[attr.aria-controls]="getTabContentId(id)"
>
@if (!headerTemplate && !_headerTemplate) {
{{ header }}
} @else {
@if (headerTemplate || _headerTemplate) {
<ng-container *ngTemplateOutlet="headerTemplate || _headerTemplate"></ng-container>
}
@if (headerFacet) {
<ng-content select="p-header" />
}
}
@if (iconTemplate || _iconTemplate) {
<ng-template *ngTemplateOutlet="iconTemplate || _iconTemplate; context: { $implicit: selected }"></ng-template>
} @else {
<ng-container *ngIf="selected">
<span *ngIf="accordion.collapseIcon" [class]="accordion.collapseIcon" [ngClass]="iconClass" [attr.aria-hidden]="true"></span>
<ChevronDownIcon *ngIf="!accordion.collapseIcon" [ngClass]="iconClass" [attr.aria-hidden]="true" />
</ng-container>
<ng-container *ngIf="!selected">
<span *ngIf="accordion.expandIcon" [class]="accordion.expandIcon" [ngClass]="iconClass" [attr.aria-hidden]="true"></span>
<ChevronUpIcon *ngIf="!accordion.expandIcon" [ngClass]="iconClass" [attr.aria-hidden]="true" />
</ng-container>
}
</button>
<div
[attr.id]="getTabContentId(id)"
class="p-accordioncontent"
[@tabContent]="selected ? { value: 'visible', params: { transitionParams: transitionOptions } } : { value: 'hidden', params: { transitionParams: transitionOptions } }"
role="region"
[attr.aria-hidden]="!selected"
[attr.aria-labelledby]="getTabHeaderActionId(id)"
[attr.data-pc-section]="'toggleablecontent'"
>
<div class="p-accordioncontent-content" [ngClass]="contentStyleClass" [ngStyle]="contentStyle">
<ng-content />
<ng-container *ngIf="(contentTemplate || _contentTemplate) && (cache ? loaded : selected)">
<ng-container *ngTemplateOutlet="contentTemplate || _contentTemplate"></ng-container>
</ng-container>
</div>
</div>
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i2.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: ChevronDownIcon, selector: "ChevronDownIcon" }, { kind: "component", type: ChevronUpIcon, selector: "ChevronUpIcon" }], animations: [
trigger('tabContent', [
state('hidden', style({
height: '0',
visibility: 'hidden'
})),
state('visible', style({
height: '*',
visibility: 'visible'
})),
transition('visible <=> hidden', [animate('{{transitionParams}}')]),
transition('void => *', animate(0))
])
], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: AccordionTab, decorators: [{
type: Component,
args: [{
selector: 'p-accordionTab, p-accordion-tab, p-accordiontab',
standalone: true,
imports: [CommonModule, ChevronDownIcon, ChevronUpIcon],
template: `
<button
class="p-accordionheader"
type="button"
[disabled]="disabled"
[attr.aria-expanded]="selected"
[attr.aria-level]="headerAriaLevel"
[class.p-disabled]="disabled"
[attr.data-p-disabled]="disabled"
[attr.data-pc-section]="'accordionheader'"
(click)="toggle($event)"
(keydown)="onKeydown($event)"
[ngClass]="headerStyleClass"
[ngStyle]="headerStyle"
[attr.tabindex]="disabled ? null : 0"
[attr.id]="getTabHeaderActionId(id)"
[attr.aria-controls]="getTabContentId(id)"
>
@if (!headerTemplate && !_headerTemplate) {
{{ header }}
} @else {
@if (headerTemplate || _headerTemplate) {
<ng-container *ngTemplateOutlet="headerTemplate || _headerTemplate"></ng-container>
}
@if (headerFacet) {
<ng-content select="p-header" />
}
}
@if (iconTemplate || _iconTemplate) {
<ng-template *ngTemplateOutlet="iconTemplate || _iconTemplate; context: { $implicit: selected }"></ng-template>
} @else {
<ng-container *ngIf="selected">
<span *ngIf="accordion.collapseIcon" [class]="accordion.collapseIcon" [ngClass]="iconClass" [attr.aria-hidden]="true"></span>
<ChevronDownIcon *ngIf="!accordion.collapseIcon" [ngClass]="iconClass" [attr.aria-hidden]="true" />
</ng-container>
<ng-container *ngIf="!selected">
<span *ngIf="accordion.expandIcon" [class]="accordion.expandIcon" [ngClass]="iconClass" [attr.aria-hidden]="true"></span>
<ChevronUpIcon *ngIf="!accordion.expandIcon" [ngClass]="iconClass" [attr.aria-hidden]="true" />
</ng-container>
}
</button>
<div
[attr.id]="getTabContentId(id)"
class="p-accordioncontent"
[@tabContent]="selected ? { value: 'visible', params: { transitionParams: transitionOptions } } : { value: 'hidden', params: { transitionParams: transitionOptions } }"
role="region"
[attr.aria-hidden]="!selected"
[attr.aria-labelledby]="getTabHeaderActionId(id)"
[attr.data-pc-section]="'toggleablecontent'"
>
<div class="p-accordioncontent-content" [ngClass]="contentStyleClass" [ngStyle]="contentStyle">
<ng-content />
<ng-container *ngIf="(contentTemplate || _contentTemplate) && (cache ? loaded : selected)">
<ng-container *ngTemplateOutlet="contentTemplate || _contentTemplate"></ng-container>
</ng-container>
</div>
</div>
`,
animations: [
trigger('tabContent', [
state('hidden', style({
height: '0',
visibility: 'hidden'
})),
state('visible', style({
height: '*',
visibility: 'visible'
})),
transition('visible <=> hidden', [animate('{{transitionParams}}')]),
transition('void => *', animate(0))
])
],
host: {
'[class.p-accordionpanel]': 'true',
'[class.p-accordionpanel-active]': 'selected',
'[attr.data-pc-name]': '"accordiontab"'
},
providers: [AccordionStyle],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None
}]
}], propDecorators: { hostClass: [{
type: HostBinding,
args: ['class']
}], hostStyle: [{
type: HostBinding,
args: ['style']
}], id: [{
type: Input
}], header: [{
type: Input
}], headerStyle: [{
type: Input
}], tabStyle: [{
type: Input
}], contentStyle: [{
type: Input
}], tabStyleClass: [{
type: Input
}], headerStyleClass: [{
type: Input
}], contentStyleClass: [{
type: Input
}], disabled: [{
type: Input,
args: [{ transform: booleanAttribute }]
}], cache: [{
type: Input,
args: [{ transform: booleanAttribute }]
}], transitionOptions: [{
type: Input
}], iconPos: [{
type: Input
}], selected: [{
type: Input
}], headerAriaLevel: [{
type: Input,
args: [{ transform: numberAttribute }]
}], selectedChange: [{
type: Output
}], headerFacet: [{
type: ContentChildren,
args: [Header]
}], headerTemplate: [{
type: ContentChild,
args: ['header', { descendants: false }]
}], iconTemplate: [{
type: ContentChild,
args: ['icon', { descendants: false }]
}], contentTemplate: [{
type: ContentChild,
args: ['content', { descendants: false }]
}], templates: [{
type: ContentChildren,
args: [PrimeTemplate]
}] } });
/**
* Accordion groups a collection of contents in tabs.
* @group Components
*/
class Accordion extends BaseComponent {
get hostClass() {
return this.styleClass;
}
get hostStyle() {
return this.style;
}
/**
* Value of the active tab.
* @defaultValue undefined
* @group Props
*/
value = model(undefined);
/**
* When enabled, multiple tabs can be activated at the same time.
* @defaultValue false
* @group Props
*/
multiple = input(false, { transform: (v) => transformToBoolean(v) });
/**
* Inline style of the tab header and content.
* @group Props
*/
style;
/**
* Class of the element.
* @group Props
*/
styleClass;
/**
* Icon of a collapsed tab.
* @group Props
*/
expandIcon;
/**
* Icon of an expanded tab.
* @group Props
*/
collapseIcon;
/**
* When enabled, the focused tab is activated.
* @defaultValue false
* @group Props
*/
selectOnFocus = input(false, { transform: (v) => transformToBoolean(v) });
set activeIndex(val) {
this._activeIndex = val;
if (this.preventActiveIndexPropagation) {
this.preventActiveIndexPropagation = false;
return;
}
this.updateSelectionState();
}
/**
* Transition options of the animation.
* @group Props
*/
transitionOptions = '400ms cubic-bezier(0.86, 0, 0.07, 1)';
/**
* Returns the active index.
* @param {number | number[]} value - New index.
* @deprecated use native valueChange emitter of the value model.
* @group Emits
*/
activeIndexChange = new EventEmitter();
set headerAriaLevel(val) {
if (typeof val === 'number' && val > 0) {
this._headerAriaLevel = val;
}
else if (this._headerAriaLevel !== 2) {
this._headerAriaLevel = 2;
}
}
/**
* Callback to invoke when an active tab is collapsed by clicking on the header.
* @param {AccordionTabCloseEvent} event - Custom tab close event.
* @group Emits
*/
onClose = new EventEmitter();
/**
* Callback to invoke when a tab gets expanded.
* @param {AccordionTabOpenEvent} event - Custom tab open event.
* @group Emits
*/
onOpen = new EventEmitter();
id = signal(uuid('pn_id_'));
tabList;
tabListSubscription = null;
_activeIndex;
_headerAriaLevel = 2;
preventActiveIndexPropagation = false;
tabs = [];
_componentStyle = inject(AccordionStyle);
/**
* Index of the active tab or an array of indexes in multiple mode.
* @deprecated use value property with new architecture instead.
* @group Props
*/
get activeIndex() {
return this._activeIndex;
}
/**
* The aria-level that each accordion header will have. The default value is 2 as per W3C specifications
* @deprecated use AccoridonHeader component and bind attribute to the host.
* @group Props
*/
get headerAriaLevel() {
return this._headerAriaLevel;
}
onKeydown(event) {
switch (event.code) {
case 'ArrowDown':
this.onTabArrowDownKey(event);
break;
case 'ArrowUp':
this.onTabArrowUpKey(event);
break;
case 'Home':
if (!event.shiftKey) {
this.onTabHomeKey(event);
}
break;
case 'End':
if (!event.shiftKey) {
this.onTabEndKey(event);
}
break;
}
}
onTabArrowDownKey(event) {
const nextHeaderAction = this.findNextHeaderAction(event.target.parentElement);
nextHeaderAction ? this.changeFocusedTab(nextHeaderAction) : this.onTabHomeKey(event);
event.preventDefault();
}
onTabArrowUpKey(event) {
const prevHeaderAction = this.findPrevHeaderAction(event.target.parentElement);
prevHeaderAction ? this.changeFocusedTab(prevHeaderAction) : this.onTabEndKey(event);
event.preventDefault();
}
onTabHomeKey(event) {
const firstHeaderAction = this.findFirstHeaderAction();
this.changeFocusedTab(firstHeaderAction);
event.preventDefault();
}
changeFocusedTab(element) {
if (element) {
focus(element);
if (this.selectOnFocus()) {
this.tabs.forEach((tab, i) => {
let selected = this.multiple() ? this._activeIndex.includes(i) : i === this._activeIndex;
if (this.multiple()) {
if (!this._activeIndex) {
this._activeIndex = [];
}
if (tab.id == element.id) {
tab.selected = !tab.selected;
if (!this._activeIndex.includes(i)) {
this._activeIndex.push(i);
}
else {
this._activeIndex = this._activeIndex.filter((ind) => ind !== i);
}
}
}
else {
if (tab.id == element.id) {
tab.selected = !tab.selected;
this._activeIndex = i;
}
else {
tab.selected = false;
}
}
tab.selectedChange.emit(selected);
this.activeIndexChange.emit(this._activeIndex);
tab.cd.markForCheck();
});
}
}
}
findNextHeaderAction(tabElement, selfCheck = false) {
const nextTabElement = selfCheck ? tabElement : tabElement.nextElementSibling;
const headerElement = findSingle(nextTabElement, '[data-pc-section="accordionheader"]');
return headerElement ? (getAttribute(headerElement, 'data-p-disabled') ? this.findNextHeaderAction(headerElement.parentElement) : findSingle(headerElement.parentElement, '[data-pc-section="accordionheader"]')) : null;
}
findPrevHeaderAction(tabElement, selfCheck = false) {
const prevTabElement = selfCheck ? tabElement : tabElement.previousElementSibling;
const headerElement = findSingle(prevTabElement, '[data-pc-section="accordionheader"]');
return headerElement ? (getAttribute(headerElement, 'data-p-disabled') ? this.findPrevHeaderAction(headerElement.parentElement) : findSingle(headerElement.parentElement, '[data-pc-section="accordionheader"]')) : null;
}
findFirstHeaderAction() {
const firstEl = this.el.nativeElement.firstElementChild;
return this.findNextHeaderAction(firstEl, true);
}
findLastHeaderAction() {
const lastEl = this.el.nativeElement.lastElementChild;
return this.findPrevHeaderAction(lastEl, true);
}
onTabEndKey(event) {
const lastHeaderAction = this.findLastHeaderAction();
this.changeFocusedTab(lastHeaderAction);
event.preventDefault();
}
ngAfterContentInit() {
this.initTabs();
this.tabListSubscription = this.tabList.changes.subscribe((_) => {
this.initTabs();
});
}
initTabs() {
this.tabs = this.tabList.toArray();
this.tabs.forEach((tab) => {
tab.headerAriaLevel = this._headerAriaLevel;
});
this.updateSelectionState();
this.cd.markForCheck();
}
getBlockableElement() {
return this.el.nativeElement.children[0];
}
updateSelectionState() {
if (this.tabs && this.tabs.length && this._activeIndex != null) {
for (let i = 0; i < this.tabs.length; i++) {
let selected = this.multiple() ? this._activeIndex.includes(i) : i === this._activeIndex;
let changed = selected !== this.tabs[i].selected;
if (changed) {
this.tabs[i].selected = selected;
this.tabs[i].selectedChange.emit(selected);
this.tabs[i].cd.markForCheck();
}
}
}
}
isTabActive(index) {
return this.multiple() ? this._activeIndex && this._activeIndex.includes(index) : this._activeIndex === index;
}
getTabProp(tab, name) {
return tab.props ? tab.props[name] : undefined;
}
updateActiveIndex() {
let index = this.multiple() ? [] : null;
this.tabs.forEach((tab, i) => {
if (tab.selected) {
if (this.multiple()) {
index.push(i);
}
else {
index = i;
return;
}
}
});
this.preventActiveIndexPropagation = true;
this._activeIndex = index;
this.activeIndexChange.emit(index);
}
updateValue(value) {
const currentValue = this.value();
if (this.multiple()) {
const newValue = Array.isArray(currentValue) ? [...currentValue] : [];
const index = newValue.indexOf(value);
if (index !== -1) {
newValue.splice(index, 1);
}
else {
newValue.push(value);
}