mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2026-09-09 18:02:54 +00:00
[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
This commit is contained in:
+1
-7
@@ -2,9 +2,7 @@
|
||||
<ng-container *ngIf="!data.iconButton">
|
||||
<button mat-menu-item data-automation-id="share-action-button" (click)="editSharedNode(selectionState, '.adf-context-menu-source')">
|
||||
<mat-icon>link</mat-icon>
|
||||
<ng-container *ngIf="isShared; else not_shared">
|
||||
<span>{{ 'APP.ACTIONS.SHARE_EDIT' | translate }}</span>
|
||||
</ng-container>
|
||||
<span>{{ (isShared ? 'APP.ACTIONS.SHARE_EDIT' : 'APP.ACTIONS.SHARE') | translate }}</span>
|
||||
</button>
|
||||
</ng-container>
|
||||
|
||||
@@ -21,7 +19,3 @@
|
||||
</button>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #not_shared>
|
||||
<span>{{ 'APP.ACTIONS.SHARE' | translate }}</span>
|
||||
</ng-template>
|
||||
|
||||
+5
-2
@@ -22,13 +22,13 @@
|
||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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: SelectionState;
|
||||
selectionLabel = '';
|
||||
|
||||
+62
-8
@@ -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: '<button mat-menu-item id="custom-action">Custom Component Content</button>'
|
||||
})
|
||||
class TestCustomMenuComponent {
|
||||
data: any;
|
||||
@ViewChild(MatMenuItem) menuItem: MatMenuItem;
|
||||
}
|
||||
|
||||
describe('ContextMenuComponent', () => {
|
||||
let fixture: ComponentFixture<ContextMenuComponent>;
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<DynamicExtensionComponent>;
|
||||
|
||||
@ViewChild(MatMenu)
|
||||
menu: MatMenu;
|
||||
|
||||
@ViewChildren(MatMenuItem)
|
||||
matMenuItems: QueryList<MatMenuItem>;
|
||||
|
||||
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<MatMenuItem>();
|
||||
menuItemsQueryList.reset(orderedItems);
|
||||
this.menu._allItems = menuItemsQueryList;
|
||||
this.menu.ngAfterContentInit();
|
||||
}
|
||||
|
||||
private createMenuItemsLookup(): Map<string, MatMenuItem> {
|
||||
const itemsById = new Map<string, MatMenuItem>();
|
||||
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<string, MatMenuItem>): 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;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -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;
|
||||
|
||||
+6
-4
@@ -22,7 +22,7 @@
|
||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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)"
|
||||
>
|
||||
<mat-icon *ngIf="library.isFavorite">star</mat-icon>
|
||||
<mat-icon *ngIf="!library.isFavorite">star_border</mat-icon>
|
||||
<mat-icon class="app-context-menu-item--icon">{{ library.isFavorite ? 'star' : 'star_border' }}</mat-icon>
|
||||
<span>{{ (library.isFavorite ? 'APP.ACTIONS.REMOVE_FAVORITE' : 'APP.ACTIONS.FAVORITE') | translate }}</span>
|
||||
</button>
|
||||
`,
|
||||
@@ -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(
|
||||
|
||||
+6
-4
@@ -22,7 +22,7 @@
|
||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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: `
|
||||
<button mat-menu-item #favorites="adfFavorite" (toggle)="onToggleEvent()" [adf-node-favorite]="(selection$ | async).nodes">
|
||||
<mat-icon *ngIf="favorites.hasFavorites()">star</mat-icon>
|
||||
<mat-icon *ngIf="!favorites.hasFavorites()">star_border</mat-icon>
|
||||
<mat-icon class="app-context-menu-item--icon">{{ favorites.hasFavorites() ? 'star' : 'star_border' }}</mat-icon>
|
||||
<span>{{ (favorites.hasFavorites() ? 'APP.ACTIONS.REMOVE_FAVORITE' : 'APP.ACTIONS.FAVORITE') | translate }}</span>
|
||||
</button>
|
||||
`,
|
||||
@@ -54,6 +53,9 @@ export class ToggleFavoriteComponent implements OnInit {
|
||||
selection$: Observable<SelectionState>;
|
||||
private reloadOnRoutes: string[] = [];
|
||||
|
||||
@ViewChild(MatMenuItem)
|
||||
menuItem: MatMenuItem;
|
||||
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private router: Router
|
||||
|
||||
+5
-1
@@ -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);
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<AppStore>,
|
||||
private router: Router,
|
||||
|
||||
+68
-12
@@ -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<ToolbarMenuComponent>;
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+30
-6
@@ -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<ToolbarMenuItemComponent>;
|
||||
|
||||
@ViewChildren(DynamicExtensionComponent)
|
||||
dynamicExtensionComponents: QueryList<DynamicExtensionComponent>;
|
||||
|
||||
@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<string, DynamicExtensionComponent>(
|
||||
this.dynamicExtensionComponents.map((component) => [component.id, component])
|
||||
);
|
||||
|
||||
const toolbarItemMap = new Map<string, ToolbarMenuItemComponent>();
|
||||
this.toolbarMenuItems.forEach((item) => {
|
||||
if (item.actionRef?.id) {
|
||||
toolbarItemMap.set(item.actionRef.id, item);
|
||||
}
|
||||
});
|
||||
const menuItemsQueryList: QueryList<MatMenuItem> = new QueryList<MatMenuItem>();
|
||||
|
||||
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<MatMenuItem>();
|
||||
menuItemsQueryList.reset(menuItems);
|
||||
this.menu._allItems = menuItemsQueryList;
|
||||
this.menu.ngAfterContentInit();
|
||||
|
||||
Reference in New Issue
Block a user