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
993 lines (960 loc) • 85.3 kB
JavaScript
import { trigger, state, transition, style, animate } from '@angular/animations';
import * as i1 from '@angular/common';
import { CommonModule } from '@angular/common';
import * as i0 from '@angular/core';
import { Injectable, EventEmitter, inject, forwardRef, booleanAttribute, numberAttribute, ViewChild, Output, Input, ViewEncapsulation, Component, signal, computed, ChangeDetectionStrategy, ContentChildren, ContentChild, NgModule } from '@angular/core';
import * as i2 from '@angular/router';
import { RouterModule } from '@angular/router';
import { resolve, isNotEmpty, findLast, findSingle, isPrintableCharacter, isEmpty, uuid, equals, focus, getAttribute } from '@primeuix/utils';
import { SharedModule, PrimeTemplate } from 'primeng/api';
import { BaseComponent } from 'primeng/basecomponent';
import { AngleDownIcon, AngleRightIcon, ChevronDownIcon, ChevronRightIcon } from 'primeng/icons';
import * as i3 from 'primeng/tooltip';
import { TooltipModule } from 'primeng/tooltip';
import { BaseStyle } from 'primeng/base';
const theme = ({ dt }) => `
.p-panelmenu {
display: flex;
flex-direction: column;
gap: ${dt('panelmenu.gap')};
}
.p-panelmenu-panel {
background: ${dt('panelmenu.panel.background')};
border-width: ${dt('panelmenu.panel.border.width')};
border-style: solid;
border-color: ${dt('panelmenu.panel.border.color')};
color: ${dt('panelmenu.panel.color')};
border-radius: ${dt('panelmenu.panel.border.radius')};
padding: ${dt('panelmenu.panel.padding')};
}
.p-panelmenu-panel:first-child {
border-width: ${dt('panelmenu.panel.first.border.width')};
border-start-start-radius: ${dt('panelmenu.panel.first.top.border.radius')};
border-start-end-radius: ${dt('panelmenu.panel.first.top.border.radius')};
}
.p-panelmenu-panel:last-child {
border-width: ${dt('panelmenu.panel.last.border.width')};
border-end-start-radius: ${dt('panelmenu.panel.last.bottom.border.radius')};
border-end-end-radius: ${dt('panelmenu.panel.last.bottom.border.radius')};
}
.p-panelmenu-header {
outline: 0 none;
}
.p-panelmenu-header-content {
border-radius: ${dt('panelmenu.item.border.radius')};
transition: background ${dt('panelmenu.transition.duration')}, color ${dt('panelmenu.transition.duration')}, outline-color ${dt('panelmenu.transition.duration')}, box-shadow ${dt('panelmenu.transition.duration')};
outline-color: transparent;
color: ${dt('panelmenu.item.color')};
}
.p-panelmenu-header-link {
display: flex;
gap: ${dt('panelmenu.item.gap')};
padding: ${dt('panelmenu.item.padding')};
align-items: center;
user-select: none;
cursor: pointer;
position: relative;
text-decoration: none;
color: inherit;
}
.p-panelmenu-header-icon,
.p-panelmenu-item-icon {
color: ${dt('panelmenu.item.icon.color')};
}
.p-panelmenu-submenu-icon {
color: ${dt('panelmenu.submenu.icon.color')};
}
.p-panelmenu-header:not(.p-panelmenu-header-active) .p-panelmenu-header-content .p-panelmenu-submenu-icon:dir(rtl) {
transform: rotate(180deg);
}
.p-panelmenu-header:not(.p-disabled):focus-visible .p-panelmenu-header-content {
background: ${dt('panelmenu.item.focus.background')};
color: ${dt('panelmenu.item.focus.color')};
}
.p-panelmenu-header:not(.p-disabled):focus-visible .p-panelmenu-header-content .p-panelmenu-header-icon {
color: ${dt('panelmenu.item.icon.focus.color')};
}
.p-panelmenu-header:not(.p-disabled):focus-visible .p-panelmenu-header-content .p-panelmenu-submenu-icon {
color: ${dt('panelmenu.submenu.icon.focus.color')};
}
.p-panelmenu-header:not(.p-disabled) .p-panelmenu-header-content:hover {
background: ${dt('panelmenu.item.focus.background')};
color: ${dt('panelmenu.item.focus.color')};
}
.p-panelmenu-header:not(.p-disabled) .p-panelmenu-header-content:hover .p-panelmenu-header-icon {
color: ${dt('panelmenu.item.icon.focus.color')};
}
.p-panelmenu-header:not(.p-disabled) .p-panelmenu-header-content:hover .p-panelmenu-submenu-icon {
color: ${dt('panelmenu.submenu.icon.focus.color')};
}
.p-panelmenu-submenu {
margin: 0;
padding: 0 0 0 ${dt('panelmenu.submenu.indent')};
outline: 0;
list-style: none;
}
.p-panelmenu-submenu:dir(rtl) {
padding: 0 ${dt('panelmenu.submenu.indent')} 0 0;
}
.p-panelmenu-item-link {
display: flex;
gap: ${dt('panelmenu.item.gap')};
padding: ${dt('panelmenu.item.padding')};
align-items: center;
user-select: none;
cursor: pointer;
text-decoration: none;
color: inherit;
position: relative;
overflow: hidden;
}
.p-panelmenu-item-label {
line-height: 1;
}
.p-panelmenu-item-content {
border-radius: ${dt('panelmenu.item.border.radius')};
transition: background ${dt('panelmenu.transition.duration')}, color ${dt('panelmenu.transition.duration')}, outline-color ${dt('panelmenu.transition.duration')}, box-shadow ${dt('panelmenu.transition.duration')};
color: ${dt('panelmenu.item.color')};
outline-color: transparent;
}
.p-panelmenu-item.p-focus > .p-panelmenu-item-content {
background: ${dt('panelmenu.item.focus.background')};
color: ${dt('panelmenu.item.focus.color')};
}
.p-panelmenu-item.p-focus > .p-panelmenu-item-content .p-panelmenu-item-icon {
color: ${dt('panelmenu.item.focus.color')};
}
.p-panelmenu-item.p-focus > .p-panelmenu-item-content .p-panelmenu-submenu-icon {
color: ${dt('panelmenu.submenu.icon.focus.color')};
}
.p-panelmenu-item:not(.p-disabled) > .p-panelmenu-item-content:hover {
background: ${dt('panelmenu.item.focus.background')};
color: ${dt('panelmenu.item.focus.color')};
}
.p-panelmenu-item:not(.p-disabled) > .p-panelmenu-item-content:hover .p-panelmenu-item-icon {
color: ${dt('panelmenu.item.icon.focus.color')};
}
.p-panelmenu-item:not(.p-disabled) > .p-panelmenu-item-content:hover .p-panelmenu-submenu-icon {
color: ${dt('panelmenu.submenu.icon.focus.color')};
}
/*For PrimeNG*/
.p-panelmenu-item:not(.ng-animating) {
overflow: hidden;
}
.p-panelmenu-panel {
overflow: hidden;
}
`;
const classes = {
root: 'p-panelmenu p-component',
panel: 'p-panelmenu-panel',
header: ({ instance, item }) => [
'p-panelmenu-header',
{
'p-panelmenu-header-active': instance.isItemActive(item) && !!item.items,
'p-disabled': instance.isItemDisabled(item)
}
],
headerContent: 'p-panelmenu-header-content',
headerLink: 'p-panelmenu-header-link',
headerIcon: 'p-panelmenu-header-icon',
headerLabel: 'p-panelmenu-header-label',
contentContainer: 'p-panelmenu-content-container',
content: 'p-panelmenu-content',
rootList: 'p-panelmenu-root-list',
item: ({ instance, processedItem }) => [
'p-panelmenu-item',
{
'p-focus': instance.isItemFocused(processedItem),
'p-disabled': instance.isItemDisabled(processedItem)
}
],
itemContent: 'p-panelmenu-item-content',
itemLink: 'p-panelmenu-item-link',
itemIcon: 'p-panelmenu-item-icon',
itemLabel: 'p-panelmenu-item-label',
submenuIcon: 'p-panelmenu-submenu-icon',
submenu: 'p-panelmenu-submenu',
separator: 'p-menuitem-separator'
};
class PanelMenuStyle extends BaseStyle {
name = 'panelmenu';
theme = theme;
classes = classes;
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: PanelMenuStyle, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: PanelMenuStyle });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: PanelMenuStyle, decorators: [{
type: Injectable
}] });
/**
*
* PanelMenu is a hybrid of Accordion and Tree components.
*
* [Live Demo](https://www.primeng.org/panelmenu/)
*
* @module panelmenustyle
*
*/
var PanelMenuClasses;
(function (PanelMenuClasses) {
/**
* Class name of the root element
*/
PanelMenuClasses["root"] = "p-panelmenu";
/**
* Class name of the panel element
*/
PanelMenuClasses["panel"] = "p-panelmenu-panel";
/**
* Class name of the header element
*/
PanelMenuClasses["header"] = "p-panelmenu-header";
/**
* Class name of the header content element
*/
PanelMenuClasses["headerContent"] = "p-panelmenu-header-content";
/**
* Class name of the header link element
*/
PanelMenuClasses["headerLink"] = "p-panelmenu-header-link";
/**
* Class name of the header icon element
*/
PanelMenuClasses["headerIcon"] = "p-panelmenu-header-icon";
/**
* Class name of the header label element
*/
PanelMenuClasses["headerLabel"] = "p-panelmenu-header-label";
/**
* Class name of the content container element
*/
PanelMenuClasses["contentContainer"] = "p-panelmenu-content-container";
/**
* Class name of the content element
*/
PanelMenuClasses["content"] = "p-panelmenu-content";
/**
* Class name of the root list element
*/
PanelMenuClasses["rootList"] = "p-panelmenu-root-list";
/**
* Class name of the item element
*/
PanelMenuClasses["item"] = "p-panelmenu-item";
/**
* Class name of the item content element
*/
PanelMenuClasses["itemContent"] = "p-panelmenu-item-content";
/**
* Class name of the item link element
*/
PanelMenuClasses["itemLink"] = "p-panelmenu-item-link";
/**
* Class name of the item icon element
*/
PanelMenuClasses["itemIcon"] = "p-panelmenu-item-icon";
/**
* Class name of the item label element
*/
PanelMenuClasses["itemLabel"] = "p-panelmenu-item-label";
/**
* Class name of the submenu icon element
*/
PanelMenuClasses["submenuIcon"] = "p-panelmenu-submenu-icon";
/**
* Class name of the submenu element
*/
PanelMenuClasses["submenu"] = "p-panelmenu-submenu";
PanelMenuClasses["separator"] = "p-menuitem-separator";
})(PanelMenuClasses || (PanelMenuClasses = {}));
class PanelMenuSub extends BaseComponent {
panelId;
focusedItemId;
items;
itemTemplate;
level = 0;
activeItemPath;
root;
tabindex;
transitionOptions;
parentExpanded;
itemToggle = new EventEmitter();
menuFocus = new EventEmitter();
menuBlur = new EventEmitter();
menuKeyDown = new EventEmitter();
listViewChild;
panelMenu = inject(forwardRef(() => PanelMenu));
getItemId(processedItem) {
return processedItem.item?.id ?? `${this.panelId}_${processedItem.key}`;
}
getItemKey(processedItem) {
return this.getItemId(processedItem);
}
getItemClass(processedItem) {
return {
'p-panelmenu-item': true,
'p-disabled': this.isItemDisabled(processedItem),
'p-focus': this.isItemFocused(processedItem)
};
}
getItemProp(processedItem, name, params) {
return processedItem && processedItem.item ? resolve(processedItem.item[name], params) : undefined;
}
getItemLabel(processedItem) {
return this.getItemProp(processedItem, 'label');
}
isItemExpanded(processedItem) {
return processedItem.expanded;
}
isItemActive(processedItem) {
return this.isItemExpanded(processedItem) || this.activeItemPath.some((path) => path && path.key === processedItem.key);
}
isItemVisible(processedItem) {
return this.getItemProp(processedItem, 'visible') !== false;
}
isItemDisabled(processedItem) {
return this.getItemProp(processedItem, 'disabled');
}
isItemFocused(processedItem) {
return this.focusedItemId === this.getItemId(processedItem);
}
isItemGroup(processedItem) {
return isNotEmpty(processedItem.items);
}
getAnimation(processedItem) {
return this.isItemActive(processedItem) ? { value: 'visible', params: { transitionParams: this.transitionOptions, height: '*' } } : { value: 'hidden', params: { transitionParams: this.transitionOptions, height: '0' } };
}
getAriaSetSize() {
return this.items.filter((processedItem) => this.isItemVisible(processedItem) && !this.getItemProp(processedItem, 'separator')).length;
}
getAriaPosInset(index) {
return index - this.items.slice(0, index).filter((processedItem) => this.isItemVisible(processedItem) && this.getItemProp(processedItem, 'separator')).length + 1;
}
onItemClick(event, processedItem) {
if (!this.isItemDisabled(processedItem)) {
this.getItemProp(processedItem, 'command', { originalEvent: event, item: processedItem.item });
this.itemToggle.emit({ processedItem, expanded: !this.isItemActive(processedItem) });
}
}
onItemToggle(event) {
this.itemToggle.emit(event);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: PanelMenuSub, deps: null, target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.0", version: "19.2.2", type: PanelMenuSub, isStandalone: true, selector: "p-panelMenuSub, p-panelmenu-sub", inputs: { panelId: "panelId", focusedItemId: "focusedItemId", items: "items", itemTemplate: "itemTemplate", level: ["level", "level", numberAttribute], activeItemPath: "activeItemPath", root: ["root", "root", booleanAttribute], tabindex: ["tabindex", "tabindex", numberAttribute], transitionOptions: "transitionOptions", parentExpanded: ["parentExpanded", "parentExpanded", booleanAttribute] }, outputs: { itemToggle: "itemToggle", menuFocus: "menuFocus", menuBlur: "menuBlur", menuKeyDown: "menuKeyDown" }, viewQueries: [{ propertyName: "listViewChild", first: true, predicate: ["list"], descendants: true }], usesInheritance: true, ngImport: i0, template: `
<ul
#list
[ngClass]="{ 'p-panelmenu-submenu': true }"
role="tree"
[tabindex]="-1"
[attr.aria-activedescendant]="focusedItemId"
[attr.data-pc-section]="'menu'"
[attr.aria-hidden]="!parentExpanded"
(focusin)="menuFocus.emit($event)"
(focusout)="menuBlur.emit($event)"
(keydown)="menuKeyDown.emit($event)"
>
<ng-template ngFor let-processedItem let-index="index" [ngForOf]="items">
<li *ngIf="processedItem.separator" class="p-menuitem-separator" role="separator"></li>
<li
*ngIf="!processedItem.separator && isItemVisible(processedItem)"
[ngClass]="getItemClass(processedItem)"
role="treeitem"
[attr.id]="getItemId(processedItem)"
[attr.aria-label]="getItemProp(processedItem, 'label')"
[attr.aria-expanded]="isItemGroup(processedItem) ? isItemActive(processedItem) : undefined"
[attr.aria-level]="level + 1"
[attr.aria-setsize]="getAriaSetSize()"
[attr.aria-posinset]="getAriaPosInset(index)"
[class]="getItemProp(processedItem, 'styleClass')"
[class.p-hidden]="processedItem.visible === false"
[class.p-focus]="isItemFocused(processedItem) && !isItemDisabled(processedItem)"
[ngStyle]="getItemProp(processedItem, 'style')"
[pTooltip]="getItemProp(processedItem, 'tooltip')"
[attr.data-p-disabled]="isItemDisabled(processedItem)"
[tooltipOptions]="getItemProp(processedItem, 'tooltipOptions')"
>
<div class="p-panelmenu-item-content" (click)="onItemClick($event, processedItem)">
<ng-container *ngIf="!itemTemplate">
<a
*ngIf="!getItemProp(processedItem, 'routerLink')"
[attr.href]="getItemProp(processedItem, 'url')"
class="p-panelmenu-item-link"
[ngClass]="{ 'p-disabled': getItemProp(processedItem, 'disabled') }"
[target]="getItemProp(processedItem, 'target')"
[attr.data-pc-section]="'action'"
[attr.tabindex]="!!parentExpanded ? '0' : '-1'"
>
<ng-container *ngIf="isItemGroup(processedItem)">
<ng-container *ngIf="!panelMenu.submenuIconTemplate && !panelMenu._submenuIconTemplate">
<AngleDownIcon [styleClass]="'p-panelmenu-submenu-icon'" *ngIf="isItemActive(processedItem)" [ngStyle]="getItemProp(processedItem, 'iconStyle')" />
<AngleRightIcon [styleClass]="'p-panelmenu-submenu-icon'" *ngIf="!isItemActive(processedItem)" [ngStyle]="getItemProp(processedItem, 'iconStyle')" />
</ng-container>
<ng-template *ngTemplateOutlet="panelMenu.submenuIconTemplate || panelMenu._submenuIconTemplate"></ng-template>
</ng-container>
<span class="p-panelmenu-submenu-icon" [ngClass]="processedItem.icon" *ngIf="processedItem.icon" [ngStyle]="getItemProp(processedItem, 'iconStyle')"></span>
<span class="p-panelmenu-item-label" *ngIf="processedItem.item?.escape !== false; else htmlLabel">{{ getItemProp(processedItem, 'label') }}</span>
<ng-template #htmlLabel><span class="p-panelmenu-item-label" [innerHTML]="getItemProp(processedItem, 'label')"></span></ng-template>
<span class="p-menuitem-badge" *ngIf="processedItem.badge" [ngClass]="processedItem.badgeStyleClass">{{ processedItem.badge }}</span>
</a>
<a
*ngIf="getItemProp(processedItem, 'routerLink')"
[routerLink]="getItemProp(processedItem, 'routerLink')"
[queryParams]="getItemProp(processedItem, 'queryParams')"
[routerLinkActive]="'p-panelmenu-item-link-active'"
[routerLinkActiveOptions]="getItemProp(processedItem, 'routerLinkActiveOptions') || { exact: false }"
class="p-panelmenu-item-link"
[ngClass]="{ 'p-disabled': getItemProp(processedItem, 'disabled') }"
[target]="getItemProp(processedItem, 'target')"
[attr.title]="getItemProp(processedItem, 'title')"
[fragment]="getItemProp(processedItem, 'fragment')"
[queryParamsHandling]="getItemProp(processedItem, 'queryParamsHandling')"
[preserveFragment]="getItemProp(processedItem, 'preserveFragment')"
[skipLocationChange]="getItemProp(processedItem, 'skipLocationChange')"
[replaceUrl]="getItemProp(processedItem, 'replaceUrl')"
[state]="getItemProp(processedItem, 'state')"
[attr.data-pc-section]="'action'"
[attr.tabindex]="!!parentExpanded ? '0' : '-1'"
>
<ng-container *ngIf="isItemGroup(processedItem)">
<ng-container *ngIf="!panelMenu.submenuIconTemplate && !panelMenu._submenuIconTemplate">
<AngleDownIcon *ngIf="isItemActive(processedItem)" [styleClass]="'p-panelmenu-submenu-icon'" [ngStyle]="getItemProp(processedItem, 'iconStyle')" />
<AngleRightIcon *ngIf="!isItemActive(processedItem)" [styleClass]="'p-panelmenu-submenu-icon'" [ngStyle]="getItemProp(processedItem, 'iconStyle')" />
</ng-container>
<ng-template *ngTemplateOutlet="panelMenu.submenuIconTemplate && panelMenu._submenuIconTemplate"></ng-template>
</ng-container>
<span class="p-panelmenu-submenu-icon" [ngClass]="processedItem.icon" *ngIf="processedItem.icon" [ngStyle]="getItemProp(processedItem, 'iconStyle')"></span>
<span class="p-panelmenu-item-label" *ngIf="getItemProp(processedItem, 'escape') !== false; else htmlRouteLabel">{{ getItemProp(processedItem, 'label') }}</span>
<ng-template #htmlRouteLabel><span class="p-panelmenu-item-label" [innerHTML]="getItemProp(processedItem, 'label')"></span></ng-template>
<span class="p-menuitem-badge" *ngIf="processedItem.badge" [ngClass]="getItemProp(processedItem, 'badgeStyleClass')">{{ getItemProp(processedItem, 'badge') }}</span>
</a>
</ng-container>
<ng-container *ngIf="itemTemplate">
<ng-template *ngTemplateOutlet="itemTemplate; context: { $implicit: processedItem.item }"></ng-template>
</ng-container>
</div>
<div class="p-toggleable-content" [@submenu]="getAnimation(processedItem)">
<p-panelmenu-sub
*ngIf="isItemVisible(processedItem) && isItemGroup(processedItem) && isItemExpanded(processedItem)"
[id]="getItemId(processedItem) + '_list'"
[panelId]="panelId"
[items]="processedItem?.items"
[itemTemplate]="itemTemplate"
[transitionOptions]="transitionOptions"
[focusedItemId]="focusedItemId"
[activeItemPath]="activeItemPath"
[level]="level + 1"
[parentExpanded]="!!parentExpanded && isItemExpanded(processedItem)"
(itemToggle)="onItemToggle($event)"
></p-panelmenu-sub>
</div>
</li>
</ng-template>
</ul>
`, isInline: true, dependencies: [{ kind: "component", type: PanelMenuSub, selector: "p-panelMenuSub, p-panelmenu-sub", inputs: ["panelId", "focusedItemId", "items", "itemTemplate", "level", "activeItemPath", "root", "tabindex", "transitionOptions", "parentExpanded"], outputs: ["itemToggle", "menuFocus", "menuBlur", "menuKeyDown"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i2.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i3.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "component", type: AngleDownIcon, selector: "AngleDownIcon" }, { kind: "component", type: AngleRightIcon, selector: "AngleRightIcon" }, { kind: "ngmodule", type: SharedModule }], animations: [
trigger('submenu', [
state('hidden', style({
height: '0'
})),
state('visible', style({
height: '*'
})),
transition('visible <=> hidden', [animate('{{transitionParams}}')]),
transition('void => *', animate(0))
])
], encapsulation: i0.ViewEncapsulation.None });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: PanelMenuSub, decorators: [{
type: Component,
args: [{
selector: 'p-panelMenuSub, p-panelmenu-sub',
imports: [CommonModule, RouterModule, TooltipModule, AngleDownIcon, AngleRightIcon, SharedModule],
standalone: true,
template: `
<ul
#list
[ngClass]="{ 'p-panelmenu-submenu': true }"
role="tree"
[tabindex]="-1"
[attr.aria-activedescendant]="focusedItemId"
[attr.data-pc-section]="'menu'"
[attr.aria-hidden]="!parentExpanded"
(focusin)="menuFocus.emit($event)"
(focusout)="menuBlur.emit($event)"
(keydown)="menuKeyDown.emit($event)"
>
<ng-template ngFor let-processedItem let-index="index" [ngForOf]="items">
<li *ngIf="processedItem.separator" class="p-menuitem-separator" role="separator"></li>
<li
*ngIf="!processedItem.separator && isItemVisible(processedItem)"
[ngClass]="getItemClass(processedItem)"
role="treeitem"
[attr.id]="getItemId(processedItem)"
[attr.aria-label]="getItemProp(processedItem, 'label')"
[attr.aria-expanded]="isItemGroup(processedItem) ? isItemActive(processedItem) : undefined"
[attr.aria-level]="level + 1"
[attr.aria-setsize]="getAriaSetSize()"
[attr.aria-posinset]="getAriaPosInset(index)"
[class]="getItemProp(processedItem, 'styleClass')"
[class.p-hidden]="processedItem.visible === false"
[class.p-focus]="isItemFocused(processedItem) && !isItemDisabled(processedItem)"
[ngStyle]="getItemProp(processedItem, 'style')"
[pTooltip]="getItemProp(processedItem, 'tooltip')"
[attr.data-p-disabled]="isItemDisabled(processedItem)"
[tooltipOptions]="getItemProp(processedItem, 'tooltipOptions')"
>
<div class="p-panelmenu-item-content" (click)="onItemClick($event, processedItem)">
<ng-container *ngIf="!itemTemplate">
<a
*ngIf="!getItemProp(processedItem, 'routerLink')"
[attr.href]="getItemProp(processedItem, 'url')"
class="p-panelmenu-item-link"
[ngClass]="{ 'p-disabled': getItemProp(processedItem, 'disabled') }"
[target]="getItemProp(processedItem, 'target')"
[attr.data-pc-section]="'action'"
[attr.tabindex]="!!parentExpanded ? '0' : '-1'"
>
<ng-container *ngIf="isItemGroup(processedItem)">
<ng-container *ngIf="!panelMenu.submenuIconTemplate && !panelMenu._submenuIconTemplate">
<AngleDownIcon [styleClass]="'p-panelmenu-submenu-icon'" *ngIf="isItemActive(processedItem)" [ngStyle]="getItemProp(processedItem, 'iconStyle')" />
<AngleRightIcon [styleClass]="'p-panelmenu-submenu-icon'" *ngIf="!isItemActive(processedItem)" [ngStyle]="getItemProp(processedItem, 'iconStyle')" />
</ng-container>
<ng-template *ngTemplateOutlet="panelMenu.submenuIconTemplate || panelMenu._submenuIconTemplate"></ng-template>
</ng-container>
<span class="p-panelmenu-submenu-icon" [ngClass]="processedItem.icon" *ngIf="processedItem.icon" [ngStyle]="getItemProp(processedItem, 'iconStyle')"></span>
<span class="p-panelmenu-item-label" *ngIf="processedItem.item?.escape !== false; else htmlLabel">{{ getItemProp(processedItem, 'label') }}</span>
<ng-template #htmlLabel><span class="p-panelmenu-item-label" [innerHTML]="getItemProp(processedItem, 'label')"></span></ng-template>
<span class="p-menuitem-badge" *ngIf="processedItem.badge" [ngClass]="processedItem.badgeStyleClass">{{ processedItem.badge }}</span>
</a>
<a
*ngIf="getItemProp(processedItem, 'routerLink')"
[routerLink]="getItemProp(processedItem, 'routerLink')"
[queryParams]="getItemProp(processedItem, 'queryParams')"
[routerLinkActive]="'p-panelmenu-item-link-active'"
[routerLinkActiveOptions]="getItemProp(processedItem, 'routerLinkActiveOptions') || { exact: false }"
class="p-panelmenu-item-link"
[ngClass]="{ 'p-disabled': getItemProp(processedItem, 'disabled') }"
[target]="getItemProp(processedItem, 'target')"
[attr.title]="getItemProp(processedItem, 'title')"
[fragment]="getItemProp(processedItem, 'fragment')"
[queryParamsHandling]="getItemProp(processedItem, 'queryParamsHandling')"
[preserveFragment]="getItemProp(processedItem, 'preserveFragment')"
[skipLocationChange]="getItemProp(processedItem, 'skipLocationChange')"
[replaceUrl]="getItemProp(processedItem, 'replaceUrl')"
[state]="getItemProp(processedItem, 'state')"
[attr.data-pc-section]="'action'"
[attr.tabindex]="!!parentExpanded ? '0' : '-1'"
>
<ng-container *ngIf="isItemGroup(processedItem)">
<ng-container *ngIf="!panelMenu.submenuIconTemplate && !panelMenu._submenuIconTemplate">
<AngleDownIcon *ngIf="isItemActive(processedItem)" [styleClass]="'p-panelmenu-submenu-icon'" [ngStyle]="getItemProp(processedItem, 'iconStyle')" />
<AngleRightIcon *ngIf="!isItemActive(processedItem)" [styleClass]="'p-panelmenu-submenu-icon'" [ngStyle]="getItemProp(processedItem, 'iconStyle')" />
</ng-container>
<ng-template *ngTemplateOutlet="panelMenu.submenuIconTemplate && panelMenu._submenuIconTemplate"></ng-template>
</ng-container>
<span class="p-panelmenu-submenu-icon" [ngClass]="processedItem.icon" *ngIf="processedItem.icon" [ngStyle]="getItemProp(processedItem, 'iconStyle')"></span>
<span class="p-panelmenu-item-label" *ngIf="getItemProp(processedItem, 'escape') !== false; else htmlRouteLabel">{{ getItemProp(processedItem, 'label') }}</span>
<ng-template #htmlRouteLabel><span class="p-panelmenu-item-label" [innerHTML]="getItemProp(processedItem, 'label')"></span></ng-template>
<span class="p-menuitem-badge" *ngIf="processedItem.badge" [ngClass]="getItemProp(processedItem, 'badgeStyleClass')">{{ getItemProp(processedItem, 'badge') }}</span>
</a>
</ng-container>
<ng-container *ngIf="itemTemplate">
<ng-template *ngTemplateOutlet="itemTemplate; context: { $implicit: processedItem.item }"></ng-template>
</ng-container>
</div>
<div class="p-toggleable-content" [@submenu]="getAnimation(processedItem)">
<p-panelmenu-sub
*ngIf="isItemVisible(processedItem) && isItemGroup(processedItem) && isItemExpanded(processedItem)"
[id]="getItemId(processedItem) + '_list'"
[panelId]="panelId"
[items]="processedItem?.items"
[itemTemplate]="itemTemplate"
[transitionOptions]="transitionOptions"
[focusedItemId]="focusedItemId"
[activeItemPath]="activeItemPath"
[level]="level + 1"
[parentExpanded]="!!parentExpanded && isItemExpanded(processedItem)"
(itemToggle)="onItemToggle($event)"
></p-panelmenu-sub>
</div>
</li>
</ng-template>
</ul>
`,
animations: [
trigger('submenu', [
state('hidden', style({
height: '0'
})),
state('visible', style({
height: '*'
})),
transition('visible <=> hidden', [animate('{{transitionParams}}')]),
transition('void => *', animate(0))
])
],
encapsulation: ViewEncapsulation.None
}]
}], propDecorators: { panelId: [{
type: Input
}], focusedItemId: [{
type: Input
}], items: [{
type: Input
}], itemTemplate: [{
type: Input
}], level: [{
type: Input,
args: [{ transform: numberAttribute }]
}], activeItemPath: [{
type: Input
}], root: [{
type: Input,
args: [{ transform: booleanAttribute }]
}], tabindex: [{
type: Input,
args: [{ transform: numberAttribute }]
}], transitionOptions: [{
type: Input
}], parentExpanded: [{
type: Input,
args: [{ transform: booleanAttribute }]
}], itemToggle: [{
type: Output
}], menuFocus: [{
type: Output
}], menuBlur: [{
type: Output
}], menuKeyDown: [{
type: Output
}], listViewChild: [{
type: ViewChild,
args: ['list']
}] } });
class PanelMenuList extends BaseComponent {
panelId;
id;
items;
itemTemplate;
parentExpanded;
expanded;
transitionOptions;
root;
tabindex;
activeItem;
itemToggle = new EventEmitter();
headerFocus = new EventEmitter();
subMenuViewChild;
searchTimeout;
searchValue;
focused;
focusedItem = signal(null);
activeItemPath = signal([]);
processedItems = signal([]);
visibleItems = computed(() => {
const processedItems = this.processedItems();
return this.flatItems(processedItems);
});
get focusedItemId() {
const focusedItem = this.focusedItem();
return focusedItem && focusedItem.item?.id ? focusedItem.item.id : isNotEmpty(this.focusedItem()) ? `${this.panelId}_${this.focusedItem().key}` : undefined;
}
ngOnChanges(changes) {
this.processedItems.set(this.createProcessedItems(changes?.items?.currentValue || this.items || []));
}
getItemProp(processedItem, name) {
return processedItem && processedItem.item ? resolve(processedItem.item[name]) : undefined;
}
getItemLabel(processedItem) {
return this.getItemProp(processedItem, 'label');
}
isItemVisible(processedItem) {
return this.getItemProp(processedItem, 'visible') !== false;
}
isItemDisabled(processedItem) {
return this.getItemProp(processedItem, 'disabled');
}
isItemActive(processedItem) {
return this.activeItemPath().some((path) => path.key === processedItem.parentKey);
}
isItemGroup(processedItem) {
return isNotEmpty(processedItem.items);
}
isElementInPanel(event, element) {
const panel = event.currentTarget.closest('[data-pc-section="panel"]');
return panel && panel.contains(element);
}
isItemMatched(processedItem) {
return this.isValidItem(processedItem) && this.getItemLabel(processedItem).toLocaleLowerCase().startsWith(this.searchValue.toLocaleLowerCase());
}
isVisibleItem(processedItem) {
return !!processedItem && (processedItem.level === 0 || this.isItemActive(processedItem)) && this.isItemVisible(processedItem);
}
isValidItem(processedItem) {
return !!processedItem && !this.isItemDisabled(processedItem) && !processedItem.separator;
}
findFirstItem() {
return this.visibleItems().find((processedItem) => this.isValidItem(processedItem));
}
findLastItem() {
return findLast(this.visibleItems(), (processedItem) => this.isValidItem(processedItem));
}
findItemByEventTarget(target) {
let parentNode = target;
while (parentNode && parentNode.tagName?.toLowerCase() !== 'li') {
parentNode = parentNode?.parentNode;
}
return parentNode?.id && this.visibleItems().find((processedItem) => this.isValidItem(processedItem) && `${this.panelId}_${processedItem.key}` === parentNode.id);
}
createProcessedItems(items, level = 0, parent = {}, parentKey = '') {
const processedItems = [];
items &&
items.forEach((item, index) => {
const key = (parentKey !== '' ? parentKey + '_' : '') + index;
const newItem = {
icon: item.icon,
expanded: item.expanded,
separator: item.separator,
item,
index,
level,
key,
parent,
parentKey
};
newItem['items'] = this.createProcessedItems(item.items, level + 1, newItem, key);
processedItems.push(newItem);
});
return processedItems;
}
findProcessedItemByItemKey(key, processedItems, level = 0) {
processedItems = processedItems || this.processedItems();
if (processedItems && processedItems.length) {
for (let i = 0; i < processedItems.length; i++) {
const processedItem = processedItems[i];
if (this.getItemProp(processedItem, 'key') === key)
return processedItem;
const matchedItem = this.findProcessedItemByItemKey(key, processedItem.items, level + 1);
if (matchedItem)
return matchedItem;
}
}
}
flatItems(processedItems, processedFlattenItems = []) {
processedItems &&
processedItems.forEach((processedItem) => {
if (this.isVisibleItem(processedItem)) {
processedFlattenItems.push(processedItem);
this.flatItems(processedItem.items, processedFlattenItems);
}
});
return processedFlattenItems;
}
changeFocusedItem(event) {
const { originalEvent, processedItem, focusOnNext, selfCheck, allowHeaderFocus = true } = event;
if (isNotEmpty(this.focusedItem()) && this.focusedItem().key !== processedItem.key) {
this.focusedItem.set(processedItem);
this.scrollInView();
}
else if (allowHeaderFocus) {
this.headerFocus.emit({ originalEvent, focusOnNext, selfCheck });
}
}
scrollInView() {
const element = findSingle(this.subMenuViewChild.listViewChild.nativeElement, `li[id="${`${this.focusedItemId}`}"]`);
if (element) {
element.scrollIntoView && element.scrollIntoView({ block: 'nearest', inline: 'nearest' });
}
}
onFocus(event) {
if (!this.focused) {
this.focused = true;
const focusedItem = this.focusedItem() || (this.isElementInPanel(event, event.relatedTarget) ? this.findItemByEventTarget(event.target) || this.findFirstItem() : this.findLastItem());
if (event.relatedTarget !== null)
this.focusedItem.set(focusedItem);
}
}
onBlur(event) {
const target = event.relatedTarget;
if (this.focused && !this.el.nativeElement.contains(target)) {
this.focused = false;
this.focusedItem.set(null);
this.searchValue = '';
}
}
onItemToggle(event) {
const { processedItem, expanded } = event;
// Update the original item object's 'expanded' property
if (processedItem.item) {
processedItem.item.expanded = !processedItem.item.expanded;
}
// Recreate processedItems with updated 'expanded' states
this.processedItems.set(this.createProcessedItems(this.items || [], 0, {}, ''));
// Update activeItemPath
const activeItemPath = this.activeItemPath().filter((p) => p.parentKey !== processedItem.parentKey);
if (expanded) {
activeItemPath.push(processedItem);
}
this.activeItemPath.set(activeItemPath);
// Update focusedItem
this.focusedItem.set(processedItem);
}
onKeyDown(event) {
const metaKey = event.metaKey || event.ctrlKey;
switch (event.code) {
case 'ArrowDown':
this.onArrowDownKey(event);
break;
case 'ArrowUp':
this.onArrowUpKey(event);
break;
case 'ArrowLeft':
this.onArrowLeftKey(event);
break;
case 'ArrowRight':
this.onArrowRightKey(event);
break;
case 'Home':
this.onHomeKey(event);
break;
case 'End':
this.onEndKey(event);
break;
case 'Space':
this.onSpaceKey(event);
break;
case 'Enter':
this.onEnterKey(event);
break;
case 'Escape':
case 'Tab':
case 'PageDown':
case 'PageUp':
case 'Backspace':
case 'ShiftLeft':
case 'ShiftRight':
//NOOP
break;
default:
if (!metaKey && isPrintableCharacter(event.key)) {
this.searchItems(event, event.key);
}
break;
}
}
onArrowDownKey(event) {
const processedItem = isNotEmpty(this.focusedItem()) ? this.findNextItem(this.focusedItem()) : this.findFirstItem();
this.changeFocusedItem({ originalEvent: event, processedItem, focusOnNext: true });
event.preventDefault();
}
onArrowUpKey(event) {
const processedItem = isNotEmpty(this.focusedItem()) ? this.findPrevItem(this.focusedItem()) : this.findLastItem();
this.changeFocusedItem({ originalEvent: event, processedItem, selfCheck: true });
event.preventDefault();
}
onArrowLeftKey(event) {
if (isNotEmpty(this.focusedItem())) {
const matched = this.activeItemPath().some((p) => p.key === this.focusedItem().key);
if (matched) {
const activeItemPath = this.activeItemPath().filter((p) => p.key !== this.focusedItem().key);
this.activeItemPath.set(activeItemPath);
}
else {
const focusedItem = isNotEmpty(this.focusedItem().parent) ? this.focusedItem().parent : this.focusedItem();
this.focusedItem.set(focusedItem);
}
event.preventDefault();
}
}
onArrowRightKey(event) {
if (isNotEmpty(this.focusedItem())) {
const grouped = this.isItemGroup(this.focusedItem());
if (grouped) {
const matched = this.activeItemPath().some((p) => p.key === this.focusedItem().key);
if (matched) {
this.onArrowDownKey(event);
}
else {
const activeItemPath = this.activeItemPath().filter((p) => p.parentKey !== this.focusedItem().parentKey);
activeItemPath.push(this.focusedItem());
this.activeItemPath.set(activeItemPath);
}
}
event.preventDefault();
}
}
onHomeKey(event) {
this.changeFocusedItem({ originalEvent: event, processedItem: this.findFirstItem(), allowHeaderFocus: false });
event.preventDefault();
}
onEndKey(event) {
this.changeFocusedItem({ originalEvent: event, processedItem: this.findLastItem(), focusOnNext: true, allowHeaderFocus: false });
event.preventDefault();
}
onEnterKey(event) {
if (isNotEmpty(this.focusedItem())) {
const element = findSingle(this.subMenuViewChild.listViewChild.nativeElement, `li[id="${`${this.focusedItemId}`}"]`);
const anchorElement = element && (findSingle(element, '[data-pc-section="action"]') || findSingle(element, 'a,button'));
anchorElement ? anchorElement.click() : element && element.click();
}
event.preventDefault();
}
onSpaceKey(event) {
this.onEnterKey(event);
}
findNextItem(processedItem) {
const index = this.visibleItems().findIndex((item) => item.key === processedItem.key);
const matchedItem = index < this.visibleItems().length - 1
? this.visibleItems()
.slice(index + 1)
.find((pItem) => this.isValidItem(pItem))
: undefined;
return matchedItem || processedItem;
}
findPrevItem(processedItem) {
const index = this.visibleItems().findIndex((item) => item.key === processedItem.key);
const matchedItem = index > 0 ? findLast(this.visibleItems().slice(0, index), (pItem) => this.isValidItem(pItem)) : undefined;
return matchedItem || processedItem;
}
searchItems(event, char) {
this.searchValue = (this.searchValue || '') + char;
let matchedItem = null;
let matched = false;
if (isNotEmpty(this.focusedItem())) {
const focusedItemIndex = this.visibleItems().findIndex((processedItem) => processedItem.key === this.focusedItem().key);
matchedItem = this.visibleItems()
.slice(focusedItemIndex)
.find((processedItem) => this.isItemMatched(processedItem));
matchedItem = isEmpty(matchedItem)
? this.visibleItems()
.slice(0, focusedItemIndex)
.find((processedItem) => this.isItemMatched(processedItem))
: matchedItem;
}
else {
matchedItem = this.visibleItems().find((processedItem) => this.isItemMatched(processedItem));
}
if (isNotEmpty(matchedItem)) {
matched = true;
}
if (isEmpty(matchedItem) && isEmpty(this.focusedItem())) {
matchedItem = this.findFirstItem();
}
if (isNotEmpty(matchedItem)) {
this.changeFocusedItem({
originalEvent: event,
processedItem: matchedItem,
allowHeaderFocus: false
});
}
if (this.searchTimeout) {
clearTimeout(this.searchTimeout);
}
this.searchTimeout = setTimeout(() => {
this.searchValue = '';
this.searchTimeout = null;
}, 500);
return matched;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.2", ngImport: i0, type: PanelMenuList, deps: null, target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.0", version: "19.2.2", type: PanelMenuList, isStandalone: true, selector: "p-panelMenuList, p-panel-menu-list", inputs: { panelId: "panelId", id: "id", items: "items", itemTemplate: "itemTemplate", parentExpanded: ["parentExpanded", "parentExpanded", booleanAttribute], expanded: ["expanded", "expanded", booleanAttribute], transitionOptions: "transitionOptions", root: ["root", "root", booleanAttr