UNPKG

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
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