From e54229f4f03b1cfbce72935f3079f1d141e1f787 Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Tue, 14 Sep 2021 18:02:53 +0000 Subject: [PATCH] fix(material-experimental/mdc-menu): ensure mat-mdc- prefix on all classes (#23559) * fix(material-experimental/mdc-menu): ensure mat-mdc- prefix on all classes The MDC menu trigger had `.mat-menu-trigger` class because it was using the same directive as the non-MDC menu. This PR refactors the trigger and content directives to extend a common base class. * fixup! fix(material-experimental/mdc-menu): ensure mat-mdc- prefix on all classes * fixup! fix(material-experimental/mdc-menu): ensure mat-mdc- prefix on all classes * fixup! fix(material-experimental/mdc-menu): ensure mat-mdc- prefix on all classes (cherry picked from commit 771afd9e00c9c87d3a4ccbd084c48f12ed250001) --- .../mdc-menu/directives.ts | 27 ++++++++++ src/material-experimental/mdc-menu/module.ts | 16 ++++-- .../mdc-menu/public-api.ts | 4 +- .../mdc-menu/testing/menu-harness.ts | 2 +- src/material/menu/menu-content.ts | 19 ++++--- src/material/menu/menu-module.ts | 26 ++++----- src/material/menu/menu-trigger.ts | 54 ++++++++++++------- src/material/menu/public-api.ts | 2 +- tools/public_api_guard/material/menu.md | 50 ++++++++++------- 9 files changed, 128 insertions(+), 72 deletions(-) create mode 100644 src/material-experimental/mdc-menu/directives.ts diff --git a/src/material-experimental/mdc-menu/directives.ts b/src/material-experimental/mdc-menu/directives.ts new file mode 100644 index 000000000000..4e776cb1a4d8 --- /dev/null +++ b/src/material-experimental/mdc-menu/directives.ts @@ -0,0 +1,27 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Directive} from '@angular/core'; +import {_MatMenuContentBase, _MatMenuTriggerBase, MAT_MENU_CONTENT} from '@angular/material/menu'; + +/** Directive applied to an element that should trigger a `mat-menu`. */ +@Directive({ + selector: `[mat-menu-trigger-for], [matMenuTriggerFor]`, + host: { + 'class': 'mat-mdc-menu-trigger', + }, + exportAs: 'matMenuTrigger' +}) +export class MatMenuTrigger extends _MatMenuTriggerBase {} + +/** Menu content that will be rendered lazily once the menu is opened. */ +@Directive({ + selector: 'ng-template[matMenuContent]', + providers: [{provide: MAT_MENU_CONTENT, useExisting: MatMenuContent}], +}) +export class MatMenuContent extends _MatMenuContentBase {} diff --git a/src/material-experimental/mdc-menu/module.ts b/src/material-experimental/mdc-menu/module.ts index a297f9a3b550..6b3f97019a7f 100644 --- a/src/material-experimental/mdc-menu/module.ts +++ b/src/material-experimental/mdc-menu/module.ts @@ -10,10 +10,10 @@ import {CommonModule} from '@angular/common'; import {NgModule} from '@angular/core'; import {MatCommonModule, MatRippleModule} from '@angular/material-experimental/mdc-core'; import {OverlayModule} from '@angular/cdk/overlay'; -import {_MatMenuDirectivesModule} from '@angular/material/menu'; import {CdkScrollableModule} from '@angular/cdk/scrolling'; -import {MatMenu, MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER} from './menu'; +import {MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER, MatMenu} from './menu'; import {MatMenuItem} from './menu-item'; +import {MatMenuContent, MatMenuTrigger} from './directives'; @NgModule({ imports: [ @@ -21,10 +21,16 @@ import {MatMenuItem} from './menu-item'; MatRippleModule, MatCommonModule, OverlayModule, - _MatMenuDirectivesModule ], - exports: [CdkScrollableModule, MatMenu, MatCommonModule, MatMenuItem, _MatMenuDirectivesModule], - declarations: [MatMenu, MatMenuItem], + exports: [ + CdkScrollableModule, + MatMenu, + MatCommonModule, + MatMenuItem, + MatMenuContent, + MatMenuTrigger + ], + declarations: [MatMenu, MatMenuItem, MatMenuContent, MatMenuTrigger], providers: [MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER] }) export class MatMenuModule {} diff --git a/src/material-experimental/mdc-menu/public-api.ts b/src/material-experimental/mdc-menu/public-api.ts index d6cc7ce80642..084ee69f993a 100644 --- a/src/material-experimental/mdc-menu/public-api.ts +++ b/src/material-experimental/mdc-menu/public-api.ts @@ -8,19 +8,17 @@ export {MatMenu} from './menu'; export {MatMenuItem} from './menu-item'; +export {MatMenuTrigger, MatMenuContent} from './directives'; export * from './module'; export { - _MatMenuDirectivesModule, fadeInItems, MAT_MENU_DEFAULT_OPTIONS, MAT_MENU_PANEL, MAT_MENU_SCROLL_STRATEGY, matMenuAnimations, - MatMenuContent, MatMenuDefaultOptions, MatMenuPanel, - MatMenuTrigger, MenuPositionX, MenuPositionY, transformMenu, diff --git a/src/material-experimental/mdc-menu/testing/menu-harness.ts b/src/material-experimental/mdc-menu/testing/menu-harness.ts index 143308eb4566..04e6bbb0c5c8 100644 --- a/src/material-experimental/mdc-menu/testing/menu-harness.ts +++ b/src/material-experimental/mdc-menu/testing/menu-harness.ts @@ -18,7 +18,7 @@ import { export class MatMenuHarness extends _MatMenuHarnessBase< typeof MatMenuItemHarness, MatMenuItemHarness, MenuItemHarnessFilters> { /** The selector for the host element of a `MatMenu` instance. */ - static hostSelector = '.mat-menu-trigger'; + static hostSelector = '.mat-mdc-menu-trigger'; protected _itemClass = MatMenuItemHarness; /** diff --git a/src/material/menu/menu-content.ts b/src/material/menu/menu-content.ts index 00b97edde7cf..8697a19feef2 100644 --- a/src/material/menu/menu-content.ts +++ b/src/material/menu/menu-content.ts @@ -29,14 +29,8 @@ import {Subject} from 'rxjs'; */ export const MAT_MENU_CONTENT = new InjectionToken('MatMenuContent'); -/** - * Menu content that will be rendered lazily once the menu is opened. - */ -@Directive({ - selector: 'ng-template[matMenuContent]', - providers: [{provide: MAT_MENU_CONTENT, useExisting: MatMenuContent}], -}) -export class MatMenuContent implements OnDestroy { +@Directive() +export abstract class _MatMenuContentBase implements OnDestroy { private _portal: TemplatePortal; private _outlet: DomPortalOutlet; @@ -105,3 +99,12 @@ export class MatMenuContent implements OnDestroy { } } } + +/** + * Menu content that will be rendered lazily once the menu is opened. + */ +@Directive({ + selector: 'ng-template[matMenuContent]', + providers: [{provide: MAT_MENU_CONTENT, useExisting: MatMenuContent}], +}) +export class MatMenuContent extends _MatMenuContentBase {} diff --git a/src/material/menu/menu-module.ts b/src/material/menu/menu-module.ts index c95636d2d622..ebc1b9f61c6e 100644 --- a/src/material/menu/menu-module.ts +++ b/src/material/menu/menu-module.ts @@ -16,30 +16,22 @@ import {MatMenuContent} from './menu-content'; import {MatMenuItem} from './menu-item'; import {MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER, MatMenuTrigger} from './menu-trigger'; -/** - * Used by both the current `MatMenuModule` and the MDC `MatMenuModule` - * to declare the menu-related directives. - */ -@NgModule({ - exports: [MatMenuTrigger, MatMenuContent, MatCommonModule], - declarations: [ - MatMenuTrigger, - MatMenuContent, - ], - providers: [MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER] -}) -export class _MatMenuDirectivesModule {} - @NgModule({ imports: [ CommonModule, MatCommonModule, MatRippleModule, OverlayModule, - _MatMenuDirectivesModule, ], - exports: [CdkScrollableModule, MatCommonModule, MatMenu, MatMenuItem, _MatMenuDirectivesModule], - declarations: [MatMenu, MatMenuItem], + exports: [ + CdkScrollableModule, + MatCommonModule, + MatMenu, + MatMenuItem, + MatMenuTrigger, + MatMenuContent + ], + declarations: [MatMenu, MatMenuItem, MatMenuTrigger, MatMenuContent], providers: [MAT_MENU_SCROLL_STRATEGY_FACTORY_PROVIDER] }) export class MatMenuModule {} diff --git a/src/material/menu/menu-trigger.ts b/src/material/menu/menu-trigger.ts index 0c121d41c9f2..747d62579cba 100644 --- a/src/material/menu/menu-trigger.ts +++ b/src/material/menu/menu-trigger.ts @@ -20,8 +20,8 @@ import { Overlay, OverlayConfig, OverlayRef, - VerticalConnectionPos, ScrollStrategy, + VerticalConnectionPos, } from '@angular/cdk/overlay'; import {TemplatePortal} from '@angular/cdk/portal'; import { @@ -29,6 +29,8 @@ import { Directive, ElementRef, EventEmitter, + HostBinding, + HostListener, Inject, InjectionToken, Input, @@ -41,10 +43,10 @@ import { import {normalizePassiveListenerOptions} from '@angular/cdk/platform'; import {asapScheduler, merge, Observable, of as observableOf, Subscription} from 'rxjs'; import {delay, filter, take, takeUntil} from 'rxjs/operators'; -import {MenuCloseReason, _MatMenuBase} from './menu'; +import {_MatMenuBase, MenuCloseReason} from './menu'; import {throwMatMenuMissingError, throwMatMenuRecursiveError} from './menu-errors'; import {MatMenuItem} from './menu-item'; -import {MatMenuPanel, MAT_MENU_PANEL} from './menu-panel'; +import {MAT_MENU_PANEL, MatMenuPanel} from './menu-panel'; import {MenuPositionX, MenuPositionY} from './menu-positions'; /** Injection token that determines the scroll handling while the menu is open. */ @@ -71,21 +73,8 @@ const passiveEventListenerOptions = normalizePassiveListenerOptions({passive: tr // TODO(andrewseguin): Remove the kebab versions in favor of camelCased attribute selectors -/** Directive applied to an element that should trigger a `mat-menu`. */ -@Directive({ - selector: `[mat-menu-trigger-for], [matMenuTriggerFor]`, - host: { - 'class': 'mat-menu-trigger', - 'aria-haspopup': 'true', - '[attr.aria-expanded]': 'menuOpen || null', - '[attr.aria-controls]': 'menuOpen ? menu.panelId : null', - '(mousedown)': '_handleMousedown($event)', - '(keydown)': '_handleKeydown($event)', - '(click)': '_handleClick($event)', - }, - exportAs: 'matMenuTrigger' -}) -export class MatMenuTrigger implements AfterContentInit, OnDestroy { +@Directive() +export abstract class _MatMenuTriggerBase implements AfterContentInit, OnDestroy { private _portal: TemplatePortal; private _overlayRef: OverlayRef | null = null; private _menuOpen: boolean = false; @@ -114,6 +103,22 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy { // the first item of the list when the menu is opened via the keyboard _openedBy: Exclude | undefined = undefined; + @HostBinding('attr.aria-expanded') + // Need tp use getter for HostBinding + // tslint:disable-next-line:no-private-getters + get _ariaExpanded() { + return this.menuOpen || null; + } + + @HostBinding('attr.aria-controls') + // Need tp use getter for HostBinding + // tslint:disable-next-line:no-private-getters + get _ariaControl() { + return this.menuOpen ? this.menu.panelId : null; + } + + @HostBinding('attr.aria-haspopup') _ariaHaspopup = true; + /** * @deprecated * @breaking-change 8.0.0 @@ -508,6 +513,7 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy { } /** Handles mouse presses on the trigger. */ + @HostListener('mousedown', ['$event']) _handleMousedown(event: MouseEvent): void { if (!isFakeMousedownFromScreenReader(event)) { // Since right or middle button clicks won't trigger the `click` event, @@ -524,6 +530,7 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy { } /** Handles key presses on the trigger. */ + @HostListener('keydown', ['$event']) _handleKeydown(event: KeyboardEvent): void { const keyCode = event.keyCode; @@ -541,6 +548,7 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy { } /** Handles click events on the trigger. */ + @HostListener('click', ['$event']) _handleClick(event: MouseEvent): void { if (this.triggersSubmenu()) { // Stop event propagation to avoid closing the parent menu. @@ -597,3 +605,13 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy { } } + +/** Directive applied to an element that should trigger a `mat-menu`. */ +@Directive({ + selector: `[mat-menu-trigger-for], [matMenuTriggerFor]`, + host: { + 'class': 'mat-menu-trigger', + }, + exportAs: 'matMenuTrigger' +}) +export class MatMenuTrigger extends _MatMenuTriggerBase {} diff --git a/src/material/menu/public-api.ts b/src/material/menu/public-api.ts index 1b18cfe39ff1..0ce05047223f 100644 --- a/src/material/menu/public-api.ts +++ b/src/material/menu/public-api.ts @@ -13,7 +13,7 @@ export { _MatMenuBase, } from './menu'; export {MatMenuItem} from './menu-item'; -export {MatMenuTrigger, MAT_MENU_SCROLL_STRATEGY} from './menu-trigger'; +export {MatMenuTrigger, MAT_MENU_SCROLL_STRATEGY, _MatMenuTriggerBase} from './menu-trigger'; export {MatMenuPanel, MAT_MENU_PANEL} from './menu-panel'; export * from './menu-module'; export * from './menu-animations'; diff --git a/tools/public_api_guard/material/menu.md b/tools/public_api_guard/material/menu.md index 2d4f2da73a9d..0667b186b12a 100644 --- a/tools/public_api_guard/material/menu.md +++ b/tools/public_api_guard/material/menu.md @@ -24,8 +24,8 @@ import { FocusableOption } from '@angular/cdk/a11y'; import { FocusMonitor } from '@angular/cdk/a11y'; import { FocusOrigin } from '@angular/cdk/a11y'; import * as i0 from '@angular/core'; -import * as i3 from '@angular/material/core'; -import * as i6 from '@angular/common'; +import * as i5 from '@angular/common'; +import * as i6 from '@angular/material/core'; import * as i7 from '@angular/cdk/overlay'; import * as i8 from '@angular/cdk/scrolling'; import { InjectionToken } from '@angular/core'; @@ -162,7 +162,15 @@ export class _MatMenuBase implements AfterContentInit, MatMenuPanel } // @public -export class MatMenuContent implements OnDestroy { +export class MatMenuContent extends _MatMenuContentBase { + // (undocumented) + static ɵdir: i0.ɵɵDirectiveDeclaration; + // (undocumented) + static ɵfac: i0.ɵɵFactoryDeclaration; +} + +// @public (undocumented) +export abstract class _MatMenuContentBase implements OnDestroy { constructor(_template: TemplateRef, _componentFactoryResolver: ComponentFactoryResolver, _appRef: ApplicationRef, _injector: Injector, _viewContainerRef: ViewContainerRef, _document: any, _changeDetectorRef?: ChangeDetectorRef | undefined); attach(context?: any): void; readonly _attached: Subject; @@ -170,9 +178,9 @@ export class MatMenuContent implements OnDestroy { // (undocumented) ngOnDestroy(): void; // (undocumented) - static ɵdir: i0.ɵɵDirectiveDeclaration; + static ɵdir: i0.ɵɵDirectiveDeclaration<_MatMenuContentBase, never, never, {}, {}, never>; // (undocumented) - static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵfac: i0.ɵɵFactoryDeclaration<_MatMenuContentBase, never>; } // @public @@ -185,16 +193,6 @@ export interface MatMenuDefaultOptions { yPosition: MenuPositionY; } -// @public -export class _MatMenuDirectivesModule { - // (undocumented) - static ɵfac: i0.ɵɵFactoryDeclaration<_MatMenuDirectivesModule, never>; - // (undocumented) - static ɵinj: i0.ɵɵInjectorDeclaration<_MatMenuDirectivesModule>; - // (undocumented) - static ɵmod: i0.ɵɵNgModuleDeclaration<_MatMenuDirectivesModule, [typeof i1.MatMenuTrigger, typeof i2.MatMenuContent], never, [typeof i1.MatMenuTrigger, typeof i2.MatMenuContent, typeof i3.MatCommonModule]>; -} - // @public export class MatMenuItem extends _MatMenuItemBase implements FocusableOption, CanDisable, CanDisableRipple, AfterViewInit, OnDestroy { constructor(_elementRef: ElementRef, @@ -236,7 +234,7 @@ export class MatMenuModule { // (undocumented) static ɵinj: i0.ɵɵInjectorDeclaration; // (undocumented) - static ɵmod: i0.ɵɵNgModuleDeclaration; + static ɵmod: i0.ɵɵNgModuleDeclaration; } // @public @@ -280,8 +278,22 @@ export interface MatMenuPanel { } // @public -export class MatMenuTrigger implements AfterContentInit, OnDestroy { +export class MatMenuTrigger extends _MatMenuTriggerBase { + // (undocumented) + static ɵdir: i0.ɵɵDirectiveDeclaration; + // (undocumented) + static ɵfac: i0.ɵɵFactoryDeclaration; +} + +// @public (undocumented) +export abstract class _MatMenuTriggerBase implements AfterContentInit, OnDestroy { constructor(_overlay: Overlay, _element: ElementRef, _viewContainerRef: ViewContainerRef, scrollStrategy: any, parentMenu: MatMenuPanel, _menuItemInstance: MatMenuItem, _dir: Directionality, _focusMonitor?: FocusMonitor | undefined); + // (undocumented) + get _ariaControl(): string | null | undefined; + // (undocumented) + get _ariaExpanded(): true | null; + // (undocumented) + _ariaHaspopup: boolean; closeMenu(): void; // @deprecated (undocumented) get _deprecatedMatMenuTriggerFor(): MatMenuPanel; @@ -313,9 +325,9 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy { triggersSubmenu(): boolean; updatePosition(): void; // (undocumented) - static ɵdir: i0.ɵɵDirectiveDeclaration; + static ɵdir: i0.ɵɵDirectiveDeclaration<_MatMenuTriggerBase, never, never, { "_deprecatedMatMenuTriggerFor": "mat-menu-trigger-for"; "menu": "matMenuTriggerFor"; "menuData": "matMenuTriggerData"; "restoreFocus": "matMenuTriggerRestoreFocus"; }, { "menuOpened": "menuOpened"; "onMenuOpen": "onMenuOpen"; "menuClosed": "menuClosed"; "onMenuClose": "onMenuClose"; }, never>; // (undocumented) - static ɵfac: i0.ɵɵFactoryDeclaration; + static ɵfac: i0.ɵɵFactoryDeclaration<_MatMenuTriggerBase, [null, null, null, null, { optional: true; }, { optional: true; self: true; }, { optional: true; }, null]>; } // @public