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 104 kB
{"version":3,"file":"primeng-contextmenu.mjs","sources":["../../src/contextmenu/contextmenu-token.ts","../../src/contextmenu/style/contextmenustyle.ts","../../src/contextmenu/contextmenu-sub.ts","../../src/contextmenu/contextmenu.ts","../../src/contextmenu/primeng-contextmenu.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\n\nexport const CONTEXTMENU_INSTANCE = new InjectionToken<any>('CONTEXTMENU_INSTANCE');\nexport const CONTEXTMENUSUB_INSTANCE = new InjectionToken<any>('CONTEXTMENUSUB_INSTANCE');\n","import { Injectable } from '@angular/core';\nimport { style } from '@primeuix/styles/contextmenu';\nimport { BaseStyle } from 'primeng/base';\n\nconst inlineStyles = {\n root: ({ instance }) => ({\n position: 'absolute',\n ...instance.style()\n })\n};\n\nconst classes = {\n root: () => ['p-contextmenu p-component'],\n rootList: 'p-contextmenu-root-list',\n item: ({ instance, processedItem }) => [\n 'p-contextmenu-item',\n {\n 'p-contextmenu-item-active': instance.isItemActive(processedItem),\n 'p-focus': instance.isItemFocused(processedItem),\n 'p-disabled': instance.isItemDisabled(processedItem),\n 'p-contextmenu-mobile': instance.queryMatches()\n }\n ],\n itemContent: 'p-contextmenu-item-content',\n itemLink: 'p-contextmenu-item-link',\n itemIcon: 'p-contextmenu-item-icon',\n itemLabel: 'p-contextmenu-item-label',\n submenuIcon: 'p-contextmenu-submenu-icon',\n submenu: 'p-contextmenu-submenu',\n separator: 'p-contextmenu-separator'\n};\n\n@Injectable()\nexport class ContextMenuStyle extends BaseStyle {\n name = 'contextmenu';\n\n style = style;\n\n classes = classes;\n\n inlineStyles = inlineStyles;\n}\n\n/**\n *\n * ContextMenu displays an overlay menu on right click of its target. Note that components like DataTable has special integration with ContextMenu.\n * Refer to documentation of the individual documentation of the with context menu support.\n *\n * [Live Demo](https://www.primeng.org/contextmenu/)\n *\n * @module contextmenustyle\n *\n */\nexport enum ContextMenuClasses {\n /**\n * Class name of the root element\n */\n root = 'p-contextmenu',\n /**\n * Class name of the root list element\n */\n rootList = 'p-contextmenu-root-list',\n /**\n * Class name of the item element\n */\n item = 'p-contextmenu-item',\n /**\n * Class name of the item content element\n */\n itemContent = 'p-contextmenu-item-content',\n /**\n * Class name of the item link element\n */\n itemLink = 'p-contextmenu-item-link',\n /**\n * Class name of the item icon element\n */\n itemIcon = 'p-contextmenu-item-icon',\n /**\n * Class name of the item label element\n */\n itemLabel = 'p-contextmenu-item-label',\n /**\n * Class name of the submenu icon element\n */\n submenuIcon = 'p-contextmenu-submenu-icon',\n /**\n * Class name of the submenu element\n */\n submenu = 'p-contextmenu-submenu',\n /**\n * Class name of the separator element\n */\n separator = 'p-contextmenu-separator'\n}\n\nexport interface ContextMenuStyle extends BaseStyle {}\n","import { NgTemplateOutlet } from '@angular/common';\nimport { booleanAttribute, Component, computed, effect, ElementRef, inject, input, numberAttribute, output, Renderer2, signal, TemplateRef, viewChild, ViewEncapsulation } from '@angular/core';\nimport { RouterModule } from '@angular/router';\nimport { MotionEvent, MotionOptions } from '@primeuix/motion';\nimport { calculateScrollbarWidth, getHiddenElementOuterWidth, getOffset, getOuterWidth, getViewport, isNotEmpty, resolve } from '@primeuix/utils';\nimport { MenuItem, SharedModule } from 'primeng/api';\nimport { BadgeModule } from 'primeng/badge';\nimport { BaseComponent, PARENT_INSTANCE } from 'primeng/basecomponent';\nimport { 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 { ContextMenuItemTemplateContext, ContextMenuPassThrough, ContextMenuProcessedItem } from 'primeng/types/contextmenu';\nimport type { ContextMenu } from './contextmenu';\nimport { CONTEXTMENU_INSTANCE, CONTEXTMENUSUB_INSTANCE } from './contextmenu-token';\nimport { ContextMenuStyle } from './style/contextmenustyle';\n\n@Component({\n selector: 'p-contextmenu-sub',\n standalone: true,\n imports: [NgTemplateOutlet, RouterModule, Ripple, TooltipModule, AngleRightIcon, BadgeModule, SharedModule, BindModule, MotionModule],\n template: `\n @if (render()) {\n <ul\n #sublist\n role=\"menu\"\n [class]=\"listClass()\"\n [pBind]=\"listPtm()\"\n [attr.id]=\"listId()\"\n [tabindex]=\"tabindex()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.aria-labelledBy]=\"ariaLabelledBy()\"\n [attr.aria-activedescendant]=\"focusedItemId()\"\n [attr.aria-orientation]=\"'vertical'\"\n (keydown)=\"menuKeydown.emit($event)\"\n (focus)=\"menuFocus.emit($event)\"\n (blur)=\"menuBlur.emit($event)\"\n [pMotion]=\"motionState()\"\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 (isItemVisible(processedItem) && getItemProp(processedItem, 'separator')) {\n <li [attr.id]=\"getItemId(processedItem)\" [style]=\"getItemProp(processedItem, 'style')\" [class]=\"cn(cx('separator'), getItemProp(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]=\"getAriaDisabled(processedItem)\"\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', { 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 [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 [pBind]=\"getPTOptions(processedItem, index, 'itemIcon')\"\n [attr.aria-hidden]=\"true\"\n [attr.tabindex]=\"-1\"\n >\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')\" [unstyled]=\"unstyled()\" />\n }\n @if (isItemGroup(processedItem)) {\n @if (!contextMenu.submenuIconTemplate()) {\n <svg data-p-icon=\"angle-right\" [class]=\"cx('submenuIcon')\" [pBind]=\"getPTOptions(processedItem, index, 'submenuIcon')\" [attr.aria-hidden]=\"true\" />\n } @else {\n <ng-container *ngTemplateOutlet=\"contextMenu.submenuIconTemplate(); context: { class: 'p-contextmenu-submenu-icon' }\"></ng-container>\n }\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 [routerLinkActiveOptions]=\"getItemProp(processedItem, 'routerLinkActiveOptions') || { exact: false }\"\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 [pBind]=\"getPTOptions(processedItem, index, 'itemIcon')\"\n [attr.aria-hidden]=\"true\"\n [attr.tabindex]=\"-1\"\n >\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')\" [unstyled]=\"unstyled()\" />\n }\n @if (isItemGroup(processedItem)) {\n @if (!contextMenu.submenuIconTemplate()) {\n <svg data-p-icon=\"angle-right\" [class]=\"cx('submenuIcon')\" [pBind]=\"getPTOptions(processedItem, index, 'submenuIcon')\" [attr.aria-hidden]=\"true\" />\n } @else {\n <ng-container *ngTemplateOutlet=\"contextMenu.submenuIconTemplate(); context: { class: 'p-contextmenu-submenu-icon' }\"></ng-container>\n }\n }\n </a>\n }\n } @else {\n <ng-container *ngTemplateOutlet=\"itemTemplate(); context: { $implicit: processedItem.item }\"></ng-container>\n }\n </div>\n\n @if (isItemVisible(processedItem) && isItemGroup(processedItem)) {\n <p-contextmenu-sub\n [items]=\"processedItem.items\"\n [itemTemplate]=\"itemTemplate()\"\n [menuId]=\"menuId()\"\n [visible]=\"isItemActive(processedItem) && isItemGroup(processedItem)\"\n [activeItemPath]=\"activeItemPath()\"\n [focusedItemId]=\"focusedItemId()\"\n [level]=\"level() + 1\"\n (itemClick)=\"itemClick.emit($event)\"\n (itemMouseEnter)=\"onItemMouseEnter($event)\"\n [pt]=\"pt()\"\n [motionOptions]=\"motionOptions()\"\n [unstyled]=\"unstyled()\"\n />\n }\n </li>\n }\n }\n </ul>\n }\n `,\n encapsulation: ViewEncapsulation.None,\n providers: [ContextMenuStyle, { provide: CONTEXTMENUSUB_INSTANCE, useExisting: ContextMenuSub }, { provide: PARENT_INSTANCE, useExisting: ContextMenuSub }]\n})\nexport class ContextMenuSub extends BaseComponent<ContextMenuPassThrough> {\n visible = input<boolean>(false);\n\n items = input<any[]>();\n\n itemTemplate = input<TemplateRef<ContextMenuItemTemplateContext>>();\n\n root = input(false, { transform: booleanAttribute });\n\n autoZIndex = input(true, { transform: booleanAttribute });\n\n baseZIndex = input(0, { transform: numberAttribute });\n\n popup = 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<any[]>();\n\n motionOptions = input<MotionOptions[]>();\n\n tabindex = input(0, { transform: numberAttribute });\n\n itemClick = output<any>();\n\n itemMouseEnter = output<any>();\n\n menuFocus = output<any>();\n\n menuBlur = output<any>();\n\n menuKeydown = output<any>();\n\n sublistViewChild = viewChild<ElementRef>('sublist');\n\n render = signal<boolean>(false);\n\n listClass = computed(() => (this.root() ? this.cx('rootList') : this.cx('submenu')));\n\n listPtm = computed(() => this._ptm(this.root() ? 'rootList' : 'submenu'));\n\n listId = computed(() => this.menuId() + '_list');\n\n motionState = computed(() => (this.root() ? true : this.visible()));\n\n hostName = 'ContextMenu';\n\n _componentStyle = inject(ContextMenuStyle);\n\n $pcContextMenu: ContextMenu | undefined = inject(CONTEXTMENU_INSTANCE, { optional: true, skipSelf: true }) ?? undefined;\n\n $pcContextMenuSub: ContextMenuSub | undefined = inject(CONTEXTMENUSUB_INSTANCE, { optional: true, skipSelf: true }) ?? undefined;\n\n el = inject(ElementRef);\n\n renderer = inject(Renderer2);\n\n public contextMenu = inject<ContextMenu>(CONTEXTMENU_INSTANCE);\n\n queryMatches() {\n return this.contextMenu.queryMatches();\n }\n\n constructor() {\n super();\n\n effect(() => {\n const isVisible = this.visible();\n if (isVisible || this.root()) {\n this.render.set(true);\n }\n });\n\n this.contextMenu.handleSubmenuAfterLeave = () => {\n if (this.root()) {\n this.onAfterLeave();\n }\n };\n }\n\n getItemProp(processedItem: ContextMenuProcessedItem, 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: ContextMenuProcessedItem): string {\n return processedItem.item && processedItem.item?.id ? processedItem.item.id : `${this.menuId()}_${processedItem.key}`;\n }\n\n getItemKey(processedItem: ContextMenuProcessedItem): string {\n return this.getItemId(processedItem);\n }\n\n getItemLabel(processedItem: ContextMenuProcessedItem): string {\n return this.getItemProp(processedItem, 'label') as string;\n }\n\n getAriaSetSize() {\n return this.items()?.filter((processedItem: ContextMenuProcessedItem) => this.isItemVisible(processedItem) && !this.getItemProp(processedItem, 'separator')).length ?? 0;\n }\n\n getAriaPosInset(index: number) {\n return (\n index -\n (this.items()\n ?.slice(0, index)\n .filter((processedItem: ContextMenuProcessedItem) => this.isItemVisible(processedItem) && this.getItemProp(processedItem, 'separator')).length ?? 0) +\n 1\n );\n }\n\n isItemVisible(processedItem: ContextMenuProcessedItem): boolean {\n return this.getItemProp(processedItem, 'visible') !== false;\n }\n\n isItemActive(processedItem: ContextMenuProcessedItem): boolean | undefined {\n const path = this.activeItemPath();\n if (path) {\n return path.some((p: ContextMenuProcessedItem) => p.key === processedItem.key);\n }\n }\n\n isItemDisabled(processedItem: ContextMenuProcessedItem): boolean | undefined {\n return this.getItemProp(processedItem, 'disabled') as boolean | undefined;\n }\n\n isItemFocused(processedItem: ContextMenuProcessedItem): boolean {\n return this.focusedItemId() === this.getItemId(processedItem);\n }\n\n isItemGroup(processedItem: ContextMenuProcessedItem): boolean {\n return isNotEmpty(processedItem.items);\n }\n\n getAriaDisabled(processedItem: ContextMenuProcessedItem) {\n return this.isItemDisabled(processedItem) || undefined;\n }\n\n getAriaHaspopup(processedItem: ContextMenuProcessedItem) {\n return this.isItemGroup(processedItem) && !this.getItemProp(processedItem, 'to') ? 'menu' : undefined;\n }\n\n getAriaExpanded(processedItem: ContextMenuProcessedItem) {\n return this.isItemGroup(processedItem) ? this.isItemActive(processedItem) : undefined;\n }\n\n onItemMouseEnter(param: { event: MouseEvent; processedItem: ContextMenuProcessedItem }) {\n const { event, processedItem } = param;\n this.itemMouseEnter.emit({ originalEvent: event, processedItem });\n }\n\n onItemClick(event: MouseEvent, processedItem: ContextMenuProcessedItem) {\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.position(event.element as HTMLUListElement);\n }\n\n onAfterLeave() {\n this.render.set(false);\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.$pcContextMenu ? this.$pcContextMenu.ptm(section, options) : this.ptm(section, options);\n }\n\n getPTOptions(processedItem: ContextMenuProcessedItem, index: number, key: string) {\n return this._ptm(key, {\n context: {\n item: processedItem.item,\n index: index,\n active: this.isItemActive(processedItem),\n focused: this.isItemFocused(processedItem),\n disabled: this.isItemDisabled(processedItem)\n }\n });\n }\n\n position(sublist: HTMLUListElement) {\n const parentItem = sublist.parentElement?.parentElement;\n if (!parentItem) return;\n\n const containerOffset = getOffset(parentItem);\n const viewport = getViewport();\n const sublistWidth = sublist.offsetParent ? sublist.offsetWidth : getHiddenElementOuterWidth(sublist);\n const itemOuterWidth = getOuterWidth(parentItem.children[0]) ?? 0;\n\n sublist.style.top = '0px';\n\n if (parseInt(String(containerOffset?.left ?? 0), 10) + itemOuterWidth + sublistWidth > viewport.width - calculateScrollbarWidth()) {\n sublist.style.left = -1 * sublistWidth + 'px';\n } else {\n sublist.style.left = itemOuterWidth + 'px';\n }\n }\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport { booleanAttribute, ChangeDetectionStrategy, Component, computed, contentChild, effect, inject, input, NgModule, numberAttribute, output, signal, TemplateRef, viewChild, ViewEncapsulation } from '@angular/core';\nimport { MotionEvent, MotionOptions } from '@primeuix/motion';\nimport { appendChild, findLastIndex, findSingle, focus, getHiddenElementOuterHeight, getHiddenElementOuterWidth, getViewport, isAndroid, isEmpty, isIOS, isNotEmpty, isPrintableCharacter, resolve, uuid } from '@primeuix/utils';\nimport { MenuItem, OverlayService, SharedModule } from 'primeng/api';\nimport { BaseComponent } from 'primeng/basecomponent';\nimport { BindModule } from 'primeng/bind';\nimport { MotionModule } from 'primeng/motion';\nimport type { AppendTo, CSSProperties } from 'primeng/types/shared';\nimport { VoidListener } from 'primeng/ts-helpers';\nimport { ContextMenuItemTemplateContext, ContextMenuPassThrough, ContextMenuProcessedItem, ContextMenuSubmenuIconTemplateContext } from 'primeng/types/contextmenu';\nimport { ZIndexUtils } from 'primeng/utils';\nimport { ContextMenuSub } from './contextmenu-sub';\nimport { CONTEXTMENU_INSTANCE } from './contextmenu-token';\nimport { ContextMenuStyle } from './style/contextmenustyle';\n\n/**\n * ContextMenu displays an overlay menu on right click of its target. Note that components like Table has special integration with ContextMenu.\n * @group Components\n */\n@Component({\n selector: 'p-contextmenu, p-context-menu',\n standalone: true,\n imports: [ContextMenuSub, SharedModule, BindModule, MotionModule],\n template: `\n @if (render()) {\n <div\n #container\n [attr.id]=\"_id\"\n [class]=\"cn(cx('root'), styleClass())\"\n [style]=\"sx('root')\"\n [pBind]=\"ptm('root')\"\n [pMotion]=\"visible()\"\n [pMotionName]=\"'p-anchored-overlay'\"\n [pMotionAppear]=\"true\"\n [pMotionOptions]=\"computedMotionOptions()\"\n (pMotionOnBeforeEnter)=\"onBeforeEnter($event)\"\n (pMotionOnAfterEnter)=\"onAfterEnter()\"\n (pMotionOnAfterLeave)=\"onAfterLeave()\"\n >\n <p-contextmenu-sub\n #rootmenu\n [root]=\"true\"\n [items]=\"processedItems\"\n [itemTemplate]=\"itemTemplate()\"\n [menuId]=\"_id\"\n [ariaLabel]=\"ariaLabel()\"\n [ariaLabelledBy]=\"ariaLabelledBy()\"\n [baseZIndex]=\"baseZIndex()\"\n [autoZIndex]=\"autoZIndex()\"\n [visible]=\"submenuVisible()\"\n [focusedItemId]=\"focused ? focusedItemId : undefined\"\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 />\n </div>\n }\n `,\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n providers: [ContextMenuStyle, { provide: CONTEXTMENU_INSTANCE, useExisting: ContextMenu }]\n})\nexport class ContextMenu extends BaseComponent<ContextMenuPassThrough> {\n componentName = 'ContextMenu';\n\n /**\n * An array of menuitems.\n * @group Props\n */\n model = input<MenuItem[]>();\n /**\n * Event for which the menu must be displayed.\n * @group Props\n */\n triggerEvent = input('contextmenu');\n /**\n * Local template variable name of the element to attach the context menu.\n * @group Props\n */\n target = input<HTMLElement | string | null>();\n /**\n * Attaches the menu to document instead of a particular item.\n * @group Props\n */\n global = 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 * 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 * Current id state as a string.\n * @group Props\n */\n id = input<string>();\n\n _id: string | undefined;\n /**\n * The breakpoint to define the maximum width boundary.\n * @group Props\n */\n breakpoint = input('960px');\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 * Press delay in touch devices as miliseconds.\n * @group Props\n */\n pressDelay = input(500, { 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>(undefined);\n /**\n * The motion options.\n * @group Props\n */\n motionOptions = input<MotionOptions | undefined>(undefined);\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<void>();\n /**\n * Callback to invoke when overlay menu is hidden.\n * @group Emits\n */\n onHide = output<void>();\n\n rootmenu = viewChild<ContextMenuSub>('rootmenu');\n\n container: HTMLElement | null | undefined;\n\n handleSubmenuAfterLeave: (() => void) | null = null;\n\n outsideClickListener: VoidListener;\n\n resizeListener: VoidListener;\n\n triggerEventListener: VoidListener;\n\n documentClickListener: VoidListener;\n\n documentTriggerListener: VoidListener;\n\n touchEndListener: VoidListener;\n\n pageX: number;\n\n pageY: number;\n\n visible = signal(false);\n\n render = signal<boolean>(false);\n\n focused: boolean = false;\n\n activeItemPath = signal<any>([]);\n\n focusedItemInfo = signal<any>({ index: -1, level: 0, parentKey: '', item: null });\n\n submenuVisible = signal<boolean>(false);\n\n $appendTo = computed(() => this.appendTo() || this.config.overlayAppendTo());\n\n searchValue: string = '';\n\n searchTimeout: any;\n\n _processedItems: any[];\n\n pressTimer: any;\n\n hideCallback: any;\n\n private matchMediaListener: (() => void) | null;\n\n private query: MediaQueryList;\n\n public queryMatches = signal<boolean>(false);\n\n _componentStyle = inject(ContextMenuStyle);\n\n overlayService = inject(OverlayService);\n\n get visibleItems(): ContextMenuProcessedItem[] {\n const processedItem = this.activeItemPath().find((p: ContextMenuProcessedItem) => p.key === this.focusedItemInfo().parentKey);\n\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 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\n effect(() => {\n const value = this.model();\n this._processedItems = this.createProcessedItems(value || []);\n });\n\n effect(() => {\n const path = this.activeItemPath();\n\n if (isNotEmpty(path)) {\n this.bindGlobalListeners();\n } else if (!this.visible()) {\n this.unbindGlobalListeners();\n }\n });\n }\n\n onInit() {\n this._id = this.id() || uuid('pn_id_');\n this.bindMatchMediaListener();\n this.bindTriggerEventListener();\n }\n\n isMobile() {\n return isIOS() || isAndroid();\n }\n\n bindTriggerEventListener() {\n if (isPlatformBrowser(this.platformId)) {\n if (!this.triggerEventListener) {\n const targetEl = this.target();\n const triggerEventName = this.triggerEvent();\n if (!this.isMobile()) {\n if (this.global()) {\n this.triggerEventListener = this.renderer.listen(this.document, triggerEventName, (event) => {\n this.show(event);\n });\n } else if (targetEl) {\n this.triggerEventListener = this.renderer.listen(targetEl, triggerEventName, (event) => {\n this.show(event);\n });\n }\n } else {\n if (this.global()) {\n this.triggerEventListener = this.renderer.listen(this.document, 'touchstart', this.onTouchStart.bind(this));\n this.touchEndListener = this.renderer.listen(this.document, 'touchend', this.onTouchEnd.bind(this));\n } else if (targetEl) {\n this.triggerEventListener = this.renderer.listen(targetEl, 'touchstart', this.onTouchStart.bind(this));\n this.touchEndListener = this.renderer.listen(targetEl, 'touchend', this.onTouchEnd.bind(this));\n }\n }\n }\n }\n }\n\n bindGlobalListeners() {\n if (isPlatformBrowser(this.platformId)) {\n if (!this.documentClickListener) {\n const documentTarget: any = this.el ? this.el.nativeElement.ownerDocument : 'document';\n\n this.documentClickListener = this.renderer.listen(documentTarget, 'click', (event) => {\n if (this.container?.offsetParent && this.isOutsideClicked(event) && !event.ctrlKey && event.button !== 2) {\n this.hide();\n }\n });\n }\n if (!this.resizeListener) {\n this.resizeListener = this.renderer.listen(this.document.defaultView, 'resize', () => {\n this.hide();\n });\n }\n }\n }\n /**\n * Custom item template.\n * @group Templates\n */\n itemTemplate = contentChild<TemplateRef<ContextMenuItemTemplateContext>>('item', { descendants: false });\n\n /**\n * Custom submenu icon template.\n * @group Templates\n */\n submenuIconTemplate = contentChild<TemplateRef<ContextMenuSubmenuIconTemplateContext>>('submenuicon', { descendants: false });\n\n getPTOptions(key: string, item: MenuItem, index: number, _id: string) {\n return this.ptm(key, {\n context: {\n item: item,\n index: index,\n focused: this.isItemFocused({ index, item }),\n disabled: this.isItemDisabled(item)\n }\n });\n }\n\n isItemFocused(itemInfo: { index: number; item: MenuItem }): boolean {\n return this.focusedItemInfo().index === itemInfo.index;\n }\n\n createProcessedItems(items: MenuItem[], level: number = 0, parent: ContextMenuProcessedItem | Record<string, never> = {}, parentKey: string = ''): ContextMenuProcessedItem[] {\n const processedItems: ContextMenuProcessedItem[] = [];\n\n items &&\n items.forEach((item: MenuItem, index: number) => {\n const key = (parentKey !== '' ? parentKey + '_' : '') + index;\n const newItem: ContextMenuProcessedItem = {\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.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 as keyof MenuItem]) : undefined;\n }\n\n getProccessedItemLabel(processedItem: ContextMenuProcessedItem) {\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: ContextMenuProcessedItem): boolean {\n return processedItem && isNotEmpty(processedItem.items);\n }\n\n isSelected(processedItem: ContextMenuProcessedItem): boolean {\n return this.activeItemPath().some((p: ContextMenuProcessedItem) => p.key === processedItem.key);\n }\n\n isValidSelectedItem(processedItem: ContextMenuProcessedItem): boolean {\n return this.isValidItem(processedItem) && this.isSelected(processedItem);\n }\n\n isValidItem(processedItem: ContextMenuProcessedItem): 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') as boolean;\n }\n\n isItemSeparator(item: MenuItem): boolean {\n return this.getItemProp(item, 'separator') as boolean;\n }\n\n isItemMatched(processedItem: ContextMenuProcessedItem): boolean {\n return this.isValidItem(processedItem) && this.getProccessedItemLabel(processedItem).toLocaleLowerCase().startsWith(this.searchValue.toLocaleLowerCase());\n }\n\n isProccessedItemGroup(processedItem: ContextMenuProcessedItem): boolean {\n return processedItem && isNotEmpty(processedItem.items);\n }\n\n onItemClick(event: { processedItem: ContextMenuProcessedItem; isFocus?: boolean }) {\n const { processedItem } = event;\n const grouped = this.isProcessedItemGroup(processedItem);\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: ContextMenuProcessedItem) => key !== p.key && key.startsWith(p.key)));\n this.focusedItemInfo.set({ index, level, parentKey, item });\n\n focus(this.rootmenu()?.sublistViewChild()?.nativeElement);\n } else {\n grouped ? this.onItemChange(event) : this.hide();\n }\n }\n\n onItemMouseEnter(event: { originalEvent: MouseEvent; processedItem: ContextMenuProcessedItem }) {\n this.onItemChange(event, 'hover');\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\n if (grouped) {\n this.onItemChange({ originalEvent: event, processedItem });\n this.focusedItemInfo.set({ index: -1, parentKey: processedItem.key, item: processedItem.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.hide();\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) return;\n\n const parentItem = this.activeItemPath().find((p: ContextMenuProcessedItem) => 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: ContextMenuProcessedItem) => 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();\n const processedItem = this.findVisibleItem(this.findFirstFocusedItemIndex());\n const focusedItemInfo = this.focusedItemInfo();\n this.focusedItemInfo.set({ ...focusedItemInfo, index: this.findFirstFocusedItemIndex(), item: processedItem?.item ?? null });\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 = <any>findSingle(this.rootmenu()?.el?.nativeElement, `li[id=\"${`${this.focusedItemId}`}\"]`);\n const anchorElement = element && (<any>findSingle(element, '[data-pc-section=\"itemlink\"]') || findSingle(element, 'a,button'));\n\n anchorElement ? anchorElement.click() : element && element.click();\n\n const processedItem = this.visibleItems[this.focusedItemInfo().index];\n const grouped = this.isProccessedItemGroup(processedItem);\n\n if (!grouped) {\n const focusedItemInfo = this.focusedItemInfo();\n this.focusedItemInfo.set({ ...focusedItemInfo, index: this.findFirstFocusedItemIndex() });\n }\n }\n\n event.preventDefault();\n }\n\n onItemChange(event: { originalEvent?: Event; processedItem: ContextMenuProcessedItem; isFocus?: boolean }, type?: string) {\n const { processedItem, isFocus } = event;\n if (isEmpty(processedItem)) return;\n\n const { index, key, level, parentKey, items } = processedItem;\n const grouped = isNotEmpty(items);\n const activeItemPath = this.activeItemPath().filter((p: ContextMenuProcessedItem) => p.parentKey !== parentKey && p.parentKey !== key);\n\n if (grouped) {\n activeItemPath.push(processedItem);\n this.submenuVisible.set(true);\n }\n this.focusedItemInfo.set({ index, level, parentKey, item: processedItem.item });\n isFocus && focus(this.rootmenu()?.sublistViewChild()?.nativeElement);\n\n if (type === 'hover' && this.queryMatches()) {\n return;\n }\n\n this.activeItemPath.set(activeItemPath);\n }\n\n onMenuFocus(_event: FocusEvent) {\n this.focused = true;\n const focusedItemInfo = this.focusedItemInfo().index !== -1 ? this.focusedItemInfo() : { index: -1, level: 0, parentKey: '', item: null };\n\n this.focusedItemInfo.set(focusedItemInfo);\n }\n\n onMenuBlur(_event: F