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

941 lines 167 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, computed, forwardRef, 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"; class PanelMenuSub { panelMenu; el; panelId; focusedItemId; items; 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 `${this.panelId}_${processedItem.key}`; } getItemKey(processedItem) { return this.getItemId(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) { 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: "16.2.0", ngImport: i0, type: PanelMenuSub, deps: [{ token: forwardRef(() => PanelMenu) }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.0", type: PanelMenuSub, selector: "p-panelMenuSub", inputs: { panelId: "panelId", focusedItemId: "focusedItemId", items: "items", level: "level", activeItemPath: "activeItemPath", root: "root", tabindex: "tabindex", transitionOptions: "transitionOptions", parentExpanded: "parentExpanded" }, 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)" class="p-menuitem" role="treeitem" [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)" [ngStyle]="getItemProp(processedItem, 'style')" [pTooltip]="getItemProp(processedItem, 'tooltip')" [tooltipOptions]="getItemProp(processedItem, 'tooltipOptions')" > <div class="p-menuitem-content" (click)="onItemClick($event, processedItem)"> <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> </div> <div class="p-toggleable-content" [@submenu]="getAnimation(processedItem)"> <p-panelMenuSub *ngIf="isItemVisible(processedItem) && isItemGroup(processedItem)" [id]="getItemId(processedItem) + '_list'" [panelId]="panelId" [items]="processedItem.items" [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(function () { return i1.NgClass; }), selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i0.forwardRef(function () { return i1.NgForOf; }), selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i0.forwardRef(function () { return i1.NgIf; }), selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i0.forwardRef(function () { return i1.NgTemplateOutlet; }), selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i0.forwardRef(function () { return i1.NgStyle; }), selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: i0.forwardRef(function () { return i2.RouterLink; }), selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: i0.forwardRef(function () { return i2.RouterLinkActive; }), selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "directive", type: i0.forwardRef(function () { return 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(function () { return AngleDownIcon; }), selector: "AngleDownIcon" }, { kind: "component", type: i0.forwardRef(function () { return AngleRightIcon; }), selector: "AngleRightIcon" }, { kind: "component", type: i0.forwardRef(function () { return PanelMenuSub; }), selector: "p-panelMenuSub", inputs: ["panelId", "focusedItemId", "items", "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 }); } export { PanelMenuSub }; i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.0", 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)" class="p-menuitem" role="treeitem" [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)" [ngStyle]="getItemProp(processedItem, 'style')" [pTooltip]="getItemProp(processedItem, 'tooltip')" [tooltipOptions]="getItemProp(processedItem, 'tooltipOptions')" > <div class="p-menuitem-content" (click)="onItemClick($event, processedItem)"> <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> </div> <div class="p-toggleable-content" [@submenu]="getAnimation(processedItem)"> <p-panelMenuSub *ngIf="isItemVisible(processedItem) && isItemGroup(processedItem)" [id]="getItemId(processedItem) + '_list'" [panelId]="panelId" [items]="processedItem.items" [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: function () { return [{ type: PanelMenu, decorators: [{ type: Inject, args: [forwardRef(() => PanelMenu)] }] }, { type: i0.ElementRef }]; }, propDecorators: { panelId: [{ type: Input }], focusedItemId: [{ type: Input }], items: [{ type: Input }], level: [{ type: Input }], activeItemPath: [{ type: Input }], root: [{ type: Input }], tabindex: [{ type: Input }], transitionOptions: [{ type: Input }], parentExpanded: [{ type: Input }], itemToggle: [{ type: Output }], menuFocus: [{ type: Output }], menuBlur: [{ type: Output }], menuKeyDown: [{ type: Output }], listViewChild: [{ type: ViewChild, args: ['list'] }] } }); class PanelMenuList { panelId; id; items; 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() { return ObjectUtils.isNotEmpty(this.focusedItem()) ? `${this.panelId}_${this.focusedItem().key}` : undefined; } ngOnChanges(changes) { if (changes && changes.items && changes.items.currentValue) { this.processedItems.set(this.createProcessedItems(changes.items.currentValue || [])); } } 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); } findFirstItem() { return this.visibleItems().find((processedItem) => this.isValidItem(processedItem)); } findLastItem() { return ObjectUtils.findLast(this.visibleItems(), (processedItem) => this.isValidItem(processedItem)); } 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) { this.focused = true; const focusedItem = this.focusedItem() || (this.isElementInPanel(event, event.relatedTarget) ? this.findFirstItem() : this.findLastItem()); if (event.relatedTarget !== null) this.focusedItem.set(focusedItem); } onBlur(event) { 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.mutate((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: "16.2.0", ngImport: i0, type: PanelMenuList, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.0", type: PanelMenuList, selector: "p-panelMenuList", inputs: { panelId: "panelId", id: "id", items: "items", parentExpanded: "parentExpanded", expanded: "expanded", transitionOptions: "transitionOptions", root: "root", tabindex: "tabindex", 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" [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}.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", "level", "activeItemPath", "root", "tabindex", "transitionOptions", "parentExpanded"], outputs: ["itemToggle", "menuFocus", "menuBlur", "menuKeyDown"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); } export { PanelMenuList }; i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.0", ngImport: i0, type: PanelMenuList, decorators: [{ type: Component, args: [{ selector: 'p-panelMenuList', template: ` <p-panelMenuSub #submenu [root]="true" [id]="panelId + '_list'" [panelId]="panelId" [tabindex]="tabindex" [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}.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"] }] }], propDecorators: { panelId: [{ type: Input }], id: [{ type: Input }], items: [{ type: Input }], parentExpanded: [{ type: Input }], expanded: [{ type: Input }], transitionOptions: [{ type: Input }], root: [{ type: Input }], tabindex: [{ type: Input }], 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 */ 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; 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; } }); } 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; } 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) { return `${this.id}_${index}`; } getPanelKey(index) { return this.getPanelId(index); } getHeaderId(index) { return `${this.getPanelId(index)}_header`; } getContentId(index) { return `${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"]'); return headerElement ? (DomHandler.getAttribute(headerElement, 'data-p-disabled') ? this.findNextHeader(headerElement.parentElement) : headerElement) : null; } findPrevHeader(panelElement, selfCheck = false) { const prevPanelElement = selfCheck ? panelElement : panelElement.previousElementSibling; const headerElement = DomHandler.findSingle(prevPanelElement, '[data-pc-section="header"]'); return headerElement ? (DomHandler.getAttribute(headerElement, 'data-p-disabled') ? this.findPrevHeader(headerElement.parentElement) : headerElement) : null; } findFirstHeader() { return this.findNextHeader(this.containerViewChild.nativeElement.firstElementChild, true); } findLastHeader() { return this.findPrevHeader(this.containerViewChild.nativeElement.lastElementChild, true); } onHeaderClick(event, item, index) { if (this.isItemDisabled(item)) { event.preventDefault(); return; } if (item.command) { item.command({ originalEvent: event, item }); } if (!this.multiple) { for (let modelItem of this.model) { if (item !== modelItem && modelItem.expanded) { modelItem.expanded = false; } } } item.expanded = !item.expanded; this.changeActiveItem(event, item, index); this.animating = true; DomHandler.focus(event.currentTarget); } onHeaderKeyDown(event, item, index) { switch (event.code) { case 'ArrowDown': this.onHeaderArrowDownKey(event); break; case 'ArrowUp': this.onHeaderArrowUpKey(event); break; case 'Home': this.onHeaderHomeKey(event); break; case 'End': this.onHeaderEndKey(event); break; case 'Enter': case 'Space': this.onHeaderEnterKey(event, item, index); break; default: break; } } onHeaderArrowDownKey(event) { const rootList = DomHandler.getAttribute(event.currentTarget, 'data-p-highlight') === true ? DomHandler.findSingle(event.currentTarget.nextElementSibling, '[data-pc-section="menu"]') : null; rootList ? DomHandler.focus(rootList) : this.updateFocusedHeader({ originalEvent: event, focusOnNext: true }); event.preventDefault(); } onHeaderArrowUpKey(event) { const prevHeader = this.findPrevHeader(event.currentTarget.parentElement) || this.findLastHeader(); const rootList = DomHandler.getAttribute(prevHeader, 'data-p-highlight') === true ? DomHandler.findSingle(prevHeader.nextElementSibling, '[data-pc-section="menu"]') : null; rootList ? DomHandler.focus(rootList) : this.updateFocusedHeader({ originalEvent: event, focusOnNext: false }); event.preventDefault(); } onHeaderHomeKey(event) { this.changeFocusedHeader(event, this.findFirstHeader()); event.preventDefault(); } onHeaderEndKey(event) { this.changeFocusedHeader(event, this.findLastHeader()); event.preventDefault(); } onHeaderEnterKey(event, item, index) { const headerAction = DomHandler.findSingle(event.currentTarget, '[data-pc-section="headeraction"]'); headerAction ? headerAction.click() : this.onHeaderClick(event, item, index); event.preventDefault(); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.0", ngImport: i0, type: PanelMenu, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareCo