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 90.7 kB
{"version":3,"file":"primeng-megamenu.mjs","sources":["../../src/megamenu/style/megamenustyle.ts","../../src/megamenu/megamenu.ts","../../src/megamenu/primeng-megamenu.ts"],"sourcesContent":["import { Injectable } from '@angular/core';\nimport { style } from '@primeuix/styles/megamenu';\nimport { BaseStyle } from 'primeng/base';\n\nconst inlineStyles = {\n rootList: ({ instance }) => ({ 'max-height': instance.scrollHeight, overflow: 'auto' })\n};\n\nconst classes = {\n root: ({ instance }) => [\n 'p-megamenu p-component',\n {\n 'p-megamenu-mobile': instance.queryMatches,\n 'p-megamenu-mobile-active': instance.mobileActive,\n 'p-megamenu-horizontal': instance.orientation === 'horizontal',\n 'p-megamenu-vertical': instance.orientation === 'vertical'\n }\n ],\n start: 'p-megamenu-start',\n button: 'p-megamenu-button',\n rootList: 'p-megamenu-root-list',\n submenuLabel: ({ instance, processedItem }) => [\n 'p-megamenu-submenu-label',\n {\n 'p-disabled': instance.isItemDisabled(processedItem)\n }\n ],\n item: ({ instance, processedItem }) => [\n 'p-megamenu-item',\n instance.getItemProp(processedItem, 'styleClass'),\n instance.getItemProp(processedItem, 'class'),\n {\n 'p-megamenu-item-active': instance.isItemActive(processedItem),\n 'p-focus': instance.isItemFocused(processedItem),\n 'p-disabled': instance.isItemDisabled(processedItem)\n }\n ],\n itemContent: 'p-megamenu-item-content',\n itemLink: 'p-megamenu-item-link',\n itemIcon: 'p-megamenu-item-icon',\n itemLabel: 'p-megamenu-item-label',\n submenuIcon: 'p-megamenu-submenu-icon',\n overlay: 'p-megamenu-overlay',\n grid: 'p-megamenu-grid',\n column: ({ instance, processedItem }) => {\n let length = instance.isItemGroup(processedItem) ? processedItem.items.length : 0;\n let columnClass;\n\n if (instance.megaMenu.queryMatches) columnClass = 'p-megamenu-col-12';\n else {\n switch (length) {\n case 2:\n columnClass = 'p-megamenu-col-6';\n break;\n\n case 3:\n columnClass = 'p-megamenu-col-4';\n break;\n\n case 4:\n columnClass = 'p-megamenu-col-3';\n break;\n\n case 6:\n columnClass = 'p-megamenu-col-2';\n break;\n\n default:\n columnClass = 'p-megamenu-col-12';\n break;\n }\n }\n\n return columnClass;\n },\n submenu: 'p-megamenu-submenu',\n separator: 'p-megamenu-separator',\n end: 'p-megamenu-end'\n};\n\n@Injectable()\nexport class MegaMenuStyle extends BaseStyle {\n name = 'megamenu';\n\n theme = style;\n\n classes = classes;\n\n inlineStyles = inlineStyles;\n}\n\n/**\n *\n * MegaMenu is navigation component that displays submenus together.\n *\n * [Live Demo](https://www.primeng.org/megamenu/)\n *\n * @module megamenustyle\n *\n */\n\nexport enum MegaMenuClasses {\n /**\n * Class name of the root element\n */\n root = 'p-megamenu',\n /**\n * Class name of the start element\n */\n start = 'p-megamenu-start',\n /**\n * Class name of the button element\n */\n button = 'p-megamenu-button',\n /**\n * Class name of the root list element\n */\n rootList = 'p-megamenu-root-list',\n /**\n * Class name of the submenu item element\n */\n submenuItem = 'p-megamenu-submenu-item',\n /**\n * Class name of the item element\n */\n item = 'p-megamenu-item',\n /**\n * Class name of the item content element\n */\n itemContent = 'p-megamenu-item-content',\n /**\n * Class name of the item link element\n */\n itemLink = 'p-megamenu-item-link',\n /**\n * Class name of the item icon element\n */\n itemIcon = 'p-megamenu-item-icon',\n /**\n * Class name of the item label element\n */\n itemLabel = 'p-megamenu-item-label',\n /**\n * Class name of the submenu icon element\n */\n submenuIcon = 'p-megamenu-submenu-icon',\n /**\n * Class name of the panel element\n */\n panel = 'p-megamenu-panel',\n /**\n * Class name of the grid element\n */\n grid = 'p-megamenu-grid',\n /**\n * Class name of the submenu element\n */\n submenu = 'p-megamenu-submenu',\n /**\n * Class name of the submenu item label element\n */\n submenuItemLabel = 'p-megamenu-submenu-item-label',\n /**\n * Class name of the separator element\n */\n separator = 'p-megamenu-separator',\n /**\n * Class name of the end element\n */\n end = 'p-megamenu-end'\n}\n\nexport interface MegaMenuStyle extends BaseStyle {}\n","import { CommonModule, isPlatformBrowser } from '@angular/common';\nimport {\n AfterContentInit,\n booleanAttribute,\n ChangeDetectionStrategy,\n Component,\n ContentChild,\n ContentChildren,\n effect,\n ElementRef,\n EventEmitter,\n forwardRef,\n inject,\n Input,\n NgModule,\n numberAttribute,\n OnDestroy,\n OnInit,\n Output,\n QueryList,\n signal,\n TemplateRef,\n ViewChild,\n ViewEncapsulation\n} from '@angular/core';\nimport { RouterModule } from '@angular/router';\nimport { findLastIndex, findSingle, focus, isEmpty, isNotEmpty, isPrintableCharacter, isTouchDevice, resolve, uuid } from '@primeuix/utils';\nimport { MegaMenuItem, PrimeTemplate, SharedModule } from 'primeng/api';\nimport { BadgeModule } from 'primeng/badge';\nimport { BaseComponent } from 'primeng/basecomponent';\nimport { AngleDownIcon, AngleRightIcon, BarsIcon } from 'primeng/icons';\nimport { Ripple } from 'primeng/ripple';\nimport { TooltipModule } from 'primeng/tooltip';\nimport { VoidListener } from 'primeng/ts-helpers';\nimport { ZIndexUtils } from 'primeng/utils';\nimport { MegaMenuStyle } from './style/megamenustyle';\n\n@Component({\n selector: 'p-megaMenuSub, p-megamenu-sub',\n standalone: true,\n imports: [CommonModule, RouterModule, Ripple, TooltipModule, AngleDownIcon, AngleRightIcon, BadgeModule, SharedModule],\n template: `\n <ul\n *ngIf=\"isSubmenuVisible(submenu)\"\n #menubar\n [class]=\"root ? cx('rootList') : cx('submenu')\"\n [style]=\"sx('rootList')\"\n [attr.role]=\"root ? 'menubar' : 'menu'\"\n [attr.id]=\"id\"\n [attr.aria-orientation]=\"orientation\"\n [tabindex]=\"tabindex\"\n [attr.aria-activedescendant]=\"focusedItemId\"\n [attr.data-pc-section]=\"root ? 'root' : 'submenu'\"\n (keydown)=\"menuKeydown.emit($event)\"\n (focus)=\"menuFocus.emit($event)\"\n (blur)=\"menuBlur.emit($event)\"\n >\n <li *ngIf=\"submenu\" [class]=\"cn(cx('submenuLabel'), getItemProp(submenu, 'class'))\" [style]=\"getItemProp(submenu, 'style')\" role=\"presentation\">\n {{ getItemLabel(submenu) }}\n </li>\n <ng-template ngFor let-processedItem [ngForOf]=\"items\" let-index=\"index\">\n <li\n *ngIf=\"isItemVisible(processedItem) && getItemProp(processedItem, 'separator')\"\n [attr.id]=\"getItemId(processedItem)\"\n [style]=\"getItemProp(processedItem, 'style')\"\n [class]=\"cn(cx('separator'), this.getItemProp(processedItem, 'class'))\"\n role=\"separator\"\n [attr.data-pc-section]=\"'separator'\"\n ></li>\n <li\n #listItem\n *ngIf=\"isItemVisible(processedItem) && !getItemProp(processedItem, 'separator')\"\n role=\"menuitem\"\n [attr.id]=\"getItemId(processedItem)\"\n [attr.data-pc-section]=\"'menuitem'\"\n [attr.data-p-highlight]=\"isItemActive(processedItem)\"\n [attr.data-p-focused]=\"isItemFocused(processedItem)\"\n [attr.data-p-disabled]=\"isItemDisabled(processedItem)\"\n [attr.aria-label]=\"getItemLabel(processedItem)\"\n [attr.aria-disabled]=\"isItemDisabled(processedItem) || undefined\"\n [attr.aria-haspopup]=\"isItemGroup(processedItem) && !getItemProp(processedItem, 'to') ? 'menu' : undefined\"\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 [ngStyle]=\"getItemProp(processedItem, 'style')\"\n [class]=\"cn(cx('item', { processedItem }), getItemProp(processedItem, 'styleClass'))\"\n pTooltip\n [tooltipOptions]=\"getItemProp(processedItem, 'tooltipOptions')\"\n >\n <div [class]=\"cx('itemContent')\" [attr.data-pc-section]=\"'content'\" (click)=\"onItemClick($event, processedItem)\" (mouseenter)=\"onItemMouseEnter({ $event, processedItem })\">\n <ng-container *ngIf=\"!itemTemplate\">\n <a\n *ngIf=\"!getItemProp(processedItem, 'routerLink')\"\n [attr.href]=\"getItemProp(processedItem, 'url')\"\n [attr.data-automationid]=\"getItemProp(processedItem, 'automationId')\"\n [attr.data-pc-section]=\"'action'\"\n [target]=\"getItemProp(processedItem, 'target')\"\n [class]=\"cx('itemLink')\"\n [attr.tabindex]=\"-1\"\n pRipple\n >\n <span\n *ngIf=\"getItemProp(processedItem, 'icon')\"\n [class]=\"cn(cx('itemIcon'), getItemProp(processedItem, 'icon'))\"\n [ngStyle]=\"getItemProp(processedItem, 'iconStyle')\"\n [attr.data-pc-section]=\"'icon'\"\n [attr.tabindex]=\"-1\"\n >\n </span>\n <span *ngIf=\"getItemProp(processedItem, 'escape'); else htmlLabel\" [class]=\"cx('itemLabel')\" [attr.data-pc-section]=\"'label'\">\n {{ getItemLabel(processedItem) }}\n </span>\n <ng-template #htmlLabel>\n <span [class]=\"cx('itemLabel')\" [innerHTML]=\"getItemLabel(processedItem)\" [attr.data-pc-section]=\"'label'\"></span>\n </ng-template>\n <p-badge *ngIf=\"getItemProp(processedItem, 'badge')\" [class]=\"getItemProp(processedItem, 'badgeStyleClass')\" [value]=\"getItemProp(processedItem, 'badge')\" />\n <ng-container *ngIf=\"isItemGroup(processedItem)\">\n <ng-container *ngIf=\"!megaMenu.submenuIconTemplate && !megaMenu._submenuIconTemplate\">\n @if (orientation === 'horizontal' || mobileActive) {\n <svg data-p-icon=\"angle-down\" [class]=\"cx('submenuIcon')\" [attr.data-pc-section]=\"'submenuicon'\" [attr.aria-hidden]=\"true\" />\n } @else {\n <svg data-p-icon=\"angle-right\" [class]=\"cx('submenuIcon')\" [attr.data-pc-section]=\"'submenuicon'\" *ngIf=\"orientation === 'vertical'\" [attr.aria-hidden]=\"true\" />\n }\n </ng-container>\n <ng-template *ngTemplateOutlet=\"megaMenu.submenuIconTemplate || megaMenu._submenuIconTemplate\" [attr.data-pc-section]=\"'submenuicon'\" [attr.aria-hidden]=\"true\"></ng-template>\n </ng-container>\n </a>\n <a\n *ngIf=\"getItemProp(processedItem, 'routerLink')\"\n [routerLink]=\"getItemProp(processedItem, 'routerLink')\"\n [attr.data-automationid]=\"getItemProp(processedItem, 'automationId')\"\n [attr.tabindex]=\"-1\"\n [attr.data-pc-section]=\"'action'\"\n [queryParams]=\"getItemProp(processedItem, 'queryParams')\"\n [routerLinkActive]=\"'p-megamenu-item-link-active'\"\n [routerLinkActiveOptions]=\"getItemProp(processedItem, 'routerLinkActiveOptions') || { exact: false }\"\n [target]=\"getItemProp(processedItem, 'target')\"\n [class]=\"cx('itemLink')\"\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 pRipple\n >\n <span\n [class]=\"cn(cx('itemIcon'), getItemProp(processedItem, 'icon'))\"\n *ngIf=\"getItemProp(processedItem, 'icon')\"\n [ngStyle]=\"getItemProp(processedItem, 'iconStyle')\"\n [attr.data-pc-section]=\"'icon'\"\n [attr.tabindex]=\"-1\"\n ></span>\n <span [class]=\"cx('itemLabel')\" *ngIf=\"getItemProp(processedItem, 'escape'); else htmlRouteLabel\">{{ getItemLabel(processedItem) }}</span>\n <ng-template #htmlRouteLabel><span [class]=\"cx('itemLabel')\" [innerHTML]=\"getItemLabel(processedItem)\" [attr.data-pc-section]=\"'label'\"></span></ng-template>\n <p-badge *ngIf=\"getItemProp(processedItem, 'badge')\" [styleClass]=\"getItemProp(processedItem, 'badgeStyleClass')\" [value]=\"getItemProp(processedItem, 'badge')\" />\n <ng-container *ngIf=\"isItemGroup(processedItem)\">\n <ng-container *ngIf=\"!megaMenu.submenuIconTemplate && !megaMenu._submenuIconTemplate\">\n <svg data-p-icon=\"angle-down\" [class]=\"cx('submenuIcon')\" [attr.data-pc-section]=\"'submenuicon'\" *ngIf=\"orientation === 'horizontal'\" [attr.aria-hidden]=\"true\" />\n <svg data-p-icon=\"angle-right\" [class]=\"cx('submenuIcon')\" [attr.data-pc-section]=\"'submenuicon'\" *ngIf=\"orientation === 'vertical'\" [attr.aria-hidden]=\"true\" />\n </ng-container>\n <ng-template *ngTemplateOutlet=\"megaMenu.submenuIconTemplate || megaMenu._submenuIconTemplate\" [attr.data-pc-section]=\"'submenuicon'\" [attr.aria-hidden]=\"true\"></ng-template>\n </ng-container>\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 *ngIf=\"isItemVisible(processedItem) && isItemGroup(processedItem)\" [class]=\"cx('overlay')\" [attr.data-pc-section]=\"'panel'\">\n <div [class]=\"cx('grid')\" [attr.data-pc-section]=\"'grid'\">\n <div *ngFor=\"let col of processedItem.items\" [class]=\"cx('column', { processedItem })\">\n <p-megamenu-sub\n *ngFor=\"let submenu of col\"\n [id]=\"getSubListId(submenu)\"\n [submenu]=\"submenu\"\n [items]=\"submenu.items\"\n [itemTemplate]=\"itemTemplate\"\n [mobileActive]=\"mobileActive\"\n [menuId]=\"menuId\"\n [focusedItemId]=\"focusedItemId\"\n [level]=\"level + 1\"\n [root]=\"false\"\n (itemClick)=\"itemClick.emit($event)\"\n (itemMouseEnter)=\"onItemMouseEnter($event)\"\n >\n </p-megamenu-sub>\n </div>\n </div>\n </div>\n </li>\n </ng-template>\n </ul>\n `,\n encapsulation: ViewEncapsulation.None\n})\nexport class MegaMenuSub extends BaseComponent {\n @Input() id: string | undefined;\n\n @Input() items: any[] | undefined;\n\n @Input() itemTemplate: TemplateRef<any> | undefined;\n\n @Input() menuId: string | undefined;\n\n @Input() ariaLabel: string | undefined;\n\n @Input() ariaLabelledBy: string | undefined;\n\n @Input({ transform: numberAttribute }) level: number = 0;\n\n @Input() focusedItemId: string | undefined;\n\n @Input({ transform: booleanAttribute }) disabled: boolean = false;\n\n @Input() orientation: string | undefined;\n\n @Input() activeItem: any;\n\n @Input() submenu: any;\n\n @Input({ transform: booleanAttribute }) queryMatches: boolean = false;\n\n @Input({ transform: booleanAttribute }) mobileActive: boolean = false;\n\n @Input() scrollHeight: string;\n\n @Input({ transform: numberAttribute }) tabindex: number = 0;\n\n @Input({ transform: booleanAttribute }) root: boolean = false;\n\n @Output() itemClick: EventEmitter<any> = new EventEmitter();\n\n @Output() itemMouseEnter: EventEmitter<any> = new EventEmitter();\n\n @Output() menuFocus: EventEmitter<any> = new EventEmitter();\n\n @Output() menuBlur: EventEmitter<any> = new EventEmitter();\n\n @Output() menuKeydown: EventEmitter<any> = new EventEmitter();\n\n @ViewChild('menubar', { static: true }) menubarViewChild: ElementRef;\n\n megaMenu: MegaMenu = inject(forwardRef(() => MegaMenu));\n\n _componentStyle = inject(MegaMenuStyle);\n\n onItemClick(event: any, processedItem: any) {\n this.getItemProp(processedItem, 'command', { originalEvent: event, item: processedItem.item });\n this.itemClick.emit({ originalEvent: event, processedItem, isFocus: true });\n }\n\n getItemProp(processedItem: any, name: string, params: any | null = null): any {\n return processedItem && processedItem.item ? resolve(processedItem.item[name], params) : undefined;\n }\n\n getItemId(processedItem: any): string {\n return processedItem.item && processedItem.item?.id ? processedItem.item.id : `${this.menuId}_${processedItem.key}`;\n }\n\n getSubListId(processedItem) {\n return `${this.getItemId(processedItem)}_list`;\n }\n\n getItemLabel(processedItem: any): string {\n return this.getItemProp(processedItem, 'label');\n }\n\n isSubmenuVisible(submenu: any) {\n if (this.submenu && !this.root) {\n return this.isItemVisible(submenu);\n } else {\n return true;\n }\n }\n\n isItemVisible(processedItem: any): boolean {\n return this.getItemProp(processedItem, 'visible') !== false;\n }\n\n isItemActive(processedItem) {\n return isNotEmpty(this.activeItem) ? this.activeItem.key === processedItem.key : false;\n }\n\n isItemDisabled(processedItem: any): boolean {\n return this.getItemProp(processedItem, 'disabled');\n }\n\n isItemFocused(processedItem: any): boolean {\n return this.focusedItemId === this.getItemId(processedItem);\n }\n\n isItemGroup(processedItem: any): boolean {\n return isNotEmpty(processedItem.items);\n }\n\n getAriaSetSize() {\n return this.items.filter((processedItem) => this.isItemVisible(processedItem) && !this.getItemProp(processedItem, 'separator')).length;\n }\n\n getAriaPosInset(index: number) {\n return index - this.items.slice(0, index).filter((processedItem) => this.isItemVisible(processedItem) && this.getItemProp(processedItem, 'separator')).length + 1;\n }\n\n onItemMouseEnter(param: any) {\n const { event, processedItem } = param;\n this.itemMouseEnter.emit({ originalEvent: event, processedItem });\n }\n}\n/**\n * MegaMenu is navigation component that displays submenus together.\n * @group Components\n */\n@Component({\n selector: 'p-megaMenu, p-megamenu, p-mega-menu',\n standalone: true,\n imports: [CommonModule, RouterModule, MegaMenuSub, TooltipModule, BarsIcon, BadgeModule, SharedModule],\n template: `\n <div [class]=\"cx('start')\" *ngIf=\"startTemplate || _startTemplate\">\n <ng-container *ngTemplateOutlet=\"startTemplate || _startTemplate\"></ng-container>\n </div>\n <ng-container *ngIf=\"!buttonTemplate && !_buttonTemplate\">\n <a\n *ngIf=\"model && model.length > 0\"\n #menubutton\n role=\"button\"\n tabindex=\"0\"\n [class]=\"cx('button')\"\n [attr.aria-haspopup]=\"model.length && model.length > 0 ? true : false\"\n [attr.aria-expanded]=\"mobileActive\"\n [attr.aria-controls]=\"id\"\n [attr.aria-label]=\"config.translation.aria.navigation\"\n (click)=\"menuButtonClick($event)\"\n (keydown)=\"menuButtonKeydown($event)\"\n >\n <svg data-p-icon=\"bars\" *ngIf=\"!buttonIconTemplate && !_buttonIconTemplate\" />\n <ng-template *ngTemplateOutlet=\"buttonIconTemplate || _buttonIconTemplate\"></ng-template>\n </a>\n </ng-container>\n <ng-container *ngTemplateOutlet=\"buttonTemplate || _buttonTemplate\"></ng-container>\n <p-megamenu-sub\n #rootmenu\n [itemTemplate]=\"itemTemplate || _itemTemplate\"\n [items]=\"processedItems\"\n [attr.id]=\"id + '_list'\"\n [menuId]=\"id\"\n [root]=\"true\"\n [orientation]=\"orientation\"\n [ariaLabel]=\"ariaLabel\"\n [disabled]=\"disabled\"\n [tabindex]=\"!disabled ? tabindex : -1\"\n [activeItem]=\"activeItem()\"\n [level]=\"0\"\n [ariaLabelledBy]=\"ariaLabelledBy\"\n [focusedItemId]=\"focused ? focusedItemId : undefined\"\n [mobileActive]=\"mobileActive\"\n (itemClick)=\"onItemClick($event)\"\n (menuFocus)=\"onMenuFocus($event)\"\n (menuBlur)=\"onMenuBlur($event)\"\n (menuKeydown)=\"onKeyDown($event)\"\n (itemMouseEnter)=\"onItemMouseEnter($event)\"\n [queryMatches]=\"queryMatches\"\n [scrollHeight]=\"scrollHeight\"\n ></p-megamenu-sub>\n <div [class]=\"cx('end')\" *ngIf=\"endTemplate || _endTemplate\">\n <ng-container *ngTemplateOutlet=\"endTemplate || _endTemplate\"></ng-container>\n </div>\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n providers: [MegaMenuStyle],\n host: {\n '[class]': 'cn(cx(\"root\"), styleClass)',\n '[id]': 'id',\n 'data-pc-section': 'root',\n 'data-pc-name': 'megamenu'\n }\n})\nexport class MegaMenu extends BaseComponent implements AfterContentInit, OnDestroy, OnInit {\n /**\n * An array of menuitems.\n * @group Props\n */\n @Input() set model(value: MegaMenuItem[] | undefined) {\n this._model = value;\n this._processedItems = this.createProcessedItems(this._model || []);\n }\n get model(): MegaMenuItem[] | undefined {\n return this._model;\n }\n /**\n * Class of the element.\n * @deprecated since v20.0.0, use `class` instead.\n * @group Props\n */\n @Input() styleClass: string | undefined;\n /**\n * Defines the orientation.\n * @group Props\n */\n @Input() orientation: 'horizontal' | 'vertical' | string = 'horizontal';\n /**\n * Current id state as a string.\n * @group Props\n */\n @Input() id: string | undefined;\n /**\n * Defines a string value that labels an interactive element.\n * @group Props\n */\n @Input() ariaLabel: string | undefined;\n /**\n * Identifier of the underlying input element.\n * @group Props\n */\n @Input() ariaLabelledBy: string | undefined;\n /**\n * The breakpoint to define the maximum width boundary.\n * @group Props\n */\n @Input() breakpoint: string = '960px';\n /**\n * Height of the viewport, a scrollbar is defined if height of list exceeds this value.\n * @group Props\n */\n @Input() scrollHeight: string = '20rem';\n /**\n * When present, it specifies that the component should be disabled.\n * @group Props\n */\n @Input({ transform: booleanAttribute }) disabled: boolean = false;\n /**\n * Index of the element in tabbing order.\n * @group Props\n */\n @Input({ transform: numberAttribute }) tabindex: number = 0;\n /**\n * Defines template option for start.\n * @group Templates\n */\n @ContentChild('start', { descendants: false }) startTemplate: TemplateRef<any> | undefined;\n /**\n * Defines template option for end.\n * @group Templates\n */\n @ContentChild('end', { descendants: false }) endTemplate: TemplateRef<any> | undefined;\n /**\n * Defines template option for menu icon.\n * @group Templates\n */\n @ContentChild('menuicon', { descendants: false }) menuIconTemplate: TemplateRef<any> | undefined;\n /**\n * Defines template option for submenu icon.\n * @group Templates\n */\n @ContentChild('submenuicon', { descendants: false }) submenuIconTemplate: TemplateRef<any> | undefined;\n /**\n * Defines template option for submenu icon.\n * @group Templates\n */\n @ContentChild('item', { descendants: false }) itemTemplate: TemplateRef<any> | undefined;\n /**\n * Custom menu button template on responsive mode.\n * @group Templates\n */\n @ContentChild('button', { descendants: false }) buttonTemplate: TemplateRef<any> | undefined;\n /**\n * Custom menu button icon template on responsive mode.\n * @group Templates\n */\n @ContentChild('buttonicon', { descendants: false }) buttonIconTemplate: TemplateRef<any> | undefined;\n\n @ContentChildren(PrimeTemplate) templates: QueryList<PrimeTemplate> | undefined;\n\n @ViewChild('menubutton') menubuttonViewChild: ElementRef | undefined;\n\n @ViewChild('rootmenu') rootmenu: MegaMenuSub | undefined;\n\n _startTemplate: TemplateRef<any> | undefined;\n\n _endTemplate: TemplateRef<any> | undefined;\n\n _menuIconTemplate: TemplateRef<any> | undefined;\n\n _submenuIconTemplate: TemplateRef<any> | undefined;\n\n _itemTemplate: TemplateRef<any> | undefined;\n\n _buttonTemplate: TemplateRef<any> | undefined;\n\n _buttonIconTemplate: TemplateRef<any> | undefined;\n\n outsideClickListener: VoidListener;\n\n resizeListener: (event: any) => void;\n\n dirty: boolean = false;\n\n focused: boolean = false;\n\n activeItem = signal<any>(null);\n\n focusedItemInfo = signal<any>({ index: -1, level: 0, parentKey: '', item: null });\n\n searchValue: string = '';\n\n searchTimeout: any;\n\n _processedItems: any[];\n\n _model: MegaMenuItem[] | undefined;\n\n _componentStyle = inject(MegaMenuStyle);\n\n private matchMediaListener: () => void;\n\n private query: MediaQueryList;\n\n public queryMatches: boolean = false;\n\n public mobileActive: boolean = false;\n\n get visibleItems() {\n const processedItem = isNotEmpty(this.activeItem()) ? this.activeItem() : null;\n\n return processedItem\n ? processedItem.items.reduce((items, col) => {\n col.forEach((submenu) => {\n submenu.items.forEach((a) => {\n items.push(a);\n });\n });\n\n return items;\n }, [])\n : this.processedItems;\n }\n\n get processedItems() {\n if (!this._processedItems || !this._processedItems.length) {\n this._processedItems = this.createProcessedItems(this.model || []);\n }\n return this._processedItems;\n }\n\n get focusedItemId() {\n const focusedItem = this.focusedItemInfo();\n return focusedItem?.item && focusedItem.item?.id ? focusedItem.item.id : isNotEmpty(focusedItem.key) ? `${this.id}_${focusedItem.key}` : null;\n }\n\n constructor() {\n super();\n effect(() => {\n const activeItem = this.activeItem();\n if (isNotEmpty(activeItem)) {\n this.bindOutsideClickListener();\n this.bindResizeListener();\n } else {\n this.unbindOutsideClickListener();\n this.unbindResizeListener();\n }\n });\n }\n\n ngOnInit(): void {\n super.ngOnInit();\n this.bindMatchMediaListener();\n this.id = this.id || uuid('pn_id_');\n }\n\n ngAfterContentInit() {\n this.templates?.forEach((item) => {\n switch (item.getType()) {\n case 'start':\n this._startTemplate = item.template;\n break;\n\n case 'end':\n this._endTemplate = item.template;\n break;\n\n case 'menuicon':\n this._menuIconTemplate = item.template;\n break;\n\n case 'submenuicon':\n this._submenuIconTemplate = item.template;\n break;\n\n case 'item':\n this._itemTemplate = item.template;\n break;\n\n case 'button':\n this._buttonTemplate = item.template;\n break;\n\n case 'buttonicon':\n this._buttonIconTemplate = item.template;\n break;\n\n default:\n this._itemTemplate = item.template;\n break;\n }\n });\n }\n\n bindMatchMediaListener() {\n if (isPlatformBrowser(this.platformId)) {\n if (!this.matchMediaListener) {\n const query = window.matchMedia(`(max-width: ${this.breakpoint})`);\n\n this.query = query;\n this.queryMatches = query.matches;\n\n this.matchMediaListener = () => {\n this.queryMatches = query.matches;\n this.mobileActive = false;\n this.cd.markForCheck();\n };\n\n query.addEventListener('change', this.matchMediaListener);\n }\n }\n }\n\n unbindMatchMediaListener() {\n if (this.matchMediaListener) {\n this.query.removeEventListener('change', this.matchMediaListener);\n this.matchMediaListener = null;\n }\n }\n\n createProcessedItems(items, level = 0, parent = {}, parentKey = '', columnIndex?) {\n const processedItems = [];\n\n items &&\n items.forEach((item, index) => {\n const key = (parentKey !== '' ? parentKey + '_' : '') + (columnIndex !== undefined ? columnIndex + '_' : '') + index;\n const newItem = {\n item,\n index,\n level,\n key,\n parent,\n parentKey,\n columnIndex: columnIndex !== undefined ? columnIndex : (<any>parent).columnIndex !== undefined ? (<any>parent).columnIndex : index\n };\n\n newItem['items'] =\n level === 0 && item.items && item.items.length > 0 ? item.items.map((_items, _index) => this.createProcessedItems(_items, level + 1, newItem, key, _index)) : this.createProcessedItems(item.items, level + 1, newItem, key);\n processedItems.push(newItem);\n });\n return processedItems;\n }\n\n getItemProp(item: any, name: string) {\n return item ? resolve(item[name]) : undefined;\n }\n\n onItemClick(event: any) {\n const { originalEvent, processedItem } = event;\n const grouped = this.isProcessedItemGroup(processedItem);\n const root = isEmpty(processedItem.parent);\n const selected = this.isSelected(processedItem);\n\n if (selected) {\n const { index, key, parentKey, item } = processedItem;\n\n this.activeItem.set(null);\n this.focusedItemInfo.set({ index, key, parentKey, item });\n\n this.dirty = !root;\n if (!this.mobileActive) {\n focus(this.rootmenu?.menubarViewChild?.nativeElement, { preventScroll: true });\n }\n } else {\n if (grouped) {\n this.onItemChange(event);\n } else {\n this.hide(originalEvent);\n }\n }\n }\n\n onItemMouseEnter(event) {\n if (!this.mobileActive && this.dirty) {\n this.onItemChange(event);\n }\n }\n\n menuButtonClick(event) {\n this.toggle(event);\n }\n\n menuButtonKeydown(event) {\n (event.code === 'Enter' || event.code === 'NumpadEnter' || event.code === 'Space') && this.menuButtonClick(event);\n }\n\n toggle(event: MouseEvent) {\n if (this.mobileActive) {\n this.mobileActive = false;\n ZIndexUtils.clear(this.rootmenu.el.nativeElement);\n this.hide();\n } else {\n this.mobileActive = true;\n ZIndexUtils.set('menu', this.rootmenu.el.nativeElement, this.config.zIndex.menu);\n setTimeout(() => {\n this.show();\n }, 0);\n }\n\n this.bindOutsideClickListener();\n event.preventDefault();\n }\n\n show() {\n this.focusedItemInfo.set({ index: this.findFirstFocusedItemIndex(), level: 0, parentKey: '' });\n\n focus(this.rootmenu?.el.nativeElement);\n }\n\n scrollInView(index: number = -1) {\n const id = index !== -1 ? `${this.id}_${index}` : this.focusedItemId;\n\n let element;\n\n if (id === null && this.queryMatches) {\n element = this.menubuttonViewChild.nativeElement;\n } else {\n element = findSingle(this.rootmenu?.menubarViewChild?.nativeElement, `li[id=\"${id}\"]`);\n }\n\n if (element) {\n element.scrollIntoView && element.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'smooth' });\n }\n }\n\n onItemChange(event: any) {\n const { processedItem, isFocus } = event;\n\n if (isEmpty(processedItem)) return;\n\n const { index, key, parentKey, items, item } = processedItem;\n const grouped = isNotEmpty(items);\n\n if (grouped) {\n this.activeItem.set(processedItem);\n }\n this.focusedItemInfo.set({ index, key, parentKey, item });\n\n grouped && (this.dirty = true);\n isFocus && focus(this.rootmenu?.menubarViewChild?.nativeElement);\n }\n\n hide(event?, isFocus?: boolean) {\n if (this.mobileActive) {\n this.mobileActive = false;\n setTimeout(() => {\n focus(this.menubuttonViewChild?.nativeElement);\n this.scrollInView();\n }, 100);\n }\n\n this.activeItem.set(null);\n this.focusedItemInfo.set({ index: -1, key: '', parentKey: '', item: null });\n\n isFocus && focus(this.rootmenu?.menubarViewChild?.nativeElement);\n this.dirty = false;\n }\n\n onMenuFocus(event: any) {\n this.focused = true;\n if (this.focusedItemInfo().index === -1) {\n const index = this.findFirstFocusedItemIndex();\n const processedItem = this.findVisibleItem(index);\n\n this.focusedItemInfo.set({ index, key: processedItem.key, parentKey: processedItem.parentKey, item: processedItem.item });\n }\n }\n\n onMenuBlur(event: any) {\n this.focused = false;\n this.focusedItemInfo.set({ index: -1, level: 0, parentKey: '', item: null });\n this.searchValue = '';\n this.dirty = false;\n }\n\n onKeyDown(event: KeyboardEvent) {\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 this.onEscapeKey(event);\n break;\n\n case 'Tab':\n this.onTabKey(event);\n break;\n\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 findFirstFocusedItemIndex() {\n const selectedIndex = this.findSelectedItemIndex();\n\n return selectedIndex < 0 ? this.findFirstItemIndex() : selectedIndex;\n }\n\n findFirstItemIndex() {\n return this.visibleItems.findIndex((processedItem) => this.isValidItem(processedItem));\n }\n\n findSelectedItemIndex() {\n return this.visibleItems.findIndex((processedItem) => this.isValidSelectedItem(processedItem));\n }\n\n isProcessedItemGroup(processedItem: any): boolean {\n return processedItem && isNotEmpty(processedItem.items);\n }\n\n isSelected(processedItem: any): boolean {\n return isNotEmpty(this.activeItem()) ? this.activeItem().key === processedItem.key : false;\n }\n\n isValidSelectedItem(processedItem: any): boolean {\n return this.isValidItem(processedItem) && this.isSelected(processedItem);\n }\n\n isValidItem(processedItem: any): boolean {\n return !!processedItem && !this.isItemDisabled(processedItem.item) && !this.isItemSeparator(processedItem.item);\n }\n\n isItemDisabled(item: any): boolean {\n return this.getItemProp(item, 'disabled');\n }\n\n isItemSeparator(item: any): boolean {\n return this.getItemProp(item, 'separator');\n }\n\n isItemMatched(processedItem: any): boolean {\n return this.isValidItem(processedItem) && this.getProccessedItemLabel(processedItem).toLocaleLowerCase().startsWith(this.searchValue.toLocaleLowerCase());\n }\n\n isProccessedItemGroup(processedItem: any): boolean {\n return processedItem && isNotEmpty(processedItem.items);\n }\n\n searchItems(event: any, char: string) {\n this.searchValue = (this.searchValue || '') + char;\n\n let itemIndex = -1;\n let matched = false;\n\n if (this.focusedItemInfo().index !== -1) {\n itemIndex = this.visibleItems.slice(this.focusedItemInfo().index).findIndex((processedItem) => this.isItemMatched(processedItem));\n itemIndex = itemIndex === -1 ? this.visibleItems.slice(0, this.focusedItemInfo().index).findIndex((processedItem) => this.isItemMatched(processedItem)) : itemIndex + this.focusedItemInfo().index;\n } else {\n itemIndex = this.visibleItems.findIndex((processedItem) => this.isItemMatched(processedItem));\n }\n\n if (itemIndex !== -1) {\n matched = true;\n }\n\n if (itemIndex === -1 && this.focusedItemInfo().index === -1) {\n itemIndex = this.findFirstFocusedItemIndex();\n }\n\n if (itemIndex !== -1) {\n this.changeFocusedItemInfo(event, itemIndex);\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 getProccessedItemLabel(processedItem: any) {\n return processedItem ? this.getItemLabel(processedItem.item) : undefined;\n }\n\n getItemLabel(item: any) {\n return this.getItemProp(item, 'label');\n }\n\n changeFocusedItemInfo(event, index) {\n const processedItem = this.findVisibleItem(index);\n if (isNotEmpty(processedItem)) {\n const { key, parentKey, item } = processedItem;\n this.focusedItemInfo.set({ index, key: key ? key : '', parentKey, item });\n }\n\n this.scrollInView();\n }\n\n onArrowDownKey(event: KeyboardEvent) {\n if (this.orientation === 'horizontal') {\n if (isNotEmpty(this.activeItem()) && this.activeItem().key === this.focusedItemInfo().key) {\n const { key, item } = this.activeItem();\n this.focusedItemInfo.set({ index: -1, key: '', parentKey: key, item });\n } else {\n const processedItem = this.findVisibleItem(this.focusedItemInfo().index);\n const grouped = this.isProccessedItemGroup(processedItem);\n\n if (grouped) {\n const { parentKey, key, item } = processedItem;\n this.onItemChange({ originalEvent: event, processedItem });\n this.focusedItemInfo.set({ index: -1, key: key, parentKey: parentKey, item: item });\n this.searchValue = '';\n }\n }\n }\n\n const itemIndex = this.focusedItemInfo().index !== -1 ? this.findNextItemIndex(this.focusedItemInfo().index) : this.findFirstFocusedItemIndex();\n this.changeFocusedItemInfo(event, itemIndex);\n event.preventDefault();\n }\n\n onArrowRightKey(event: KeyboardEvent) {\n const processedItem = this.findVisibleItem(this.focusedItemInfo().index);\n const grouped = this.isProccessedItemGroup(processedItem);\n\n if (grouped) {\n if (this.orientation === 'vertical') {\n if (isNotEmpty(this.activeItem()) && this.activeItem().key === processedItem.key) {\n this.focusedItemInfo.set({ index: -1, key: '', parentKey: this.activeItem().key, item: processedItem.item });\n } else {\n const processedItem = this.findVisibleItem(this.focusedItemInfo().index);\n const grouped = this.isProccessedItemGroup(processedItem);\n\n if (grouped) {\n this.onItemChange({ originalEvent: event, processedItem });\n this.focusedItemInfo.set({\n index: -1,\n key: processedItem.key,\n parentKey: processedItem.parentKey,\n item: processedItem.item\n });\n this.searchValue = '';\n }\n }\n }\n\n const itemIndex = this.focusedItemInfo().index !== -1 ? this.findNextItemIndex(this.focusedItemInfo().index) : this.findFirstFocusedItemIndex();\n\n this.changeFocusedItemInfo(event, itemIndex);\n } else {\n const columnIndex = processedItem.columnIndex + 1;\n const itemIndex = this.visibleItems.findIndex((item) => item.columnIndex === columnIndex);\n\n itemIndex !== -1 && this.changeFocusedItemInfo(event, itemIndex);\n }\n\n event.preventDefault();\n }\n\n onArrowUpKey(event: KeyboardEvent) {\n if (event.altKey && this.orientation === 'horizontal') {\n if (this.focusedItemInfo().index !== -1) {\n const processedItem = this.findVisibleItem(this.focusedItemInfo().index);\n const grouped = this.isProccessedItemGroup(processedItem);\n\n if (!grouped && isNotEmpty(this.activeItem)) {\n if (this.focusedItemInfo().index === 0) {\n this.focusedItemInfo.set({\n index: this.activeItem().index,\n key: this.activeItem().key,\n parentKey: this.activeItem().parentKey,\n item: processedItem.item\n });\n this.activeItem.set(null);\n } else {\n this.changeFocusedItemInfo(event, this.findFirstItemIndex());\n }\n }\n }\n\n event.preventDefault();\n } else {\n const itemIndex = this.focusedItemInfo().index !== -1 ? this.findPrevItemIndex(this.focusedItemInfo().index) : this.findLastFocusedItemIndex();\n\n this.changeFocusedItemInfo(event, itemIndex);\n event.preventDefault();\n }\n }\n\n onArrowLeftKey(event: KeyboardEvent) {\n const processedItem = this.findVisibleItem(this.focusedItemInfo().index);\n const grouped = this.isProccessedItemGroup(processedItem);\n\n if (grouped) {\n if (this.orientation === 'horizontal') {\n const itemIndex = this.focusedItemInfo().index !== -1 ? this.findPrevItemIndex(this.focusedItemInfo().index) : this.findLastFocusedItemIndex();\n\n this.changeFocusedItemInfo(event, itemIndex);\n }\n } else {\n if (this.orientation === 'vertical' && isNotEmpty(this.activeItem())) {\n if (processedItem.columnIndex === 0) {\n this.focusedItemInfo.set({\n index: this.activeItem().index,\n key: this.activeItem().key,\n parentKey: this.activeItem().parentKey,\n item: processedItem.item\n });\n this.activeItem.set(null);\n }\n }\n\n const columnIndex = processedItem.columnIndex - 1;\n const itemIndex = this.visibleItems.findIndex((item) => item.columnIndex === columnIndex);\n\n itemIndex !== -1 && this.changeFocusedItemInfo(event, itemIndex);\n }\n\n event.preventDefault();\n }\n\n onHomeKey(event: KeyboardEvent) {\n this.changeFocusedItemInfo(event, this.findFirstItemIndex());\n event.preventDefault();\n }\n\n onEndKey(event: KeyboardEvent) {\n this.changeFocusedItemInfo(event, this.findLastItemIndex());\n event.preventDefault();\n }\n\n onSpaceKey(event: KeyboardEvent) {\n this.onEnterKey(event);\n }\n\n onEscapeKey(event: KeyboardEvent) {\n if (isNotEmpty(this.activeItem())) {\n this.focusedItemInfo.set({ index: this.activeItem().index, key: this.activeItem().key, item: this.activeItem().item });\n this.activeItem.set(null);\n }\n\n event.preventDefault();\n }\n\n onTabKey(event: KeyboardEvent) {\n if (this.focusedItemInfo().index !== -1) {\n const processedItem = this.findVisibleItem(this.focusedItemInfo().index);\n const grouped = this.isProccessedItemGroup(processedItem);\n\n !grouped && this.onItemChange({ originalEvent: event, processedItem });\n }\n\n this.hide();\n }\n\n onEnterKey(event: KeyboardEvent) {\n if (this.focusedItemInfo().index !== -1) {\n const element = <any>findSingle(this.rootmenu?.el?.na