ACS-11160: improve context menu overlay service implementation (#11661)

* refactor: improve context menu overlay service implementation

- Introduced a new interface for overlay references with backdrop handling.
- Updated event listener for backdrop clicks to use the new interface, enhancing type safety.
- Refactored the injector creation method to utilize the Injector.create method for better clarity and maintainability.
- Improved the fake element's type definition in the getOverlayConfig method for better type inference.

These changes enhance the overall robustness and readability of the context menu overlay service.

* refactor: enhance type safety in context menu components

- Introduced the ContextMenuItem interface to improve type definitions for context menu items.
- Updated the ContextMenuListComponent to use ContextMenuItem for links and menu item parameters, enhancing clarity and maintainability.
- Modified CONTEXT_MENU_DATA injection token to specify ContextMenuItem[] for better type safety.
- Adjusted the ContextMenuOverlayConfig interface to accept an array of ContextMenuItem, ensuring consistent data handling.

These changes improve the overall robustness and readability of the context menu implementation.
[ci:force]

* refactor: remove unnecessary eslint-disable comments in category management and search components

- Eliminated eslint-disable comments related to no-underscore-dangle in the CategoriesManagementComponent and SearchControlComponent tests, improving code clarity and adherence to linting rules.
- This change enhances the overall readability of the test files by ensuring consistent coding standards.

* refactor: simplify backdrop click handling in context menu overlay service

- Removed the unnecessary backdrop click handling logic, replacing it with a direct call to close the overlay reference. This change enhances code clarity and reduces complexity in the event listener implementation.

* refactor: streamline backdrop handling in context menu overlay service

- Removed the custom interface for overlay references with backdrop handling, simplifying the code.
- Updated the event listener for backdrop clicks to directly use the overlay's backdropElement, enhancing clarity and maintainability.
- This change improves the overall robustness of the context menu overlay service.
This commit is contained in:
Denys Vuika
2026-02-17 13:18:41 +00:00
committed by GitHub
parent 1f49af4c8f
commit fafa5d6c41
5 changed files with 45 additions and 26 deletions
@@ -328,7 +328,6 @@ describe('CategoriesManagementComponent', () => {
const categoriesChangeSpy = spyOn(component.categoriesChange, 'emit').and.callThrough();
typeCategory('test');
const options = getExistingCategoriesList();
// eslint-disable-next-line no-underscore-dangle
options[0].click();
expect(component.categories.length).toBe(3);
@@ -342,7 +341,6 @@ describe('CategoriesManagementComponent', () => {
it('should remove selected category from categories list and add it back to existing categories', fakeAsync(() => {
typeCategory('test');
const options = getExistingCategoriesList();
// eslint-disable-next-line no-underscore-dangle
options[0].click();
fixture.detectChanges();
@@ -26,6 +26,7 @@ import { NgForOf, NgIf } from '@angular/common';
import { TranslatePipe } from '@ngx-translate/core';
import { DOWN_ARROW, UP_ARROW } from '@angular/cdk/keycodes';
import { IconModule } from '../icon/icon.module';
import { ContextMenuItem } from './interfaces';
@Component({
selector: 'adf-context-menu',
@@ -42,10 +43,10 @@ import { IconModule } from '../icon/icon.module';
export class ContextMenuListComponent implements AfterViewInit {
private keyManager: FocusKeyManager<MatMenuItem>;
@ViewChildren(MatMenuItem) items: QueryList<MatMenuItem>;
links: any[];
links: ContextMenuItem[];
@HostListener('document:keydown.Escape', ['$event'])
handleKeydownEscape(event: KeyboardEvent) {
handleKeydownEscape(event: Event) {
if (event) {
this.contextMenuOverlayRef.close();
}
@@ -63,12 +64,12 @@ export class ContextMenuListComponent implements AfterViewInit {
constructor(
@Inject(ContextMenuOverlayRef) private contextMenuOverlayRef: ContextMenuOverlayRef,
@Optional() @Inject(CONTEXT_MENU_DATA) private data: any
@Optional() @Inject(CONTEXT_MENU_DATA) private data: ContextMenuItem[]
) {
this.links = this.data;
}
onMenuItemClick(event: Event, menuItem: any) {
onMenuItemClick(event: Event, menuItem: ContextMenuItem) {
if (menuItem?.model?.disabled) {
event.preventDefault();
event.stopImmediatePropagation();
@@ -17,7 +17,7 @@
import { Injectable, Injector, ElementRef, ComponentRef } from '@angular/core';
import { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { PortalInjector, ComponentPortal } from '@angular/cdk/portal';
import { ComponentPortal } from '@angular/cdk/portal';
import { ContextMenuOverlayRef } from './context-menu-overlay';
import { ContextMenuOverlayConfig } from './interfaces';
import { CONTEXT_MENU_DATA } from './context-menu.tokens';
@@ -33,7 +33,10 @@ const DEFAULT_CONFIG: ContextMenuOverlayConfig = {
providedIn: 'root'
})
export class ContextMenuOverlayService {
constructor(private injector: Injector, private overlay: Overlay) {}
constructor(
private injector: Injector,
private overlay: Overlay
) {}
open(config: ContextMenuOverlayConfig): ContextMenuOverlayRef {
const overlayConfig = { ...DEFAULT_CONFIG, ...config };
@@ -47,14 +50,12 @@ export class ContextMenuOverlayService {
overlay.backdropClick().subscribe(() => overlayRef.close());
// prevent native contextmenu on overlay element if config.hasBackdrop is true
if (overlayConfig.hasBackdrop) {
// eslint-disable-next-line no-underscore-dangle
(overlay as any).backdropElement.addEventListener(
if (overlayConfig.hasBackdrop && overlay.backdropElement) {
overlay.backdropElement.addEventListener(
'contextmenu',
(event) => {
event.preventDefault();
// eslint-disable-next-line no-underscore-dangle
(overlay as any)._backdropClick.next(null);
overlayRef.close();
},
true
);
@@ -77,28 +78,32 @@ export class ContextMenuOverlayService {
return containerRef.instance;
}
private createInjector(config: ContextMenuOverlayConfig, contextMenuOverlayRef: ContextMenuOverlayRef): PortalInjector {
const injectionTokens = new WeakMap();
injectionTokens.set(ContextMenuOverlayRef, contextMenuOverlayRef);
injectionTokens.set(CONTEXT_MENU_DATA, config.data);
return new PortalInjector(this.injector, injectionTokens);
private createInjector(config: ContextMenuOverlayConfig, contextMenuOverlayRef: ContextMenuOverlayRef): Injector {
return Injector.create({
parent: this.injector,
providers: [
{ provide: ContextMenuOverlayRef, useValue: contextMenuOverlayRef },
{ provide: CONTEXT_MENU_DATA, useValue: config.data }
]
});
}
private getOverlayConfig(config: ContextMenuOverlayConfig): OverlayConfig {
const { clientY, clientX } = config.source;
const fakeElement: any = {
getBoundingClientRect: (): ClientRect =>
const fakeElement: Pick<HTMLElement, 'getBoundingClientRect'> = {
getBoundingClientRect: (): DOMRect =>
({
bottom: clientY,
height: 0,
left: clientX,
right: clientX,
top: clientY,
width: 0
} as any)
width: 0,
x: clientX,
y: clientY,
toJSON: () => ({})
}) as DOMRect
};
const positionStrategy = this.overlay
@@ -16,5 +16,6 @@
*/
import { InjectionToken } from '@angular/core';
import { ContextMenuItem } from './interfaces';
export const CONTEXT_MENU_DATA = new InjectionToken<any>('CONTEXT_MENU_DATA');
export const CONTEXT_MENU_DATA = new InjectionToken<ContextMenuItem[]>('CONTEXT_MENU_DATA');
+15 -1
View File
@@ -20,5 +20,19 @@ export interface ContextMenuOverlayConfig {
hasBackdrop?: boolean;
backdropClass?: string;
source?: MouseEvent;
data?: any;
data?: ContextMenuItem[];
}
export interface ContextMenuItem {
title?: string;
model?: {
title?: string;
visible?: boolean;
disabled?: boolean;
icon?: string;
tooltip?: string;
};
subject: {
next: (value: ContextMenuItem) => void;
};
}