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,020 lines (1,014 loc) • 68.1 kB
JavaScript
export * from 'primeng/types/menu';
import { isPlatformBrowser, NgTemplateOutlet } from '@angular/common';
import * as i0 from '@angular/core';
import { Injectable, InjectionToken, inject, PLATFORM_ID, Pipe, input, booleanAttribute, numberAttribute, output, ViewEncapsulation, Component, model, contentChild, viewChild, computed, signal, ChangeDetectionStrategy, NgModule } from '@angular/core';
import { uuid, addStyle, absolutePosition, focus, appendChild, isTouchDevice, find, findSingle } from '@primeuix/utils';
import { SharedModule, OverlayService } from 'primeng/api';
import { BaseComponent, PARENT_INSTANCE } from 'primeng/basecomponent';
import * as i1$1 from 'primeng/bind';
import { BindModule, Bind } from 'primeng/bind';
import { ConnectedOverlayScrollHandler } from 'primeng/dom';
import * as i3$1 from 'primeng/motion';
import { MotionModule } from 'primeng/motion';
import * as i2 from 'primeng/tooltip';
import { TooltipModule } from 'primeng/tooltip';
import { ZIndexUtils } from 'primeng/utils';
import { DomSanitizer } from '@angular/platform-browser';
import * as i1 from '@angular/router';
import { RouterModule } from '@angular/router';
import * as i3 from 'primeng/badge';
import { BadgeModule } from 'primeng/badge';
import { Ripple } from 'primeng/ripple';
import { ChevronDown } from '@primeicons/angular/chevron-down';
import { style } from '@primeuix/styles/menu';
import { BaseStyle } from 'primeng/base';
const inlineStyles = {
root: ({ instance }) => ({
position: instance.popup() ? 'absolute' : 'relative',
...instance.style()
})
};
const classes = {
root: ({ instance }) => [
'p-menu p-component',
{
'p-menu-overlay': instance.popup()
}
],
start: 'p-menu-start',
list: 'p-menu-list',
submenuLabel: 'p-menu-submenu-label',
submenuList: 'p-menu-submenu-list',
separator: 'p-menu-separator',
end: 'p-menu-end',
item: ({ instance, item, id, toggleable }) => [
'p-menu-item',
{
'p-menu-item-toggleable': toggleable,
'p-focus': instance.focusedOptionId() && id === instance.focusedOptionId(),
'p-disabled': instance.disabled(item.disabled)
},
item.styleClass
],
itemContent: 'p-menu-item-content',
itemLink: 'p-menu-item-link',
itemIcon: ({ item }) => ['p-menu-item-icon', item.icon, item.iconClass],
itemLabel: 'p-menu-item-label',
itemSubmenuIcon: 'p-menu-item-submenu-icon'
};
class MenuStyle extends BaseStyle {
name = 'menu';
style = style;
classes = classes;
inlineStyles = inlineStyles;
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: MenuStyle, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: MenuStyle });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: MenuStyle, decorators: [{
type: Injectable
}] });
/**
*
* Menu is a navigation / command component that supports dynamic and static positioning.
*
* [Live Demo](https://www.primeng.org/menu/)
*
* @module menustyle
*
*/
var MenuClasses;
(function (MenuClasses) {
/**
* Class name of the root element
*/
MenuClasses["root"] = "p-menu";
/**
* Class name of the start element
*/
MenuClasses["start"] = "p-menu-start";
/**
* Class name of the list element
*/
MenuClasses["list"] = "p-menu-list";
/**
* Class name of the submenu label element (non-toggleable group headers)
*/
MenuClasses["submenuLabel"] = "p-menu-submenu-label";
/**
* Class name of the nested submenu list element
*/
MenuClasses["submenuList"] = "p-menu-submenu-list";
/**
* Class name of the separator element
*/
MenuClasses["separator"] = "p-menu-separator";
/**
* Class name of the end element
*/
MenuClasses["end"] = "p-menu-end";
/**
* Class name of the item element
*/
MenuClasses["item"] = "p-menu-item";
/**
* Class name of the item content element
*/
MenuClasses["itemContent"] = "p-menu-item-content";
/**
* Class name of the item link element
*/
MenuClasses["itemLink"] = "p-menu-item-link";
/**
* Class name of the item icon element
*/
MenuClasses["itemIcon"] = "p-menu-item-icon";
/**
* Class name of the item label element
*/
MenuClasses["itemLabel"] = "p-menu-item-label";
/**
* Class name of the item submenu toggle icon element (toggleable items)
*/
MenuClasses["itemSubmenuIcon"] = "p-menu-item-submenu-icon";
})(MenuClasses || (MenuClasses = {}));
const MENU_INSTANCE = new InjectionToken('MENU_INSTANCE');
class SafeHtmlPipe {
platformId = inject(PLATFORM_ID);
sanitizer = inject(DomSanitizer);
transform(value) {
if (!value || !isPlatformBrowser(this.platformId)) {
return value;
}
return this.sanitizer.bypassSecurityTrustHtml(value);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SafeHtmlPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.6", ngImport: i0, type: SafeHtmlPipe, isStandalone: true, name: "safeHtml" });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SafeHtmlPipe, decorators: [{
type: Pipe,
args: [{
name: 'safeHtml',
standalone: true
}]
}] });
class MenuItemContent extends BaseComponent {
item = input(undefined, { ...(ngDevMode ? { debugName: "item" } : /* istanbul ignore next */ {}), alias: 'pMenuItemContent' });
itemTemplate = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "itemTemplate" }] : /* istanbul ignore next */ []));
menuitemId = input('', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "menuitemId" }] : /* istanbul ignore next */ []));
idx = input(0, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "idx" }] : /* istanbul ignore next */ []));
toggleable = input(false, { ...(ngDevMode ? { debugName: "toggleable" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
expanded = input(false, { ...(ngDevMode ? { debugName: "expanded" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
depth = input(0, { ...(ngDevMode ? { debugName: "depth" } : /* istanbul ignore next */ {}), transform: numberAttribute });
submenuIconTemplate = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "submenuIconTemplate" }] : /* istanbul ignore next */ []));
onMenuItemClick = output();
menu = inject(MENU_INSTANCE);
_componentStyle = inject(MenuStyle);
hostName = 'Menu';
onItemClick(event, item) {
if (this.toggleable()) {
event.preventDefault();
this.menu.toggleSubmenu(item, this.menuitemId(), this.depth());
this.menu.focusedOptionIndex.set(this.menuitemId());
this.menu.focusMenuList();
return;
}
this.onMenuItemClick.emit({ originalEvent: event, item });
}
getRouterLinkActiveOptions(item) {
return item?.routerLinkActiveOptions || { exact: false };
}
getPTOptions(key) {
return this.menu.getPTOptions(key, this.item(), this.idx(), this.menuitemId());
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: MenuItemContent, deps: null, target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: MenuItemContent, isStandalone: true, selector: "[pMenuItemContent]", inputs: { item: { classPropertyName: "item", publicName: "pMenuItemContent", isSignal: true, isRequired: false, transformFunction: null }, itemTemplate: { classPropertyName: "itemTemplate", publicName: "itemTemplate", isSignal: true, isRequired: false, transformFunction: null }, menuitemId: { classPropertyName: "menuitemId", publicName: "menuitemId", isSignal: true, isRequired: false, transformFunction: null }, idx: { classPropertyName: "idx", publicName: "idx", isSignal: true, isRequired: false, transformFunction: null }, toggleable: { classPropertyName: "toggleable", publicName: "toggleable", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null }, depth: { classPropertyName: "depth", publicName: "depth", isSignal: true, isRequired: false, transformFunction: null }, submenuIconTemplate: { classPropertyName: "submenuIconTemplate", publicName: "submenuIconTemplate", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onMenuItemClick: "onMenuItemClick" }, providers: [MenuStyle], usesInheritance: true, ngImport: i0, template: `
@let _item = item();
<div [class]="cx('itemContent')" (click)="onItemClick($event, _item)" [attr.data-pc-section]="'itemcontent'" [pBind]="getPTOptions('itemContent')">
@if (!itemTemplate()) {
@if (!_item?.routerLink) {
<a
[attr.title]="_item?.title"
[attr.href]="_item?.url || null"
[attr.data-automationid]="_item?.automationId"
[attr.tabindex]="-1"
[class]="cn(cx('itemLink'), _item?.linkClass)"
[style]="_item?.linkStyle"
[target]="_item?.target"
[pBind]="getPTOptions('itemLink')"
[attr.data-pc-section]="'itemlink'"
pRipple
>
<ng-container *ngTemplateOutlet="itemContent; context: { $implicit: _item }"></ng-container>
</a>
} @else {
<a
[routerLink]="_item?.routerLink"
[attr.data-automationid]="_item?.automationId"
[attr.tabindex]="-1"
[attr.title]="_item?.title"
[queryParams]="_item?.queryParams"
routerLinkActive="p-menu-item-link-active"
[routerLinkActiveOptions]="getRouterLinkActiveOptions(_item)"
[class]="cn(cx('itemLink'), _item?.linkClass)"
[style]="_item?.linkStyle"
[target]="_item?.target"
[fragment]="_item?.fragment"
[queryParamsHandling]="_item?.queryParamsHandling"
[preserveFragment]="_item?.preserveFragment"
[skipLocationChange]="_item?.skipLocationChange"
[replaceUrl]="_item?.replaceUrl"
[state]="_item?.state"
[pBind]="getPTOptions('itemLink')"
[attr.data-pc-section]="'itemlink'"
pRipple
>
<ng-container *ngTemplateOutlet="itemContent; context: { $implicit: _item }"></ng-container>
</a>
}
} @else {
<ng-container *ngTemplateOutlet="itemTemplate(); context: { $implicit: _item }"></ng-container>
}
<ng-template #itemContent>
@if (_item?.icon) {
<span [class]="cn(cx('itemIcon', { item: _item }), _item?.iconClass)" [pBind]="getPTOptions('itemIcon')" [style]="_item?.iconStyle" [attr.data-pc-section]="'itemicon'"></span>
}
@if (_item?.escape !== false) {
<span [class]="cn(cx('itemLabel'), _item?.labelClass)" [style]="_item?.labelStyle" [pBind]="getPTOptions('itemLabel')" [attr.data-pc-section]="'itemlabel'">{{ _item?.label }}</span>
} @else {
<span [class]="cn(cx('itemLabel'), _item?.labelClass)" [style]="_item?.labelStyle" [attr.data-pc-section]="'itemlabel'" [innerHTML]="_item?.label | safeHtml" [pBind]="getPTOptions('itemLabel')"></span>
}
@if (toggleable()) {
@if (submenuIconTemplate()) {
<ng-container *ngTemplateOutlet="submenuIconTemplate()!; context: { $implicit: _item }"></ng-container>
} @else {
<svg [pBind]="getPTOptions('itemSubmenuIcon')" data-p-icon="chevron-down" [class]="cx('itemSubmenuIcon')" [attr.aria-hidden]="true" [attr.data-expanded]="expanded() || null" />
}
}
@if (_item?.badge) {
<p-badge [class]="_item?.badgeStyleClass" [value]="_item?.badge" [pt]="getPTOptions('pcBadge')" [unstyled]="unstyled()" />
}
</ng-template>
</div>
<ng-content></ng-content>
`, isInline: true, dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: i1.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "directive", type: Ripple, selector: "[pRipple]" }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i1$1.Bind, selector: "[pBind]", inputs: ["pBind"] }, { kind: "ngmodule", type: BadgeModule }, { kind: "component", type: i3.Badge, selector: "p-badge", inputs: ["badgeSize", "size", "severity", "value", "badgeDisabled"] }, { kind: "ngmodule", type: SharedModule }, { kind: "ngmodule", type: BindModule }, { kind: "component", type: ChevronDown, selector: "svg[data-p-icon=\"chevron-down\"]" }, { kind: "pipe", type: SafeHtmlPipe, name: "safeHtml" }], encapsulation: i0.ViewEncapsulation.None });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: MenuItemContent, decorators: [{
type: Component,
args: [{
selector: '[pMenuItemContent]',
standalone: true,
imports: [NgTemplateOutlet, RouterModule, Ripple, TooltipModule, BadgeModule, SharedModule, SafeHtmlPipe, BindModule, ChevronDown],
template: `
@let _item = item();
<div [class]="cx('itemContent')" (click)="onItemClick($event, _item)" [attr.data-pc-section]="'itemcontent'" [pBind]="getPTOptions('itemContent')">
@if (!itemTemplate()) {
@if (!_item?.routerLink) {
<a
[attr.title]="_item?.title"
[attr.href]="_item?.url || null"
[attr.data-automationid]="_item?.automationId"
[attr.tabindex]="-1"
[class]="cn(cx('itemLink'), _item?.linkClass)"
[style]="_item?.linkStyle"
[target]="_item?.target"
[pBind]="getPTOptions('itemLink')"
[attr.data-pc-section]="'itemlink'"
pRipple
>
<ng-container *ngTemplateOutlet="itemContent; context: { $implicit: _item }"></ng-container>
</a>
} @else {
<a
[routerLink]="_item?.routerLink"
[attr.data-automationid]="_item?.automationId"
[attr.tabindex]="-1"
[attr.title]="_item?.title"
[queryParams]="_item?.queryParams"
routerLinkActive="p-menu-item-link-active"
[routerLinkActiveOptions]="getRouterLinkActiveOptions(_item)"
[class]="cn(cx('itemLink'), _item?.linkClass)"
[style]="_item?.linkStyle"
[target]="_item?.target"
[fragment]="_item?.fragment"
[queryParamsHandling]="_item?.queryParamsHandling"
[preserveFragment]="_item?.preserveFragment"
[skipLocationChange]="_item?.skipLocationChange"
[replaceUrl]="_item?.replaceUrl"
[state]="_item?.state"
[pBind]="getPTOptions('itemLink')"
[attr.data-pc-section]="'itemlink'"
pRipple
>
<ng-container *ngTemplateOutlet="itemContent; context: { $implicit: _item }"></ng-container>
</a>
}
} @else {
<ng-container *ngTemplateOutlet="itemTemplate(); context: { $implicit: _item }"></ng-container>
}
<ng-template #itemContent>
@if (_item?.icon) {
<span [class]="cn(cx('itemIcon', { item: _item }), _item?.iconClass)" [pBind]="getPTOptions('itemIcon')" [style]="_item?.iconStyle" [attr.data-pc-section]="'itemicon'"></span>
}
@if (_item?.escape !== false) {
<span [class]="cn(cx('itemLabel'), _item?.labelClass)" [style]="_item?.labelStyle" [pBind]="getPTOptions('itemLabel')" [attr.data-pc-section]="'itemlabel'">{{ _item?.label }}</span>
} @else {
<span [class]="cn(cx('itemLabel'), _item?.labelClass)" [style]="_item?.labelStyle" [attr.data-pc-section]="'itemlabel'" [innerHTML]="_item?.label | safeHtml" [pBind]="getPTOptions('itemLabel')"></span>
}
@if (toggleable()) {
@if (submenuIconTemplate()) {
<ng-container *ngTemplateOutlet="submenuIconTemplate()!; context: { $implicit: _item }"></ng-container>
} @else {
<svg [pBind]="getPTOptions('itemSubmenuIcon')" data-p-icon="chevron-down" [class]="cx('itemSubmenuIcon')" [attr.aria-hidden]="true" [attr.data-expanded]="expanded() || null" />
}
}
@if (_item?.badge) {
<p-badge [class]="_item?.badgeStyleClass" [value]="_item?.badge" [pt]="getPTOptions('pcBadge')" [unstyled]="unstyled()" />
}
</ng-template>
</div>
<ng-content></ng-content>
`,
encapsulation: ViewEncapsulation.None,
providers: [MenuStyle]
}]
}], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "pMenuItemContent", required: false }] }], itemTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemTemplate", required: false }] }], menuitemId: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuitemId", required: false }] }], idx: [{ type: i0.Input, args: [{ isSignal: true, alias: "idx", required: false }] }], toggleable: [{ type: i0.Input, args: [{ isSignal: true, alias: "toggleable", required: false }] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }], depth: [{ type: i0.Input, args: [{ isSignal: true, alias: "depth", required: false }] }], submenuIconTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "submenuIconTemplate", required: false }] }], onMenuItemClick: [{ type: i0.Output, args: ["onMenuItemClick"] }] } });
/**
* Menu is a navigation / command component that supports dynamic and static positioning.
* @group Components
*/
class Menu extends BaseComponent {
componentName = 'Menu';
/**
* An array of menuitems.
* @group Props
*/
model = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "model" }] : /* istanbul ignore next */ []));
/**
* Defines if menu would displayed as a popup.
* @group Props
*/
popup = input(false, { ...(ngDevMode ? { debugName: "popup" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
/**
* Inline style of the component.
* @group Props
*/
style = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "style" }] : /* istanbul ignore next */ []));
/**
* Style class of the component.
* @group Props
*/
styleClass = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "styleClass" }] : /* istanbul ignore next */ []));
/**
* Whether to automatically manage layering.
* @group Props
*/
autoZIndex = input(true, { ...(ngDevMode ? { debugName: "autoZIndex" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
/**
* Base zIndex value to use in layering.
* @group Props
*/
baseZIndex = input(0, { ...(ngDevMode ? { debugName: "baseZIndex" } : /* istanbul ignore next */ {}), transform: numberAttribute });
/**
* Defines a string value that labels an interactive element.
* @group Props
*/
ariaLabel = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
/**
* Identifier of the underlying input element.
* @group Props
*/
ariaLabelledBy = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "ariaLabelledBy" }] : /* istanbul ignore next */ []));
/**
* Current id state as a string.
* @group Props
*/
id = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "id" }] : /* istanbul ignore next */ []));
/**
* Index of the element in tabbing order.
* @group Props
*/
tabindex = input(0, { ...(ngDevMode ? { debugName: "tabindex" } : /* istanbul ignore next */ {}), transform: numberAttribute });
/**
* 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).
* @defaultValue 'self'
* @group Props
*/
appendTo = input(undefined, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "appendTo" }] : /* istanbul ignore next */ []));
/**
* The motion options.
* @group Props
*/
motionOptions = input(undefined, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "motionOptions" }] : /* istanbul ignore next */ []));
/**
* When provided, switches the component to controlled mode; each key is the DOM id of a toggleable submenu header and the value its expansion flag.
* Use two-way binding with `[(expandedKeys)]`.
* @group Props
*/
expandedKeys = model(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "expandedKeys" }] : /* istanbul ignore next */ []));
/**
* Callback to invoke when overlay menu is shown.
* @group Emits
*/
onShow = output();
/**
* Callback to invoke when overlay menu is hidden.
* @group Emits
*/
onHide = output();
/**
* Callback to invoke when the list loses focus.
* @param {Event} event - blur event.
* @group Emits
*/
onBlur = output();
/**
* Callback to invoke when the list receives focus.
* @param {Event} event - focus event.
* @group Emits
*/
onFocus = output();
/**
* Defines template option for start.
* @group Templates
*/
startTemplate = contentChild('start', { ...(ngDevMode ? { debugName: "startTemplate" } : /* istanbul ignore next */ {}), descendants: false });
/**
* Defines template option for end.
* @group Templates
*/
endTemplate = contentChild('end', { ...(ngDevMode ? { debugName: "endTemplate" } : /* istanbul ignore next */ {}), descendants: false });
/**
* Custom item template.
* @param {MenuItemTemplateContext} context - item context.
* @see {@link MenuItemTemplateContext}
* @group Templates
*/
itemTemplate = contentChild('item', { ...(ngDevMode ? { debugName: "itemTemplate" } : /* istanbul ignore next */ {}), descendants: false });
/**
* Custom submenu header template.
* @param {MenuSubmenuHeaderTemplateContext} context - submenu header context.
* @see {@link MenuSubmenuHeaderTemplateContext}
* @group Templates
*/
submenuHeaderTemplate = contentChild('submenuheader', { ...(ngDevMode ? { debugName: "submenuHeaderTemplate" } : /* istanbul ignore next */ {}), descendants: false });
/**
* Custom submenu toggle icon template.
* @param {MenuSubmenuHeaderTemplateContext} context - submenu header context.
* @see {@link MenuSubmenuHeaderTemplateContext}
* @group Templates
*/
submenuIconTemplate = contentChild('submenuicon', { ...(ngDevMode ? { debugName: "submenuIconTemplate" } : /* istanbul ignore next */ {}), descendants: false });
listViewChild = viewChild('list', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "listViewChild" }] : /* istanbul ignore next */ []));
containerViewChild = viewChild('container', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "containerViewChild" }] : /* istanbul ignore next */ []));
_internalId = uuid('pn_id_');
$id = computed(() => this.id() || this._internalId, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "$id" }] : /* istanbul ignore next */ []));
$appendTo = computed(() => this.appendTo() || this.config.overlayAppendTo(), /* @ts-ignore */
...(ngDevMode ? [{ debugName: "$appendTo" }] : /* istanbul ignore next */ []));
computedMotionOptions = computed(() => {
return {
...this.ptm('motion'),
...this.motionOptions()
};
}, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "computedMotionOptions" }] : /* istanbul ignore next */ []));
container;
scrollHandler;
documentClickListener;
documentResizeListener;
preventDocumentDefault;
target = null;
visible = signal(false, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
focusedOptionId = computed(() => {
return this.focusedOptionIndex() !== -1 ? this.focusedOptionIndex() : null;
}, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "focusedOptionId" }] : /* istanbul ignore next */ []));
focusedOptionIndex = signal(-1, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "focusedOptionIndex" }] : /* istanbul ignore next */ []));
selectedOptionIndex = signal(-1, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "selectedOptionIndex" }] : /* istanbul ignore next */ []));
focused = signal(false, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
overlayVisible = signal(false, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "overlayVisible" }] : /* istanbul ignore next */ []));
d_expandedKeys = signal({}, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "d_expandedKeys" }] : /* istanbul ignore next */ []));
currentExpandedKeys = computed(() => {
return this.expandedKeys() !== undefined ? this.expandedKeys() : this.d_expandedKeys();
}, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "currentExpandedKeys" }] : /* istanbul ignore next */ []));
$pcMenu = inject(MENU_INSTANCE, { optional: true, skipSelf: true }) ?? undefined;
overlayService = inject(OverlayService);
_componentStyle = inject(MenuStyle);
bindDirectiveInstance = inject(Bind, { self: true });
onAfterViewChecked() {
this.bindDirectiveInstance.setAttrs(this.ptm('host'));
}
dataP = computed(() => {
return this.cn({
popup: this.popup()
});
}, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "dataP" }] : /* istanbul ignore next */ []));
getPTOptions(key, item, index, id) {
return this.ptm(key, {
context: {
item: item,
index: index,
focused: this.isItemFocused(id),
disabled: this.disabled(item?.disabled)
}
});
}
/**
* Toggles the visibility of the popup menu.
* @param {Event} event - Browser event.
* @group Method
*/
toggle(event) {
if (this.visible())
this.hide();
else
this.show(event);
this.preventDocumentDefault = true;
}
/**
* Displays the popup menu.
* @param {Event} event - Browser event.
* @group Method
*/
show(event) {
// Clear container if exists but overlay is not currently visible (fast toggle case)
if (this.container && !this.overlayVisible()) {
this.container = undefined;
}
this.target = event.currentTarget;
this.visible.set(true);
this.preventDocumentDefault = true;
this.overlayVisible.set(true);
}
onInit() {
if (!this.popup()) {
this.bindDocumentClickListener();
}
}
getTabIndexValue() {
const tabindexValue = this.tabindex();
return tabindexValue !== undefined ? tabindexValue.toString() : null;
}
onOverlayBeforeEnter(event) {
this.container = event.element;
if (this.container) {
addStyle(this.container, { position: 'absolute', top: '0' });
this.appendOverlay();
this.moveOnTop();
this.$attrSelector && this.container?.setAttribute(this.$attrSelector, '');
this.bindDocumentClickListener();
this.bindDocumentResizeListener();
this.bindScrollListener();
absolutePosition(this.container, this.target);
focus(this.listViewChild()?.nativeElement);
this.onShow.emit({});
}
}
onOverlayAfterLeave() {
this.restoreOverlayAppend();
this.onOverlayHide();
this.overlayVisible.set(false);
this.onHide.emit({});
}
appendOverlay() {
if (this.$appendTo() && this.$appendTo() !== 'self') {
if (this.$appendTo() === 'body') {
appendChild(this.document.body, this.container);
}
else {
appendChild(this.$appendTo(), this.container);
}
}
}
restoreOverlayAppend() {
if (this.container && this.$appendTo() !== 'self') {
appendChild(this.el.nativeElement, this.container);
}
}
moveOnTop() {
if (this.autoZIndex()) {
ZIndexUtils.set('menu', this.container, this.baseZIndex() + this.config.zIndex.menu);
}
}
/**
* Hides the popup menu.
* @group Method
*/
hide() {
this.visible.set(false);
}
onWindowResize() {
if (this.visible() && !isTouchDevice()) {
this.hide();
}
}
menuitemId(item, id, index, childIndex) {
return item?.id ?? `${id}_${index}${childIndex !== undefined ? '_' + childIndex : ''}`;
}
isItemFocused(id) {
return this.focusedOptionId() === id;
}
label(label) {
return typeof label === 'function' ? label() : label;
}
disabled(disabled) {
return typeof disabled === 'function' ? disabled() : typeof disabled === 'undefined' ? false : disabled;
}
isVisible(item) {
return item.visible !== false;
}
isSeparatorVisible(item) {
return item.separator && this.isVisible(item);
}
isItemVisible(item) {
return !item.separator && this.isVisible(item);
}
isSubmenuSeparatorVisible(item, submenu) {
return item.separator && (this.isVisible(item) || this.isVisible(submenu));
}
isSubmenuItemVisible(item, submenu) {
return !item.separator && this.isVisible(item) && (item.visible !== undefined || this.isVisible(submenu));
}
activedescendant = computed(() => {
return this.focused() ? this.focusedOptionId() : undefined;
}, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "activedescendant" }] : /* istanbul ignore next */ []));
onListFocus(event) {
if (!this.focused()) {
this.focused.set(true);
if (!this.popup() && !this.focusedOptionId()) {
this.changeFocusedOptionIndex(0);
}
this.onFocus.emit(event);
}
}
onListBlur(event) {
if (this.focused()) {
this.focused.set(false);
this.changeFocusedOptionIndex(-1);
this.selectedOptionIndex.set(-1);
this.focusedOptionIndex.set(-1);
this.onBlur.emit(event);
}
}
onListKeyDown(event) {
switch (event.code) {
case 'ArrowDown':
this.onArrowDownKey(event);
break;
case 'ArrowUp':
this.onArrowUpKey(event);
break;
case 'Home':
this.onHomeKey(event);
break;
case 'End':
this.onEndKey(event);
break;
case 'Enter':
this.onEnterKey(event);
break;
case 'NumpadEnter':
this.onEnterKey(event);
break;
case 'Space':
this.onSpaceKey(event);
break;
case 'Escape':
case 'Tab':
if (this.popup()) {
this.target && focus(this.target);
this.hide();
}
this.overlayVisible() && this.hide();
break;
default:
break;
}
}
onArrowDownKey(event) {
const optionIndex = this.findNextOptionIndex(this.focusedOptionIndex());
this.changeFocusedOptionIndex(optionIndex);
event.preventDefault();
}
onArrowUpKey(event) {
if (event.altKey && this.popup()) {
this.target && focus(this.target);
this.hide();
event.preventDefault();
}
else {
const optionIndex = this.findPrevOptionIndex(this.focusedOptionIndex());
this.changeFocusedOptionIndex(optionIndex);
event.preventDefault();
}
}
onHomeKey(event) {
this.changeFocusedOptionIndex(0);
event.preventDefault();
}
onEndKey(event) {
this.changeFocusedOptionIndex(find(this.containerViewChild()?.nativeElement, 'li[data-pc-section="item"][data-disabled="false"]').length - 1);
event.preventDefault();
}
onEnterKey(event) {
event.preventDefault();
const element = findSingle(this.listViewChild()?.nativeElement, `[id="${`${this.focusedOptionIndex()}`}"]`);
if (!element)
return;
if (element.getAttribute('data-toggleable') === 'true') {
const content = (findSingle(element, '[data-pc-section="itemcontent"]') || element);
content.click();
return;
}
const anchorElement = findSingle(element, 'a[data-pc-section="itemlink"]');
this.popup() && this.target && focus(this.target);
(anchorElement || element).click();
}
onSpaceKey(event) {
this.onEnterKey(event);
}
findNextOptionIndex(index) {
const links = find(this.containerViewChild()?.nativeElement, 'li[data-pc-section="item"][data-disabled="false"]');
const matchedOptionIndex = [...links].findIndex((link) => link.id === index);
return matchedOptionIndex > -1 ? matchedOptionIndex + 1 : 0;
}
findPrevOptionIndex(index) {
const links = find(this.containerViewChild()?.nativeElement, 'li[data-pc-section="item"][data-disabled="false"]');
const matchedOptionIndex = [...links].findIndex((link) => link.id === index);
return matchedOptionIndex > -1 ? matchedOptionIndex - 1 : 0;
}
changeFocusedOptionIndex(index) {
const links = find(this.containerViewChild()?.nativeElement, 'li[data-pc-section="item"][data-disabled="false"]');
if (links.length > 0) {
let order = index >= links.length ? links.length - 1 : index < 0 ? 0 : index;
order > -1 && this.focusedOptionIndex.set(links[order].getAttribute('id') ?? -1);
}
}
itemClick(event, id) {
const { originalEvent, item } = event;
if (!this.focused()) {
this.focused.set(true);
this.onFocus.emit(originalEvent);
}
if (item.disabled) {
originalEvent.preventDefault();
return;
}
if (!item.url && !item.routerLink) {
originalEvent.preventDefault();
}
if (item.command) {
item.command({
originalEvent: originalEvent,
item: item
});
}
if (this.popup()) {
this.hide();
}
if (!this.popup() && this.focusedOptionIndex() !== id) {
this.focusedOptionIndex.set(id);
}
}
onOverlayClick(event) {
if (this.popup()) {
this.overlayService.add({
originalEvent: event,
target: this.el.nativeElement
});
}
this.preventDocumentDefault = true;
}
bindDocumentClickListener() {
if (!this.documentClickListener && isPlatformBrowser(this.platformId)) {
const documentTarget = this.el ? this.el.nativeElement.ownerDocument : this.document;
this.documentClickListener = this.renderer.listen(documentTarget, 'click', (event) => {
const isOutsideContainer = this.containerViewChild()?.nativeElement && !this.containerViewChild()?.nativeElement.contains(event.target);
const isOutsideTarget = !(this.target && (this.target === event.target || this.target.contains(event.target)));
if (!this.popup() && isOutsideContainer && isOutsideTarget) {
this.onListBlur(event);
}
if (this.preventDocumentDefault && this.overlayVisible() && isOutsideContainer && isOutsideTarget) {
this.hide();
this.preventDocumentDefault = false;
}
});
}
}
unbindDocumentClickListener() {
if (this.documentClickListener) {
this.documentClickListener();
this.documentClickListener = null;
}
}
bindDocumentResizeListener() {
if (!this.documentResizeListener && isPlatformBrowser(this.platformId)) {
const window = this.document.defaultView;
this.documentResizeListener = this.renderer.listen(window, 'resize', this.onWindowResize.bind(this));
}
}
unbindDocumentResizeListener() {
if (this.documentResizeListener) {
this.documentResizeListener();
this.documentResizeListener = null;
}
}
bindScrollListener() {
if (!this.scrollHandler && isPlatformBrowser(this.platformId)) {
this.scrollHandler = new ConnectedOverlayScrollHandler(this.target, () => {
if (this.visible()) {
this.hide();
}
});
}
this.scrollHandler?.bindScrollListener();
}
unbindScrollListener() {
if (this.scrollHandler) {
this.scrollHandler.unbindScrollListener();
this.scrollHandler = null;
}
}
onOverlayHide() {
this.unbindDocumentClickListener();
this.unbindDocumentResizeListener();
this.unbindScrollListener();
this.preventDocumentDefault = false;
if (!this.cd.destroyed) {
this.target = null;
}
if (this.container) {
if (this.autoZIndex()) {
ZIndexUtils.clear(this.container);
}
this.container = undefined;
}
}
onDestroy() {
if (this.popup()) {
if (this.scrollHandler) {
this.scrollHandler.destroy();
this.scrollHandler = null;
}
if (this.container) {
if (this.autoZIndex()) {
ZIndexUtils.clear(this.container);
}
this.container = undefined;
}
this.restoreOverlayAppend();
this.onOverlayHide();
}
if (!this.popup()) {
this.unbindDocumentClickListener();
}
}
hasSubMenu() {
return this.model()?.some((item) => item.items) ?? false;
}
getSubmenuKey(submenu, id) {
return submenu?.key != null ? String(submenu.key) : id;
}
isSubmenuToggleable(submenu, depth = 0) {
if (submenu.toggleable !== undefined) {
return submenu.toggleable;
}
return depth > 0;
}
isSubmenuExpanded(submenu, depth = 0, id) {
if (!this.isSubmenuToggleable(submenu, depth)) {
return true;
}
if (id) {
const key = this.getSubmenuKey(submenu, id);
const expandedKeys = this.currentExpandedKeys();
if (expandedKeys && key in expandedKeys) {
return expandedKeys[key];
}
}
return submenu.expanded === true;
}
toggleSubmenu(submenu, id, depth = 0) {
const key = this.getSubmenuKey(submenu, id);
const isExpanded = this.isSubmenuExpanded(submenu, depth, id);
const next = { ...(this.currentExpandedKeys() || {}), [key]: !isExpanded };
if (this.expandedKeys() !== undefined) {
this.expandedKeys.set(next);
}
else {
this.d_expandedKeys.set(next);
}
submenu.expanded = !isExpanded;
}
onItemLiClick(event, item, id, depth) {
if (item.items?.length && this.isSubmenuToggleable(item, depth)) {
event.preventDefault();
}
}
onItemLiMousedown(event, item, depth) {
if (item.items?.length && this.isSubmenuToggleable(item, depth)) {
event.preventDefault();
}
}
focusMenuList() {
const list = this.listViewChild()?.nativeElement;
if (list)
focus(list);
}
isItemHidden(item) {
if (item.separator) {
return item.visible === false || !!(item.items && item.items.some((subitem) => subitem.visible !== false));
}
return item.visible === false;
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: Menu, deps: null, target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: Menu, isStandalone: true, selector: "p-menu", inputs: { model: { classPropertyName: "model", publicName: "model", isSignal: true, isRequired: false, transformFunction: null }, popup: { classPropertyName: "popup", publicName: "popup", isSignal: true, isRequired: false, transformFunction: null }, style: { classPropertyName: "style", publicName: "style", isSignal: true, isRequired: false, transformFunction: null }, styleClass: { classPropertyName: "styleClass", publicName: "styleClass", isSignal: true, isRequired: false, transformFunction: null }, autoZIndex: { classPropertyName: "autoZIndex", publicName: "autoZIndex", isSignal: true, isRequired: false, transformFunction: null }, baseZIndex: { classPropertyName: "baseZIndex", publicName: "baseZIndex", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledBy: { classPropertyName: "ariaLabelledBy", publicName: "ariaLabelledBy", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, tabindex: { classPropertyName: "tabindex", publicName: "tabindex", isSignal: true, isRequired: false, transformFunction: null }, appendTo: { classPropertyName: "appendTo", publicName: "appendTo", isSignal: true, isRequired: false, transformFunction: null }, motionOptions: { classPropertyName: "motionOptions", publicName: "motionOptions", isSignal: true, isRequired: false, transformFunction: null }, expandedKeys: { classPropertyName: "expandedKeys", publicName: "expandedKeys", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { expandedKeys: "expandedKeysChange", onShow: "onShow", onHide: "onHide", onBlur: "onBlur", onFocus: "onFocus" }, providers: [MenuStyle, { provide: MENU_INSTANCE, useExisting: Menu }, { provide: PARENT_INSTANCE, useExisting: Menu }], queries: [{ propertyName: "startTemplate", first: true, predicate: ["start"], isSignal: true }, { propertyName: "endTemplate", first: true, predicate: ["end"], isSignal: true }, { propertyName: "itemTemplate", first: true, predicate: ["item"], isSignal: true }, { propertyName: "submenuHeaderTemplate", first: true, predicate: ["submenuheader"], isSignal: true }, { propertyName: "submenuIconTemplate", first: true, predicate: ["submenuicon"], isSignal: true }], viewQueries: [{ propertyName: "listViewChild", first: true, predicate: ["list"], descendants: true, isSignal: true }, { propertyName: "containerViewChild", first: true, predicate: ["container"], descendants: true, isSignal: true }], usesInheritance: true, hostDirectives: [{ directive: i1$1.Bind }], ngImport: i0, template: `
@if (!popup() || overlayVisible()) {
<div
#container
[class]="cn(cx('root'), styleClass())"
[style]="sx('root')"
(click)="onOverlayClick($event)"
[attr.id]="$id()"
[pBind]="ptm('root')"
[attr.data-p]="dataP()"
[pMotion]="visible() || !popup()"
[pMotionName]="'p-anchored-overlay'"
[pMotionAppear]="!!popup()"
[pMotionDisabled]="!popup()"
[pMotionOptions]="computedMotionOptions()"
(pMotionOnBeforeEnter)="onOverlayBeforeEnter($event)"
(pMotionOnAfterLeave)="onOverlayAfterLeave()"
>
@if (startTemplate()) {
<div [class]="cx('start')" [pBind]="ptm('start')" [attr.data-pc-section]="'start'">
<ng-container *ngTemplateOutlet="startTemplate()"></ng-container>
</div>
}
<ul
#list
[class]="cx('list')"
[pBind]="ptm('list')"
role="menu"
[attr.id]="$id() + '_list'"
[attr.tabindex]="getTabIndexValue()"
[attr.data-pc-section]="'menu'"
[attr.aria-activedescendant]="activedescendant()"
[attr.aria-label]="ariaLabel()"
[attr.aria-labelledBy]="ariaLabelledBy()"
(focus)="onListFocus($event)"
(blur)="onListBlur($event)"
(keydown)="onListKeyDown($event)"
>
<ng-container *ngTemplateOutlet="recursiveItems; context: { $implicit: model(), depth: 0, parentId: $id() }"></ng-container>
<ng-template #recursiveItems let-items let-depth="depth" let-parentId="parentId">
@for (item of items; track menuitemId(item, parentId, i); let i = $index) {
@if (isSeparatorVisible(item)) {
<li [class]="cx('separator')" [pBind]="ptm('separator')" role="separator" [attr.data-pc-section]="'separator'"></li>
}
@if (!item.separator && isVisible(item)) {
@if (item.items?.length && !isSubmenuToggleable(item, depth)) {
<li
[class]="cx('submenuLabel')"
[attr.data-depth]="depth || null"
[pBind]="ptm('submenuLabel')"
[attr.data-automationid]="item.automationId"
pTooltip
[tooltipOptions]="item.tooltipOptions"
[pTooltipUnstyled]="unstyled()"
role="none"
[attr.id]="menuitemId(item, parentId, i)"
[attr.data-pc-section]="'submenulabel'"
>
@if (!submenuHeaderTemplate()) {
@if (item.escape !== false) {
<span>{{ item.label }}</span>
} @else {
<span [innerHTML]="item.label | safeHtml"></span>
}
}
<ng-container *ngTemplateOutlet="submenuHeaderTemplate(); context: { $implicit: item }"