UNPKG

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 98.3 kB
{"version":3,"file":"primeng-menubar.mjs","sources":["../../src/menubar/menubar.service.ts","../../src/menubar/style/menubarstyle.ts","../../src/menubar/menubar-sub.ts","../../src/menubar/menubar.ts","../../src/menubar/primeng-menubar.ts"],"sourcesContent":["import { Injectable } from '@angular/core';\nimport { interval, Subject } from 'rxjs';\nimport { debounce, filter } from 'rxjs/operators';\n\n@Injectable()\nexport class MenubarService {\n autoHide: boolean | undefined;\n\n autoHideDelay: number | undefined;\n\n readonly mouseLeaves = new Subject<boolean>();\n\n readonly mouseLeft$ = this.mouseLeaves.pipe(\n debounce(() => interval(this.autoHideDelay)),\n filter((mouseLeft) => (this.autoHide as boolean) && mouseLeft)\n );\n}\n","import { Injectable } from '@angular/core';\nimport { style as baseStyle } from '@primeuix/styles/menubar';\nimport { BaseStyle } from 'primeng/base';\n\n// Reveal an active submenu with flex so the pMotion directive (which owns the inline\n// display) can reset to a visible baseline on open. The theme keeps submenus at\n// display:none; pMotion's inline display:none still wins while closed.\nconst style = `\n${baseStyle}\n.p-menubar-root-list > .p-menubar-item-active > .p-menubar-submenu,\n.p-menubar-submenu > .p-menubar-item-active > .p-menubar-submenu {\n display: flex;\n}\n`;\n\nconst classes = {\n root: ({ instance }) => [\n 'p-menubar p-component',\n {\n 'p-menubar-mobile': instance.queryMatches(),\n 'p-menubar-mobile-active': instance.mobileActive\n }\n ],\n start: 'p-menubar-start',\n button: 'p-menubar-button',\n rootList: 'p-menubar-root-list',\n item: ({ instance, processedItem }) => [\n 'p-menubar-item',\n {\n 'p-menubar-item-active': instance.isItemActive(processedItem),\n 'p-focus': instance.isItemFocused(processedItem),\n 'p-disabled': instance.isItemDisabled(processedItem)\n }\n ],\n itemContent: 'p-menubar-item-content',\n itemLink: 'p-menubar-item-link',\n itemIcon: 'p-menubar-item-icon',\n itemLabel: 'p-menubar-item-label',\n submenuIcon: 'p-menubar-submenu-icon',\n submenu: 'p-menubar-submenu',\n separator: 'p-menubar-separator',\n end: 'p-menubar-end'\n};\n\n@Injectable()\nexport class MenuBarStyle extends BaseStyle {\n name = 'menubar';\n\n style = style;\n\n classes = classes;\n}\n\n/**\n *\n * Menubar is a horizontal menu component.\n *\n * [Live Demo](https://www.primeng.org/menubar/)\n *\n * @module menubarstyle\n *\n */\nexport enum MenubarClasses {\n /**\n * Class name of the root element\n */\n root = 'p-menubar',\n /**\n * Class name of the start element\n */\n start = 'p-menubar-start',\n /**\n * Class name of the button element\n */\n button = 'p-menubar-button',\n /**\n * Class name of the root list element\n */\n rootList = 'p-menubar-root-list',\n /**\n * Class name of the item element\n */\n item = 'p-menubar-item',\n /**\n * Class name of the item content element\n */\n itemContent = 'p-menubar-item-content',\n /**\n * Class name of the item link element\n */\n itemLink = 'p-menubar-item-link',\n /**\n * Class name of the item icon element\n */\n itemIcon = 'p-menubar-item-icon',\n /**\n * Class name of the item label element\n */\n itemLabel = 'p-menubar-item-label',\n /**\n * Class name of the submenu icon element\n */\n submenuIcon = 'p-menubar-submenu-icon',\n /**\n * Class name of the submenu element\n */\n submenu = 'p-menubar-submenu',\n /**\n * Class name of the separator element\n */\n separator = 'p-menubar-separator',\n /**\n * Class name of the end element\n */\n end = 'p-menubar-end'\n}\n\nexport interface MenubarStyle extends BaseStyle {}\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 { MenuItem, SharedModule } from 'primeng/api';\nimport { BadgeModule } from 'primeng/badge';\nimport { BaseComponent } from 'primeng/basecomponent';\nimport { BindModule } 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 { MenubarItemClickEvent, MenubarItemMouseEnterEvent, MenubarItemTemplateContext, MenubarPassThrough, ProcessedMenuItem } from 'primeng/types/menubar';\nimport type { CSSProperties } from 'primeng/types/shared';\nimport { Subscription } from 'rxjs';\nimport { MenubarService } from './menubar.service';\nimport { MenuBarStyle } from './style/menubarstyle';\n\n@Component({\n selector: 'p-menubarsub, [pMenubarSub]',\n standalone: true,\n imports: [NgTemplateOutlet, RouterModule, Ripple, TooltipModule, AngleDownIcon, AngleRightIcon, BadgeModule, SharedModule, BindModule, MotionModule],\n template: `\n @for (processedItem of items(); track processedItem.key) {\n @if (isItemVisible(processedItem) && getItemProp(processedItem, 'separator')) {\n <li [attr.id]=\"getItemId(processedItem)\" [style]=\"getItemProp(processedItem, 'style')\" [class]=\"cn(cx('separator'), processedItem?.styleClass)\" role=\"separator\" [pBind]=\"ptm('separator')\"></li>\n }\n @if (isItemVisible(processedItem) && !getItemProp(processedItem, 'separator')) {\n <li\n #listItem\n role=\"menuitem\"\n [attr.id]=\"getItemId(processedItem)\"\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-setsize]=\"getAriaSetSize()\"\n [attr.aria-posinset]=\"getAriaPosInset($index)\"\n [style]=\"getItemProp(processedItem, 'style')\"\n [class]=\"cn(cx('item', { instance: this, processedItem }), getItemProp(processedItem, 'styleClass'))\"\n [pBind]=\"getPTOptions(processedItem, $index, 'item')\"\n pTooltip\n [tooltipOptions]=\"getItemProp(processedItem, 'tooltipOptions')\"\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 (!getItemProp(processedItem, 'routerLink')) {\n <a\n [attr.href]=\"getItemProp(processedItem, 'url')\"\n [attr.data-automationid]=\"getItemProp(processedItem, 'automationId')\"\n [attr.title]=\"getItemProp(processedItem, 'title')\"\n [attr.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 (getItemProp(processedItem, 'icon')) {\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 (getItemProp(processedItem, 'escape')) {\n <span\n [class]=\"cn(cx('itemLabel'), getItemProp(processedItem, 'labelClass'))\"\n [style]=\"getItemProp(processedItem, 'labelStyle')\"\n [id]=\"getItemLabelId(processedItem)\"\n [pBind]=\"getPTOptions(processedItem, $index, 'itemLabel')\"\n >\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 [id]=\"getItemLabelId(processedItem)\"\n [pBind]=\"getPTOptions(processedItem, $index, 'itemLabel')\"\n ></span>\n }\n @if (getItemProp(processedItem, 'badge')) {\n <p-badge [class]=\"getItemProp(processedItem, 'badgeStyleClass')\" [value]=\"getItemProp(processedItem, 'badge')\" [pt]=\"getPTOptions(processedItem, $index, 'pcBadge')\" [unstyled]=\"unstyled()\" />\n }\n @if (isItemGroup(processedItem)) {\n @if (!submenuiconTemplate()) {\n @if (root()) {\n <svg data-p-icon=\"angle-down\" [class]=\"cx('submenuIcon')\" [pBind]=\"getPTOptions(processedItem, $index, 'submenuIcon')\" />\n } @else {\n <svg data-p-icon=\"angle-right\" [class]=\"cx('submenuIcon')\" [pBind]=\"getPTOptions(processedItem, $index, 'submenuIcon')\" />\n }\n }\n <ng-container *ngTemplateOutlet=\"submenuiconTemplate()\"></ng-container>\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-menubar-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 (getItemProp(processedItem, 'icon')) {\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 (getItemProp(processedItem, 'escape')) {\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 (getItemProp(processedItem, 'badge')) {\n <p-badge [class]=\"getItemProp(processedItem, 'badgeStyleClass')\" [value]=\"getItemProp(processedItem, 'badge')\" [pt]=\"getPTOptions(processedItem, $index, 'pcBadge')\" [unstyled]=\"unstyled()\" />\n }\n @if (isItemGroup(processedItem)) {\n @if (!submenuiconTemplate()) {\n @if (root()) {\n <svg data-p-icon=\"angle-down\" [class]=\"cx('submenuIcon')\" [pBind]=\"getPTOptions(processedItem, $index, 'submenuIcon')\" />\n } @else {\n <svg data-p-icon=\"angle-right\" [class]=\"cx('submenuIcon')\" [pBind]=\"getPTOptions(processedItem, $index, 'submenuIcon')\" />\n }\n }\n <ng-container *ngTemplateOutlet=\"submenuiconTemplate()\"></ng-container>\n }\n </a>\n }\n } @else {\n <ng-container *ngTemplateOutlet=\"itemTemplate(); context: getItemTemplateContext(processedItem.item, root())\"></ng-container>\n }\n </div>\n @if (isItemVisible(processedItem) && isItemGroup(processedItem)) {\n <ul\n pMenubarSub\n [itemTemplate]=\"itemTemplate()\"\n [items]=\"processedItem.items\"\n [mobileActive]=\"mobileActive()\"\n [autoDisplay]=\"autoDisplay()\"\n [menuId]=\"menuId()\"\n [activeItemPath]=\"activeItemPath()\"\n [focusedItemId]=\"focusedItemId()\"\n [level]=\"level() + 1\"\n [attr.aria-labelledby]=\"getItemLabelId(processedItem)\"\n (itemClick)=\"itemClick.emit($event)\"\n (itemMouseEnter)=\"onItemMouseEnter($event)\"\n [pMotion]=\"isItemActive(processedItem)\"\n [pMotionDisabled]=\"mobileActive()\"\n [pMotionName]=\"'p-anchored-overlay'\"\n [pMotionAppear]=\"true\"\n [pMotionOptions]=\"motionOptions()\"\n [motionOptions]=\"motionOptions()\"\n [pt]=\"pt()\"\n [pBind]=\"ptm('submenu')\"\n [unstyled]=\"unstyled()\"\n [submenuiconTemplate]=\"submenuiconTemplate()\"\n ></ul>\n }\n </li>\n }\n }\n `,\n encapsulation: ViewEncapsulation.None,\n host: {\n '[attr.id]': 'hostId()',\n '[attr.aria-activedescendant]': 'focusedItemId()',\n '[class]': 'hostClass()',\n '[attr.role]': \"'menubar'\",\n '[style]': 'inlineStyles()'\n }\n})\nexport class MenubarSub extends BaseComponent<MenubarPassThrough> {\n hostId = computed(() => (this.root() ? this.menuId() : null));\n\n hostClass = computed(() => (this.level() === 0 ? this.cx('rootList') : this.cx('submenu')));\n\n items = input<ProcessedMenuItem[]>();\n\n itemTemplate = input<TemplateRef<MenubarItemTemplateContext>>();\n\n root = input(false, { transform: booleanAttribute });\n\n autoZIndex = input(true, { transform: booleanAttribute });\n\n baseZIndex = input(0, { transform: numberAttribute });\n\n mobileActive = input(undefined, { transform: booleanAttribute });\n\n autoDisplay = input(undefined, { transform: booleanAttribute });\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 activeItemPath = input<ProcessedMenuItem[]>();\n\n inlineStyles = input<CSSProperties>();\n\n motionOptions = input<MotionOptions>();\n\n submenuiconTemplate = input<TemplateRef<void>>();\n\n itemClick = output<MenubarItemClickEvent>();\n\n itemMouseEnter = output<MenubarItemMouseEnterEvent>();\n\n menuFocus = output<FocusEvent>();\n\n menuBlur = output<FocusEvent>();\n\n menuKeydown = output<KeyboardEvent>();\n\n mouseLeaveSubscriber: Subscription | undefined;\n\n menubarService = inject(MenubarService);\n\n _componentStyle = inject(MenuBarStyle);\n\n hostName = 'Menubar';\n\n onInit() {\n this.mouseLeaveSubscriber = this.menubarService.mouseLeft$.subscribe(() => {\n this.cd.markForCheck();\n });\n }\n\n onItemClick(event: Event, processedItem: ProcessedMenuItem) {\n this.getItemProp(processedItem, 'command', { originalEvent: event, item: processedItem.item });\n this.itemClick.emit({ originalEvent: event, processedItem, isFocus: true });\n }\n\n getItemProp(processedItem: ProcessedMenuItem, name: string, params: { originalEvent: Event; item: MenuItem } | null = null) {\n return processedItem && processedItem.item ? resolve(processedItem.item[name], params) : undefined;\n }\n\n getItemId(processedItem: ProcessedMenuItem): string {\n return processedItem.item && processedItem.item?.id ? processedItem.item.id : `${this.menuId()}_${processedItem.key}`;\n }\n\n getItemLabelId(processedItem: ProcessedMenuItem): string {\n return `${this.menuId()}_${processedItem.key}_label`;\n }\n\n getItemLabel(processedItem: ProcessedMenuItem): string {\n return this.getItemProp(processedItem, 'label');\n }\n\n isItemVisible(processedItem: ProcessedMenuItem): boolean {\n return this.getItemProp(processedItem, 'visible') !== false;\n }\n\n isItemActive(processedItem: ProcessedMenuItem): boolean {\n const path = this.activeItemPath();\n if (path) {\n return path.some((p) => p.key === processedItem.key);\n }\n return false;\n }\n\n isItemDisabled(processedItem: ProcessedMenuItem): boolean {\n return this.getItemProp(processedItem, 'disabled');\n }\n\n isItemFocused(processedItem: ProcessedMenuItem): boolean {\n return this.focusedItemId() === this.getItemId(processedItem);\n }\n\n isItemGroup(processedItem: ProcessedMenuItem): boolean {\n return isNotEmpty(processedItem.items);\n }\n\n getAriaSetSize() {\n const items = this.items();\n return items ? items.filter((processedItem) => this.isItemVisible(processedItem) && !this.getItemProp(processedItem, 'separator')).length : 0;\n }\n\n getAriaPosInset(index: number) {\n const items = this.items();\n return items ? index - items.slice(0, index).filter((processedItem) => this.isItemVisible(processedItem) && this.getItemProp(processedItem, 'separator')).length + 1 : 0;\n }\n\n onItemMouseEnter(param: { $event: Event; processedItem: ProcessedMenuItem }) {\n if (this.autoDisplay()) {\n const { $event, processedItem } = param;\n this.itemMouseEnter.emit({ originalEvent: $event, processedItem });\n }\n }\n\n getRouterLinkActiveOptions(processedItem: ProcessedMenuItem) {\n return this.getItemProp(processedItem, 'routerLinkActiveOptions') || { exact: false };\n }\n\n getPTOptions(processedItem: ProcessedMenuItem, index: number, key: string) {\n return this.ptm(key, {\n context: {\n item: processedItem.item,\n index,\n active: this.isItemActive(processedItem),\n focused: this.isItemFocused(processedItem),\n disabled: this.isItemDisabled(processedItem),\n level: this.level()\n }\n });\n }\n\n getItemTemplateContext(item: MenuItem, isRoot: boolean): MenubarItemTemplateContext {\n return { $implicit: item, root: isRoot };\n }\n\n onDestroy() {\n this.mouseLeaveSubscriber?.unsubscribe();\n }\n}\n","import { isPlatformBrowser, NgTemplateOutlet } from '@angular/common';\nimport { booleanAttribute, ChangeDetectionStrategy, Component, computed, contentChild, effect, ElementRef, inject, InjectionToken, input, NgModule, numberAttribute, output, 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 { MenuItem, SharedModule } from 'primeng/api';\nimport { BaseComponent, PARENT_INSTANCE } from 'primeng/basecomponent';\nimport { Bind, BindModule } from 'primeng/bind';\nimport { Bars as BarsIcon } from '@primeicons/angular/bars';\nimport { VoidListener } from 'primeng/ts-helpers';\nimport { FocusedItemInfo, MenubarItemClickEvent, MenubarItemMouseEnterEvent, MenubarItemTemplateContext, MenubarPassThrough, ProcessedMenuItem } from 'primeng/types/menubar';\nimport { ZIndexUtils } from 'primeng/utils';\nimport { Subscription } from 'rxjs';\nimport { MenubarSub } from './menubar-sub';\nimport { MenubarService } from './menubar.service';\nimport { MenuBarStyle } from './style/menubarstyle';\n\nconst MENUBAR_INSTANCE = new InjectionToken<Menubar>('MENUBAR_INSTANCE');\n\n/**\n * Menubar is a horizontal menu component.\n * @group Components\n */\n@Component({\n selector: 'p-menubar',\n standalone: true,\n imports: [NgTemplateOutlet, MenubarSub, BarsIcon, SharedModule, BindModule],\n template: `\n @if (startTemplate()) {\n <div [class]=\"cx('start')\" [pBind]=\"ptm('start')\">\n <ng-container *ngTemplateOutlet=\"startTemplate()\"></ng-container>\n </div>\n }\n @if (model()?.length) {\n <a\n #menubutton\n tabindex=\"0\"\n role=\"button\"\n [attr.aria-haspopup]=\"true\"\n [attr.aria-expanded]=\"mobileActive\"\n [attr.aria-controls]=\"$id()\"\n [attr.aria-label]=\"navigationAriaLabel\"\n [class]=\"cx('button')\"\n [pBind]=\"ptm('button')\"\n (click)=\"menuButtonClick($event)\"\n (keydown)=\"menuButtonKeydown($event)\"\n >\n @if (!menuIconTemplate()) {\n <svg data-p-icon=\"bars\" [pBind]=\"ptm('buttonIcon')\" />\n }\n <ng-container *ngTemplateOutlet=\"menuIconTemplate()\"></ng-container>\n </a>\n }\n <ul\n pMenubarSub\n #rootmenu\n [items]=\"processedItems()\"\n [itemTemplate]=\"itemTemplate()\"\n [motionOptions]=\"computedMotionOptions()\"\n tabindex=\"0\"\n [menuId]=\"$id()\"\n [root]=\"true\"\n [baseZIndex]=\"baseZIndex()\"\n [autoZIndex]=\"autoZIndex()\"\n [mobileActive]=\"mobileActive\"\n [autoDisplay]=\"autoDisplay()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-labelledby]=\"ariaLabelledBy()\"\n [focusedItemId]=\"focused ? focusedItemId : undefined\"\n [submenuiconTemplate]=\"submenuIconTemplate()\"\n [activeItemPath]=\"activeItemPath()\"\n (itemClick)=\"onItemClick($event)\"\n (mousedown)=\"onMenuMouseDown($event)\"\n (focus)=\"onMenuFocus($event)\"\n (blur)=\"onMenuBlur($event)\"\n (keydown)=\"onKeyDown($event)\"\n (itemMouseEnter)=\"onItemMouseEnter($event)\"\n (mouseleave)=\"onMouseLeave($event)\"\n [pt]=\"pt()\"\n [pBind]=\"ptm('rootList')\"\n [unstyled]=\"unstyled()\"\n ></ul>\n @if (endTemplate()) {\n <div [class]=\"cx('end')\" [pBind]=\"ptm('end')\">\n <ng-container *ngTemplateOutlet=\"endTemplate()\"></ng-container>\n </div>\n } @else {\n <div [class]=\"cx('end')\">\n <ng-content></ng-content>\n </div>\n }\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n providers: [MenubarService, MenuBarStyle, { provide: MENUBAR_INSTANCE, useExisting: Menubar }, { provide: PARENT_INSTANCE, useExisting: Menubar }],\n host: {\n '[class]': 'cx(\"root\")'\n },\n hostDirectives: [Bind]\n})\nexport class Menubar extends BaseComponent<MenubarPassThrough> {\n componentName = 'Menubar';\n\n $pcMenubar: Menubar | undefined = inject(MENUBAR_INSTANCE, { optional: true, skipSelf: true }) ?? undefined;\n\n bindDirectiveInstance = inject(Bind, { self: true });\n\n menubarService = inject(MenubarService);\n\n onAfterViewChecked() {\n this.bindDirectiveInstance.setAttrs(this.ptms(['host', 'root']));\n }\n\n /**\n * An array of menuitems.\n * @group Props\n */\n model = input<MenuItem[]>();\n /**\n * Whether to automatically manage layering.\n * @group Props\n */\n autoZIndex = input(true, { transform: booleanAttribute });\n /**\n * Base zIndex value to use in layering.\n * @group Props\n */\n baseZIndex = input(0, { transform: numberAttribute });\n /**\n * Defines the motion options for the submenu open/close animation.\n * @group Props\n */\n motionOptions = input<MotionOptions>();\n /**\n * Whether to show a root submenu on mouse over.\n * @defaultValue true\n * @group Props\n */\n autoDisplay = input(true, { transform: booleanAttribute });\n /**\n * Whether to hide a root submenu when mouse leaves.\n * @group Props\n */\n autoHide = input(undefined, { transform: booleanAttribute });\n /**\n * The breakpoint to define the maximum width boundary.\n * @group Props\n */\n breakpoint = input('960px');\n /**\n * Delay to hide the root submenu in milliseconds when mouse leaves.\n * @group Props\n */\n autoHideDelay = input(100, { transform: numberAttribute });\n /**\n * Current id state as a string.\n * @group Props\n */\n id = input<string>();\n /**\n * Defines a string value that labels an interactive element.\n * @group Props\n */\n ariaLabel = input<string>();\n /**\n * Identifier of the underlying input element.\n * @group Props\n */\n ariaLabelledBy = input<string>();\n /**\n * Callback to execute when button is focused.\n * @param {FocusEvent} event - Focus event.\n * @group Emits\n */\n onFocus = output<FocusEvent>();\n /**\n * Callback to execute when button loses focus.\n * @param {FocusEvent} event - Focus event.\n * @group Emits\n */\n onBlur = output<FocusEvent>();\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 * Custom item template.\n * @param {MenubarItemTemplateContext} context - item context.\n * @see {@link MenubarItemTemplateContext}\n * @group Templates\n */\n itemTemplate = contentChild<TemplateRef<MenubarItemTemplateContext>>('item', { descendants: false });\n /**\n * Defines template option for menu icon.\n * @group Templates\n */\n menuIconTemplate = contentChild<TemplateRef<void>>('menuicon', { descendants: false });\n /**\n * Defines template option for submenu icon.\n * @group Templates\n */\n submenuIconTemplate = contentChild<TemplateRef<void>>('submenuicon', { descendants: false });\n\n menubutton = viewChild<ElementRef>('menubutton');\n\n rootmenu = viewChild<MenubarSub>('rootmenu');\n\n private _internalId = uuid('pn_id_');\n\n $id = computed(() => this.id() || this._internalId);\n\n computedMotionOptions = computed<MotionOptions>(() => {\n return {\n ...this.ptm('motion'),\n ...this.motionOptions()\n };\n });\n\n mobileActive: boolean | undefined;\n\n private matchMediaListener: () => void;\n\n private query: MediaQueryList;\n\n public queryMatches = signal<boolean>(false);\n\n outsideClickListener: VoidListener;\n\n resizeListener: VoidListener;\n\n mouseLeaveSubscriber: Subscription | undefined;\n\n dirty: boolean = false;\n\n focused: boolean = false;\n\n activeItemPath = signal<ProcessedMenuItem[]>([]);\n\n focusedItemInfo = signal<FocusedItemInfo>({ index: -1, level: 0, parentKey: '', item: null });\n\n searchValue: string = '';\n\n searchTimeout: ReturnType<typeof setTimeout> | null = null;\n\n _componentStyle = inject(MenuBarStyle);\n\n processedItems = computed<ProcessedMenuItem[]>(() => this.createProcessedItems(this.model() || []));\n\n get visibleItems(): ProcessedMenuItem[] {\n const processedItem = this.activeItemPath().find((p) => p.key === this.focusedItemInfo().parentKey);\n\n return processedItem ? processedItem.items! : this.processedItems();\n }\n\n get navigationAriaLabel(): string | undefined {\n return this.config.translation?.aria?.navigation;\n }\n\n get focusedItemId() {\n const focusedItem = this.focusedItemInfo();\n return focusedItem.item && focusedItem.item?.id ? focusedItem.item.id : focusedItem.index !== -1 ? `${this.$id()}${isNotEmpty(focusedItem.parentKey) ? '_' + focusedItem.parentKey : ''}_${focusedItem.index}` : null;\n }\n\n constructor() {\n super();\n effect(() => {\n const path = this.activeItemPath();\n\n if (isNotEmpty(path)) {\n this.bindOutsideClickListener();\n this.bindResizeListener();\n } else {\n this.unbindOutsideClickListener();\n this.unbindResizeListener();\n }\n });\n }\n\n onInit() {\n this.bindMatchMediaListener();\n this.menubarService.autoHide = this.autoHide();\n this.menubarService.autoHideDelay = this.autoHideDelay();\n this.mouseLeaveSubscriber = this.menubarService.mouseLeft$.subscribe(() => {\n this.hide();\n });\n }\n\n createProcessedItems(items: MenuItem[], level: number = 0, parent: ProcessedMenuItem | Record<string, never> = {}, parentKey: string = ''): ProcessedMenuItem[] {\n const processedItems: ProcessedMenuItem[] = [];\n\n items &&\n items.forEach((item, index) => {\n const key = (parentKey !== '' ? parentKey + '_' : '') + index;\n const newItem: ProcessedMenuItem = {\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\n return processedItems;\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.set(query.matches);\n\n this.matchMediaListener = () => {\n this.queryMatches.set(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 getItemProp(item: MenuItem, name: string) {\n return item ? resolve(item[name]) : undefined;\n }\n\n menuButtonClick(event: MouseEvent) {\n this.toggle(event);\n }\n\n menuButtonKeydown(event: KeyboardEvent) {\n (event.code === 'Enter' || event.code === 'Space') && this.menuButtonClick(event as unknown as MouseEvent);\n }\n\n onItemClick(event: MenubarItemClickEvent) {\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, level, parentKey, item } = processedItem;\n\n this.activeItemPath.set(this.activeItemPath().filter((p) => key !== p.key && key.startsWith(p.key)));\n this.focusedItemInfo.set({ index, level, parentKey, item });\n\n this.dirty = !root;\n focus(this.rootmenu()?.el.nativeElement);\n } else {\n if (grouped) {\n this.onItemChange(event);\n } else {\n const rootProcessedItem = root ? processedItem : this.activeItemPath().find((p) => p.parentKey === '');\n this.hide(originalEvent);\n this.changeFocusedItemIndex(originalEvent, rootProcessedItem ? rootProcessedItem.index : -1);\n\n this.mobileActive = false;\n focus(this.rootmenu()?.el.nativeElement);\n }\n }\n }\n\n onItemMouseEnter(event: MenubarItemMouseEnterEvent) {\n if (!isTouchDevice()) {\n if (this.dirty) {\n this.onItemChange(event, 'hover');\n }\n } else {\n this.onItemChange({ originalEvent: event.originalEvent, processedItem: event.processedItem, isFocus: this.autoDisplay() }, 'hover');\n }\n }\n\n onMouseLeave(_event: MouseEvent) {\n const autoHideEnabled = this.menubarService.autoHide;\n const autoHideDelay = this.menubarService.autoHideDelay;\n\n if (autoHideEnabled) {\n setTimeout(() => {\n this.menubarService.mouseLeaves.next(true);\n }, autoHideDelay);\n }\n }\n\n changeFocusedItemIndex(_event: Event, index: number) {\n const processedItem = this.findVisibleItem(index);\n if (this.focusedItemInfo().index !== index) {\n const focusedItemInfo = this.focusedItemInfo();\n this.focusedItemInfo.set({ ...focusedItemInfo, item: processedItem?.item ?? null, index });\n this.scrollInView();\n }\n }\n\n scrollInView(index: number = -1) {\n const id = index !== -1 ? `${this.$id()}_${index}` : this.focusedItemId;\n const element = findSingle(this.rootmenu()?.el.nativeElement, `li[id=\"${id}\"]`);\n\n if (element) {\n element.scrollIntoView && element.scrollIntoView({ block: 'nearest', inline: 'nearest' });\n }\n }\n\n onItemChange(event: MenubarItemClickEvent, type?: string) {\n const { processedItem, isFocus } = event;\n\n if (isEmpty(processedItem)) return;\n\n const { index, key, level, parentKey, items, item } = processedItem;\n const grouped = isNotEmpty(items);\n const activeItemPath = this.activeItemPath().filter((p) => p.parentKey !== parentKey && p.parentKey !== key);\n\n grouped && activeItemPath.push(processedItem);\n this.focusedItemInfo.set({ index, level, parentKey, item });\n\n grouped && (this.dirty = true);\n isFocus && focus(this.rootmenu()?.el.nativeElement);\n\n if (type === 'hover' && this.queryMatches()) {\n return;\n }\n\n this.activeItemPath.set(activeItemPath);\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 hide(_event?: Event, isFocus?: boolean) {\n if (this.mobileActive) {\n setTimeout(() => {\n focus(this.menubutton()?.nativeElement);\n }, 0);\n }\n\n this.activeItemPath.set([]);\n this.focusedItemInfo.set({ index: -1, level: 0, parentKey: '', item: null });\n\n isFocus && focus(this.rootmenu()?.el.nativeElement);\n this.dirty = false;\n }\n\n show() {\n const processedItem = this.findVisibleItem(this.findFirstFocusedItemIndex());\n this.focusedItemInfo.set({ index: this.findFirstFocusedItemIndex(), level: 0, parentKey: '', item: processedItem?.item ?? null });\n focus(this.rootmenu()?.el.nativeElement);\n }\n\n onMenuMouseDown(_event: MouseEvent) {\n this.dirty = true;\n }\n\n onMenuFocus(event: FocusEvent) {\n this.focused = true;\n\n const relatedTarget = event.relatedTarget;\n const isFromOutside = !relatedTarget || !this.el.nativeElement.contains(relatedTarget);\n\n if (isFromOutside && this.focusedItemInfo().index === -1 && !this.activeItemPath().length && !this.dirty) {\n const processedItem = this.findVisibleItem(this.findFirstFocusedItemIndex());\n this.focusedItemInfo.set({ index: this.findFirstFocusedItemIndex(), level: 0, parentKey: '', item: processedItem?.item ?? null });\n }\n\n this.onFocus.emit(event);\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 = false;\n this.focusedItemInfo.set({ index: -1, level: 0, parentKey: '', item: null });\n this.searchValue = '';\n this.dirty = false;\n this.onBlur.emit(event);\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 findVisibleItem(index: number): ProcessedMenuItem | null {\n return isNotEmpty(this.visibleItems) ? this.visibleItems[index] : null;\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: ProcessedMenuItem): boolean {\n return processedItem && isNotEmpty(processedItem.items);\n }\n\n isSelected(processedItem: ProcessedMenuItem): boolean {\n return this.activeItemPath().some((p) => p.key === processedItem.key);\n }\n\n isValidSelectedItem(processedItem: ProcessedMenuItem): boolean {\n return this.isValidItem(processedItem) && this.isSelected(processedItem);\n }\n\n isValidItem(processedItem: ProcessedMenuItem): boolean {\n return !!processedItem && !this.isItemDisabled(processedItem.item) && !this.isItemSeparator(processedItem.item);\n }\n\n isItemDisabled(item: MenuItem): boolean {\n return this.getItemProp(item, 'disabled');\n }\n\n isItemSeparator(item: MenuItem): boolean {\n return this.getItemProp(item, 'separator');\n }\n\n isItemMatched(processedItem: ProcessedMenuItem): boolean {\n return this.isValidItem(processedItem) && !!this.getProccessedItemLabel(processedItem)?.toLocaleLowerCase().startsWith(this.searchValue.toLocaleLowerCase());\n }\n\n isProccessedItemGroup(processedItem: ProcessedMenuItem): boolean {\n return processedItem && isNotEmpty(processedItem.items);\n }\n\n searchItems(event: KeyboardEvent, 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.changeFocusedItemIndex(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: ProcessedMenuItem): string | undefined {\n return processedItem ? this.getItemLabel(processedItem.item) : undefined;\n }\n\n getItemLabel(item: MenuItem): string {\n return this.getItemProp(item, 'label');\n }\n\n onArrowDownKey(event: KeyboardEvent) {\n const processedItem = this.visibleItems[this.focusedItemInfo().index];\n const root = processedItem ? isEmpty(processedItem.parent) : null;\n\n if (root) {\n const grouped = this.isProccessedItemGroup(processedItem);\n\n if (grouped) {\n this.onItemChange({ originalEvent: event, processedItem });\n this.focusedItemInfo.set({ index: -1, level: processedItem.level + 1, parentKey: processedItem.key, item: processedItem.item });\n this.onArrowRightKey(event);\n }\n } else {\n const itemIndex = this.focusedItemInfo().index !== -1 ? this.findNextItemIndex(this.focusedItemInfo().index) : this.findFirstFocusedItemIndex();\n\n this.changeFocusedItemIndex(event, itemIndex);\n event.preventDefault();\n }\n }\n\n onArrowRightKey(event: KeyboardEvent) {\n const processedItem = this.visibleItems[this.focusedItemInfo().index];\n const parentItem = processedItem ? this.activeItemPath().find((p) => p.key === processedItem.parentKey) : null;\n\n if (parentItem) {\n const grouped = this.isProccessedItemGroup(processedItem);\n\n if (grouped) {\n this.onItemChange({ originalEvent: event, processedItem });\n this.focusedItemInfo.set({ index: -1, level: processedItem.level + 1, parentKey: processedItem.key, item: processedItem.item });\n this.onArrowDownKey(event);\n }\n } else {\n const itemIndex = this.focusedItemInfo().index !== -1 ? this.findNextItemIndex(this.focusedItemInfo().index) : this.findFirstFocusedItemIndex();\n\n this.changeFocusedItemIndex(event, itemIndex);\n event.preventDefault();\n }\n }\n\n onArrowUpKey(event: KeyboardEvent) {\n const processedItem = this.visibleItems[this.focusedItemInfo().index];\n const root = isEmpty(processedItem.parent);\n\n if (root) {\n const grouped = this.isProccessedItemGroup(processedItem);\n\n if (grouped) {\n this.onItemChange({ originalEvent: event, processedItem });\n this.focusedItemInfo.set({ index: -1, level: processedItem.level + 1, parentKey: processedItem.key, item: processedItem.item });\n const itemIndex = this.findLastItemIndex();\n\n this.changeFocusedItemIndex(event, itemIndex);\n }\n } else {\n const