primeng
Version:
PrimeNG is a premium UI library for Angular featuring a rich set of 90+ components, a theme designer, various theme alternatives such as Material, Bootstrap, Tailwind, premium templates and professional support. In addition, it integrates with PrimeBlock,
1 lines • 110 kB
Source Map (JSON)
{"version":3,"file":"primeng-megamenu.mjs","sources":["../../src/megamenu/style/megamenustyle.ts","../../src/megamenu/megamenu-token.ts","../../src/megamenu/megamenu-sub.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 style = 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 { InjectionToken } from '@angular/core';\n\nexport const MEGAMENU_INSTANCE = new InjectionToken<any>('MEGAMENU_INSTANCE');\nexport const MEGAMENU_SUB_INSTANCE = new InjectionToken<any>('MEGAMENU_SUB_INSTANCE');\n","import { NgTemplateOutlet } from '@angular/common';\nimport { booleanAttribute, Component, computed, inject, input, numberAttribute, output, TemplateRef, ViewEncapsulation } from '@angular/core';\nimport { RouterModule } from '@angular/router';\nimport { MotionOptions } from '@primeuix/motion';\nimport { isNotEmpty, resolve } from '@primeuix/utils';\nimport { MegaMenuItem, SharedModule } from 'primeng/api';\nimport { BadgeModule } from 'primeng/badge';\nimport { BaseComponent, PARENT_INSTANCE } from 'primeng/basecomponent';\nimport { Bind } from 'primeng/bind';\nimport { AngleDown as AngleDownIcon } from '@primeicons/angular/angle-down';\nimport { AngleRight as AngleRightIcon } from '@primeicons/angular/angle-right';\nimport { MotionModule } from 'primeng/motion';\nimport { Ripple } from 'primeng/ripple';\nimport { TooltipModule } from 'primeng/tooltip';\nimport { MegaMenuItemClickEvent, MegaMenuItemMouseEnterEvent, MegaMenuItemTemplateContext, MegaMenuOrientation, MegaMenuPassThrough, ProcessedMegaMenuItem } from 'primeng/types/megamenu';\nimport { MegaMenuStyle } from './style/megamenustyle';\nimport type { MegaMenu } from './megamenu';\nimport { MEGAMENU_INSTANCE, MEGAMENU_SUB_INSTANCE } from './megamenu-token';\n\n@Component({\n selector: 'p-megamenu-sub, ul[pMegaMenuSub]',\n standalone: true,\n imports: [NgTemplateOutlet, RouterModule, Ripple, TooltipModule, AngleDownIcon, AngleRightIcon, BadgeModule, SharedModule, Bind, MotionModule],\n template: `\n @if (submenu()) {\n <li [class]=\"cn(cx('submenuLabel'), getItemProp(submenu(), 'class'))\" [style]=\"getItemProp(submenu(), 'style')\" role=\"presentation\" [pBind]=\"ptm('submenuLabel')\">\n {{ getItemLabel(submenu()) }}\n </li>\n }\n @for (processedItem of items(); track processedItem.key; let index = $index) {\n @if (isItemVisible(processedItem)) {\n @if (isSeparator(processedItem)) {\n <li [attr.id]=\"getItemId(processedItem)\" [style]=\"getItemProp(processedItem, 'style')\" [class]=\"cn(cx('separator'), this.getItemProp(processedItem, 'class'))\" role=\"separator\" [pBind]=\"ptm('separator')\"></li>\n } @else {\n <li\n #listItem\n role=\"menuitem\"\n [attr.id]=\"getItemId(processedItem)\"\n [attr.data-p-active]=\"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]=\"getAriaHasPopup(processedItem)\"\n [attr.aria-expanded]=\"getAriaExpanded(processedItem)\"\n [attr.aria-level]=\"level() + 1\"\n [attr.aria-setsize]=\"getAriaSetSize()\"\n [attr.aria-posinset]=\"getAriaPosInset(index)\"\n [style]=\"getItemProp(processedItem, 'style')\"\n [class]=\"cn(cx('item', { processedItem }), getItemProp(processedItem, 'styleClass'))\"\n pTooltip\n [tooltipOptions]=\"getItemProp(processedItem, 'tooltipOptions')\"\n [pBind]=\"getPTOptions(processedItem, index, 'item')\"\n [pTooltipUnstyled]=\"unstyled()\"\n >\n <div [class]=\"cx('itemContent')\" [pBind]=\"getPTOptions(processedItem, index, 'itemContent')\" (click)=\"onItemClick($event, processedItem)\" (mouseenter)=\"onItemMouseEnter({ $event, processedItem })\">\n @if (!itemTemplate()) {\n @if (!hasRouterLink(processedItem)) {\n <a\n [attr.href]=\"getItemProp(processedItem, 'url')\"\n [attr.data-automationid]=\"getItemProp(processedItem, 'automationId')\"\n [attr.title]=\"getItemProp(processedItem, 'title')\"\n [target]=\"getItemProp(processedItem, 'target')\"\n [class]=\"cn(cx('itemLink'), getItemProp(processedItem, 'linkClass'))\"\n [style]=\"getItemProp(processedItem, 'linkStyle')\"\n [attr.tabindex]=\"-1\"\n [pBind]=\"getPTOptions(processedItem, index, 'itemLink')\"\n pRipple\n >\n @if (hasIcon(processedItem)) {\n <span\n [class]=\"cn(cx('itemIcon'), getItemProp(processedItem, 'icon'), getItemProp(processedItem, 'iconClass'))\"\n [style]=\"getItemProp(processedItem, 'iconStyle')\"\n [attr.tabindex]=\"-1\"\n [pBind]=\"getPTOptions(processedItem, index, 'itemIcon')\"\n >\n </span>\n }\n @if (shouldEscape(processedItem)) {\n <span [class]=\"cn(cx('itemLabel'), getItemProp(processedItem, 'labelClass'))\" [style]=\"getItemProp(processedItem, 'labelStyle')\" [pBind]=\"getPTOptions(processedItem, index, 'itemLabel')\">\n {{ getItemLabel(processedItem) }}\n </span>\n } @else {\n <span\n [class]=\"cn(cx('itemLabel'), getItemProp(processedItem, 'labelClass'))\"\n [style]=\"getItemProp(processedItem, 'labelStyle')\"\n [innerHTML]=\"getItemLabel(processedItem)\"\n [pBind]=\"getPTOptions(processedItem, index, 'itemLabel')\"\n ></span>\n }\n @if (hasBadge(processedItem)) {\n <p-badge [class]=\"getItemProp(processedItem, 'badgeStyleClass')\" [value]=\"getItemProp(processedItem, 'badge')\" [unstyled]=\"unstyled()\" />\n }\n @if (isItemGroup(processedItem)) {\n @if (!hasSubmenuIcon()) {\n @if (isHorizontalOrMobile()) {\n <svg data-p-icon=\"angle-down\" [class]=\"cx('submenuIcon')\" [pBind]=\"getPTOptions(processedItem, index, 'submenuIcon')\" aria-hidden=\"true\" />\n } @else {\n <svg data-p-icon=\"angle-right\" [class]=\"cx('submenuIcon')\" [pBind]=\"getPTOptions(processedItem, index, 'submenuIcon')\" aria-hidden=\"true\" />\n }\n }\n <ng-container *ngTemplateOutlet=\"megaMenu.submenuiconTemplate()\" />\n }\n </a>\n } @else {\n <a\n [routerLink]=\"getItemProp(processedItem, 'routerLink')\"\n [attr.data-automationid]=\"getItemProp(processedItem, 'automationId')\"\n [attr.title]=\"getItemProp(processedItem, 'title')\"\n [attr.tabindex]=\"-1\"\n [queryParams]=\"getItemProp(processedItem, 'queryParams')\"\n [routerLinkActive]=\"'p-megamenu-item-link-active'\"\n [routerLinkActiveOptions]=\"getRouterLinkActiveOptions(processedItem)\"\n [target]=\"getItemProp(processedItem, 'target')\"\n [class]=\"cn(cx('itemLink'), getItemProp(processedItem, 'linkClass'))\"\n [style]=\"getItemProp(processedItem, 'linkStyle')\"\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 [pBind]=\"getPTOptions(processedItem, index, 'itemLink')\"\n pRipple\n >\n @if (hasIcon(processedItem)) {\n <span\n [class]=\"cn(cx('itemIcon'), getItemProp(processedItem, 'icon'), getItemProp(processedItem, 'iconClass'))\"\n [style]=\"getItemProp(processedItem, 'iconStyle')\"\n [attr.tabindex]=\"-1\"\n [pBind]=\"getPTOptions(processedItem, index, 'itemIcon')\"\n ></span>\n }\n @if (shouldEscape(processedItem)) {\n <span [class]=\"cn(cx('itemLabel'), getItemProp(processedItem, 'labelClass'))\" [style]=\"getItemProp(processedItem, 'labelStyle')\" [pBind]=\"getPTOptions(processedItem, index, 'itemLabel')\">{{\n getItemLabel(processedItem)\n }}</span>\n } @else {\n <span\n [class]=\"cn(cx('itemLabel'), getItemProp(processedItem, 'labelClass'))\"\n [style]=\"getItemProp(processedItem, 'labelStyle')\"\n [innerHTML]=\"getItemLabel(processedItem)\"\n [pBind]=\"getPTOptions(processedItem, index, 'itemLabel')\"\n ></span>\n }\n @if (hasBadge(processedItem)) {\n <p-badge [class]=\"getItemProp(processedItem, 'badgeStyleClass')\" [value]=\"getItemProp(processedItem, 'badge')\" [unstyled]=\"unstyled()\" />\n }\n @if (isItemGroup(processedItem)) {\n @if (!hasSubmenuIcon()) {\n @if (isHorizontal()) {\n <svg data-p-icon=\"angle-down\" [class]=\"cx('submenuIcon')\" [pBind]=\"getPTOptions(processedItem, index, 'submenuIcon')\" aria-hidden=\"true\" />\n } @else {\n <svg data-p-icon=\"angle-right\" [class]=\"cx('submenuIcon')\" [pBind]=\"getPTOptions(processedItem, index, 'submenuIcon')\" aria-hidden=\"true\" />\n }\n }\n <ng-container *ngTemplateOutlet=\"megaMenu.submenuiconTemplate()\" />\n }\n </a>\n }\n } @else {\n <ng-container *ngTemplateOutlet=\"itemTemplate(); context: { $implicit: processedItem.item }\" />\n }\n </div>\n @if (isVisibleItemGroup(processedItem)) {\n <div [class]=\"cx('overlay')\" [pBind]=\"ptm('overlay')\" [pMotion]=\"isItemActive(processedItem)\" [pMotionName]=\"'p-anchored-overlay'\" [pMotionOptions]=\"motionOptions()\">\n <div [class]=\"cx('grid')\" [pBind]=\"ptm('grid')\">\n @for (col of processedItem.items; track $index) {\n <div [class]=\"cx('column', { processedItem })\" [pBind]=\"ptm('column')\">\n @for (submenu of col; track submenu.key) {\n <ul\n pMegaMenuSub\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 [orientation]=\"orientation()\"\n [motionOptions]=\"motionOptions()\"\n (itemClick)=\"itemClick.emit($event)\"\n (itemMouseEnter)=\"onItemMouseEnter($event)\"\n [pt]=\"pt()\"\n [unstyled]=\"unstyled()\"\n ></ul>\n }\n </div>\n }\n </div>\n </div>\n }\n </li>\n }\n }\n }\n `,\n encapsulation: ViewEncapsulation.None,\n providers: [\n { provide: MEGAMENU_SUB_INSTANCE, useExisting: MegaMenuSub },\n { provide: PARENT_INSTANCE, useExisting: MegaMenuSub }\n ],\n host: {\n '[class]': 'hostClass()',\n '[style]': 'sx(\"rootList\")',\n '[style.display]': 'hostDisplay()',\n '[attr.role]': 'hostRole()',\n '[attr.id]': 'id()',\n '[attr.aria-orientation]': 'orientation()',\n '[tabindex]': 'tabindex()',\n '[attr.aria-activedescendant]': 'focusedItemId()',\n '[attr.data-pc-section]': 'hostDataPcSection()',\n '(keydown)': 'menuKeydown.emit($event)',\n '(focus)': 'menuFocus.emit($event)',\n '(blur)': 'menuBlur.emit($event)',\n '(mousedown)': 'menuMouseDown.emit($event)'\n },\n hostDirectives: [Bind]\n})\nexport class MegaMenuSub extends BaseComponent<MegaMenuPassThrough> {\n bindDirectiveInstance = inject(Bind, { self: true });\n\n $pcMegaMenu: MegaMenu | undefined = inject(MEGAMENU_INSTANCE, { optional: true, skipSelf: true }) ?? undefined;\n\n $pcMegaMenuSub: MegaMenuSub | undefined = inject(MEGAMENU_SUB_INSTANCE, { optional: true, skipSelf: true }) ?? undefined;\n\n id = input<string>();\n\n items = input<ProcessedMegaMenuItem[]>();\n\n itemTemplate = input<TemplateRef<MegaMenuItemTemplateContext>>();\n\n menuId = input<string>();\n\n ariaLabel = input<string>();\n\n ariaLabelledBy = input<string>();\n\n level = input(0, { transform: numberAttribute });\n\n focusedItemId = input<string>();\n\n disabled = input(false, { transform: booleanAttribute });\n\n orientation = input<MegaMenuOrientation>();\n\n activeItem = input<ProcessedMegaMenuItem | null>();\n\n submenu = input<ProcessedMegaMenuItem>();\n\n motionOptions = input<MotionOptions>();\n\n queryMatches = input(false, { transform: booleanAttribute });\n\n mobileActive = input(false, { transform: booleanAttribute });\n\n scrollHeight = input<string>();\n\n tabindex = input(0, { transform: numberAttribute });\n\n root = input(false, { transform: booleanAttribute });\n\n itemClick = output<MegaMenuItemClickEvent>();\n\n itemMouseEnter = output<MegaMenuItemMouseEnterEvent>();\n\n menuFocus = output<FocusEvent>();\n\n menuBlur = output<FocusEvent>();\n\n menuKeydown = output<KeyboardEvent>();\n\n menuMouseDown = output<MouseEvent>();\n\n megaMenu = inject<MegaMenu>(MEGAMENU_INSTANCE);\n\n _componentStyle = inject(MegaMenuStyle);\n\n hostName = 'MegaMenu';\n\n hostClass = computed(() => (this.root() ? this.cx('rootList') : this.cx('submenu')));\n\n hostDisplay = computed(() => (this.root() ? undefined : this.isSubmenuVisible(this.submenu()) ? null : 'none'));\n\n hostRole = computed(() => (this.root() ? 'menubar' : 'menu'));\n\n hostDataPcSection = computed(() => (this.root() ? 'rootlist' : 'submenu'));\n\n isHorizontalOrMobile = computed(() => this.orientation() === 'horizontal' || this.mobileActive());\n\n isHorizontal = computed(() => this.orientation() === 'horizontal');\n\n isSeparator(processedItem: ProcessedMegaMenuItem) {\n return this.getItemProp(processedItem, 'separator');\n }\n\n hasRouterLink(processedItem: ProcessedMegaMenuItem) {\n return this.getItemProp(processedItem, 'routerLink');\n }\n\n hasIcon(processedItem: ProcessedMegaMenuItem) {\n return this.getItemProp(processedItem, 'icon');\n }\n\n shouldEscape(processedItem: ProcessedMegaMenuItem) {\n return this.getItemProp(processedItem, 'escape');\n }\n\n hasBadge(processedItem: ProcessedMegaMenuItem) {\n return this.getItemProp(processedItem, 'badge');\n }\n\n isVisibleItemGroup(processedItem: ProcessedMegaMenuItem) {\n return this.isItemVisible(processedItem) && this.isItemGroup(processedItem);\n }\n\n getAriaHasPopup(processedItem: ProcessedMegaMenuItem) {\n return this.isItemGroup(processedItem) && !this.getItemProp(processedItem, 'to') ? 'menu' : undefined;\n }\n\n getAriaExpanded(processedItem: ProcessedMegaMenuItem) {\n return this.isItemGroup(processedItem) ? this.isItemActive(processedItem) : undefined;\n }\n\n getRouterLinkActiveOptions(processedItem: ProcessedMegaMenuItem) {\n return this.getItemProp(processedItem, 'routerLinkActiveOptions') || { exact: false };\n }\n\n hasSubmenuIcon = computed(() => !!this.megaMenu.submenuiconTemplate());\n\n onAfterViewChecked() {\n this.bindDirectiveInstance.setAttrs(this.ptm(this.root() ? 'rootList' : 'submenu'));\n }\n\n onItemClick(event: Event, processedItem: ProcessedMegaMenuItem) {\n this.getItemProp(processedItem, 'command', { originalEvent: event, item: processedItem.item });\n this.itemClick.emit({ originalEvent: event, processedItem, isFocus: true });\n }\n\n getItemProp(processedItem: ProcessedMegaMenuItem | undefined, name: string, params: { originalEvent: Event; item: MegaMenuItem } | null = null): any {\n return processedItem && processedItem.item ? resolve(processedItem.item[name], params) : undefined;\n }\n\n getItemId(processedItem: ProcessedMegaMenuItem): string {\n return processedItem.item && processedItem.item?.id ? processedItem.item.id : `${this.menuId()}_${processedItem.key}`;\n }\n\n getSubListId(processedItem: ProcessedMegaMenuItem) {\n return `${this.getItemId(processedItem)}_list`;\n }\n\n getItemLabel(processedItem: ProcessedMegaMenuItem | undefined): string {\n return this.getItemProp(processedItem, 'label');\n }\n\n isSubmenuVisible(submenu: ProcessedMegaMenuItem | undefined) {\n if (this.submenu() && !this.root()) {\n return this.isItemVisible(submenu);\n } else {\n return true;\n }\n }\n\n isItemVisible(processedItem: ProcessedMegaMenuItem | undefined): boolean {\n return this.getItemProp(processedItem, 'visible') !== false;\n }\n\n isItemActive(processedItem: ProcessedMegaMenuItem) {\n const activeItem = this.activeItem();\n return isNotEmpty(activeItem) && activeItem ? activeItem.key === processedItem.key : false;\n }\n\n isItemDisabled(processedItem: ProcessedMegaMenuItem): boolean {\n return this.getItemProp(processedItem, 'disabled');\n }\n\n isItemFocused(processedItem: ProcessedMegaMenuItem): boolean {\n return this.focusedItemId() === this.getItemId(processedItem);\n }\n\n isItemGroup(processedItem: ProcessedMegaMenuItem): 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 (\n index -\n (this.items()\n ?.slice(0, index)\n .filter((processedItem) => this.isItemVisible(processedItem) && this.getItemProp(processedItem, 'separator')).length || 0) +\n 1\n );\n }\n\n onItemMouseEnter(param: { $event: Event; processedItem: ProcessedMegaMenuItem }) {\n const { $event, processedItem } = param;\n this.itemMouseEnter.emit({ originalEvent: $event, processedItem });\n }\n\n getPTOptions(processedItem: ProcessedMegaMenuItem, index: number, key: string) {\n const ptContext = {\n context: {\n item: processedItem.item,\n index,\n active: this.isItemActive(processedItem),\n focused: this.isItemFocused(processedItem),\n disabled: this.isItemDisabled(processedItem)\n }\n };\n\n return this.ptm(key, ptContext);\n }\n}\n","import { isPlatformBrowser, NgTemplateOutlet } from '@angular/common';\nimport { booleanAttribute, ChangeDetectionStrategy, Component, computed, contentChild, effect, ElementRef, inject, input, NgModule, numberAttribute, signal, TemplateRef, viewChild, ViewEncapsulation } from '@angular/core';\nimport { MotionOptions } from '@primeuix/motion';\nimport { findLastIndex, findSingle, focus, isEmpty, isNotEmpty, isPrintableCharacter, isTouchDevice, resolve, uuid } from '@primeuix/utils';\nimport { MegaMenuItem, SharedModule } from 'primeng/api';\nimport { BaseComponent, PARENT_INSTANCE } from 'primeng/basecomponent';\nimport { Bind } from 'primeng/bind';\nimport { Bars as BarsIcon } from '@primeicons/angular/bars';\nimport { MotionModule } from 'primeng/motion';\nimport { VoidListener } from 'primeng/ts-helpers';\nimport { MegaMenuFocusedItemInfo, MegaMenuItemClickEvent, MegaMenuItemMouseEnterEvent, MegaMenuItemTemplateContext, MegaMenuOrientation, MegaMenuPassThrough, ProcessedMegaMenuItem } from 'primeng/types/megamenu';\nimport { ZIndexUtils } from 'primeng/utils';\nimport { MegaMenuSub } from './megamenu-sub';\nimport { MEGAMENU_INSTANCE } from './megamenu-token';\nimport { MegaMenuStyle } from './style/megamenustyle';\n\n/**\n * MegaMenu is navigation component that displays submenus together.\n * @group Components\n */\n@Component({\n selector: 'p-megamenu, p-mega-menu',\n standalone: true,\n imports: [NgTemplateOutlet, MegaMenuSub, BarsIcon, SharedModule, Bind, MotionModule],\n template: `\n @if (startTemplate()) {\n <div [class]=\"cx('start')\" [pBind]=\"ptm('start')\">\n <ng-container *ngTemplateOutlet=\"startTemplate()\" />\n </div>\n }\n @if (!buttonTemplate()) {\n @if (model()?.length) {\n <a\n #menubutton\n role=\"button\"\n tabindex=\"0\"\n [class]=\"cx('button')\"\n [attr.aria-haspopup]=\"true\"\n [attr.aria-expanded]=\"mobileActive()\"\n [attr.aria-controls]=\"$id()\"\n [attr.aria-label]=\"navigationAriaLabel\"\n [pBind]=\"ptm('button')\"\n (click)=\"menuButtonClick($event)\"\n (keydown)=\"menuButtonKeydown($event)\"\n >\n @if (!buttoniconTemplate()) {\n <svg data-p-icon=\"bars\" [pBind]=\"ptm('buttonIcon')\" />\n }\n <ng-container *ngTemplateOutlet=\"buttoniconTemplate()\" />\n </a>\n }\n }\n <ng-container *ngTemplateOutlet=\"buttonTemplate()\" />\n <ul\n pMegaMenuSub\n #rootmenu\n [pMotion]=\"!queryMatches() || mobileActive()\"\n [pMotionName]=\"'p-anchored-overlay'\"\n [pMotionOptions]=\"computedMotionOptions()\"\n [motionOptions]=\"computedMotionOptions()\"\n [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]=\"rootMenuTabindex()\"\n [activeItem]=\"activeItem()\"\n [level]=\"0\"\n [ariaLabelledBy]=\"ariaLabelledBy()\"\n [focusedItemId]=\"activeFocusedItemId()\"\n [mobileActive]=\"mobileActive()\"\n (itemClick)=\"onItemClick($event)\"\n (menuFocus)=\"onMenuFocus($event)\"\n (menuBlur)=\"onMenuBlur($event)\"\n (menuKeydown)=\"onKeyDown($event)\"\n (menuMouseDown)=\"onMenuMouseDown($event)\"\n (itemMouseEnter)=\"onItemMouseEnter($event)\"\n [queryMatches]=\"queryMatches()\"\n [scrollHeight]=\"scrollHeight()\"\n [pt]=\"pt()\"\n [unstyled]=\"unstyled()\"\n ></ul>\n @if (endTemplate()) {\n <div [class]=\"cx('end')\" [pBind]=\"ptm('end')\">\n <ng-container *ngTemplateOutlet=\"endTemplate()\" />\n </div>\n }\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n providers: [MegaMenuStyle, { provide: MEGAMENU_INSTANCE, useExisting: MegaMenu }, { provide: PARENT_INSTANCE, useExisting: MegaMenu }],\n host: {\n '[class]': 'cx(\"root\")',\n '[id]': '$id()'\n },\n hostDirectives: [Bind]\n})\nexport class MegaMenu extends BaseComponent<MegaMenuPassThrough> {\n componentName = 'MegaMenu';\n\n bindDirectiveInstance = inject(Bind, { self: true });\n\n /**\n * An array of menuitems.\n * @group Props\n */\n model = input<MegaMenuItem[]>();\n\n /**\n * Defines the orientation.\n * @group Props\n */\n orientation = input<MegaMenuOrientation>('horizontal');\n\n /**\n * Current id state as a string.\n * @group Props\n */\n id = input<string>();\n\n /**\n * Defines a string value that labels an interactive element.\n * @group Props\n */\n ariaLabel = input<string>();\n\n /**\n * Identifier of the underlying input element.\n * @group Props\n */\n ariaLabelledBy = input<string>();\n\n /**\n * The breakpoint to define the maximum width boundary.\n * @group Props\n */\n breakpoint = input('960px');\n\n /**\n * Height of the viewport, a scrollbar is defined if height of list exceeds this value.\n * @group Props\n */\n scrollHeight = input('20rem');\n\n /**\n * When present, it specifies that the component should be disabled.\n * @group Props\n */\n disabled = input(false, { transform: booleanAttribute });\n\n /**\n * Index of the element in tabbing order.\n * @group Props\n */\n tabindex = input(0, { transform: numberAttribute });\n\n /**\n * Defines the motion options for the submenu open/close animation.\n * @group Props\n */\n motionOptions = input<MotionOptions>();\n\n computedMotionOptions = computed<MotionOptions>(() => {\n return {\n ...this.ptm('motion'),\n ...this.motionOptions()\n };\n });\n\n /**\n * Defines template option for start.\n * @group Templates\n */\n startTemplate = contentChild<TemplateRef<void>>('start', { descendants: false });\n\n /**\n * Defines template option for end.\n * @group Templates\n */\n endTemplate = contentChild<TemplateRef<void>>('end', { descendants: false });\n\n /**\n * Defines template option for menu icon.\n * @group Templates\n */\n menuiconTemplate = contentChild<TemplateRef<void>>('menuicon', { descendants: false });\n\n /**\n * Defines template option for submenu icon.\n * @group Templates\n */\n submenuiconTemplate = contentChild<TemplateRef<void>>('submenuicon', { descendants: false });\n\n /**\n * Custom item template.\n * @param {MegaMenuItemTemplateContext} context - item context.\n * @see {@link MegaMenuItemTemplateContext}\n * @group Templates\n */\n itemTemplate = contentChild<TemplateRef<MegaMenuItemTemplateContext>>('item', { descendants: false });\n\n /**\n * Custom menu button template on responsive mode.\n * @group Templates\n */\n buttonTemplate = contentChild<TemplateRef<void>>('button', { descendants: false });\n\n /**\n * Custom menu button icon template on responsive mode.\n * @group Templates\n */\n buttoniconTemplate = contentChild<TemplateRef<void>>('buttonicon', { descendants: false });\n\n menubuttonViewChild = viewChild<ElementRef>('menubutton');\n\n rootmenu = viewChild<MegaMenuSub>('rootmenu');\n\n outsideClickListener: VoidListener;\n\n resizeListener: ((event: Event) => void) | null = null;\n\n dirty: boolean = false;\n\n focused = signal(false);\n\n activeItem = signal<ProcessedMegaMenuItem | null>(null);\n\n focusedItemInfo = signal<MegaMenuFocusedItemInfo>({ index: -1, level: 0, parentKey: '', item: null });\n\n searchValue: string = '';\n\n searchTimeout: ReturnType<typeof setTimeout> | null = null;\n\n _componentStyle = inject(MegaMenuStyle);\n\n private matchMediaListener: (() => void) | null = null;\n\n private query: MediaQueryList | null = null;\n\n queryMatches = signal<boolean>(false);\n\n mobileActive = signal(false);\n\n private _internalId = uuid('pn_id_');\n\n $id = computed(() => this.id() || this._internalId);\n\n processedItems = computed<ProcessedMegaMenuItem[]>(() => this.createProcessedItems(this.model() || []));\n\n visibleItems = computed<ProcessedMegaMenuItem[]>(() => {\n const activeItemValue = this.activeItem();\n const processedItem = isNotEmpty(activeItemValue) ? activeItemValue : null;\n\n return processedItem\n ? (processedItem.items as ProcessedMegaMenuItem[][]).reduce((items: ProcessedMegaMenuItem[], col) => {\n col.forEach((submenu) => {\n (submenu.items as ProcessedMegaMenuItem[]).forEach((a) => {\n items.push(a);\n });\n });\n\n return items;\n }, [])\n : this.processedItems();\n });\n\n focusedItemId = computed(() => {\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 rootMenuTabindex = computed(() => (!this.disabled() ? this.tabindex() : -1));\n\n activeFocusedItemId = computed(() => (this.focused() ? this.focusedItemId() : undefined));\n\n get navigationAriaLabel(): string | undefined {\n return this.config.translation?.aria?.navigation;\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 onInit() {\n this.bindMatchMediaListener();\n }\n\n onAfterViewChecked() {\n this.bindDirectiveInstance.setAttrs(this.ptms(['host', 'root']));\n }\n\n bindMatchMediaListener() {\n if (isPlatformBrowser(this.platformId) && !this.matchMediaListener) {\n const query = window.matchMedia(`(max-width: ${this.breakpoint()})`);\n\n this.query = query;\n this.queryMatches.set(query.matches);\n\n this.matchMediaListener = () => {\n this.queryMatches.set(query.matches);\n this.mobileActive.set(false);\n };\n\n query.addEventListener('change', this.matchMediaListener);\n }\n }\n\n unbindMatchMediaListener() {\n if (this.matchMediaListener && this.query) {\n this.query.removeEventListener('change', this.matchMediaListener);\n this.matchMediaListener = null;\n }\n }\n\n createProcessedItems(items: MegaMenuItem[], level = 0, parent: ProcessedMegaMenuItem | Record<string, never> = {}, parentKey = '', columnIndex?: number): ProcessedMegaMenuItem[] {\n const processedItems: ProcessedMegaMenuItem[] = [];\n\n items &&\n items.forEach((item, index) => {\n const key = (parentKey !== '' ? parentKey + '_' : '') + (columnIndex !== undefined ? columnIndex + '_' : '') + index;\n const newItem: ProcessedMegaMenuItem = {\n item,\n index,\n level,\n key,\n parent,\n parentKey,\n columnIndex: columnIndex !== undefined ? columnIndex : (parent as ProcessedMegaMenuItem).columnIndex !== undefined ? (parent as ProcessedMegaMenuItem).columnIndex : index\n };\n\n newItem.items =\n level === 0 && item.items && item.items.length > 0\n ? item.items.map((_items, _index) => this.createProcessedItems(_items as MegaMenuItem[], level + 1, newItem, key, _index))\n : this.createProcessedItems((item.items || []) as MegaMenuItem[], level + 1, newItem, key);\n processedItems.push(newItem);\n });\n return processedItems;\n }\n\n getItemProp(item: MegaMenuItem, name: string) {\n return item ? resolve(item[name]) : undefined;\n }\n\n onItemClick(event: MegaMenuItemClickEvent) {\n this.dirty = true;\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, level: 0, key, parentKey, item });\n\n this.dirty = !root;\n if (!this.mobileActive()) {\n focus(this.rootmenu()?.el?.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: MegaMenuItemMouseEnterEvent) {\n if (!this.mobileActive() && this.dirty) {\n this.onItemChange(event);\n }\n }\n\n menuButtonClick(event: MouseEvent) {\n this.toggle(event);\n }\n\n menuButtonKeydown(event: KeyboardEvent) {\n (event.code === 'Enter' || event.code === 'NumpadEnter' || event.code === 'Space') && this.menuButtonClick(event as unknown as MouseEvent);\n }\n\n toggle(event: MouseEvent) {\n if (this.mobileActive()) {\n this.mobileActive.set(false);\n ZIndexUtils.clear(this.rootmenu()?.el.nativeElement);\n this.hide();\n } else {\n this.mobileActive.set(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: '', item: null });\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: HTMLElement | null | undefined;\n\n if (id === null && this.queryMatches()) {\n element = this.menubuttonViewChild()?.nativeElement;\n } else {\n element = findSingle(this.rootmenu()?.el?.nativeElement, `li[id=\"${id}\"]`) as HTMLElement | null;\n }\n\n if (element) {\n element.scrollIntoView && element.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'smooth' });\n }\n }\n\n onItemChange(event: MegaMenuItemClickEvent | MegaMenuItemMouseEnterEvent) {\n const { processedItem } = 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, level: 0, key, parentKey, item });\n\n grouped && (this.dirty = true);\n if ('isFocus' in event && event.isFocus) {\n focus(this.rootmenu()?.el?.nativeElement);\n }\n }\n\n hide(_event?: Event, isFocus?: boolean) {\n if (this.mobileActive()) {\n this.mobileActive.set(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, level: 0, key: '', parentKey: '', item: null });\n\n isFocus && focus(this.rootmenu()?.el?.nativeElement);\n this.dirty = false;\n }\n\n onMenuMouseDown(_event: MouseEvent) {\n this.dirty = true;\n }\n\n onMenuFocus(event: FocusEvent) {\n this.focused.set(true);\n\n const relatedTarget = event.relatedTarget as HTMLElement | null;\n const isFromOutside = !relatedTarget || !this.el.nativeElement.contains(relatedTarget);\n\n if (isFromOutside && this.focusedItemInfo().index === -1 && isEmpty(this.activeItem()) && !this.dirty) {\n const index = this.findFirstFocusedItemIndex();\n const processedItem = this.findVisibleItem(index);\n\n if (processedItem) {\n this.focusedItemInfo.set({ index, level: 0, key: processedItem.key, parentKey: processedItem.parentKey, item: processedItem.item });\n }\n }\n }\n\n onMenuBlur(event: FocusEvent) {\n const relatedTarget = event.relatedTarget as HTMLElement | null;\n if (relatedTarget && this.el.nativeElement.contains(relatedTarget)) {\n return;\n }\n\n setTimeout(() => {\n const activeElement = this.document.activeElement;\n if (activeElement && this.el.nativeElement.contains(activeElement)) {\n return;\n }\n\n this.focused.set(false);\n this.focusedItemInfo.set({ index: -1, level: 0, parentKey: '', item: null });\n this.searchValue = '';\n this.dirty = false;\n });\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: ProcessedMegaMenuItem): boolean {\n return processedItem && isNotEmpty(processedItem.items);\n }\n\n isSelected(processedItem: ProcessedMegaMenuItem): boolean {\n const activeItemValue = this.activeItem();\n return isNotEmpty(activeItemValue) && activeItemValue ? activeItemValue.key === processedItem.key : false;\n }\n\n isValidSelectedItem(processedItem: ProcessedMegaMenuItem): boolean {\n return this.isValidItem(processedItem) && this.isSelected(processedItem);\n }\n\n isValidItem(processedItem: ProcessedMegaMenuItem): boolean {\n return !!processedItem && !this.isItemDisabled(processedItem.item) && !this.isItemSeparator(processedItem.item);\n }\n\n isItemDisabled(item: MegaMenuItem): boolean {\n return this.getItemProp(item, 'disabled');\n }\n\n isItemSeparator(item: MegaMenuItem): boolean {\n return this.getItemProp(item, 'separator');\n }\n\n isItemMatched(processedItem: ProcessedMegaMenuItem): boolean {\n return this.isValidItem(processedItem) && this.getProccessedItemLabel(processed