diff --git a/src/app/components/api/menuitem.ts b/src/app/components/api/menuitem.ts index e577a050dca..cc1f17f50a4 100755 --- a/src/app/components/api/menuitem.ts +++ b/src/app/components/api/menuitem.ts @@ -1,6 +1,6 @@ import { QueryParamsHandling } from '@angular/router'; -import { TooltipOptions } from './tooltipoptions'; import { MegaMenuItem } from './megamenuitem'; +import { TooltipOptions } from './tooltipoptions'; /** * MenuItem provides the following properties. Note that not all of them may be utilized by the tabmenu component. @@ -143,6 +143,10 @@ export interface MenuItem { * @see {TooltipOptions} */ tooltipOptions?: TooltipOptions; + /** + * Optional + */ + [key: string]: any; } /** diff --git a/src/app/components/breadcrumb/breadcrumb.ts b/src/app/components/breadcrumb/breadcrumb.ts index 0d37f4d5746..d5c267b0345 100755 --- a/src/app/components/breadcrumb/breadcrumb.ts +++ b/src/app/components/breadcrumb/breadcrumb.ts @@ -87,10 +87,15 @@ import { BreadcrumbItemClickEvent } from './breadcrumb.interface'; [attr.tabindex]="item.disabled ? null : '0'" [ariaCurrentWhenActive]="isCurrentUrl(item)" > - - - {{ item.label }} - + + + + {{ item.label }} + + + + + - - - {{ item.label }} - + + + + {{ item.label }} + + + + + @@ -171,6 +181,8 @@ export class Breadcrumb implements AfterContentInit { separatorTemplate: TemplateRef | undefined; + itemTemplate: TemplateRef | undefined; + constructor(private router: Router) {} onClick(event: MouseEvent, item: MenuItem) { @@ -208,6 +220,12 @@ export class Breadcrumb implements AfterContentInit { case 'separator': this.separatorTemplate = item.template; break; + case 'item': + this.itemTemplate = item.template; + break; + default: + this.itemTemplate = item.template; + break; } }); } diff --git a/src/app/components/contextmenu/contextmenu.ts b/src/app/components/contextmenu/contextmenu.ts index ad444896356..1d55827c471 100755 --- a/src/app/components/contextmenu/contextmenu.ts +++ b/src/app/components/contextmenu/contextmenu.ts @@ -11,14 +11,12 @@ import { Inject, Input, NgModule, - OnChanges, OnDestroy, OnInit, Output, PLATFORM_ID, QueryList, Renderer2, - SimpleChanges, TemplateRef, ViewChild, ViewContainerRef, @@ -34,8 +32,8 @@ import { DomHandler } from 'primeng/dom'; import { AngleRightIcon } from 'primeng/icons/angleright'; import { RippleModule } from 'primeng/ripple'; import { TooltipModule } from 'primeng/tooltip'; -import { ObjectUtils, UniqueComponentId, ZIndexUtils } from 'primeng/utils'; import { Nullable, VoidListener } from 'primeng/ts-helpers'; +import { ObjectUtils, UniqueComponentId, ZIndexUtils } from 'primeng/utils'; @Component({ selector: 'p-contextMenuSub', @@ -90,88 +88,94 @@ import { Nullable, VoidListener } from 'primeng/ts-helpers'; [tooltipOptions]="getItemProp(processedItem, 'tooltipOptions')" > >; + itemTemplate: Nullable>; + container: HTMLDivElement | undefined; outsideClickListener: VoidListener; @@ -592,6 +601,12 @@ export class ContextMenu implements OnInit, AfterContentInit, OnDestroy { case 'submenuicon': this.submenuIconTemplate = item.template; break; + case 'item': + this.itemTemplate = item.template; + break; + default: + this.itemTemplate = item.template; + break; } }); } diff --git a/src/app/components/megamenu/megamenu.ts b/src/app/components/megamenu/megamenu.ts index 58eab5bafa1..0978589245e 100755 --- a/src/app/components/megamenu/megamenu.ts +++ b/src/app/components/megamenu/megamenu.ts @@ -24,7 +24,7 @@ import { signal } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { MegaMenuItem, MenuItem, PrimeNGConfig, PrimeTemplate, SharedModule } from 'primeng/api'; +import { MegaMenuItem, PrimeNGConfig, PrimeTemplate, SharedModule } from 'primeng/api'; import { DomHandler } from 'primeng/dom'; import { AngleDownIcon } from 'primeng/icons/angledown'; import { AngleRightIcon } from 'primeng/icons/angleright'; @@ -82,83 +82,88 @@ import { ObjectUtils, UniqueComponentId } from 'primeng/utils'; [tooltipOptions]="getItemProp(processedItem, 'tooltipOptions')" >
@@ -168,6 +173,7 @@ import { ObjectUtils, UniqueComponentId } from 'primeng/utils'; [id]="getSubListId(submenu)" [submenu]="submenu" [items]="submenu.items" + [itemTemplate]="itemTemplate" [menuId]="menuId" [focusedItemId]="focusedItemId" [level]="level + 1" @@ -193,6 +199,8 @@ export class MegaMenuSub { @Input() items: any[] | undefined; + @Input() itemTemplate: HTMLElement | undefined; + @Input() menuId: string | undefined; @Input() ariaLabel: string | undefined; @@ -357,6 +365,7 @@ export class MegaMenuSub {
| undefined; + itemTemplate: TemplateRef | undefined; + outsideClickListener: VoidListener; resizeListener: VoidListener; @@ -542,6 +553,14 @@ export class MegaMenu implements AfterContentInit, OnDestroy, OnInit { case 'submenuicon': this.submenuIconTemplate = item.template; break; + + case 'item': + this.itemTemplate = item.template; + break; + + default: + this.itemTemplate = item.template; + break; } }); } diff --git a/src/app/components/menu/menu.ts b/src/app/components/menu/menu.ts index 989706e57e3..5009e2314a2 100755 --- a/src/app/components/menu/menu.ts +++ b/src/app/components/menu/menu.ts @@ -1,36 +1,38 @@ +import { AnimationEvent, animate, style, transition, trigger } from '@angular/animations'; +import { CommonModule, DOCUMENT, isPlatformBrowser } from '@angular/common'; import { - NgModule, + ChangeDetectionStrategy, + ChangeDetectorRef, Component, + ContentChildren, ElementRef, - OnDestroy, - Input, - Output, EventEmitter, - Renderer2, Inject, - forwardRef, - ChangeDetectorRef, - ChangeDetectionStrategy, - ViewEncapsulation, - ViewRef, + Input, + NgModule, + OnDestroy, + Output, PLATFORM_ID, - TemplateRef, Pipe, PipeTransform, + QueryList, + Renderer2, + TemplateRef, ViewChild, - signal, - computed + ViewEncapsulation, + ViewRef, + computed, + forwardRef, + signal } from '@angular/core'; -import { trigger, style, transition, animate, AnimationEvent } from '@angular/animations'; -import { CommonModule, DOCUMENT, isPlatformBrowser } from '@angular/common'; -import { DomHandler, ConnectedOverlayScrollHandler } from 'primeng/dom'; -import { MenuItem, OverlayService, PrimeNGConfig } from 'primeng/api'; -import { UniqueComponentId, ZIndexUtils } from 'primeng/utils'; +import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { RouterModule } from '@angular/router'; +import { MenuItem, OverlayService, PrimeNGConfig, PrimeTemplate } from 'primeng/api'; +import { ConnectedOverlayScrollHandler, DomHandler } from 'primeng/dom'; import { RippleModule } from 'primeng/ripple'; import { TooltipModule } from 'primeng/tooltip'; import { Nullable, VoidListener } from 'primeng/ts-helpers'; -import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; +import { UniqueComponentId, ZIndexUtils } from 'primeng/utils'; @Pipe({ name: 'safeHtml' @@ -50,48 +52,52 @@ export class SafeHtmlPipe implements PipeTransform { @Component({ selector: '[pMenuItemContent]', template: ` -
- - - - - - +
+ + + + + + + + + + + + @@ -109,7 +115,9 @@ export class SafeHtmlPipe implements PipeTransform { export class MenuItemContent { @Input('pMenuItemContent') item: MenuItem | undefined; - @Input('id') id: string; + @Input() itemTemplate: HTMLElement | undefined; + + @Input() id: string; @Output() onMenuItemClick: EventEmitter = new EventEmitter(); @@ -144,6 +152,9 @@ export class MenuItemContent { [attr.data-pc-name]="'menu'" [attr.id]="id" > +
+ +
+
+ +
`, animations: [trigger('overlayAnimation', [transition(':enter', [style({ opacity: 0, transform: 'scaleY(0.8)' }), animate('{{showTransitionParams}}')]), transition(':leave', [animate('{{hideTransitionParams}}', style({ opacity: 0 }))])])], @@ -320,6 +336,14 @@ export class Menu implements OnDestroy { @ViewChild('container') containerViewChild: Nullable; + @ContentChildren(PrimeTemplate) templates: QueryList | undefined; + + startTemplate: TemplateRef | undefined; + + endTemplate: TemplateRef | undefined; + + itemTemplate: TemplateRef | undefined; + container: HTMLDivElement | undefined; scrollHandler: ConnectedOverlayScrollHandler | null | undefined; @@ -390,6 +414,25 @@ export class Menu implements OnDestroy { } } + ngAfterContentInit() { + this.templates?.forEach((item) => { + switch (item.getType()) { + case 'start': + this.startTemplate = item.template; + break; + case 'end': + this.endTemplate = item.template; + break; + case 'itemTemplate': + this.itemTemplate = item.template; + break; + default: + this.itemTemplate = item.template; + break; + } + }); + } + onOverlayAnimationStart(event: AnimationEvent) { switch (event.toState) { case 'visible': @@ -615,6 +658,8 @@ export class Menu implements OnDestroy { itemClick(event: any) { const { originalEvent, item } = event; + console.log(item); + if (item.disabled) { originalEvent.preventDefault(); return; diff --git a/src/app/components/menubar/menubar.ts b/src/app/components/menubar/menubar.ts index 9514d447381..970b422902c 100755 --- a/src/app/components/menubar/menubar.ts +++ b/src/app/components/menubar/menubar.ts @@ -99,86 +99,92 @@ export class MenubarService { [tooltipOptions]="getItemProp(processedItem, 'tooltipOptions')" > | undefined; + itemTemplate: TemplateRef | undefined; + mobileActive: boolean | undefined; outsideClickListener: VoidListener; @@ -577,6 +588,12 @@ export class Menubar implements AfterContentInit, OnDestroy, OnInit { case 'submenuicon': this.submenuIconTemplate = item.template; break; + case 'item': + this.itemTemplate = item.template; + break; + default: + this.itemTemplate = item.template; + break; } }); } diff --git a/src/app/components/panelmenu/panelmenu.ts b/src/app/components/panelmenu/panelmenu.ts index 66c654eb048..690d791b0ba 100644 --- a/src/app/components/panelmenu/panelmenu.ts +++ b/src/app/components/panelmenu/panelmenu.ts @@ -68,58 +68,63 @@ import { ObjectUtils, UniqueComponentId } from 'primeng/utils'; [tooltipOptions]="getItemProp(processedItem, 'tooltipOptions')" >
| undefined; + itemTemplate: TemplateRef | undefined; + public animating: boolean | undefined; activeItem = signal(null); @@ -875,6 +889,12 @@ export class PanelMenu implements AfterContentInit { case 'submenuicon': this.submenuIconTemplate = item.template; break; + case 'item': + this.itemTemplate = item.template; + break; + default: + this.itemTemplate = item.template; + break; } }); } diff --git a/src/app/components/tabmenu/tabmenu.ts b/src/app/components/tabmenu/tabmenu.ts index 0d073f5c84a..c8f167150cb 100644 --- a/src/app/components/tabmenu/tabmenu.ts +++ b/src/app/components/tabmenu/tabmenu.ts @@ -20,8 +20,6 @@ import { ViewChild, ViewChildren, ViewEncapsulation, - computed, - effect, signal } from '@angular/core'; import { ActivatedRoute, Router, RouterModule } from '@angular/router'; @@ -57,13 +55,16 @@ import { ObjectUtils } from 'primeng/utils'; [class]="item.styleClass" [attr.data-p-disabled]="disabled(item)" [attr.data-p-highlight]="focusedItemInfo() === item" + (click)="itemClick($event, item)" + (keydown)="onKeydownItem($event, i, item)" + (focus)="onMenuItemFocus(item)" [ngClass]="{ 'p-tabmenuitem': true, 'p-disabled': getItemProp(item, 'disabled'), 'p-highlight': isActive(item), 'p-hidden': item.visible === false }" pTooltip [tooltipOptions]="item.tooltipOptions" > - + {{ getItemProp(item, 'label') }} {{ getItemProp(item, 'badge') }} - - + {{ getItemProp(item, 'label') }} {{ getItemProp(item, 'badge') }} - +
  • @@ -223,7 +217,7 @@ export class TabMenu implements AfterContentInit, AfterViewInit, AfterViewChecke private timerIdForInitialAutoScroll: any = null; - _focusableItems: MenuItem[] | undefined; + _focusableItems: MenuItem[] | undefined | any; _model: MenuItem[] | undefined; diff --git a/src/app/components/tieredmenu/tieredmenu.ts b/src/app/components/tieredmenu/tieredmenu.ts index 408134a5daf..9f063f8f3ed 100755 --- a/src/app/components/tieredmenu/tieredmenu.ts +++ b/src/app/components/tieredmenu/tieredmenu.ts @@ -84,88 +84,94 @@ import { ObjectUtils, UniqueComponentId, ZIndexUtils } from 'primeng/utils'; [tooltipOptions]="getItemProp(processedItem, 'tooltipOptions')" > >; + itemTemplate: Nullable>; + container: HTMLDivElement | undefined; outsideClickListener: VoidListener; @@ -556,6 +567,12 @@ export class TieredMenu implements OnInit, AfterContentInit, OnDestroy { case 'submenuicon': this.submenuIconTemplate = item.template; break; + case 'item': + this.itemTemplate = item.template; + break; + default: + this.itemTemplate = item.template; + break; } }); } diff --git a/src/app/showcase/doc/menu/customdoc.ts b/src/app/showcase/doc/menu/customdoc.ts index 949359f36e7..b1fd68804fb 100644 --- a/src/app/showcase/doc/menu/customdoc.ts +++ b/src/app/showcase/doc/menu/customdoc.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, HostListener, Input, OnInit } from '@angular/core'; import { MenuItem } from 'primeng/api'; import { Code } from '../../domain/code'; @@ -9,7 +9,20 @@ import { Code } from '../../domain/code';

    Label of a menuitem both supports simple strings and html values as well. By default, html values are escaped, use escape property to allow html.

    ` @@ -27,16 +40,22 @@ export class CustomContentDoc implements OnInit { label: 'Options', items: [ { - label: 'Refresh', - escape: false, + label: 'Refresh', icon: 'pi pi-refresh', - iconClass: 'text-xl' + shortcut: '⌘+U', + shortcutClass: 'p-1 font-medium border-round text-sm surface-ground', + command: () => { + console.log('Command + U'); + } }, { - label: 'Delete', - escape: false, + label: 'Delete', icon: 'pi pi-times', - iconClass: 'text-xl' + shortcut: '⌘+B', + shortcutClass: 'p-1 font-medium border-round text-sm surface-ground', + command: () => { + console.log('Command + B'); + } } ] }, @@ -46,29 +65,70 @@ export class CustomContentDoc implements OnInit { { label: 'Angular', icon: 'pi pi-external-link', + badge: '2', + badgeSeverity: 'success', url: 'http://angular.io' }, { - label: 'Router', + label: 'File Upload', icon: 'pi pi-upload', - routerLink: '/fileupload' + routerLink: '/fileupload', + badge: '2' } ] } ]; } + @HostListener('document:keydown', ['$event']) + handleKeyboardEvent(event: KeyboardEvent): void { + if (event.metaKey) { + if (event.metaKey && event.key === 'u') { + console.log('Command + U'); + } + if (event.metaKey && event.key === 'b') { + console.log('Command + B'); + } + } + } + code: Code = { basic: ` -`, + + + +
    + + {{ item.label }} +
    +
    + {{ item.shortcut }} + +
    +
    +
    +
    `, html: ` `, typescript: ` -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, HostListener } from '@angular/core'; import { MenuItem } from 'primeng/api'; @Component({ @@ -114,6 +174,18 @@ export class MenuCustomContentDemo implements OnInit { } ]; } + + @HostListener('document:keydown', ['$event']) + handleKeyboardEvent(event: KeyboardEvent): void { + if (event.metaKey) { + if (event.metaKey && event.key === 'u') { + console.log('Command + U'); + } + if (event.metaKey && event.key === 'b') { + console.log('Command + B'); + } + } + } }` }; } diff --git a/src/app/showcase/doc/menu/menudoc.module.ts b/src/app/showcase/doc/menu/menudoc.module.ts index 7d8db27b13d..6b083dbd696 100644 --- a/src/app/showcase/doc/menu/menudoc.module.ts +++ b/src/app/showcase/doc/menu/menudoc.module.ts @@ -1,23 +1,25 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; -import { MenuModule } from 'primeng/menu'; +import { AvatarModule } from 'primeng/avatar'; +import { BadgeModule } from 'primeng/badge'; import { ButtonModule } from 'primeng/button'; +import { MenuModule } from 'primeng/menu'; import { ToastModule } from 'primeng/toast'; import { AppDocModule } from '../../layout/doc/app.doc.module'; import { AppCodeModule } from '../../layout/doc/code/app.code.component'; +import { AccessibilityDoc } from './accessibilitydoc'; import { BasicDoc } from './basicdoc'; -import { CustomContentDoc } from './customdoc'; import { CommandDoc } from './commanddoc'; +import { CustomContentDoc } from './customdoc'; import { GroupDoc } from './groupdoc'; import { ImportDoc } from './importdoc'; import { NavigationDoc } from './navigationdoc'; import { PopupDoc } from './popupdoc'; import { StyleDoc } from './styledoc'; -import { AccessibilityDoc } from './accessibilitydoc'; @NgModule({ - imports: [CommonModule, AppCodeModule, RouterModule, MenuModule, ToastModule, ButtonModule, AppDocModule], + imports: [CommonModule, AppCodeModule, RouterModule, MenuModule, ToastModule, ButtonModule, AvatarModule, AppDocModule, BadgeModule], declarations: [BasicDoc, CommandDoc, CustomContentDoc, GroupDoc, ImportDoc, NavigationDoc, PopupDoc, StyleDoc, AccessibilityDoc], exports: [AppDocModule] }) diff --git a/src/app/showcase/doc/tabmenu/templatedoc.ts b/src/app/showcase/doc/tabmenu/templatedoc.ts index fcebd025c4f..7d3f156b391 100644 --- a/src/app/showcase/doc/tabmenu/templatedoc.ts +++ b/src/app/showcase/doc/tabmenu/templatedoc.ts @@ -10,7 +10,18 @@ import { Code } from '../../domain/code';
    - {{ i }} - Custom {{ item.label }} + + +
    + + {{ item.label }} +
    +
    + {{ item.shortcut }} + +
    +
    +
    @@ -40,16 +51,34 @@ export class TemplateDoc implements OnInit { code: Code = { basic: ` - - {{i}} - Custom {{item.label}} + + +
    + + {{ item.label }} +
    +
    + {{ item.shortcut }} + +
    +
    `, html: `
    - - {{i}} - Custom {{item.label}} + + +
    + + {{ item.label }} +
    +
    + {{ item.shortcut }} + +
    +
    `,