UNPKG

primeng

Version:

[![npm version](https://badge.fury.io/js/primeng.svg)](https://badge.fury.io/js/primeng) [![npm downloads](https://img.shields.io/npm/dm/primeng.svg)](https://www.npmjs.com/package/primeng) [![Actions CI](https://github.com/primefaces/primeng/workflows/No

917 lines 179 kB
import { animate, state, style, transition, trigger } from '@angular/animations'; import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, Component, ContentChildren, EventEmitter, Inject, Input, NgModule, Output, ViewChild, ViewEncapsulation, booleanAttribute, computed, forwardRef, numberAttribute, signal } from '@angular/core'; import { RouterModule } from '@angular/router'; import { PrimeTemplate, SharedModule } from 'primeng/api'; import { DomHandler } from 'primeng/dom'; import { AngleDownIcon } from 'primeng/icons/angledown'; import { AngleRightIcon } from 'primeng/icons/angleright'; import { ChevronDownIcon } from 'primeng/icons/chevrondown'; import { ChevronRightIcon } from 'primeng/icons/chevronright'; import { TooltipModule } from 'primeng/tooltip'; import { ObjectUtils, UniqueComponentId } from 'primeng/utils'; import * as i0 from "@angular/core"; import * as i1 from "@angular/common"; import * as i2 from "@angular/router"; import * as i3 from "primeng/tooltip"; export class PanelMenuSub { panelMenu; el; panelId; focusedItemId; items; itemTemplate; level = 0; activeItemPath; root; tabindex; transitionOptions; parentExpanded; itemToggle = new EventEmitter(); 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