From 26416586a7c483ddd41f692a63d476f22e032026 Mon Sep 17 00:00:00 2001
From: Mykyta Maliarchuk <84377976+nikita-web-ua@users.noreply.github.com>
Date: Mon, 27 Oct 2025 11:28:57 +0100
Subject: [PATCH] [ACS-10303] Fix context menu keyboard navigation (#4844)
* [ACS-10303] Fix context menu keyboard navigation
* [ACS-10303] cr fixes
* [ACS-10303] cr fixes
* [ACS-10303] cr fixes
---
.../toggle-shared.component.html | 8 +-
.../toggle-shared/toggle-shared.component.ts | 7 +-
.../context-menu.component.spec.ts | 70 ++++++++++++++--
.../context-menu/context-menu.component.ts | 51 +++++++++++-
.../toggle-edit-offline.component.ts | 7 +-
.../toggle-favorite-library.component.ts | 10 ++-
.../toggle-favorite.component.ts | 10 ++-
.../toggle-join-library-button.component.ts | 6 +-
.../toolbar/view-node/view-node.component.ts | 7 +-
.../toolbar-menu.component.spec.ts | 80 ++++++++++++++++---
.../toolbar-menu/toolbar-menu.component.ts | 36 +++++++--
11 files changed, 241 insertions(+), 51 deletions(-)
diff --git a/projects/aca-content/src/lib/components/common/toggle-shared/toggle-shared.component.html b/projects/aca-content/src/lib/components/common/toggle-shared/toggle-shared.component.html
index ba165602c..a9f88f5d0 100644
--- a/projects/aca-content/src/lib/components/common/toggle-shared/toggle-shared.component.html
+++ b/projects/aca-content/src/lib/components/common/toggle-shared/toggle-shared.component.html
@@ -2,9 +2,7 @@
@@ -21,7 +19,3 @@
-
-
- {{ 'APP.ACTIONS.SHARE' | translate }}
-
diff --git a/projects/aca-content/src/lib/components/common/toggle-shared/toggle-shared.component.ts b/projects/aca-content/src/lib/components/common/toggle-shared/toggle-shared.component.ts
index efda59840..1ba33f0b3 100644
--- a/projects/aca-content/src/lib/components/common/toggle-shared/toggle-shared.component.ts
+++ b/projects/aca-content/src/lib/components/common/toggle-shared/toggle-shared.component.ts
@@ -22,13 +22,13 @@
* from Hyland Software. If not, see .
*/
-import { Component, DestroyRef, inject, Input, OnInit, ViewEncapsulation } from '@angular/core';
+import { Component, DestroyRef, inject, Input, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { Observable } from 'rxjs';
import { Store } from '@ngrx/store';
import { SelectionState } from '@alfresco/adf-extensions';
import { AppStore, getAppSelection, ShareNodeAction } from '@alfresco/aca-shared/store';
import { CommonModule } from '@angular/common';
-import { MatMenuModule } from '@angular/material/menu';
+import { MatMenuItem, MatMenuModule } from '@angular/material/menu';
import { MatIconModule } from '@angular/material/icon';
import { TranslatePipe } from '@ngx-translate/core';
import { MatButtonModule } from '@angular/material/button';
@@ -46,6 +46,9 @@ export class ToggleSharedComponent implements OnInit {
iconButton?: string;
};
+ @ViewChild(MatMenuItem)
+ menuItem: MatMenuItem;
+
selection$: Observable;
selectionState: SelectionState;
selectionLabel = '';
diff --git a/projects/aca-content/src/lib/components/context-menu/context-menu.component.spec.ts b/projects/aca-content/src/lib/components/context-menu/context-menu.component.spec.ts
index 67c891617..53fe858ff 100644
--- a/projects/aca-content/src/lib/components/context-menu/context-menu.component.spec.ts
+++ b/projects/aca-content/src/lib/components/context-menu/context-menu.component.spec.ts
@@ -26,18 +26,35 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AppTestingModule } from '../../testing/app-testing.module';
import { ContextMenuComponent } from './context-menu.component';
import { ContextMenuOverlayRef } from './context-menu-overlay';
-import { ContentActionType } from '@alfresco/adf-extensions';
+import { ContentActionRef, ContentActionType, ExtensionService } from '@alfresco/adf-extensions';
import { of } from 'rxjs';
import { Store } from '@ngrx/store';
import { AppExtensionService } from '@alfresco/aca-shared';
+import { Component, ViewChild } from '@angular/core';
+import { MatMenuItem, MatMenuModule } from '@angular/material/menu';
+import { UnitTestingUtils } from '@alfresco/adf-core';
+
+@Component({
+ selector: 'aca-custom-menu-component',
+ standalone: true,
+ imports: [MatMenuModule],
+ // eslint-disable-next-line @alfresco/eslint-angular/no-angular-material-selectors
+ template: ''
+})
+class TestCustomMenuComponent {
+ data: any;
+ @ViewChild(MatMenuItem) menuItem: MatMenuItem;
+}
describe('ContextMenuComponent', () => {
let fixture: ComponentFixture;
let component: ContextMenuComponent;
let extensionsService: AppExtensionService;
+ let extensionService: ExtensionService;
+ let unitTestingUtils: UnitTestingUtils;
- const contextItem = {
+ const contextItem: ContentActionRef = {
type: ContentActionType.button,
id: 'action-button',
title: 'Test Button',
@@ -48,7 +65,7 @@ describe('ContextMenuComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
- imports: [AppTestingModule],
+ imports: [AppTestingModule, TestCustomMenuComponent],
providers: [
{
provide: ContextMenuOverlayRef,
@@ -70,6 +87,8 @@ describe('ContextMenuComponent', () => {
component = fixture.componentInstance;
extensionsService = TestBed.inject(AppExtensionService);
+ extensionService = TestBed.inject(ExtensionService);
+ unitTestingUtils = new UnitTestingUtils(fixture.debugElement);
});
it('should load context menu actions on init', () => {
@@ -84,11 +103,13 @@ describe('ContextMenuComponent', () => {
fixture.detectChanges();
await fixture.whenStable();
- const contextMenuElements = document.body.querySelector('.aca-context-menu')?.querySelectorAll('button');
- const actionButtonLabel: HTMLElement = contextMenuElements?.[0].querySelector(`[data-automation-id="${contextItem.id}-label"]`);
+ const contextMenuButtons = unitTestingUtils.getAllByCSS('.aca-context-menu button');
+ const actionButtonLabel = unitTestingUtils.getInnerTextByCSS(
+ `.aca-context-menu button:first-child [data-automation-id="${contextItem.id}-label"]`
+ );
- expect(contextMenuElements?.length).toBe(1);
- expect(actionButtonLabel.innerText).toBe(contextItem.title);
+ expect(contextMenuButtons?.length).toBe(1);
+ expect(actionButtonLabel).toBe(contextItem.title);
});
it('should not render context menu if no actions items', async () => {
@@ -96,8 +117,41 @@ describe('ContextMenuComponent', () => {
fixture.detectChanges();
await fixture.whenStable();
- const contextMenuElements = document.body.querySelector('.aca-context-menu');
+ const contextMenuElements = unitTestingUtils.getByCSS('.aca-context-menu');
expect(contextMenuElements).toBeNull();
});
+
+ it('should append menu items in the correct order according to actions array', async () => {
+ const customComponentAction: ContentActionRef = {
+ type: ContentActionType.custom,
+ component: 'test-custom-component',
+ id: 'custom-action',
+ data: { testProp: 'test-value' }
+ };
+
+ const buttonAction2: ContentActionRef = {
+ type: ContentActionType.button,
+ id: 'button-action-2',
+ title: 'Button 2',
+ actions: {
+ click: 'EVENT_2'
+ }
+ };
+
+ const orderedActions = [contextItem, customComponentAction, buttonAction2];
+
+ spyOn(extensionsService, 'getAllowedContextMenuActions').and.returnValue(of(orderedActions));
+ spyOn(extensionService, 'getComponentById').and.returnValue(TestCustomMenuComponent);
+
+ fixture.detectChanges();
+ await fixture.whenStable();
+
+ const menuItems = component.menu._allItems.toArray();
+ expect(menuItems.length).toBe(3);
+
+ const menuItemsIds = menuItems.map((item) => item._getHostElement().getAttribute('id'));
+ const domIds: string[] = Array.from(unitTestingUtils.getAllByCSS('button')).map((button) => button.nativeElement.getAttribute('id'));
+ expect(domIds).toEqual(menuItemsIds);
+ });
});
diff --git a/projects/aca-content/src/lib/components/context-menu/context-menu.component.ts b/projects/aca-content/src/lib/components/context-menu/context-menu.component.ts
index fe275c8d3..70c11b9b1 100644
--- a/projects/aca-content/src/lib/components/context-menu/context-menu.component.ts
+++ b/projects/aca-content/src/lib/components/context-menu/context-menu.component.ts
@@ -22,9 +22,9 @@
* from Hyland Software. If not, see .
*/
-import { AfterViewInit, Component, DestroyRef, inject, Inject, OnInit, ViewEncapsulation } from '@angular/core';
-import { MatMenuModule } from '@angular/material/menu';
-import { DynamicExtensionComponent } from '@alfresco/adf-extensions';
+import { AfterViewInit, Component, DestroyRef, inject, Inject, OnInit, QueryList, ViewChild, ViewChildren, ViewEncapsulation } from '@angular/core';
+import { MatMenu, MatMenuItem, MatMenuModule } from '@angular/material/menu';
+import { ContentActionType, DynamicExtensionComponent } from '@alfresco/adf-extensions';
import { ContextMenuOverlayRef } from './context-menu-overlay';
import { CONTEXT_MENU_DIRECTION } from './direction.token';
import { Direction } from '@angular/cdk/bidi';
@@ -58,6 +58,15 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
encapsulation: ViewEncapsulation.None
})
export class ContextMenuComponent extends BaseContextMenuDirective implements OnInit, AfterViewInit {
+ @ViewChildren(DynamicExtensionComponent)
+ dynamicExtensionComponents: QueryList;
+
+ @ViewChild(MatMenu)
+ menu: MatMenu;
+
+ @ViewChildren(MatMenuItem)
+ matMenuItems: QueryList;
+
private readonly destroyRef = inject(DestroyRef);
constructor(contextMenuOverlayRef: ContextMenuOverlayRef, extensions: AppExtensionService, @Inject(CONTEXT_MENU_DIRECTION) direction: Direction) {
@@ -77,5 +86,41 @@ export class ContextMenuComponent extends BaseContextMenuDirective implements On
if (this.actions.length) {
setTimeout(() => this.trigger.openMenu(), 0);
}
+
+ const itemsById = this.createMenuItemsLookup();
+ const orderedItems = this.createOrderedItemsList(itemsById);
+
+ const menuItemsQueryList = new QueryList();
+ menuItemsQueryList.reset(orderedItems);
+ this.menu._allItems = menuItemsQueryList;
+ this.menu.ngAfterContentInit();
+ }
+
+ private createMenuItemsLookup(): Map {
+ const itemsById = new Map();
+ this.matMenuItems.forEach((item) => {
+ itemsById.set(item._getHostElement()?.getAttribute('id'), item);
+ });
+
+ this.dynamicExtensionComponents.forEach((component) => {
+ if (component.menuItem && component.id) {
+ itemsById.set(component.id, component.menuItem);
+ }
+ });
+ return itemsById;
+ }
+
+ private createOrderedItemsList(itemsById: Map): MatMenuItem[] {
+ const orderedItems: MatMenuItem[] = [];
+
+ this.actions.forEach((action) => {
+ const lookupId = action.type === ContentActionType.custom ? action.component : action.id;
+ const item = lookupId ? itemsById.get(lookupId) : undefined;
+
+ if (item) {
+ orderedItems.push(item);
+ }
+ });
+ return orderedItems;
}
}
diff --git a/projects/aca-content/src/lib/components/toolbar/toggle-edit-offline/toggle-edit-offline.component.ts b/projects/aca-content/src/lib/components/toolbar/toggle-edit-offline/toggle-edit-offline.component.ts
index b99dae132..35ec6b1c9 100644
--- a/projects/aca-content/src/lib/components/toolbar/toggle-edit-offline/toggle-edit-offline.component.ts
+++ b/projects/aca-content/src/lib/components/toolbar/toggle-edit-offline/toggle-edit-offline.component.ts
@@ -24,14 +24,14 @@
import { AppStore, DownloadNodesAction, EditOfflineAction, SetSelectedNodesAction, getAppSelection } from '@alfresco/aca-shared/store';
import { NodeEntry, SharedLinkEntry, Node, NodesApi } from '@alfresco/js-api';
-import { Component, inject, OnInit, ViewEncapsulation } from '@angular/core';
+import { Component, inject, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppExtensionService, isLocked } from '@alfresco/aca-shared';
import { NotificationService } from '@alfresco/adf-core';
import { AlfrescoApiService } from '@alfresco/adf-content-services';
import { CommonModule } from '@angular/common';
import { TranslatePipe } from '@ngx-translate/core';
-import { MatMenuModule } from '@angular/material/menu';
+import { MatMenuItem, MatMenuModule } from '@angular/material/menu';
import { MatIconModule } from '@angular/material/icon';
@Component({
@@ -47,6 +47,9 @@ import { MatIconModule } from '@angular/material/icon';
host: { class: 'app-toggle-edit-offline' }
})
export class ToggleEditOfflineComponent implements OnInit {
+ @ViewChild(MatMenuItem)
+ menuItem: MatMenuItem;
+
private notificationService = inject(NotificationService);
private nodesApi: NodesApi;
diff --git a/projects/aca-content/src/lib/components/toolbar/toggle-favorite-library/toggle-favorite-library.component.ts b/projects/aca-content/src/lib/components/toolbar/toggle-favorite-library/toggle-favorite-library.component.ts
index 30a54efe3..a10a3cb35 100644
--- a/projects/aca-content/src/lib/components/toolbar/toggle-favorite-library/toggle-favorite-library.component.ts
+++ b/projects/aca-content/src/lib/components/toolbar/toggle-favorite-library/toggle-favorite-library.component.ts
@@ -22,7 +22,7 @@
* from Hyland Software. If not, see .
*/
-import { Component, DestroyRef, inject, OnInit, ViewEncapsulation } from '@angular/core';
+import { Component, DestroyRef, inject, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppHookService } from '@alfresco/aca-shared';
import { AppStore, getAppSelection } from '@alfresco/aca-shared/store';
@@ -33,7 +33,7 @@ import { CommonModule } from '@angular/common';
import { TranslatePipe } from '@ngx-translate/core';
import { LibraryFavoriteDirective } from '@alfresco/adf-content-services';
import { MatIconModule } from '@angular/material/icon';
-import { MatMenuModule } from '@angular/material/menu';
+import { MatMenuItem, MatMenuModule } from '@angular/material/menu';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
@@ -46,8 +46,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
[adf-favorite-library]="library"
[attr.title]="library.isFavorite ? ('APP.ACTIONS.REMOVE_FAVORITE' | translate) : ('APP.ACTIONS.FAVORITE' | translate)"
>
- star
- star_border
+
{{ (library.isFavorite ? 'APP.ACTIONS.REMOVE_FAVORITE' : 'APP.ACTIONS.FAVORITE') | translate }}
`,
@@ -57,6 +56,9 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
export class ToggleFavoriteLibraryComponent implements OnInit {
library;
+ @ViewChild(MatMenuItem)
+ menuItem: MatMenuItem;
+
private readonly destroyRef = inject(DestroyRef);
constructor(
diff --git a/projects/aca-content/src/lib/components/toolbar/toggle-favorite/toggle-favorite.component.ts b/projects/aca-content/src/lib/components/toolbar/toggle-favorite/toggle-favorite.component.ts
index 7b9543dc1..82e4e0ac1 100644
--- a/projects/aca-content/src/lib/components/toolbar/toggle-favorite/toggle-favorite.component.ts
+++ b/projects/aca-content/src/lib/components/toolbar/toggle-favorite/toggle-favorite.component.ts
@@ -22,7 +22,7 @@
* from Hyland Software. If not, see .
*/
-import { Component, inject, Input, OnInit, ViewEncapsulation } from '@angular/core';
+import { Component, inject, Input, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { SelectionState } from '@alfresco/adf-extensions';
@@ -32,15 +32,14 @@ import { CommonModule } from '@angular/common';
import { DocumentListService, NodeFavoriteDirective } from '@alfresco/adf-content-services';
import { MatIconModule } from '@angular/material/icon';
import { TranslatePipe } from '@ngx-translate/core';
-import { MatMenuModule } from '@angular/material/menu';
+import { MatMenuItem, MatMenuModule } from '@angular/material/menu';
@Component({
imports: [CommonModule, TranslatePipe, MatIconModule, MatMenuModule, NodeFavoriteDirective],
selector: 'app-toggle-favorite',
template: `
`,
@@ -54,6 +53,9 @@ export class ToggleFavoriteComponent implements OnInit {
selection$: Observable;
private reloadOnRoutes: string[] = [];
+ @ViewChild(MatMenuItem)
+ menuItem: MatMenuItem;
+
constructor(
private store: Store,
private router: Router
diff --git a/projects/aca-content/src/lib/components/toolbar/toggle-join-library/toggle-join-library-button.component.ts b/projects/aca-content/src/lib/components/toolbar/toggle-join-library/toggle-join-library-button.component.ts
index a20264bd6..0ecfff65b 100644
--- a/projects/aca-content/src/lib/components/toolbar/toggle-join-library/toggle-join-library-button.component.ts
+++ b/projects/aca-content/src/lib/components/toolbar/toggle-join-library/toggle-join-library-button.component.ts
@@ -25,7 +25,7 @@
import { AppStore, SetSelectedNodesAction, getAppSelection } from '@alfresco/aca-shared/store';
import { AppHookService, UserProfileService } from '@alfresco/aca-shared';
import { SelectionState } from '@alfresco/adf-extensions';
-import { Component, inject, ViewEncapsulation } from '@angular/core';
+import { Component, inject, ViewChild, ViewEncapsulation } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { LibraryMembershipDirective, LibraryMembershipErrorEvent, LibraryMembershipToggleEvent } from '@alfresco/adf-content-services';
@@ -34,6 +34,7 @@ import { MatButtonModule } from '@angular/material/button';
import { TranslatePipe } from '@ngx-translate/core';
import { MatIconModule } from '@angular/material/icon';
import { NotificationService } from '@alfresco/adf-core';
+import { MatMenuItem } from '@angular/material/menu';
@Component({
imports: [CommonModule, TranslatePipe, MatButtonModule, MatIconModule, LibraryMembershipDirective],
@@ -57,6 +58,9 @@ import { NotificationService } from '@alfresco/adf-core';
host: { class: 'app-toggle-join-library' }
})
export class ToggleJoinLibraryButtonComponent {
+ @ViewChild(MatMenuItem)
+ menuItem: MatMenuItem;
+
private userProfileService = inject(UserProfileService);
private notificationService = inject(NotificationService);
private appHookService = inject(AppHookService);
diff --git a/projects/aca-content/src/lib/components/toolbar/view-node/view-node.component.ts b/projects/aca-content/src/lib/components/toolbar/view-node/view-node.component.ts
index 3eefd72cb..7681220f6 100644
--- a/projects/aca-content/src/lib/components/toolbar/view-node/view-node.component.ts
+++ b/projects/aca-content/src/lib/components/toolbar/view-node/view-node.component.ts
@@ -22,7 +22,7 @@
* from Hyland Software. If not, see .
*/
-import { Component, inject, Input, ViewEncapsulation } from '@angular/core';
+import { Component, inject, Input, ViewChild, ViewEncapsulation } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppStore, getAppSelection, ViewNodeAction } from '@alfresco/aca-shared/store';
import { ActivatedRoute, Router } from '@angular/router';
@@ -33,7 +33,7 @@ import { CommonModule } from '@angular/common';
import { TranslatePipe } from '@ngx-translate/core';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
-import { MatMenuModule } from '@angular/material/menu';
+import { MatMenuItem, MatMenuModule } from '@angular/material/menu';
import { MatDialogModule } from '@angular/material/dialog';
@Component({
@@ -63,6 +63,9 @@ export class ViewNodeComponent {
@Input() data: { title?: string; menuButton?: boolean; iconButton?: boolean };
+ @ViewChild(MatMenuItem)
+ menuItem: MatMenuItem;
+
constructor(
private store: Store,
private router: Router,
diff --git a/projects/aca-shared/src/lib/components/toolbar/toolbar-menu/toolbar-menu.component.spec.ts b/projects/aca-shared/src/lib/components/toolbar/toolbar-menu/toolbar-menu.component.spec.ts
index c585acc4d..2b888c2b1 100644
--- a/projects/aca-shared/src/lib/components/toolbar/toolbar-menu/toolbar-menu.component.spec.ts
+++ b/projects/aca-shared/src/lib/components/toolbar/toolbar-menu/toolbar-menu.component.spec.ts
@@ -24,9 +24,9 @@
import { ToolbarMenuComponent } from './toolbar-menu.component';
import { ComponentFixture, TestBed } from '@angular/core/testing';
-import { ContentActionRef, ContentActionType } from '@alfresco/adf-extensions';
-import { QueryList } from '@angular/core';
+import { ContentActionRef, ContentActionType, DynamicExtensionComponent } from '@alfresco/adf-extensions';
import { LibTestingModule } from '@alfresco/aca-shared';
+import { ToolbarMenuItemComponent } from '../toolbar-menu-item/toolbar-menu-item.component';
describe('ToolbarMenuComponent', () => {
let fixture: ComponentFixture;
@@ -53,16 +53,6 @@ describe('ToolbarMenuComponent', () => {
expect(component.matTrigger.closeMenu).toHaveBeenCalled();
});
- it('should populate underlying menu with toolbar items', () => {
- component.toolbarMenuItems = new QueryList();
- component.toolbarMenuItems.reset([{ menuItem: {} } as any]);
- expect(component.toolbarMenuItems.length).toBe(1);
-
- expect(component.menu._allItems.length).toBe(0);
- component.ngAfterViewInit();
- expect(component.menu._allItems.length).toBe(1);
- });
-
it('should track elements by content action id', () => {
const contentActionRef: ContentActionRef = {
id: 'action1',
@@ -71,4 +61,70 @@ describe('ToolbarMenuComponent', () => {
expect(component.trackByActionId(0, contentActionRef)).toBe('action1');
});
+
+ describe('ngAfterViewInit', () => {
+ it('should set mixed custom and standard menu items in right order', () => {
+ const customMenuItem = jasmine.createSpyObj('MatMenuItem', ['focus'], { id: 'customMenuItem' });
+ const dynamicComponent = { id: 'comp1', menuItem: customMenuItem } as DynamicExtensionComponent;
+
+ component.dynamicExtensionComponents.reset([dynamicComponent]);
+
+ const standardMenuItem = jasmine.createSpyObj('MatMenuItem', ['focus'], { id: 'standardMenuItem' });
+ const toolbarItem = { actionRef: { id: 'item1' }, menuItem: standardMenuItem } as ToolbarMenuItemComponent;
+
+ component.toolbarMenuItems.reset([toolbarItem]);
+
+ component.actionRef = {
+ id: 'parent',
+ children: [
+ { id: 'custom1', type: ContentActionType.custom, component: 'comp1' },
+ { id: 'item1', type: ContentActionType.button },
+ { id: 'custom2', type: ContentActionType.custom, component: 'comp1' }
+ ]
+ } as ContentActionRef;
+
+ component.ngAfterViewInit();
+
+ expect(component.menu._allItems.length).toBe(3);
+ expect(component.menu._allItems.toArray()).toEqual([customMenuItem, standardMenuItem, customMenuItem]);
+ });
+
+ it('should skip items without menuItem property', () => {
+ const customMenuItem = jasmine.createSpyObj('MatMenuItem', ['focus'], { id: 'menuItem1' });
+ const toolbarItem1 = { actionRef: { id: 'item1' }, menuItem: customMenuItem } as ToolbarMenuItemComponent;
+ const toolbarItem2 = { actionRef: { id: 'item2' } } as ToolbarMenuItemComponent;
+
+ component.toolbarMenuItems.reset([toolbarItem1, toolbarItem2]);
+
+ component.actionRef = {
+ id: 'parent',
+ children: [
+ { id: 'item1', type: ContentActionType.button },
+ { id: 'item2', type: ContentActionType.button }
+ ]
+ } as ContentActionRef;
+
+ component.ngAfterViewInit();
+
+ expect(component.menu._allItems.length).toBe(1);
+ expect(component.menu._allItems.toArray()).toEqual([customMenuItem]);
+ });
+
+ it('should use component ID when component property is not specified for custom item', () => {
+ const menuItem = jasmine.createSpyObj('MatMenuItem', ['focus'], { id: 'menuItem' });
+ const dynamicComponent = { id: 'custom1', menuItem: menuItem } as DynamicExtensionComponent;
+
+ component.dynamicExtensionComponents.reset([dynamicComponent]);
+
+ component.actionRef = {
+ id: 'parent',
+ children: [{ id: 'custom1', type: ContentActionType.custom }]
+ } as ContentActionRef;
+
+ component.ngAfterViewInit();
+
+ expect(component.menu._allItems.length).toBe(1);
+ expect(component.menu._allItems.first).toBe(menuItem);
+ });
+ });
});
diff --git a/projects/aca-shared/src/lib/components/toolbar/toolbar-menu/toolbar-menu.component.ts b/projects/aca-shared/src/lib/components/toolbar/toolbar-menu/toolbar-menu.component.ts
index 8569f2310..bb8414541 100644
--- a/projects/aca-shared/src/lib/components/toolbar/toolbar-menu/toolbar-menu.component.ts
+++ b/projects/aca-shared/src/lib/components/toolbar/toolbar-menu/toolbar-menu.component.ts
@@ -23,7 +23,7 @@
*/
import { Component, Input, ViewEncapsulation, HostListener, ViewChild, ViewChildren, QueryList, AfterViewInit, OnInit } from '@angular/core';
-import { ContentActionRef, DynamicExtensionComponent } from '@alfresco/adf-extensions';
+import { ContentActionRef, ContentActionType, DynamicExtensionComponent } from '@alfresco/adf-extensions';
import { MatMenu, MatMenuItem, MatMenuModule, MatMenuTrigger } from '@angular/material/menu';
import { ThemePalette } from '@angular/material/core';
import { ToolbarMenuItemComponent } from '../toolbar-menu-item/toolbar-menu-item.component';
@@ -55,6 +55,9 @@ export class ToolbarMenuComponent implements OnInit, AfterViewInit {
@ViewChildren(ToolbarMenuItemComponent)
toolbarMenuItems: QueryList;
+ @ViewChildren(DynamicExtensionComponent)
+ dynamicExtensionComponents: QueryList;
+
@Input()
data: {
menuType?: string;
@@ -73,13 +76,34 @@ export class ToolbarMenuComponent implements OnInit, AfterViewInit {
}
ngAfterViewInit(): void {
- const menuItems: MatMenuItem[] = [];
- this.toolbarMenuItems.forEach((toolbarMenuItem: ToolbarMenuItemComponent) => {
- if (toolbarMenuItem.menuItem !== undefined) {
- menuItems.push(toolbarMenuItem.menuItem);
+ const dynamicComponentMap = new Map(
+ this.dynamicExtensionComponents.map((component) => [component.id, component])
+ );
+
+ const toolbarItemMap = new Map();
+ this.toolbarMenuItems.forEach((item) => {
+ if (item.actionRef?.id) {
+ toolbarItemMap.set(item.actionRef.id, item);
}
});
- const menuItemsQueryList: QueryList = new QueryList();
+
+ const menuItems: MatMenuItem[] = [];
+ this.actionRef.children?.forEach((child) => {
+ if (child.type === ContentActionType.custom) {
+ const componentId = child.component || child.id;
+ const component = dynamicComponentMap.get(componentId);
+ if (component?.menuItem) {
+ menuItems.push(component.menuItem);
+ }
+ } else {
+ const toolbarItem = toolbarItemMap.get(child.id);
+ if (toolbarItem?.menuItem) {
+ menuItems.push(toolbarItem.menuItem);
+ }
+ }
+ });
+
+ const menuItemsQueryList = new QueryList();
menuItemsQueryList.reset(menuItems);
this.menu._allItems = menuItemsQueryList;
this.menu.ngAfterContentInit();