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

1 lines 88.2 kB
{"version":3,"file":"primeng-panelmenu.mjs","sources":["../../src/panelmenu/style/panelmenustyle.ts","../../src/panelmenu/panelmenu.ts","../../src/panelmenu/primeng-panelmenu.ts"],"sourcesContent":["import { Injectable } from '@angular/core';\nimport { style } from '@primeuix/styles/panelmenu';\nimport { BaseStyle } from 'primeng/base';\n\nconst theme = /*css*/ `\n ${style}\n /*For PrimeNG*/\n .p-panelmenu-item:not(.ng-animating) {\n overflow: hidden;\n }\n\n .p-panelmenu-panel {\n overflow: hidden;\n }\n\n .p-panelmenu-root-list,\n .p-panelmenu-submenu,\n .p-panelmenu-item-link {\n outline: 0 none;\n }\n`;\n\nconst classes = {\n root: () => ['p-panelmenu p-component'],\n panel: 'p-panelmenu-panel',\n header: ({ instance, item }) => [\n 'p-panelmenu-header',\n {\n 'p-panelmenu-header-active': instance.isItemActive(item) && !!item.items,\n 'p-disabled': instance.isItemDisabled(item)\n }\n ],\n headerContent: 'p-panelmenu-header-content',\n headerLink: 'p-panelmenu-header-link',\n headerIcon: 'p-panelmenu-header-icon',\n headerLabel: 'p-panelmenu-header-label',\n contentContainer: ({ instance, processedItem }) => ['p-panelmenu-content-container', { 'p-panelmenu-expanded': instance.isItemActive(processedItem) }],\n content: 'p-panelmenu-content',\n rootList: 'p-panelmenu-root-list',\n item: ({ instance, processedItem }) => [\n 'p-panelmenu-item',\n {\n 'p-focus': instance.isItemFocused(processedItem) && !instance.isItemDisabled(processedItem),\n 'p-disabled': instance.isItemDisabled(processedItem)\n }\n ],\n itemContent: 'p-panelmenu-item-content',\n itemLink: 'p-panelmenu-item-link',\n itemIcon: 'p-panelmenu-item-icon',\n itemLabel: 'p-panelmenu-item-label',\n submenuIcon: 'p-panelmenu-submenu-icon',\n submenu: 'p-panelmenu-submenu',\n separator: 'p-menuitem-separator',\n badge: 'p-menuitem-badge'\n};\n\n@Injectable()\nexport class PanelMenuStyle extends BaseStyle {\n name = 'panelmenu';\n\n theme = theme;\n\n classes = classes;\n}\n\n/**\n *\n * PanelMenu is a hybrid of Accordion and Tree components.\n *\n * [Live Demo](https://www.primeng.org/panelmenu/)\n *\n * @module panelmenustyle\n *\n */\nexport enum PanelMenuClasses {\n /**\n * Class name of the root element\n */\n root = 'p-panelmenu',\n /**\n * Class name of the panel element\n */\n panel = 'p-panelmenu-panel',\n /**\n * Class name of the header element\n */\n header = 'p-panelmenu-header',\n /**\n * Class name of the header content element\n */\n headerContent = 'p-panelmenu-header-content',\n /**\n * Class name of the header link element\n */\n headerLink = 'p-panelmenu-header-link',\n /**\n * Class name of the header icon element\n */\n headerIcon = 'p-panelmenu-header-icon',\n /**\n * Class name of the header label element\n */\n headerLabel = 'p-panelmenu-header-label',\n /**\n * Class name of the content container element\n */\n contentContainer = 'p-panelmenu-content-container',\n /**\n * Class name of the content element\n */\n content = 'p-panelmenu-content',\n /**\n * Class name of the root list element\n */\n rootList = 'p-panelmenu-root-list',\n /**\n * Class name of the item element\n */\n item = 'p-panelmenu-item',\n /**\n * Class name of the item content element\n */\n itemContent = 'p-panelmenu-item-content',\n /**\n * Class name of the item link element\n */\n itemLink = 'p-panelmenu-item-link',\n /**\n * Class name of the item icon element\n */\n itemIcon = 'p-panelmenu-item-icon',\n /**\n * Class name of the item label element\n */\n itemLabel = 'p-panelmenu-item-label',\n /**\n * Class name of the submenu icon element\n */\n submenuIcon = 'p-panelmenu-submenu-icon',\n /**\n * Class name of the submenu element\n */\n submenu = 'p-panelmenu-submenu',\n separator = 'p-menuitem-separator'\n}\n\nexport interface PanelMenuStyle extends BaseStyle {}\n","import { animate, state, style, transition, trigger } from '@angular/animations';\nimport { CommonModule } from '@angular/common';\nimport {\n AfterContentInit,\n booleanAttribute,\n ChangeDetectionStrategy,\n Component,\n computed,\n ContentChild,\n ContentChildren,\n ElementRef,\n EventEmitter,\n forwardRef,\n inject,\n Input,\n NgModule,\n numberAttribute,\n OnChanges,\n Output,\n QueryList,\n signal,\n SimpleChanges,\n TemplateRef,\n ViewChild,\n ViewEncapsulation\n} from '@angular/core';\nimport { RouterModule } from '@angular/router';\nimport { equals, findLast, findSingle, focus, getAttribute, isEmpty, isNotEmpty, isPrintableCharacter, resolve, uuid } from '@primeuix/utils';\nimport { MenuItem, PrimeTemplate, SharedModule } from 'primeng/api';\nimport { BaseComponent } from 'primeng/basecomponent';\nimport { ChevronDownIcon, ChevronRightIcon } from 'primeng/icons';\nimport { TooltipModule } from 'primeng/tooltip';\nimport { PanelMenuStyle } from './style/panelmenustyle';\n\n@Component({\n selector: 'p-panelMenuSub, p-panelmenu-sub',\n imports: [CommonModule, RouterModule, TooltipModule, ChevronDownIcon, ChevronRightIcon, SharedModule],\n standalone: true,\n template: `\n <ul\n #list\n [class]=\"root ? cn(cx('rootList'), cx('submenu')) : cx('submenu')\"\n role=\"tree\"\n [tabindex]=\"-1\"\n [attr.aria-activedescendant]=\"focusedItemId\"\n [attr.data-pc-section]=\"'menu'\"\n [attr.aria-hidden]=\"!parentExpanded\"\n (focusin)=\"menuFocus.emit($event)\"\n (focusout)=\"menuBlur.emit($event)\"\n (keydown)=\"menuKeyDown.emit($event)\"\n >\n <ng-template ngFor let-processedItem let-index=\"index\" [ngForOf]=\"items\">\n <li *ngIf=\"processedItem.separator\" [class]=\"cn(cx('separator'), getItemProp(processedItem, 'styleClass'))\" role=\"separator\"></li>\n <li\n *ngIf=\"!processedItem.separator && isItemVisible(processedItem)\"\n role=\"treeitem\"\n [attr.id]=\"getItemId(processedItem)\"\n [attr.aria-label]=\"getItemProp(processedItem, 'label')\"\n [attr.aria-expanded]=\"isItemGroup(processedItem) ? isItemActive(processedItem) : undefined\"\n [attr.aria-level]=\"level + 1\"\n [attr.aria-setsize]=\"getAriaSetSize()\"\n [attr.aria-posinset]=\"getAriaPosInset(index)\"\n [class]=\"cn(cx('item', { processedItem }), getItemProp(processedItem, 'styleClass'))\"\n [ngStyle]=\"getItemProp(processedItem, 'style')\"\n [pTooltip]=\"getItemProp(processedItem, 'tooltip')\"\n [attr.data-p-disabled]=\"isItemDisabled(processedItem)\"\n [tooltipOptions]=\"getItemProp(processedItem, 'tooltipOptions')\"\n >\n <div [class]=\"cx('itemContent')\" (click)=\"onItemClick($event, processedItem)\">\n <ng-container *ngIf=\"!itemTemplate\">\n <a\n *ngIf=\"!getItemProp(processedItem, 'routerLink')\"\n [attr.href]=\"getItemProp(processedItem, 'url')\"\n [class]=\"cx('itemLink')\"\n [target]=\"getItemProp(processedItem, 'target')\"\n [attr.data-pc-section]=\"'action'\"\n [attr.tabindex]=\"!!parentExpanded ? '0' : '-1'\"\n >\n <ng-container *ngIf=\"isItemGroup(processedItem)\">\n <ng-container *ngIf=\"!panelMenu.submenuIconTemplate && !panelMenu._submenuIconTemplate\">\n <svg data-p-icon=\"chevron-down\" [class]=\"cn(cx('submenuIcon'), getItemProp(processedItem, 'icon'))\" *ngIf=\"isItemActive(processedItem)\" [ngStyle]=\"getItemProp(processedItem, 'iconStyle')\" />\n <svg data-p-icon=\"chevron-right\" [class]=\"cn(cx('submenuIcon'), getItemProp(processedItem, 'icon'))\" *ngIf=\"!isItemActive(processedItem)\" [ngStyle]=\"getItemProp(processedItem, 'iconStyle')\" />\n </ng-container>\n <ng-template *ngTemplateOutlet=\"panelMenu.submenuIconTemplate || panelMenu._submenuIconTemplate\"></ng-template>\n </ng-container>\n <span [class]=\"cx('itemIcon', { processedItem })\" *ngIf=\"processedItem.icon\" [ngStyle]=\"getItemProp(processedItem, 'iconStyle')\"></span>\n <span [class]=\"cx('itemLabel')\" *ngIf=\"processedItem.item?.escape !== false; else htmlLabel\">{{ getItemProp(processedItem, 'label') }}</span>\n <ng-template #htmlLabel><span [class]=\"cx('itemLabel')\" [innerHTML]=\"getItemProp(processedItem, 'label')\"></span></ng-template>\n </a>\n <a\n *ngIf=\"getItemProp(processedItem, 'routerLink')\"\n [routerLink]=\"getItemProp(processedItem, 'routerLink')\"\n [queryParams]=\"getItemProp(processedItem, 'queryParams')\"\n [routerLinkActive]=\"'p-panelmenu-item-link-active'\"\n [routerLinkActiveOptions]=\"getItemProp(processedItem, 'routerLinkActiveOptions') || { exact: false }\"\n [class]=\"cx('itemLink')\"\n [target]=\"getItemProp(processedItem, 'target')\"\n [attr.title]=\"getItemProp(processedItem, 'title')\"\n [fragment]=\"getItemProp(processedItem, 'fragment')\"\n [queryParamsHandling]=\"getItemProp(processedItem, 'queryParamsHandling')\"\n [preserveFragment]=\"getItemProp(processedItem, 'preserveFragment')\"\n [skipLocationChange]=\"getItemProp(processedItem, 'skipLocationChange')\"\n [replaceUrl]=\"getItemProp(processedItem, 'replaceUrl')\"\n [state]=\"getItemProp(processedItem, 'state')\"\n [attr.data-pc-section]=\"'action'\"\n [attr.tabindex]=\"!!parentExpanded ? '0' : '-1'\"\n >\n <ng-container *ngIf=\"isItemGroup(processedItem)\">\n <ng-container *ngIf=\"!panelMenu.submenuIconTemplate && !panelMenu._submenuIconTemplate\">\n <svg data-p-icon=\"chevron-down\" *ngIf=\"isItemActive(processedItem)\" [class]=\"cn(cx('submenuIcon'), getItemProp(processedItem, 'icon'))\" [ngStyle]=\"getItemProp(processedItem, 'iconStyle')\" />\n <svg data-p-icon=\"chevron-right\" *ngIf=\"!isItemActive(processedItem)\" [class]=\"cn(cx('submenuIcon'), getItemProp(processedItem, 'icon'))\" [ngStyle]=\"getItemProp(processedItem, 'iconStyle')\" />\n </ng-container>\n <ng-template *ngTemplateOutlet=\"panelMenu.submenuIconTemplate && panelMenu._submenuIconTemplate\"></ng-template>\n </ng-container>\n <span [class]=\"cn(cx('itemIcon'), getItemProp(processedItem, 'icon'))\" *ngIf=\"processedItem.icon\" [ngStyle]=\"getItemProp(processedItem, 'iconStyle')\"></span>\n <span *ngIf=\"getItemProp(processedItem, 'label')\" [class]=\"cx('itemLabel')\" [innerHTML]=\"getItemProp(processedItem, 'label')\"></span>\n\n <span [class]=\"cn(cx('badge'), getItemProp(processedItem, 'badgeStyleClass'))\" *ngIf=\"processedItem.badge\">{{ processedItem.badge }}</span>\n </a>\n </ng-container>\n <ng-container *ngIf=\"itemTemplate\">\n <ng-template *ngTemplateOutlet=\"itemTemplate; context: { $implicit: processedItem.item }\"></ng-template>\n </ng-container>\n </div>\n <div [@submenu]=\"getAnimation(processedItem)\">\n <p-panelmenu-sub\n *ngIf=\"isItemVisible(processedItem) && isItemGroup(processedItem) && isItemExpanded(processedItem)\"\n [id]=\"getItemId(processedItem) + '_list'\"\n [panelId]=\"panelId\"\n [items]=\"processedItem?.items\"\n [itemTemplate]=\"itemTemplate\"\n [transitionOptions]=\"transitionOptions\"\n [focusedItemId]=\"focusedItemId\"\n [activeItemPath]=\"activeItemPath\"\n [level]=\"level + 1\"\n [parentExpanded]=\"!!parentExpanded && isItemExpanded(processedItem)\"\n (itemToggle)=\"onItemToggle($event)\"\n ></p-panelmenu-sub>\n </div>\n </li>\n </ng-template>\n </ul>\n `,\n animations: [\n trigger('submenu', [\n state(\n 'hidden',\n style({\n height: '0'\n })\n ),\n state(\n 'visible',\n style({\n height: '*'\n })\n ),\n transition('visible <=> hidden', [animate('{{transitionParams}}')]),\n transition('void => *', animate(0))\n ])\n ],\n encapsulation: ViewEncapsulation.None,\n providers: [PanelMenuStyle]\n})\nexport class PanelMenuSub extends BaseComponent {\n @Input() panelId: string | undefined;\n\n @Input() focusedItemId: string | undefined;\n\n @Input() items: any[];\n\n @Input() itemTemplate: TemplateRef<any> | undefined;\n\n @Input({ transform: numberAttribute }) level: number = 0;\n\n @Input() activeItemPath: any[];\n\n @Input({ transform: booleanAttribute }) root: boolean | undefined;\n\n @Input({ transform: numberAttribute }) tabindex: number | undefined;\n\n @Input() transitionOptions: string | undefined;\n\n @Input({ transform: booleanAttribute }) parentExpanded: boolean | undefined;\n\n @Output() itemToggle: EventEmitter<any> = new EventEmitter<any>();\n\n @Output() menuFocus: EventEmitter<any> = new EventEmitter<any>();\n\n @Output() menuBlur: EventEmitter<any> = new EventEmitter<any>();\n\n @Output() menuKeyDown: EventEmitter<any> = new EventEmitter<any>();\n\n @ViewChild('list') listViewChild: ElementRef;\n\n panelMenu: PanelMenu = inject(forwardRef(() => PanelMenu));\n\n _componentStyle = inject(PanelMenuStyle);\n\n getItemId(processedItem) {\n return processedItem.item?.id ?? `${this.panelId}_${processedItem.key}`;\n }\n\n getItemKey(processedItem) {\n return this.getItemId(processedItem);\n }\n\n getItemClass(processedItem) {\n return {\n 'p-panelmenu-item': true,\n 'p-disabled': this.isItemDisabled(processedItem),\n 'p-focus': this.isItemFocused(processedItem)\n };\n }\n\n getItemProp(processedItem, name?, params?): any {\n return processedItem && processedItem.item ? resolve(processedItem.item[name], params) : undefined;\n }\n\n getItemLabel(processedItem) {\n return this.getItemProp(processedItem, 'label');\n }\n\n isItemExpanded(processedItem) {\n return processedItem.expanded;\n }\n\n isItemActive(processedItem) {\n return this.isItemExpanded(processedItem) || this.activeItemPath.some((path) => path && path.key === processedItem.key);\n }\n\n isItemVisible(processedItem) {\n return this.getItemProp(processedItem, 'visible') !== false;\n }\n\n isItemDisabled(processedItem) {\n return this.getItemProp(processedItem, 'disabled');\n }\n\n isItemFocused(processedItem) {\n return this.focusedItemId === this.getItemId(processedItem);\n }\n\n isItemGroup(processedItem) {\n return isNotEmpty(processedItem.items);\n }\n\n getAnimation(processedItem) {\n return this.isItemActive(processedItem) ? { value: 'visible', params: { transitionParams: this.transitionOptions, height: '*' } } : { value: 'hidden', params: { transitionParams: this.transitionOptions, height: '0' } };\n }\n\n getAriaSetSize() {\n return this.items.filter((processedItem) => this.isItemVisible(processedItem) && !this.getItemProp(processedItem, 'separator')).length;\n }\n\n getAriaPosInset(index) {\n return index - this.items.slice(0, index).filter((processedItem) => this.isItemVisible(processedItem) && this.getItemProp(processedItem, 'separator')).length + 1;\n }\n\n onItemClick(event, processedItem) {\n if (!this.isItemDisabled(processedItem)) {\n this.getItemProp(processedItem, 'command', { originalEvent: event, item: processedItem.item });\n this.itemToggle.emit({ processedItem, expanded: !this.isItemActive(processedItem) });\n }\n }\n\n onItemToggle(event) {\n this.itemToggle.emit(event);\n }\n}\n\n@Component({\n selector: 'p-panelMenuList, p-panel-menu-list',\n imports: [CommonModule, PanelMenuSub, RouterModule, TooltipModule, SharedModule],\n standalone: true,\n template: `\n <p-panelmenu-sub\n #submenu\n [root]=\"true\"\n [id]=\"panelId + '_list'\"\n [panelId]=\"panelId\"\n [tabindex]=\"tabindex\"\n [itemTemplate]=\"itemTemplate\"\n [focusedItemId]=\"focused ? focusedItemId : undefined\"\n [activeItemPath]=\"activeItemPath()\"\n [transitionOptions]=\"transitionOptions\"\n [items]=\"processedItems()\"\n [parentExpanded]=\"parentExpanded\"\n (itemToggle)=\"onItemToggle($event)\"\n (keydown)=\"onKeyDown($event)\"\n (menuFocus)=\"onFocus($event)\"\n (menuBlur)=\"onBlur($event)\"\n ></p-panelmenu-sub>\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None\n})\nexport class PanelMenuList extends BaseComponent implements OnChanges {\n @Input() panelId: string | undefined;\n\n @Input() id: string | undefined;\n\n @Input() items: any[];\n\n @Input() itemTemplate: TemplateRef<any> | undefined;\n\n @Input({ transform: booleanAttribute }) parentExpanded: boolean | undefined;\n\n @Input({ transform: booleanAttribute }) expanded: boolean | undefined;\n\n @Input() transitionOptions: string | undefined;\n\n @Input({ transform: booleanAttribute }) root: boolean | undefined;\n\n @Input({ transform: numberAttribute }) tabindex: number | undefined;\n\n @Input() activeItem: any;\n\n @Output() itemToggle: EventEmitter<any> = new EventEmitter<any>();\n\n @Output() headerFocus: EventEmitter<any> = new EventEmitter<any>();\n\n @ViewChild('submenu') subMenuViewChild: PanelMenuSub;\n\n searchTimeout: any;\n\n searchValue: any;\n\n focused: boolean | undefined;\n\n focusedItem = signal<any>(null);\n\n activeItemPath = signal<any[]>([]);\n\n processedItems = signal<any[]>([]);\n\n visibleItems = computed(() => {\n const processedItems = this.processedItems();\n return this.flatItems(processedItems);\n });\n\n get focusedItemId() {\n const focusedItem = this.focusedItem();\n return focusedItem && focusedItem.item?.id ? focusedItem.item.id : isNotEmpty(this.focusedItem()) ? `${this.panelId}_${this.focusedItem().key}` : undefined;\n }\n\n ngOnChanges(changes: SimpleChanges) {\n this.processedItems.set(this.createProcessedItems(changes?.items?.currentValue || this.items || []));\n }\n\n getItemProp(processedItem, name) {\n return processedItem && processedItem.item ? resolve(processedItem.item[name]) : undefined;\n }\n\n getItemLabel(processedItem) {\n return this.getItemProp(processedItem, 'label');\n }\n\n isItemVisible(processedItem) {\n return this.getItemProp(processedItem, 'visible') !== false;\n }\n\n isItemDisabled(processedItem) {\n return this.getItemProp(processedItem, 'disabled');\n }\n\n isItemActive(processedItem) {\n return this.activeItemPath().some((path) => path.key === processedItem.parentKey);\n }\n\n isItemGroup(processedItem) {\n return isNotEmpty(processedItem.items);\n }\n\n isElementInPanel(event, element) {\n const panel = event.currentTarget.closest('[data-pc-section=\"panel\"]');\n\n return panel && panel.contains(element);\n }\n\n isItemMatched(processedItem) {\n return this.isValidItem(processedItem) && this.getItemLabel(processedItem).toLocaleLowerCase().startsWith(this.searchValue.toLocaleLowerCase());\n }\n\n isVisibleItem(processedItem) {\n return !!processedItem && (processedItem.level === 0 || this.isItemActive(processedItem)) && this.isItemVisible(processedItem);\n }\n\n isValidItem(processedItem) {\n return !!processedItem && !this.isItemDisabled(processedItem) && !processedItem.separator;\n }\n\n findFirstItem() {\n return this.visibleItems().find((processedItem) => this.isValidItem(processedItem));\n }\n\n findLastItem() {\n return findLast(this.visibleItems(), (processedItem) => this.isValidItem(processedItem));\n }\n\n findItemByEventTarget(target: EventTarget): undefined | any {\n let parentNode = target as ParentNode & Element;\n\n while (parentNode && parentNode.tagName?.toLowerCase() !== 'li') {\n parentNode = parentNode?.parentNode as Element;\n }\n\n return parentNode?.id && this.visibleItems().find((processedItem) => this.isValidItem(processedItem) && `${this.panelId}_${processedItem.key}` === parentNode.id);\n }\n\n createProcessedItems(items, level = 0, parent = {}, parentKey = '') {\n const processedItems = [];\n items &&\n items.forEach((item, index) => {\n const key = (parentKey !== '' ? parentKey + '_' : '') + index;\n const newItem = {\n icon: item.icon,\n expanded: item.expanded,\n separator: item.separator,\n item,\n index,\n level,\n key,\n parent,\n parentKey\n };\n\n newItem['items'] = this.createProcessedItems(item.items, level + 1, newItem, key);\n processedItems.push(newItem);\n });\n return processedItems;\n }\n\n findProcessedItemByItemKey(key, processedItems?, level = 0) {\n processedItems = processedItems || this.processedItems();\n if (processedItems && processedItems.length) {\n for (let i = 0; i < processedItems.length; i++) {\n const processedItem = processedItems[i];\n\n if (this.getItemProp(processedItem, 'key') === key) return processedItem;\n const matchedItem = this.findProcessedItemByItemKey(key, processedItem.items, level + 1);\n if (matchedItem) return matchedItem;\n }\n }\n }\n\n flatItems(processedItems, processedFlattenItems = []) {\n processedItems &&\n processedItems.forEach((processedItem) => {\n if (this.isVisibleItem(processedItem)) {\n processedFlattenItems.push(processedItem);\n this.flatItems(processedItem.items, processedFlattenItems);\n }\n });\n\n return processedFlattenItems;\n }\n\n changeFocusedItem(event) {\n const { originalEvent, processedItem, focusOnNext, selfCheck, allowHeaderFocus = true } = event;\n\n if (isNotEmpty(this.focusedItem()) && this.focusedItem().key !== processedItem.key) {\n this.focusedItem.set(processedItem);\n this.scrollInView();\n } else if (allowHeaderFocus) {\n this.headerFocus.emit({ originalEvent, focusOnNext, selfCheck });\n }\n }\n\n scrollInView() {\n const element = findSingle(this.subMenuViewChild.listViewChild.nativeElement, `li[id=\"${`${this.focusedItemId}`}\"]`);\n\n if (element) {\n element.scrollIntoView && element.scrollIntoView({ block: 'nearest', inline: 'nearest' });\n }\n }\n\n onFocus(event) {\n if (!this.focused) {\n this.focused = true;\n const focusedItem = this.focusedItem() || (this.isElementInPanel(event, event.relatedTarget) ? this.findItemByEventTarget(event.target) || this.findFirstItem() : this.findLastItem());\n if (event.relatedTarget !== null) this.focusedItem.set(focusedItem);\n }\n }\n\n onBlur(event) {\n const target = event.relatedTarget;\n\n if (this.focused && !this.el.nativeElement.contains(target)) {\n this.focused = false;\n this.focusedItem.set(null);\n this.searchValue = '';\n }\n }\n\n onItemToggle(event) {\n const { processedItem, expanded } = event;\n\n // Update the original item object's 'expanded' property\n if (processedItem.item) {\n processedItem.item.expanded = !processedItem.item.expanded;\n }\n\n // Recreate processedItems with updated 'expanded' states\n this.processedItems.set(this.createProcessedItems(this.items || [], 0, {}, ''));\n\n // Update activeItemPath\n const activeItemPath = this.activeItemPath().filter((p) => p.parentKey !== processedItem.parentKey);\n if (expanded) {\n activeItemPath.push(processedItem);\n }\n this.activeItemPath.set(activeItemPath);\n\n // Update focusedItem\n this.focusedItem.set(processedItem);\n }\n\n onKeyDown(event) {\n const metaKey = event.metaKey || event.ctrlKey;\n\n switch (event.code) {\n case 'ArrowDown':\n this.onArrowDownKey(event);\n break;\n\n case 'ArrowUp':\n this.onArrowUpKey(event);\n break;\n\n case 'ArrowLeft':\n this.onArrowLeftKey(event);\n break;\n\n case 'ArrowRight':\n this.onArrowRightKey(event);\n break;\n\n case 'Home':\n this.onHomeKey(event);\n break;\n\n case 'End':\n this.onEndKey(event);\n break;\n\n case 'Space':\n this.onSpaceKey(event);\n break;\n\n case 'Enter':\n this.onEnterKey(event);\n break;\n\n case 'Escape':\n case 'Tab':\n case 'PageDown':\n case 'PageUp':\n case 'Backspace':\n case 'ShiftLeft':\n case 'ShiftRight':\n //NOOP\n break;\n\n default:\n if (!metaKey && isPrintableCharacter(event.key)) {\n this.searchItems(event, event.key);\n }\n\n break;\n }\n }\n\n onArrowDownKey(event) {\n const processedItem = isNotEmpty(this.focusedItem()) ? this.findNextItem(this.focusedItem()) : this.findFirstItem();\n this.changeFocusedItem({ originalEvent: event, processedItem, focusOnNext: true });\n event.preventDefault();\n }\n onArrowUpKey(event) {\n const processedItem = isNotEmpty(this.focusedItem()) ? this.findPrevItem(this.focusedItem()) : this.findLastItem();\n\n this.changeFocusedItem({ originalEvent: event, processedItem, selfCheck: true });\n event.preventDefault();\n }\n\n onArrowLeftKey(event) {\n if (isNotEmpty(this.focusedItem())) {\n const matched = this.activeItemPath().some((p) => p.key === this.focusedItem().key);\n\n if (matched) {\n const activeItemPath = this.activeItemPath().filter((p) => p.key !== this.focusedItem().key);\n this.activeItemPath.set(activeItemPath);\n } else {\n const focusedItem = isNotEmpty(this.focusedItem().parent) ? this.focusedItem().parent : this.focusedItem();\n this.focusedItem.set(focusedItem);\n }\n\n event.preventDefault();\n }\n }\n\n onArrowRightKey(event) {\n if (isNotEmpty(this.focusedItem())) {\n const grouped = this.isItemGroup(this.focusedItem());\n\n if (grouped) {\n const matched = this.activeItemPath().some((p) => p.key === this.focusedItem().key);\n\n if (matched) {\n this.onArrowDownKey(event);\n } else {\n const activeItemPath = this.activeItemPath().filter((p) => p.parentKey !== this.focusedItem().parentKey);\n activeItemPath.push(this.focusedItem());\n\n this.activeItemPath.set(activeItemPath);\n }\n }\n\n event.preventDefault();\n }\n }\n\n onHomeKey(event) {\n this.changeFocusedItem({ originalEvent: event, processedItem: this.findFirstItem(), allowHeaderFocus: false });\n event.preventDefault();\n }\n\n onEndKey(event) {\n this.changeFocusedItem({ originalEvent: event, processedItem: this.findLastItem(), focusOnNext: true, allowHeaderFocus: false });\n event.preventDefault();\n }\n\n onEnterKey(event) {\n if (isNotEmpty(this.focusedItem())) {\n const element = <any>findSingle(this.subMenuViewChild.listViewChild.nativeElement, `li[id=\"${`${this.focusedItemId}`}\"]`);\n const anchorElement = element && (<any>findSingle(element, '[data-pc-section=\"action\"]') || findSingle(element, 'a,button'));\n\n anchorElement ? anchorElement.click() : element && element.click();\n }\n\n event.preventDefault();\n }\n\n onSpaceKey(event) {\n this.onEnterKey(event);\n }\n\n findNextItem(processedItem) {\n const index = this.visibleItems().findIndex((item) => item.key === processedItem.key);\n\n const matchedItem =\n index < this.visibleItems().length - 1\n ? this.visibleItems()\n .slice(index + 1)\n .find((pItem) => this.isValidItem(pItem))\n : undefined;\n return matchedItem || processedItem;\n }\n\n findPrevItem(processedItem) {\n const index = this.visibleItems().findIndex((item) => item.key === processedItem.key);\n const matchedItem = index > 0 ? findLast(this.visibleItems().slice(0, index), (pItem) => this.isValidItem(pItem)) : undefined;\n\n return matchedItem || processedItem;\n }\n\n searchItems(event, char) {\n this.searchValue = (this.searchValue || '') + char;\n\n let matchedItem = null;\n let matched = false;\n\n if (isNotEmpty(this.focusedItem())) {\n const focusedItemIndex = this.visibleItems().findIndex((processedItem) => processedItem.key === this.focusedItem().key);\n\n matchedItem = this.visibleItems()\n .slice(focusedItemIndex)\n .find((processedItem) => this.isItemMatched(processedItem));\n matchedItem = isEmpty(matchedItem)\n ? this.visibleItems()\n .slice(0, focusedItemIndex)\n .find((processedItem) => this.isItemMatched(processedItem))\n : matchedItem;\n } else {\n matchedItem = this.visibleItems().find((processedItem) => this.isItemMatched(processedItem));\n }\n\n if (isNotEmpty(matchedItem)) {\n matched = true;\n }\n\n if (isEmpty(matchedItem) && isEmpty(this.focusedItem())) {\n matchedItem = this.findFirstItem();\n }\n\n if (isNotEmpty(matchedItem)) {\n this.changeFocusedItem({\n originalEvent: event,\n processedItem: matchedItem,\n allowHeaderFocus: false\n });\n }\n\n if (this.searchTimeout) {\n clearTimeout(this.searchTimeout);\n }\n\n this.searchTimeout = setTimeout(() => {\n this.searchValue = '';\n this.searchTimeout = null;\n }, 500);\n\n return matched;\n }\n}\n\n/**\n * PanelMenu is a hybrid of Accordion and Tree components.\n * @group Components\n */\n@Component({\n selector: 'p-panelMenu, p-panelmenu, p-panel-menu',\n imports: [CommonModule, PanelMenuList, RouterModule, TooltipModule, ChevronDownIcon, ChevronRightIcon, SharedModule],\n standalone: true,\n template: `\n <ng-container *ngFor=\"let item of model; let f = first; let l = last; let i = index\">\n <div *ngIf=\"isItemVisible(item)\" [class]=\"cn(cx('panel'), getItemProp(item, 'headerClass'))\" [ngStyle]=\"getItemProp(item, 'style')\" [attr.data-pc-section]=\"'panel'\">\n <div\n [class]=\"cn(cx('header', { item }), getItemProp(item, 'styleClass'))\"\n [ngStyle]=\"getItemProp(item, 'style')\"\n [pTooltip]=\"getItemProp(item, 'tooltip')\"\n [attr.id]=\"getHeaderId(item, i)\"\n [tabindex]=\"0\"\n role=\"button\"\n [tooltipOptions]=\"getItemProp(item, 'tooltipOptions')\"\n [attr.aria-expanded]=\"isItemActive(item)\"\n [attr.aria-label]=\"getItemProp(item, 'label')\"\n [attr.aria-controls]=\"getContentId(item, i)\"\n [attr.aria-disabled]=\"isItemDisabled(item)\"\n [attr.data-p-highlight]=\"isItemActive(item)\"\n [attr.data-p-disabled]=\"isItemDisabled(item)\"\n [attr.data-pc-section]=\"'header'\"\n (click)=\"onHeaderClick($event, item, i)\"\n (keydown)=\"onHeaderKeyDown($event, item, i)\"\n >\n <div [class]=\"cx('headerContent')\">\n <ng-container *ngIf=\"!itemTemplate && !_itemTemplate\">\n <a\n *ngIf=\"!getItemProp(item, 'routerLink')\"\n [attr.href]=\"getItemProp(item, 'url')\"\n [attr.tabindex]=\"-1\"\n [target]=\"getItemProp(item, 'target')\"\n [attr.title]=\"getItemProp(item, 'title')\"\n [class]=\"cx('headerLink')\"\n [attr.data-pc-section]=\"'headeraction'\"\n >\n <ng-container *ngIf=\"isItemGroup(item)\">\n <ng-container *ngIf=\"!headerIconTemplate && !_headerIconTemplate\">\n <svg data-p-icon=\"chevron-down\" [class]=\"cx('headerIcon')\" *ngIf=\"isItemActive(item)\" />\n <svg data-p-icon=\"chevron-right\" [class]=\"cx('headerIcon')\" *ngIf=\"!isItemActive(item)\" />\n </ng-container>\n <ng-template *ngTemplateOutlet=\"headerIconTemplate || _headerIconTemplate\"></ng-template>\n </ng-container>\n <span [class]=\"cn(cx('headerIcon'), item.icon)\" *ngIf=\"item.icon\" [ngStyle]=\"getItemProp(item, 'iconStyle')\"></span>\n <span [class]=\"cx('headerLabel')\" *ngIf=\"getItemProp(item, 'escape') !== false; else htmlLabel\">{{ getItemProp(item, 'label') }}</span>\n <ng-template #htmlLabel><span [class]=\"cx('headerLabel')\" [innerHTML]=\"getItemProp(item, 'label')\"></span></ng-template>\n <span [class]=\"cn(cx('badge'), getItemProp(item, 'badgeStyleClass'))\" *ngIf=\"getItemProp(item, 'badge')\">{{ getItemProp(item, 'badge') }}</span>\n </a>\n </ng-container>\n <ng-container *ngTemplateOutlet=\"itemTemplate; context: { $implicit: item }\"></ng-container>\n <a\n *ngIf=\"getItemProp(item, 'routerLink')\"\n [routerLink]=\"getItemProp(item, 'routerLink')\"\n [queryParams]=\"getItemProp(item, 'queryParams')\"\n [routerLinkActive]=\"'p-panelmenu-item-link-active'\"\n [routerLinkActiveOptions]=\"getItemProp(item, 'routerLinkActiveOptions') || { exact: false }\"\n [target]=\"getItemProp(item, 'target')\"\n [class]=\"cx('headerLink')\"\n [attr.tabindex]=\"-1\"\n [fragment]=\"getItemProp(item, 'fragment')\"\n [queryParamsHandling]=\"getItemProp(item, 'queryParamsHandling')\"\n [preserveFragment]=\"getItemProp(item, 'preserveFragment')\"\n [skipLocationChange]=\"getItemProp(item, 'skipLocationChange')\"\n [replaceUrl]=\"getItemProp(item, 'replaceUrl')\"\n [state]=\"getItemProp(item, 'state')\"\n [attr.data-pc-section]=\"'headeraction'\"\n >\n <ng-container *ngIf=\"isItemGroup(item)\">\n <ng-container *ngIf=\"!headerIconTemplate && !_headerIconTemplate\">\n <svg data-p-icon=\"chevron-down\" [class]=\"cx('headerIcon')\" *ngIf=\"isItemActive(item)\" />\n <svg data-p-icon=\"chevron-right\" [class]=\"cx('headerIcon')\" *ngIf=\"!isItemActive(item)\" />\n </ng-container>\n <ng-template *ngTemplateOutlet=\"headerIconTemplate || _headerIconTemplate\"></ng-template>\n </ng-container>\n <span [class]=\"cn(cx('headerIcon'), item.icon)\" *ngIf=\"item.icon\" [ngStyle]=\"getItemProp(item, 'iconStyle')\"></span>\n <span [class]=\"cx('headerLabel')\" *ngIf=\"getItemProp(item, 'escape') !== false; else htmlRouteLabel\">{{ getItemProp(item, 'label') }}</span>\n <ng-template #htmlRouteLabel><span [class]=\"cx('headerLabel')\" [innerHTML]=\"getItemProp(item, 'label')\"></span></ng-template>\n <span *ngIf=\"getItemProp(item, 'badge')\" [class]=\"cn(cx('badge'), getItemProp(item, 'badgeStyleClass'))\">{{ getItemProp(item, 'badge') }}</span>\n </a>\n </div>\n </div>\n <div\n *ngIf=\"isItemGroup(item)\"\n [class]=\"cx('contentContainer', { processedItem: item })\"\n [@rootItem]=\"getAnimation(item)\"\n (@rootItem.done)=\"onToggleDone()\"\n role=\"region\"\n [attr.id]=\"getContentId(item, i)\"\n [attr.aria-labelledby]=\"getHeaderId(item, i)\"\n [attr.data-pc-section]=\"'toggleablecontent'\"\n >\n <div [class]=\"cx('content')\" [attr.data-pc-section]=\"'menucontent'\">\n <p-panelMenuList\n [panelId]=\"getPanelId(i, item)\"\n [items]=\"getItemProp(item, 'items')\"\n [itemTemplate]=\"itemTemplate || _itemTemplate\"\n [transitionOptions]=\"transitionOptions\"\n [root]=\"true\"\n [activeItem]=\"activeItem()\"\n [tabindex]=\"tabindex\"\n [parentExpanded]=\"isItemActive(item)\"\n (headerFocus)=\"updateFocusedHeader($event)\"\n ></p-panelMenuList>\n </div>\n </div>\n </div>\n </ng-container>\n `,\n animations: [\n trigger('rootItem', [\n state(\n 'hidden',\n style({\n height: '0',\n visibility: 'hidden'\n })\n ),\n state(\n 'visible',\n style({\n height: '*',\n visibility: '*'\n })\n ),\n transition('visible <=> hidden', [animate('{{transitionParams}}')]),\n transition('void => *', animate(0))\n ])\n ],\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n providers: [PanelMenuStyle],\n host: {\n '[class]': 'cn(cx(\"root\"), styleClass)',\n 'data-pc-section': 'root',\n 'data-pc-name': 'panelmenu'\n }\n})\nexport class PanelMenu extends BaseComponent implements AfterContentInit {\n /**\n * An array of menuitems.\n * @group Props\n */\n @Input() model: MenuItem[] | undefined;\n /**\n * Style class of the component.\n * @deprecated since v20.0.0, use `class` instead.\n * @group Props\n */\n @Input() styleClass: string | undefined;\n /**\n * Whether multiple tabs can be activated at the same time or not.\n * @group Props\n */\n @Input({ transform: booleanAttribute }) multiple: boolean = false;\n /**\n * Transition options of the animation.\n * @group Props\n */\n @Input() transitionOptions: string = '400ms cubic-bezier(0.86, 0, 0.07, 1)';\n /**\n * Current id state as a string.\n * @group Props\n */\n @Input() id: string | undefined;\n /**\n * Index of the element in tabbing order.\n * @group Props\n */\n @Input({ transform: numberAttribute }) tabindex: number | undefined = 0;\n\n @ViewChild('container') containerViewChild: ElementRef | undefined;\n /**\n * Template option of submenu icon.\n * @group Templates\n */\n @ContentChild('submenuicon', { descendants: false }) submenuIconTemplate: TemplateRef<any> | undefined;\n /**\n * Template option of header icon.\n * @group Templates\n */\n @ContentChild('headericon', { descendants: false }) headerIconTemplate: TemplateRef<any> | undefined;\n /**\n * Template option of item.\n * @group Templates\n */\n @ContentChild('item', { descendants: false }) itemTemplate: TemplateRef<any> | undefined;\n\n @ContentChildren(PrimeTemplate) templates: QueryList<PrimeTemplate> | undefined;\n\n _submenuIconTemplate: TemplateRef<any> | undefined;\n\n _headerIconTemplate: TemplateRef<any> | undefined;\n\n _itemTemplate: TemplateRef<any> | undefined;\n\n public animating: boolean | undefined;\n\n activeItem = signal<any>(null);\n\n _componentStyle = inject(PanelMenuStyle);\n\n ngOnInit() {\n super.ngOnInit();\n this.id = this.id || uuid('pn_id_');\n }\n\n ngAfterContentInit() {\n this.templates?.forEach((item) => {\n switch (item.getType()) {\n case 'submenuicon':\n this._submenuIconTemplate = item.template;\n break;\n\n case 'headericon':\n this._headerIconTemplate = item.template;\n break;\n\n case 'item':\n this._itemTemplate = item.template;\n break;\n\n default:\n this._itemTemplate = item.template;\n break;\n }\n });\n }\n\n /**\n * Collapses open panels.\n * @group Method\n */\n collapseAll() {\n for (let item of this.model!) {\n if (item.expanded) {\n item.expanded = false;\n }\n }\n\n this.cd.detectChanges();\n }\n\n onToggleDone() {\n this.animating = false;\n this.cd.markForCheck();\n }\n\n changeActiveItem(event, item, index?: number, selfActive = false) {\n if (!this.isItemDisabled(item)) {\n const activeItem = selfActive ? item : this.activeItem && equals(item, this.activeItem) ? null : item;\n this.activeItem.set(activeItem);\n }\n }\n\n getAnimation(item: MenuItem) {\n return item.expanded ? { value: 'visible', params: { transitionParams: this.animating ? this.transitionOptions : '0ms', height: '*' } } : { value: 'hidden', params: { transitionParams: this.transitionOptions, height: '0' } };\n }\n\n getItemProp(item, name): any {\n return item ? resolve(item[name]) : undefined;\n }\n\n getItemLabel(item) {\n return this.getItemProp(item, 'label');\n }\n\n isItemActive(item) {\n return item.expanded;\n }\n\n isItemVisible(item) {\n return this.getItemProp(item, 'visible') !== false;\n }\n\n isItemDisabled(item) {\n return this.getItemProp(item, 'disabled');\n }\n\n isItemGroup(item) {\n return isNotEmpty(item.items);\n }\n\n getPanelId(index, item?) {\n return item && item.id ? item.id : `${this.id}_${index}`;\n }\n\n getHeaderId(item, index) {\n return item.id ? item.id + '_header' : `${this.getPanelId(index)}_header`;\n }\n\n getContentId(item, index) {\n return item.id ? item.id + '_content' : `${this.getPanelId(index)}_content`;\n }\n\n updateFocusedHeader(event) {\n const { originalEvent, focusOnNext, selfCheck } = event;\n const panelElement = originalEvent.currentTarget.closest('[data-pc-section=\"panel\"]');\n const header = selfCheck ? findSingle(panelElement, '[data-pc-section=\"header\"]') : focusOnNext ? this.findNextHeader(panelElement) : this.findPrevHeader(panelElement);\n\n header ? this.changeFocusedHeader(originalEvent, header) : focusOnNext ? this.onHeaderHomeKey(originalEvent) : this.onHeaderEndKey(originalEvent);\n }\n\n changeFocusedHeader(event, element) {\n element && focus(element);\n }\n\n findNextHeader(panelElement, selfCheck = false) {\n const nextPanelElement = selfCheck ? panelElement : panelElement.nextElementSibling;\n const headerElement = findSingle(nextPanelElement, '[data-pc-section=\"header\"]');\n\n return headerElement ? (getAttribute(headerElement, 'data-p-disabled') ? this.findNextHeader(headerElement.parentElement) : headerElement) : null;\n }\n\n findPrevHeader(panelElement, selfCheck = false) {\n const prevPanelElement = selfCheck ? panelElement : panelElement.previousElementSibling;\n const headerElement = findSingle(prevPanelElement, '[data-pc-section=\"header\"]');\n\n return headerElement ? (getAttribute(headerElement, 'data-p-disabled') ? this.findPrevHeader(headerElement.parentElement) : headerElement) : null;\n }\n\n findFirstHeader() {\n return this.findNextHeader(this.containerViewChild.nativeElement.firstElementChild, true);\n }\n\n findLastHeader() {\n return this.findPrevHeader(this.containerViewChild.nativeElement.lastElementChild, true);\n }\n\n onHeaderClick(event, item, index) {\n if (this.isItemDisabled(item)) {\n event.preventDefault();\n\n return;\n }\n\n if (item.command) {\n item.command({ originalEvent: event, item });\n }\n\n if (!this.multiple) {\n for (let modelItem of this.model!) {\n if (item !== modelItem && modelItem.expanded) {\n modelItem.expanded = false;\n }\n }\n }\n\n item.expanded = !item.expanded;\n this.changeActiveItem(event, item, index);\n this.animating = true;\n focus(event.currentTarget as HTMLElement);\n }\n\n onHeaderKeyDown(event, item, index) {\n switch (event.code) {\n case 'ArrowDown':\n this.onHeaderArrowDownKey(event);\n break;\n\n case 'ArrowUp':\n this.onHeaderArrowUpKey(event);\n break;\n\n case 'Home':\n this.onHeaderHomeKey(event);\n break;\n\n case 'End':\n this.onHeaderEndKey(event);\n break;\n\n case 'Enter':\n case 'Space':\n this.onHeaderEnterKey(event, item, index);\n break;\n\n default:\n break;\n }\n }\n\n onHeaderArrowDownKey(ev