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 106 kB
{"version":3,"file":"primeng-tieredmenu.mjs","sources":["../../src/tieredmenu/style/tieredmenustyle.ts","../../src/tieredmenu/tieredmenu-token.ts","../../src/tieredmenu/tieredmenu-sub.ts","../../src/tieredmenu/tieredmenu.ts","../../src/tieredmenu/primeng-tieredmenu.ts"],"sourcesContent":["import { Injectable } from '@angular/core';\nimport { style } from '@primeuix/styles/tieredmenu';\nimport { BaseStyle } from 'primeng/base';\n\nconst inlineStyles = {\n submenu: ({ instance, processedItem }) => ({ display: instance.isItemActive(processedItem) ? 'flex' : 'none' })\n};\n\nconst classes = {\n root: ({ instance }) => [\n 'p-tieredmenu p-component',\n {\n 'p-tieredmenu-overlay': instance.popup(),\n 'p-tieredmenu-mobile': instance.queryMatches()\n }\n ],\n start: 'p-tieredmenu-start',\n rootList: 'p-tieredmenu-root-list',\n item: ({ instance, processedItem }) => [\n 'p-tieredmenu-item',\n {\n 'p-tieredmenu-item-active': instance.isItemActive(processedItem),\n 'p-focus': instance.isItemFocused(processedItem),\n 'p-disabled': instance.isItemDisabled(processedItem)\n }\n ],\n itemContent: 'p-tieredmenu-item-content',\n itemLink: 'p-tieredmenu-item-link',\n itemIcon: 'p-tieredmenu-item-icon',\n itemLabel: 'p-tieredmenu-item-label',\n itemBadge: 'p-menuitem-badge',\n submenuIcon: 'p-tieredmenu-submenu-icon',\n submenu: 'p-tieredmenu-submenu',\n separator: 'p-tieredmenu-separator',\n end: 'p-tieredmenu-end'\n};\n\n@Injectable()\nexport class TieredMenuStyle extends BaseStyle {\n name = 'tieredmenu';\n\n style = style;\n\n classes = classes;\n\n inlineStyles = inlineStyles;\n}\n\n/**\n *\n * TieredMenu displays submenus in nested overlays.\n *\n * [Live Demo](https://www.primeng.org/menu/)\n *\n * @module tieredmenustyle\n *\n */\nexport enum TieredMenuClasses {\n /**\n * Class name of the root element\n */\n root = 'p-tieredmenu',\n /**\n * Class name of the start element\n */\n start = 'p-tieredmenu-start',\n /**\n * Class name of the root list element\n */\n rootList = 'p-tieredmenu-root-list',\n /**\n * Class name of the item element\n */\n item = 'p-tieredmenu-item',\n /**\n * Class name of the item content element\n */\n itemContent = 'p-tieredmenu-item-content',\n /**\n * Class name of the item link element\n */\n itemLink = 'p-tieredmenu-item-link',\n /**\n * Class name of the item icon element\n */\n itemIcon = 'p-tieredmenu-item-icon',\n /**\n * Class name of the item label element\n */\n itemLabel = 'p-tieredmenu-item-label',\n /**\n * Class name of the submenu icon element\n */\n submenuIcon = 'p-tieredmenu-submenu-icon',\n /**\n * Class name of the submenu element\n */\n submenu = 'p-tieredmenu-submenu',\n /**\n * Class name of the separator element\n */\n separator = 'p-tieredmenu-separator',\n /**\n * Class name of the end element\n */\n end = 'p-tieredmenu-end'\n}\n\nexport interface TieredMenuStyle extends BaseStyle {}\n","import { InjectionToken } from '@angular/core';\n\nexport const TIEREDMENU_INSTANCE = new InjectionToken<any>('TIEREDMENU_INSTANCE');\nexport const TIEREDMENUSUB_INSTANCE = new InjectionToken<any>('TIEREDMENUSUB_INSTANCE');\n","import { isPlatformBrowser, NgTemplateOutlet } from '@angular/common';\nimport { booleanAttribute, Component, effect, ElementRef, inject, input, numberAttribute, output, signal, TemplateRef, viewChild, ViewEncapsulation } from '@angular/core';\nimport { RouterModule } from '@angular/router';\nimport { MotionEvent, MotionOptions } from '@primeuix/motion';\nimport { isNotEmpty, nestedPosition, resolve } from '@primeuix/utils';\nimport { MenuItem, SharedModule } from 'primeng/api';\nimport { BaseComponent, PARENT_INSTANCE } from 'primeng/basecomponent';\nimport { Bind, BindModule } from 'primeng/bind';\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 type { CSSProperties } from 'primeng/types/shared';\nimport { TieredMenuItemClickEvent, TieredMenuItemMouseEnterEvent, TieredMenuItemTemplateContext, TieredMenuPassThrough, TieredMenuProcessedItem } from 'primeng/types/tieredmenu';\nimport { TieredMenuStyle } from './style/tieredmenustyle';\nimport type { TieredMenu } from './tieredmenu';\nimport { TIEREDMENU_INSTANCE, TIEREDMENUSUB_INSTANCE } from './tieredmenu-token';\n\n@Component({\n selector: 'p-tieredmenusub',\n standalone: true,\n imports: [NgTemplateOutlet, RouterModule, Ripple, TooltipModule, AngleRightIcon, SharedModule, BindModule, MotionModule],\n template: `\n @if (render()) {\n <ul\n #sublist\n role=\"menu\"\n [class]=\"getListClass()\"\n [id]=\"menuId() + '_list'\"\n [tabindex]=\"tabindex()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-labelledBy]=\"ariaLabelledBy()\"\n [attr.aria-activedescendant]=\"focusedItemId()\"\n [attr.aria-orientation]=\"'vertical'\"\n [pBind]=\"_ptm(getListSection())\"\n (keydown)=\"menuKeydown.emit($event)\"\n (focus)=\"menuFocus.emit($event)\"\n (blur)=\"menuBlur.emit($event)\"\n [style]=\"inlineStyles()\"\n [pMotion]=\"getMotionState()\"\n [pMotionDisabled]=\"root() || tieredMenu.queryMatches()\"\n [pMotionAppear]=\"true\"\n [pMotionName]=\"'p-anchored-overlay'\"\n [pMotionOptions]=\"motionOptions()\"\n (pMotionOnBeforeEnter)=\"onBeforeEnter($event)\"\n (pMotionOnAfterLeave)=\"onAfterLeave()\"\n >\n @for (processedItem of items(); track processedItem.key; let index = $index) {\n @if (isSeparator(processedItem)) {\n <li\n [attr.id]=\"getItemId(processedItem)\"\n [style]=\"getItemProp(processedItem, 'style')\"\n [class]=\"cn(cx('separator'), getItemProp(processedItem, 'class'), getItemProp(processedItem, 'styleClass'))\"\n role=\"separator\"\n [pBind]=\"_ptm('separator')\"\n ></li>\n } @else if (isMenuItem(processedItem)) {\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]=\"getAriaDisabled(processedItem)\"\n [attr.aria-haspopup]=\"getAriaHaspopup(processedItem)\"\n [attr.aria-expanded]=\"getAriaExpanded(processedItem)\"\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 [pBind]=\"getPTOptions(processedItem, index, 'item')\"\n [pTooltip]=\"getItemProp(processedItem, 'tooltip')\"\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 (!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 [pBind]=\"getPTOptions(processedItem, index, 'itemIcon')\"\n [attr.tabindex]=\"-1\"\n ></span>\n }\n @if (shouldEscapeLabel(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 <span [class]=\"cn(cx('itemBadge'), getItemProp(processedItem, 'badgeStyleClass'))\">{{ getItemProp(processedItem, 'badge') }}</span>\n }\n @if (isItemGroup(processedItem)) {\n @if (!tieredMenu.submenuIconTemplate()) {\n <svg data-p-icon=\"angle-right\" [class]=\"cx('submenuIcon')\" [pBind]=\"getPTOptions(processedItem, index, 'submenuIcon')\" [attr.aria-hidden]=\"true\" />\n }\n <ng-container *ngTemplateOutlet=\"tieredMenu.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-tieredmenu-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 [pBind]=\"getPTOptions(processedItem, index, 'itemIcon')\"\n [attr.aria-hidden]=\"true\"\n [attr.tabindex]=\"-1\"\n ></span>\n }\n @if (shouldEscapeLabel(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 <span [class]=\"cn(cx('itemBadge'), getItemProp(processedItem, 'badgeStyleClass'))\">{{ getItemProp(processedItem, 'badge') }}</span>\n }\n @if (isItemGroup(processedItem)) {\n @if (!tieredMenu.submenuIconTemplate()) {\n <svg data-p-icon=\"angle-right\" [class]=\"cx('submenuIcon')\" [pBind]=\"getPTOptions(processedItem, index, 'submenuIcon')\" [attr.aria-hidden]=\"true\" />\n }\n <ng-container *ngTemplateOutlet=\"tieredMenu.submenuIconTemplate()\"></ng-container>\n }\n </a>\n }\n } @else {\n <ng-container *ngTemplateOutlet=\"itemTemplate(); context: getItemTemplateContext(processedItem)\"></ng-container>\n }\n </div>\n\n @if (shouldShowSubmenu(processedItem)) {\n <p-tieredmenusub\n [items]=\"processedItem.items\"\n [itemTemplate]=\"itemTemplate()\"\n [autoDisplay]=\"autoDisplay()\"\n [menuId]=\"menuId()\"\n [visible]=\"isSubmenuVisible(processedItem)\"\n [activeItemPath]=\"activeItemPath()\"\n [focusedItemId]=\"focusedItemId()\"\n [ariaLabelledBy]=\"getItemId(processedItem)\"\n [level]=\"level() + 1\"\n (itemClick)=\"itemClick.emit($event)\"\n (itemMouseEnter)=\"onItemMouseEnter($event)\"\n [pt]=\"pt()\"\n [motionOptions]=\"motionOptions()\"\n [unstyled]=\"unstyled()\"\n ></p-tieredmenusub>\n }\n </li>\n }\n }\n </ul>\n }\n `,\n encapsulation: ViewEncapsulation.None,\n providers: [\n { provide: TIEREDMENUSUB_INSTANCE, useExisting: TieredMenuSub },\n { provide: PARENT_INSTANCE, useExisting: TieredMenuSub }\n ],\n hostDirectives: [Bind]\n})\nexport class TieredMenuSub extends BaseComponent<TieredMenuPassThrough> {\n visible = input(false);\n\n items = input<TieredMenuProcessedItem[]>([]);\n\n itemTemplate = input<TemplateRef<TieredMenuItemTemplateContext>>();\n\n root = input(false, { transform: booleanAttribute });\n\n autoDisplay = input(false, { transform: booleanAttribute });\n\n autoZIndex = input(true, { transform: booleanAttribute });\n\n baseZIndex = input(0, { transform: numberAttribute });\n\n popup = input(false, { 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<TieredMenuProcessedItem[]>([]);\n\n motionOptions = input<MotionOptions[]>();\n\n tabindex = input(0, { transform: numberAttribute });\n\n inlineStyles = input<CSSProperties>();\n\n itemClick = output<TieredMenuItemClickEvent>();\n\n itemMouseEnter = output<TieredMenuItemMouseEnterEvent>();\n\n menuFocus = output<FocusEvent>();\n\n menuBlur = output<FocusEvent>();\n\n menuKeydown = output<KeyboardEvent>();\n\n sublistViewChild = viewChild<ElementRef>('sublist');\n\n render = signal<boolean>(false);\n\n _componentStyle = inject(TieredMenuStyle);\n\n bindDirectiveInstance = inject(Bind, { self: true });\n\n $pcTieredMenu: TieredMenu | undefined = inject(TIEREDMENU_INSTANCE, { optional: true, skipSelf: true }) ?? undefined;\n\n $pcTieredMenuSub: TieredMenuSub | undefined = inject(TIEREDMENUSUB_INSTANCE, { optional: true, skipSelf: true }) ?? undefined;\n\n tieredMenu = inject<TieredMenu>(TIEREDMENU_INSTANCE);\n\n constructor() {\n super();\n effect(() => {\n const isVisible = this.visible();\n if (isVisible || this.root()) {\n this.render.set(true);\n }\n });\n }\n\n onAfterViewChecked() {\n this.bindDirectiveInstance.setAttrs(this.ptms(['host']));\n }\n\n positionSubmenu(sublist: HTMLElement) {\n if (isPlatformBrowser(this.tieredMenu.platformId)) {\n if (sublist) {\n nestedPosition(sublist, this.level());\n }\n }\n }\n\n getItemProp(processedItem: TieredMenuProcessedItem, name: string, params: Record<string, unknown> | null = null) {\n return processedItem && processedItem.item ? resolve(processedItem.item[name as keyof MenuItem], params) : undefined;\n }\n\n getItemId(processedItem: TieredMenuProcessedItem): string {\n return processedItem.item?.id ?? `${this.menuId()}_${processedItem.key}`;\n }\n\n getItemKey(processedItem: TieredMenuProcessedItem): string {\n return this.getItemId(processedItem);\n }\n\n getItemLabel(processedItem: TieredMenuProcessedItem): string {\n return this.getItemProp(processedItem, 'label') as string;\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) => {\n const isItemVisible = this.isItemVisible(processedItem);\n const isVisibleSeparator = isItemVisible && this.getItemProp(processedItem, 'separator');\n return !isItemVisible || isVisibleSeparator;\n }).length +\n 1\n );\n }\n\n isItemVisible(processedItem: TieredMenuProcessedItem): boolean {\n return this.getItemProp(processedItem, 'visible') !== false;\n }\n\n isItemActive(processedItem: TieredMenuProcessedItem): boolean {\n if (this.activeItemPath()) {\n return this.activeItemPath().some((path) => path.key === processedItem.key);\n }\n return false;\n }\n\n isItemDisabled(processedItem: TieredMenuProcessedItem): boolean {\n return !!this.getItemProp(processedItem, 'disabled');\n }\n\n isItemFocused(processedItem: TieredMenuProcessedItem): boolean {\n return this.focusedItemId() === this.getItemId(processedItem);\n }\n\n isItemGroup(processedItem: TieredMenuProcessedItem): boolean {\n return isNotEmpty(processedItem.items);\n }\n\n hasIcon(processedItem: TieredMenuProcessedItem): boolean {\n return !!this.getItemProp(processedItem, 'icon');\n }\n\n hasBadge(processedItem: TieredMenuProcessedItem): boolean {\n return !!this.getItemProp(processedItem, 'badge');\n }\n\n shouldEscapeLabel(processedItem: TieredMenuProcessedItem): boolean {\n return this.getItemProp(processedItem, 'escape') !== false;\n }\n\n getRouterLinkActiveOptions(processedItem: TieredMenuProcessedItem) {\n return this.getItemProp(processedItem, 'routerLinkActiveOptions') || { exact: false };\n }\n\n getItemTemplateContext(processedItem: TieredMenuProcessedItem): TieredMenuItemTemplateContext {\n return { $implicit: processedItem.item, hasSubmenu: !!this.getItemProp(processedItem, 'items') };\n }\n\n getAriaHaspopup(processedItem: TieredMenuProcessedItem) {\n return this.isItemGroup(processedItem) && !this.getItemProp(processedItem, 'to') ? 'menu' : undefined;\n }\n\n getAriaExpanded(processedItem: TieredMenuProcessedItem) {\n return this.isItemGroup(processedItem) ? this.isItemActive(processedItem) : undefined;\n }\n\n shouldShowSubmenu(processedItem: TieredMenuProcessedItem): boolean {\n return this.isItemVisible(processedItem) && this.isItemGroup(processedItem);\n }\n\n isSubmenuVisible(processedItem: TieredMenuProcessedItem): boolean {\n return this.isItemActive(processedItem) && this.isItemGroup(processedItem);\n }\n\n hasRouterLink(processedItem: TieredMenuProcessedItem): boolean {\n return !!this.getItemProp(processedItem, 'routerLink');\n }\n\n isSeparator(processedItem: TieredMenuProcessedItem): boolean {\n return this.isItemVisible(processedItem) && !!this.getItemProp(processedItem, 'separator');\n }\n\n isMenuItem(processedItem: TieredMenuProcessedItem): boolean {\n return this.isItemVisible(processedItem) && !this.getItemProp(processedItem, 'separator');\n }\n\n getListClass() {\n return this.root() ? this.cx('rootList') : this.cx('submenu');\n }\n\n getListSection() {\n return this.root() ? 'rootList' : 'submenu';\n }\n\n getMotionState() {\n return this.root() ? true : this.visible();\n }\n\n getAriaDisabled(processedItem: TieredMenuProcessedItem) {\n return this.isItemDisabled(processedItem) || undefined;\n }\n\n // TODO: will be removed later. Helper method to get PT from parent ContextMenu if available, otherwise use own PT\n _ptm(section: string, options?: Record<string, unknown>) {\n return this.$pcTieredMenu ? this.$pcTieredMenu.ptm(section, options) : this.ptm(section, options);\n }\n\n getPTOptions(processedItem: TieredMenuProcessedItem, 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 }\n });\n }\n\n onItemMouseEnter(param: { $event: Event; processedItem: TieredMenuProcessedItem }) {\n if (this.autoDisplay()) {\n const { $event, processedItem } = param;\n this.itemMouseEnter.emit({ originalEvent: $event, processedItem });\n }\n }\n\n onItemClick(event: Event, processedItem: TieredMenuProcessedItem) {\n this.getItemProp(processedItem, 'command', { originalEvent: event, item: processedItem.item });\n this.itemClick.emit({ originalEvent: event, processedItem, isFocus: true });\n }\n\n onBeforeEnter(event: MotionEvent) {\n this.positionSubmenu(event.element as HTMLElement);\n }\n\n onAfterLeave() {\n this.render.set(false);\n }\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport { booleanAttribute, ChangeDetectionStrategy, Component, computed, contentChild, effect, ElementRef, inject, input, NgModule, numberAttribute, output, signal, TemplateRef, viewChild, ViewEncapsulation, ViewRef } from '@angular/core';\nimport { MotionEvent, MotionOptions } from '@primeuix/motion';\nimport { absolutePosition, addStyle, appendChild, findLastIndex, findSingle, focus, getOuterWidth, isEmpty, isNotEmpty, isPrintableCharacter, isTouchDevice, relativePosition, resolve, uuid } from '@primeuix/utils';\nimport { MenuItem, OverlayService, SharedModule } from 'primeng/api';\nimport { BaseComponent, PARENT_INSTANCE } from 'primeng/basecomponent';\nimport { Bind, BindModule } from 'primeng/bind';\nimport { ConnectedOverlayScrollHandler } from 'primeng/dom';\nimport { MotionModule } from 'primeng/motion';\nimport type { AppendTo, CSSProperties } from 'primeng/types/shared';\nimport { Nullable, VoidListener } from 'primeng/ts-helpers';\nimport { TieredMenuFocusedItemInfo, TieredMenuItemClickEvent, TieredMenuItemMouseEnterEvent, TieredMenuItemTemplateContext, TieredMenuPassThrough, TieredMenuProcessedItem, TieredMenuToggleEvent } from 'primeng/types/tieredmenu';\nimport { ZIndexUtils } from 'primeng/utils';\nimport { TieredMenuStyle } from './style/tieredmenustyle';\nimport { TieredMenuSub } from './tieredmenu-sub';\nimport { TIEREDMENU_INSTANCE } from './tieredmenu-token';\n\n/**\n * TieredMenu displays submenus in nested overlays.\n * @group Components\n */\n@Component({\n selector: 'p-tieredmenu, p-tiered-menu',\n standalone: true,\n imports: [TieredMenuSub, SharedModule, BindModule, MotionModule],\n template: `\n @if (shouldRender()) {\n <div\n #container\n [id]=\"$id()\"\n [class]=\"cn(cx('root'), styleClass())\"\n [style]=\"style()\"\n [pBind]=\"ptm('root')\"\n (click)=\"onOverlayClick($event)\"\n [pMotion]=\"shouldAnimate()\"\n [pMotionName]=\"'p-anchored-overlay'\"\n [pMotionAppear]=\"true\"\n [pMotionDisabled]=\"!popup()\"\n [pMotionOptions]=\"computedMotionOptions()\"\n (pMotionOnBeforeEnter)=\"onOverlayBeforeEnter($event)\"\n (pMotionOnAfterEnter)=\"onOverlayAfterEnter()\"\n (pMotionOnAfterLeave)=\"onOverlayAfterLeave()\"\n >\n <p-tieredmenusub\n #rootmenu\n [root]=\"true\"\n [visible]=\"true\"\n [items]=\"processedItems\"\n [itemTemplate]=\"itemTemplate()\"\n [menuId]=\"$id()\"\n [tabindex]=\"getMenuTabindex()\"\n [ariaLabel]=\"ariaLabel()\"\n [ariaLabelledBy]=\"ariaLabelledBy()\"\n [baseZIndex]=\"baseZIndex()\"\n [autoZIndex]=\"autoZIndex()\"\n [autoDisplay]=\"autoDisplay()\"\n [popup]=\"popup()\"\n [focusedItemId]=\"getFocusedItemIdValue()\"\n [activeItemPath]=\"activeItemPath()\"\n (itemClick)=\"onItemClick($event)\"\n (menuFocus)=\"onMenuFocus($event)\"\n (menuBlur)=\"onMenuBlur($event)\"\n (menuKeydown)=\"onKeyDown($event)\"\n (itemMouseEnter)=\"onItemMouseEnter($event)\"\n [pt]=\"pt()\"\n [unstyled]=\"unstyled()\"\n [motionOptions]=\"computedMotionOptions()\"\n ></p-tieredmenusub>\n </div>\n }\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n providers: [TieredMenuStyle, { provide: TIEREDMENU_INSTANCE, useExisting: TieredMenu }, { provide: PARENT_INSTANCE, useExisting: TieredMenu }],\n hostDirectives: [Bind]\n})\nexport class TieredMenu extends BaseComponent<TieredMenuPassThrough> {\n componentName = 'TieredMenu';\n\n /**\n * An array of menuitems.\n * @group Props\n */\n model = input<MenuItem[]>();\n /**\n * Defines if menu would displayed as a popup.\n * @group Props\n */\n popup = input(false, { transform: booleanAttribute });\n /**\n * Inline style of the component.\n * @group Props\n */\n style = input<CSSProperties>();\n /**\n * Style class of the component.\n * @group Props\n */\n styleClass = input<string>();\n /**\n * The breakpoint to define the maximum width boundary.\n * @group Props\n */\n breakpoint = input('960px');\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 * Whether to show a root submenu on mouse over.\n * @defaultValue true\n * @group Props\n */\n autoDisplay = input(true, { transform: booleanAttribute });\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 * When present, it specifies that the component should be disabled.\n * @group Props\n */\n disabled = input(false, { transform: booleanAttribute });\n /**\n * Index of the element in tabbing order.\n * @group Props\n */\n tabindex = input(0, { transform: numberAttribute });\n /**\n * Target element to attach the overlay, valid values are \"body\" or a local ng-template variable of another element (note: use binding with brackets for template variables, e.g. [appendTo]=\"mydiv\" for a div element having #mydiv as variable name).\n * @defaultValue 'self'\n * @group Props\n */\n appendTo = input<AppendTo>();\n /**\n * The motion options.\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 * Callback to invoke when overlay menu is shown.\n * @group Emits\n */\n onShow = output<Record<string, never>>();\n /**\n * Callback to invoke when overlay menu is hidden.\n * @group Emits\n */\n onHide = output<Record<string, never>>();\n\n rootmenu = viewChild<TieredMenuSub>('rootmenu');\n\n containerViewChild = viewChild<ElementRef<HTMLDivElement>>('container');\n /**\n * Custom submenu icon template.\n * @group Templates\n */\n submenuIconTemplate = contentChild<TemplateRef<void>>('submenuicon', { descendants: false });\n /**\n * Custom item template.\n * @param {TieredMenuItemTemplateContext} context - item context.\n * @see {@link TieredMenuItemTemplateContext}\n * @group Templates\n */\n itemTemplate = contentChild<TemplateRef<TieredMenuItemTemplateContext>>('item', { descendants: false });\n\n $appendTo = computed(() => this.appendTo() || this.config.overlayAppendTo());\n\n render = signal<boolean>(false);\n\n container: HTMLElement | undefined;\n\n outsideClickListener: VoidListener;\n\n resizeListener: VoidListener;\n\n scrollHandler: Nullable<ConnectedOverlayScrollHandler>;\n\n target: HTMLElement | null = null;\n\n relatedTarget: HTMLElement | null = null;\n\n visible: boolean | undefined;\n\n dirty: boolean = false;\n\n focused: boolean = false;\n\n activeItemPath = signal<TieredMenuProcessedItem[]>([]);\n\n focusedItemInfo = signal<TieredMenuFocusedItemInfo>({ index: -1, level: 0, parentKey: '', item: null });\n\n searchValue: string = '';\n\n searchTimeout: ReturnType<typeof setTimeout> | undefined;\n\n _processedItems: TieredMenuProcessedItem[] = [];\n\n _componentStyle = inject(TieredMenuStyle);\n\n bindDirectiveInstance = inject(Bind, { self: true });\n\n overlayService = inject(OverlayService);\n\n private matchMediaListener: (() => void) | null = null;\n\n private query: MediaQueryList;\n\n public queryMatches = signal<boolean>(false);\n\n private _internalId = uuid('pn_id_');\n\n $id = computed(() => this.id() || this._internalId);\n\n shouldRender(): boolean {\n return this.render() || !this.popup();\n }\n\n shouldAnimate(): boolean {\n return this.visible || !this.popup();\n }\n\n getMenuTabindex() {\n return !this.disabled() ? this.tabindex() : -1;\n }\n\n getFocusedItemIdValue() {\n return this.focused ? this.focusedItemId : undefined;\n }\n\n get visibleItems(): TieredMenuProcessedItem[] {\n const processedItem = this.activeItemPath().find((p: TieredMenuProcessedItem) => p.key === this.focusedItemInfo().parentKey);\n return processedItem ? processedItem.items : 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 focusedItemInfo = this.focusedItemInfo();\n return focusedItemInfo.item?.id ? focusedItemInfo.item.id : focusedItemInfo.index !== -1 ? `${this.$id()}${isNotEmpty(focusedItemInfo.parentKey) ? '_' + focusedItemInfo.parentKey : ''}_${focusedItemInfo.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 effect(() => {\n const value = this.model();\n if (value) {\n this._processedItems = this.createProcessedItems(value);\n }\n });\n }\n\n onAfterViewChecked() {\n this.bindDirectiveInstance.setAttrs(this.ptms(['host', 'root']));\n }\n\n onInit() {\n this.bindMatchMediaListener();\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 };\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: MenuItem[] | undefined, level: number = 0, parent: TieredMenuProcessedItem | Record<string, never> = {}, parentKey: string = ''): TieredMenuProcessedItem[] {\n const processedItems: TieredMenuProcessedItem[] = [];\n\n items &&\n items.forEach((item: MenuItem, index: number) => {\n const key = (parentKey !== '' ? parentKey + '_' : '') + index;\n const newItem: TieredMenuProcessedItem = {\n item,\n index,\n level,\n key,\n parent,\n parentKey,\n items: []\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 getItemProp(item: MenuItem, name: keyof MenuItem) {\n return item ? resolve(item[name]) : undefined;\n }\n\n getProccessedItemLabel(processedItem: TieredMenuProcessedItem) {\n return processedItem ? this.getItemLabel(processedItem.item) : undefined;\n }\n\n getItemLabel(item: MenuItem) {\n return this.getItemProp(item, 'label');\n }\n\n isProcessedItemGroup(processedItem: TieredMenuProcessedItem): boolean {\n return processedItem && isNotEmpty(processedItem.items);\n }\n\n isSelected(processedItem: TieredMenuProcessedItem): boolean {\n return this.activeItemPath().some((p: TieredMenuProcessedItem) => p.key === processedItem.key);\n }\n\n isValidSelectedItem(processedItem: TieredMenuProcessedItem): boolean {\n return this.isValidItem(processedItem) && this.isSelected(processedItem);\n }\n\n isValidItem(processedItem: TieredMenuProcessedItem): boolean {\n return !!processedItem && !this.isItemDisabled(processedItem.item) && !this.isItemSeparator(processedItem.item) && this.isItemVisible(processedItem.item);\n }\n\n isItemDisabled(item: MenuItem): boolean {\n return !!this.getItemProp(item, 'disabled');\n }\n\n isItemVisible(item: MenuItem): boolean {\n return this.getItemProp(item, 'visible') !== false;\n }\n\n isItemSeparator(item: MenuItem): boolean {\n return !!this.getItemProp(item, 'separator');\n }\n\n isItemMatched(processedItem: TieredMenuProcessedItem): boolean {\n const label = this.getProccessedItemLabel(processedItem);\n return this.isValidItem(processedItem) && !!label && label.toLocaleLowerCase().startsWith(this.searchValue.toLocaleLowerCase());\n }\n\n isProccessedItemGroup(processedItem: TieredMenuProcessedItem): boolean {\n return processedItem && isNotEmpty(processedItem.items);\n }\n\n onOverlayClick(event: MouseEvent) {\n if (this.popup()) {\n this.overlayService.add({\n originalEvent: event,\n target: this.el.nativeElement\n });\n }\n }\n\n onItemClick(event: TieredMenuItemClickEvent) {\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: TieredMenuProcessedItem) => key !== p.key && key.startsWith(p.key)));\n this.focusedItemInfo.set({ index, level, parentKey, item });\n\n this.dirty = true;\n focus(this.rootmenu()?.sublistViewChild()?.nativeElement);\n } else {\n if (grouped) {\n this.onItemChange(event);\n } else {\n const rootProcessedItem = root ? processedItem : this.activeItemPath().find((p: TieredMenuProcessedItem) => p.parentKey === '');\n this.hide(originalEvent);\n this.changeFocusedItemIndex(originalEvent, rootProcessedItem?.index ?? -1);\n\n focus(this.rootmenu()?.sublistViewChild()?.nativeElement);\n }\n }\n }\n\n onItemMouseEnter(event: TieredMenuItemMouseEnterEvent) {\n if (!isTouchDevice()) {\n if (this.dirty) {\n this.onItemChange(event, 'hover');\n }\n } else {\n this.onItemChange({ ...event, focus: this.autoDisplay() }, 'hover');\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 onArrowDownKey(event: KeyboardEvent) {\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 onArrowRightKey(event: KeyboardEvent) {\n const processedItem = this.visibleItems[this.focusedItemInfo().index];\n const grouped = this.isProccessedItemGroup(processedItem);\n const item = processedItem?.item;\n\n if (grouped) {\n this.onItemChange({ originalEvent: event, processedItem });\n this.focusedItemInfo.set({ index: -1, parentKey: processedItem.key, item });\n this.searchValue = '';\n this.onArrowDownKey(event);\n }\n\n event.preventDefault();\n }\n\n onArrowUpKey(event: KeyboardEvent) {\n if (event.altKey) {\n if (this.focusedItemInfo().index !== -1) {\n const processedItem = this.visibleItems[this.focusedItemInfo().index];\n const grouped = this.isProccessedItemGroup(processedItem);\n\n !grouped && this.onItemChange({ originalEvent: event, processedItem });\n }\n\n this.popup() && this.hide(event, true);\n event.preventDefault();\n } else {\n const itemIndex = this.focusedItemInfo().index !== -1 ? this.findPrevItemIndex(this.focusedItemInfo().index) : this.findLastFocusedItemIndex();\n\n this.changeFocusedItemIndex(event, itemIndex);\n event.preventDefault();\n }\n }\n\n onArrowLeftKey(event: KeyboardEvent) {\n const processedItem = this.visibleItems[this.focusedItemInfo().index];\n if (!processedItem) {\n event.preventDefault();\n return;\n }\n const parentItem = this.activeItemPath().find((p: TieredMenuProcessedItem) => p.key === processedItem.parentKey);\n const root = isEmpty(processedItem.parent);\n\n if (!root) {\n this.focusedItemInfo.set({ index: -1, parentKey: parentItem ? parentItem.parentKey : '', item: processedItem.item });\n this.searchValue = '';\n this.onArrowDownKey(event);\n }\n\n const activeItemPath = this.activeItemPath().filter((p: TieredMenuProcessedItem) => p.parentKey !== this.focusedItemInfo().parentKey);\n this.activeItemPath.set(activeItemPath);\n\n event.preventDefault();\n }\n\n onHomeKey(event: KeyboardEvent) {\n this.changeFocusedItemIndex(event, this.findFirstItemIndex());\n event.preventDefault();\n }\n\n onEndKey(event: KeyboardEvent) {\n this.changeFocusedItemIndex(event, this.findLastItemIndex());\n event.preventDefault();\n }\n\n onSpaceKey(event: KeyboardEvent) {\n this.onEnterKey(event);\n }\n\n onEscapeKey(event: KeyboardEvent) {\n this.hide(event, true);\n this.focusedItemInfo().index = this.findFirstFocusedItemIndex();\n\n event.preventDefault();\n }\n\n onTabKey(event: KeyboardEvent) {\n if (this.focusedItemInfo().index !== -1) {\n const processedItem = this.visibleItems[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 = findSingle(this.rootmenu()?.el?.nativeElement, `li[id=\"${`${this.focusedItemId}`}\"]`) as HTMLElement | null;\n const anchorElement = element && ((findSingle(element, '[data-pc-section=\"itemlink\"]') as HTMLElement) || (findSingle(element, 'a,button') as HTMLElement));\n\n anchorElement ? anchorElement.click() : element?.click();\n\n if (!this.popup()) {\n const processedItem = this.visibleItems[this.focusedItemInfo().index];\n const grouped = this.isProccessedItemGroup(processedItem);\n\n !grouped && (this.focusedItemInfo().index = this.findFirstFocusedItemIndex());\n }\n }\n\n event.preventDefault();\n }\n\n onItemChange(event: TieredMenuItemClickEvent | TieredMenuItemMouseEnterEvent, type?: string) {\n const { processedItem } = event;\n const isFocus = 'isFocus' in event ? event.isFocus : 'focus' in event ? event.focus : false;\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: TieredMenuProcessedItem) => 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()?.sublistViewChild()?.nativeElement);\n\