primeng
Version:
[](https://badge.fury.io/js/primeng) [](https://www.npmjs.com/package/primeng) [;
menuFocus = new EventEmitter();
menuBlur = new EventEmitter();
menuKeyDown = new EventEmitter();
listViewChild;
constructor(panelMenu, el) {
this.panelMenu = panelMenu;
this.el = el;
}
getItemId(processedItem) {
return processedItem.item?.id ?? `${this.panelId}_${processedItem.key}`;
}
getItemKey(processedItem) {
return this.getItemId(processedItem);
}
getItemClass(processedItem) {
return {
'p-menuitem': true,
'p-disabled': this.isItemDisabled(processedItem)
};
}
getItemProp(processedItem, name, params) {
return processedItem && processedItem.item ? ObjectUtils.getItemValue(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 ObjectUtils.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: "18.0.1", ngImport: i0, type: PanelMenuSub, deps: [{ token: forwardRef(() => PanelMenu) }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.0", version: "18.0.1", type: PanelMenuSub, selector: "p-panelMenuSub", 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" }, host: { classAttribute: "p-element" }, viewQueries: [{ propertyName: "listViewChild", first: true, predicate: ["list"], descendants: true }], ngImport: i0, template: `
<ul
#list
[ngClass]="{ 'p-submenu-list': true, 'p-panelmenu-root-list': root }"
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-menuitem-content" (click)="onItemClick($event, processedItem)">
<ng-container *ngIf="!itemTemplate">
<a
*ngIf="!getItemProp(processedItem, 'routerLink')"
[attr.href]="getItemProp(processedItem, 'url')"
class="p-menuitem-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">
<AngleDownIcon [styleClass]="'p-submenu-icon'" *ngIf="isItemActive(processedItem)" [ngStyle]="getItemProp(processedItem, 'iconStyle')" />
<AngleRightIcon [styleClass]="'p-submenu-icon'" *ngIf="!isItemActive(processedItem)" [ngStyle]="getItemProp(processedItem, 'iconStyle')" />
</ng-container>
<ng-template *ngTemplateOutlet="panelMenu.submenuIconTemplate"></ng-template>
</ng-container>
<span class="p-menuitem-icon" [ngClass]="processedItem.icon" *ngIf="processedItem.icon" [ngStyle]="getItemProp(processedItem, 'iconStyle')"></span>
<span class="p-menuitem-text" *ngIf="processedItem.item?.escape !== false; else htmlLabel">{{ getItemProp(processedItem, 'label') }}</span>
<ng-template #htmlLabel><span class="p-menuitem-text" [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-menuitem-link-active'"
[routerLinkActiveOptions]="getItemProp(processedItem, 'routerLinkActiveOptions') || { exact: false }"
class="p-menuitem-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">
<AngleDownIcon *ngIf="isItemActive(processedItem)" [styleClass]="'p-submenu-icon'" [ngStyle]="getItemProp(processedItem, 'iconStyle')" />
<AngleRightIcon *ngIf="!isItemActive(processedItem)" [styleClass]="'p-submenu-icon'" [ngStyle]="getItemProp(processedItem, 'iconStyle')" />
</ng-container>
<ng-template *ngTemplateOutlet="panelMenu.submenuIconTemplate"></ng-template>
</ng-container>
<span class="p-menuitem-icon" [ngClass]="processedItem.icon" *ngIf="processedItem.icon" [ngStyle]="getItemProp(processedItem, 'iconStyle')"></span>
<span class="p-menuitem-text" *ngIf="getItemProp(processedItem, 'escape') !== false; else htmlRouteLabel">{{ getItemProp(processedItem, 'label') }}</span>
<ng-template #htmlRouteLabel><span class="p-menuitem-text" [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-panelMenuSub
*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-panelMenuSub>
</div>
</li>
</ng-template>
</ul>
`, isInline: true, dependencies: [{ kind: "directive", type: i0.forwardRef(() => i1.NgClass), selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i0.forwardRef(() => i1.NgForOf), selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i0.forwardRef(() => i1.NgIf), selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i0.forwardRef(() => i1.NgTemplateOutlet), selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i0.forwardRef(() => i1.NgStyle), selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i0.forwardRef(() => i2.RouterLink), selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i0.forwardRef(() => i2.RouterLinkActive), selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "directive", type: i0.forwardRef(() => 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: i0.forwardRef(() => AngleDownIcon), selector: "AngleDownIcon" }, { kind: "component", type: i0.forwardRef(() => AngleRightIcon), selector: "AngleRightIcon" }, { kind: "component", type: i0.forwardRef(() => PanelMenuSub), selector: "p-panelMenuSub", inputs: ["panelId", "focusedItemId", "items", "itemTemplate", "level", "activeItemPath", "root", "tabindex", "transitionOptions", "parentExpanded"], outputs: ["itemToggle", "menuFocus", "menuBlur", "menuKeyDown"] }], 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: "18.0.1", ngImport: i0, type: PanelMenuSub, decorators: [{
type: Component,
args: [{
selector: 'p-panelMenuSub',
template: `
<ul
#list
[ngClass]="{ 'p-submenu-list': true, 'p-panelmenu-root-list': root }"
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-menuitem-content" (click)="onItemClick($event, processedItem)">
<ng-container *ngIf="!itemTemplate">
<a
*ngIf="!getItemProp(processedItem, 'routerLink')"
[attr.href]="getItemProp(processedItem, 'url')"
class="p-menuitem-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">
<AngleDownIcon [styleClass]="'p-submenu-icon'" *ngIf="isItemActive(processedItem)" [ngStyle]="getItemProp(processedItem, 'iconStyle')" />
<AngleRightIcon [styleClass]="'p-submenu-icon'" *ngIf="!isItemActive(processedItem)" [ngStyle]="getItemProp(processedItem, 'iconStyle')" />
</ng-container>
<ng-template *ngTemplateOutlet="panelMenu.submenuIconTemplate"></ng-template>
</ng-container>
<span class="p-menuitem-icon" [ngClass]="processedItem.icon" *ngIf="processedItem.icon" [ngStyle]="getItemProp(processedItem, 'iconStyle')"></span>
<span class="p-menuitem-text" *ngIf="processedItem.item?.escape !== false; else htmlLabel">{{ getItemProp(processedItem, 'label') }}</span>
<ng-template #htmlLabel><span class="p-menuitem-text" [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-menuitem-link-active'"
[routerLinkActiveOptions]="getItemProp(processedItem, 'routerLinkActiveOptions') || { exact: false }"
class="p-menuitem-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">
<AngleDownIcon *ngIf="isItemActive(processedItem)" [styleClass]="'p-submenu-icon'" [ngStyle]="getItemProp(processedItem, 'iconStyle')" />
<AngleRightIcon *ngIf="!isItemActive(processedItem)" [styleClass]="'p-submenu-icon'" [ngStyle]="getItemProp(processedItem, 'iconStyle')" />
</ng-container>
<ng-template *ngTemplateOutlet="panelMenu.submenuIconTemplate"></ng-template>
</ng-container>
<span class="p-menuitem-icon" [ngClass]="processedItem.icon" *ngIf="processedItem.icon" [ngStyle]="getItemProp(processedItem, 'iconStyle')"></span>
<span class="p-menuitem-text" *ngIf="getItemProp(processedItem, 'escape') !== false; else htmlRouteLabel">{{ getItemProp(processedItem, 'label') }}</span>
<ng-template #htmlRouteLabel><span class="p-menuitem-text" [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-panelMenuSub
*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-panelMenuSub>
</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,
host: {
class: 'p-element'
}
}]
}], ctorParameters: () => [{ type: PanelMenu, decorators: [{
type: Inject,
args: [forwardRef(() => PanelMenu)]
}] }, { type: i0.ElementRef }], 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']
}] } });
export class PanelMenuList {
el;
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 : ObjectUtils.isNotEmpty(this.focusedItem()) ? `${this.panelId}_${this.focusedItem().key}` : undefined;
}
constructor(el) {
this.el = el;
}
ngOnChanges(changes) {
this.processedItems.set(this.createProcessedItems(changes?.items?.currentValue || this.items || []));
}
getItemProp(processedItem, name) {
return processedItem && processedItem.item ? ObjectUtils.getItemValue(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 ObjectUtils.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 ObjectUtils.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 (ObjectUtils.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 = DomHandler.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;
processedItem.expanded = !processedItem.expanded;
const activeItemPath = this.activeItemPath().filter((p) => p.parentKey !== processedItem.parentKey);
expanded && activeItemPath.push(processedItem);
this.activeItemPath.set(activeItemPath);
this.processedItems.update((value) => value.map((i) => (i === processedItem ? processedItem : i)));
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 && ObjectUtils.isPrintableCharacter(event.key)) {
this.searchItems(event, event.key);
}
break;
}
}
onArrowDownKey(event) {
const processedItem = ObjectUtils.isNotEmpty(this.focusedItem()) ? this.findNextItem(this.focusedItem()) : this.findFirstItem();
this.changeFocusedItem({ originalEvent: event, processedItem, focusOnNext: true });
event.preventDefault();
}
onArrowUpKey(event) {
const processedItem = ObjectUtils.isNotEmpty(this.focusedItem()) ? this.findPrevItem(this.focusedItem()) : this.findLastItem();
this.changeFocusedItem({ originalEvent: event, processedItem, selfCheck: true });
event.preventDefault();
}
onArrowLeftKey(event) {
if (ObjectUtils.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 = ObjectUtils.isNotEmpty(this.focusedItem().parent) ? this.focusedItem().parent : this.focusedItem();
this.focusedItem.set(focusedItem);
}
event.preventDefault();
}
}
onArrowRightKey(event) {
if (ObjectUtils.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 (ObjectUtils.isNotEmpty(this.focusedItem())) {
const element = DomHandler.findSingle(this.subMenuViewChild.listViewChild.nativeElement, `li[id="${`${this.focusedItemId}`}"]`);
const anchorElement = element && (DomHandler.findSingle(element, '[data-pc-section="action"]') || DomHandler.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 ? ObjectUtils.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 (ObjectUtils.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 = ObjectUtils.isEmpty(matchedItem)
? this.visibleItems()
.slice(0, focusedItemIndex)
.find((processedItem) => this.isItemMatched(processedItem))
: matchedItem;
}
else {
matchedItem = this.visibleItems().find((processedItem) => this.isItemMatched(processedItem));
}
if (ObjectUtils.isNotEmpty(matchedItem)) {
matched = true;
}
if (ObjectUtils.isEmpty(matchedItem) && ObjectUtils.isEmpty(this.focusedItem())) {
matchedItem = this.findFirstItem();
}
if (ObjectUtils.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: "18.0.1", ngImport: i0, type: PanelMenuList, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.0", version: "18.0.1", type: PanelMenuList, selector: "p-panelMenuList", inputs: { panelId: "panelId", id: "id", items: "items", itemTemplate: "itemTemplate", parentExpanded: ["parentExpanded", "parentExpanded", booleanAttribute], expanded: ["expanded", "expanded", booleanAttribute], transitionOptions: "transitionOptions", root: ["root", "root", booleanAttribute], tabindex: ["tabindex", "tabindex", numberAttribute], activeItem: "activeItem" }, outputs: { itemToggle: "itemToggle", headerFocus: "headerFocus" }, host: { classAttribute: "p-element" }, viewQueries: [{ propertyName: "subMenuViewChild", first: true, predicate: ["submenu"], descendants: true }], usesOnChanges: true, ngImport: i0, template: `
<p-panelMenuSub
#submenu
[root]="true"
[id]="panelId + '_list'"
[panelId]="panelId"
[tabindex]="tabindex"
[itemTemplate]="itemTemplate"
[focusedItemId]="focused ? focusedItemId : undefined"
[activeItemPath]="activeItemPath()"
[transitionOptions]="transitionOptions"
[items]="processedItems()"
[parentExpanded]="parentExpanded"
(itemToggle)="onItemToggle($event)"
(keydown)="onKeyDown($event)"
(menuFocus)="onFocus($event)"
(menuBlur)="onBlur($event)"
></p-panelMenuSub>
`, isInline: true, styles: ["@layer primeng{.p-panelmenu .p-panelmenu-header-action{display:flex;align-items:center;-webkit-user-select:none;user-select:none;cursor:pointer;position:relative;text-decoration:none}.p-panelmenu .p-panelmenu-header-action:focus{z-index:1}.p-panelmenu .p-submenu-list{margin:0;padding:0;list-style:none}.p-panelmenu .p-menuitem-link{display:flex;align-items:center;-webkit-user-select:none;user-select:none;cursor:pointer;text-decoration:none;position:relative;overflow:hidden;outline:none}.p-panelmenu .p-menuitem-text{line-height:1}.p-panelmenu-expanded.p-toggleable-content:not(.ng-animating),.p-panelmenu .p-submenu-expanded:not(.ng-animating){overflow:visible}.p-panelmenu .p-toggleable-content,.p-panelmenu .p-submenu-list{overflow:hidden}}\n"], dependencies: [{ kind: "component", type: PanelMenuSub, selector: "p-panelMenuSub", inputs: ["panelId", "focusedItemId", "items", "itemTemplate", "level", "activeItemPath", "root", "tabindex", "transitionOptions", "parentExpanded"], outputs: ["itemToggle", "menuFocus", "menuBlur", "menuKeyDown"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.0.1", ngImport: i0, type: PanelMenuList, decorators: [{
type: Component,
args: [{ selector: 'p-panelMenuList', template: `
<p-panelMenuSub
#submenu
[root]="true"
[id]="panelId + '_list'"
[panelId]="panelId"
[tabindex]="tabindex"
[itemTemplate]="itemTemplate"
[focusedItemId]="focused ? focusedItemId : undefined"
[activeItemPath]="activeItemPath()"
[transitionOptions]="transitionOptions"
[items]="processedItems()"
[parentExpanded]="parentExpanded"
(itemToggle)="onItemToggle($event)"
(keydown)="onKeyDown($event)"
(menuFocus)="onFocus($event)"
(menuBlur)="onBlur($event)"
></p-panelMenuSub>
`, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
class: 'p-element'
}, styles: ["@layer primeng{.p-panelmenu .p-panelmenu-header-action{display:flex;align-items:center;-webkit-user-select:none;user-select:none;cursor:pointer;position:relative;text-decoration:none}.p-panelmenu .p-panelmenu-header-action:focus{z-index:1}.p-panelmenu .p-submenu-list{margin:0;padding:0;list-style:none}.p-panelmenu .p-menuitem-link{display:flex;align-items:center;-webkit-user-select:none;user-select:none;cursor:pointer;text-decoration:none;position:relative;overflow:hidden;outline:none}.p-panelmenu .p-menuitem-text{line-height:1}.p-panelmenu-expanded.p-toggleable-content:not(.ng-animating),.p-panelmenu .p-submenu-expanded:not(.ng-animating){overflow:visible}.p-panelmenu .p-toggleable-content,.p-panelmenu .p-submenu-list{overflow:hidden}}\n"] }]
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { panelId: [{
type: Input
}], id: [{
type: Input
}], items: [{
type: Input
}], itemTemplate: [{
type: Input
}], parentExpanded: [{
type: Input,
args: [{ transform: booleanAttribute }]
}], expanded: [{
type: Input,
args: [{ transform: booleanAttribute }]
}], transitionOptions: [{
type: Input
}], root: [{
type: Input,
args: [{ transform: booleanAttribute }]
}], tabindex: [{
type: Input,
args: [{ transform: numberAttribute }]
}], activeItem: [{
type: Input
}], itemToggle: [{
type: Output
}], headerFocus: [{
type: Output
}], subMenuViewChild: [{
type: ViewChild,
args: ['submenu']
}] } });
/**
* PanelMenu is a hybrid of Accordion and Tree components.
* @group Components
*/
export class PanelMenu {
cd;
/**
* An array of menuitems.
* @group Props
*/
model;
/**
* Inline style of the component.
* @group Props
*/
style;
/**
* Style class of the component.
* @group Props
*/
styleClass;
/**
* Whether multiple tabs can be activated at the same time or not.
* @group Props
*/
multiple = false;
/**
* Transition options of the animation.
* @group Props
*/
transitionOptions = '400ms cubic-bezier(0.86, 0, 0.07, 1)';
/**
* Current id state as a string.
* @group Props
*/
id;
/**
* Index of the element in tabbing order.
* @group Props
*/
tabindex = 0;
templates;
containerViewChild;
submenuIconTemplate;
itemTemplate;
animating;
activeItem = signal(null);
ngOnInit() {
this.id = this.id || UniqueComponentId();
}
ngAfterContentInit() {
this.templates?.forEach((item) => {
switch (item.getType()) {
case 'submenuicon':
this.submenuIconTemplate = item.template;
break;
case 'item':
this.itemTemplate = item.template;
break;
default:
this.itemTemplate = item.template;
break;
}
});
}
constructor(cd) {
this.cd = cd;
}
/**
* Collapses open panels.
* @group Method
*/
collapseAll() {
for (let item of this.model) {
if (item.expanded) {
item.expanded = false;
}
}
this.cd.detectChanges();
}
onToggleDone() {
this.animating = false;
this.cd.markForCheck();
}
changeActiveItem(event, item, index, selfActive = false) {
if (!this.isItemDisabled(item)) {
const activeItem = selfActive ? item : this.activeItem && ObjectUtils.equals(item, this.activeItem) ? null : item;
this.activeItem.set(activeItem);
}
}
getAnimation(item) {
return item.expanded ? { value: 'visible', params: { transitionParams: this.animating ? this.transitionOptions : '0ms', height: '*' } } : { value: 'hidden', params: { transitionParams: this.transitionOptions, height: '0' } };
}
getItemProp(item, name) {
return item ? ObjectUtils.getItemValue(item[name]) : undefined;
}
getItemLabel(item) {
return this.getItemProp(item, 'label');
}
isItemActive(item) {
return item.expanded;
}
isItemVisible(item) {
return this.getItemProp(item, 'visible') !== false;
}
isItemDisabled(item) {
return this.getItemProp(item, 'disabled');
}
isItemGroup(item) {
return ObjectUtils.isNotEmpty(item.items);
}
getPanelId(index, item) {
return item && item.id ? item.id : `${this.id}_${index}`;
}
getHeaderId(item, index) {
return item.id ? item.id + '_header' : `${this.getPanelId(index)}_header`;
}
getContentId(item, index) {
return item.id ? item.id + '_content' : `${this.getPanelId(index)}_content`;
}
updateFocusedHeader(event) {
const { originalEvent, focusOnNext, selfCheck } = event;
const panelElement = originalEvent.currentTarget.closest('[data-pc-section="panel"]');
const header = selfCheck ? DomHandler.findSingle(panelElement, '[data-pc-section="header"]') : focusOnNext ? this.findNextHeader(panelElement) : this.findPrevHeader(panelElement);
header ? this.changeFocusedHeader(originalEvent, header) : focusOnNext ? this.onHeaderHomeKey(originalEvent) : this.onHeaderEndKey(originalEvent);
}
changeFocusedHeader(event, element) {
element && DomHandler.focus(element);
}
findNextHeader(panelElement, selfCheck = false) {
const nextPanelElement = selfCheck ? panelElement : panelElement.nextElementSibling;
const headerElement = DomHandler.findSingle(nextPanelElement, '[data-pc-section="header"]');
r