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,020 lines (1,014 loc) 68.1 kB
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 }"