中心垫菜单,中心垫菜单覆盖到按钮 [英] Center Mat-Menu, Center MatMenu Overlay to Button

查看:24
本文介绍了中心垫菜单,中心垫菜单覆盖到按钮的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

根据

解决方案

我选择了一个指令,因为 MatMenuTrigger 是一个 Directive,复制使用的逻辑是有意义的材料来源.

到目前为止,这是我提出的方法,我不确定像这样进入课堂本身是否可以接受",或者这样做是否有任何负面影响.

<块引用>

对于任何建议或改进,我愿意就这种方法进行建设性讨论.

<小时>

Stackblitz

https:///stackblitz.com/edit/angular-jpgjdc-nqmyht?embed=1&file=app/center-matmenu.directive.ts

从本质上讲,我将 matMenuTrigger 与按钮分离,将其放置在包裹 mat-menudiv 上...就是这样我可以通过指令以编程方式打开菜单,而不是按钮.

  • 我还将 menuTrigger 分配给 div 上的 templateRef 并将其作为输入传递给我的 center-mat-菜单选择器
<块引用>

<button mat-button [center-mat-menu]="menuTrigger">Menu</button><div #menuTrigger="matMenuTrigger" [matMenuTriggerFor]="menu"><mat-menu #menu="matMenu">

从那里我通过 @HostListener

创建一个监听器

@HostListener('click', ['$event'])

然后复制 MatMenuTrigger 指令的逻辑来操作位置,初始化菜单,并在点击时触发打开动画.

这基本上是 menu-trigger.ts 源代码中 openMenu() 方法的复制,除了我在菜单初始化后操作左侧和顶部样式, 在我调用 this.menuTrigger.menu['_startAnimation'](); 打开菜单之前.

menu-ts

<块引用>

我将源按钮的尺寸存储到变量中使用该信息来计算按钮的中心点,然后我将其与用于计算left

的初始化菜单的宽度

@Directive({选择器:'[中心垫菜单]'})导出类 CenterMatmenuDirective {覆盖引用:覆盖引用;overlayConf: OverlayConfig;下拉:HTMLElement;覆盖位置框:HTMLElement;菜单:MatMenuPanel;按钮:HTMLElement;按钮宽度:数量;buttonLeft:数字;按钮底部:数字;@Input('center-mat-menu') 私有 menuTrigger: MatMenuTrigger;构造函数(私有_menuButton:ElementRef,私有_renderer:Renderer2){}@HostListener('click', ['$event'])点击(e){this._setVariables();//菜单不是通过键盘向下箭头打开的,必须设置它以便 MatMenuTrigger 知道菜单是通过鼠标点击打开的this.menuTrigger['_openedBy'] = e.button === 0 ?'鼠标':空;this._overrideMatMenu();this.dropDown = this.overlayRef.overlayElement.children[0].children[0] as HTMLElement;this.overlayPositionBox = this.overlayRef.hostElement;setTimeout(() => {this._styleDropDown(this.dropDown);this._setOverlayPosition(this.dropDown, this.overlayPositionBox);this._openMenu();})}私有_setVariables() {const config = this.menuTrigger['_getOverlayConfig']();this.menuTrigger['_overlayRef'] = this.menuTrigger['_overlay'].create(config);this.overlayRef = this.menuTrigger['_overlayRef'];this.overlayConf = this.overlayRef.getConfig();this.overlayRef.keydownEvents().subscribe();this.menu = this.menuTrigger.menu;this._setButtonVars();}私人 _setButtonVars() {this.button = this._menuButton.nativeElement;this.buttonWidth = this.button.getBoundingClientRect().width;this.buttonLeft = this.button.getBoundingClientRect().left;this.buttonBottom = this.button.getBoundingClientRect().bottom;}私人 _overrideMatMenu() {让strat = this.overlayConf.positionStrategy as FlexibleConnectedPositionStrategy;this.menuTrigger['_setPosition'](strat);str.positionChanges.subscribe(() => {this._setButtonVars();this._setOverlayPosition(this.dropDown, this.overlayPositionBox);})this.overlayConf.hasBackdrop = this.menu.hasBackdrop == null ?!this.menuTrigger.triggersSubmenu() : this.menu.hasBackdrop;this.overlayRef.attach(this.menuTrigger['_getPortal']());如果(this.menu.lazyContent){this.menu.lazyContent.attach()}this.menuTrigger['_closeSubscription'] = this.menuTrigger['_menuClosingActions']().subscribe(() => {this.menuTrigger.closeMenu();});this.menuTrigger['_initMenu']();}私人_styleDropDown(下拉:HTMLElement){this._renderer.setStyle(this._renderer.parentNode(dropDown), 'transform-origin', 'center top 0px');}私有_setOverlayPosition(下拉:HTMLElement,overlayPositionBox:HTMLElement){让 dropDownleft = ((this.buttonWidth/2 + this.buttonLeft) - dropDown.offsetWidth/2);this._renderer.setStyle(overlayPositionBox, 'top', this.buttonBottom + 5 + 'px');this._renderer.setStyle(overlayPositionBox, 'left', dropDownleft + 'px');this._renderer.setStyle(overlayPositionBox, 'height', '100%');}私人 _openMenu() {this.menuTrigger.menu['_startAnimation']();}}

Per Material Issue 9631 centering the mat-menu to the button is as follows.

the issue is that supporting it deviates from the spec and may end up looking weird.


I have a need for this functionality... Because writing my own CDK overlay would be more time intensive than overriding the mat-menu component... I am not interested in re-creating the mat-menu, and simply need the menu to be centered... I am also not interested in any other library's to accomplish this, I want to use Material mat-menu, so my question is as follows.

Question:

Utilizing an angular directive, how can I override the private variables and methods of the MatMenuTrigger to center the CDK overlay to the button?

解决方案

I chose a Directive because MatMenuTrigger is a Directive, it made sense to replicate the logic used by the Material source.

So far this is the approach I have come up with, I am not sure if reaching into the class like this is "acceptable" per se, or if there are any negative ramifications for doing it this way.

I am open to constructive discussion on this approach for any recommendations or improvements.


Stackblitz

https://stackblitz.com/edit/angular-jpgjdc-nqmyht?embed=1&file=app/center-matmenu.directive.ts

Essentially I am decoupling the matMenuTrigger from the button by placing it on a div wrapping the mat-menu... this is so I can programmatically open the menu from the directive and not the button.

  • I am also assigning menuTrigger to a templateRef on the div and passing it as an input to my center-mat-menu selector

<button mat-button [center-mat-menu]="menuTrigger">Menu</button>
<div #menuTrigger="matMenuTrigger" [matMenuTriggerFor]="menu">
 <mat-menu #menu="matMenu">

From there I am creating a listener via @HostListener

@HostListener('click', ['$event'])

And then replicating the logic of the MatMenuTrigger directive to manipulation the placement, initialize the menu, and fire the open animation on click.

This is basically a replication of the openMenu() method in the menu-trigger.ts source except I am manipulating the left and top styles after the menu is initialized, and before I call this.menuTrigger.menu['_startAnimation'](); to open the menu.

menu-trigger.ts

I store the dimension of the source button into variables and use that information to calculate the center point of the button, I then use that with the width of the initialized menu to calculate left

@Directive({
  selector: '[center-mat-menu]'
})
export class CenterMatmenuDirective {
  overlayRef: OverlayRef;
  overlayConf: OverlayConfig;
  dropDown: HTMLElement;
  overlayPositionBox: HTMLElement;
  menu: MatMenuPanel;
  button: HTMLElement;
  buttonWidth: number;
  buttonLeft: number;
  buttonBottom: number;

  @Input('center-mat-menu') private menuTrigger: MatMenuTrigger;

  constructor(private _menuButton: ElementRef, private _renderer: Renderer2) { }

  @HostListener('click', ['$event'])
  onclick(e) {
    this._setVariables();
    //menu not opened by keyboard down arrow, have to set this so MatMenuTrigger knows the menu was opened with a mouse click
    this.menuTrigger['_openedBy'] = e.button === 0 ? 'mouse' : null;

    this._overrideMatMenu();

    this.dropDown = this.overlayRef.overlayElement.children[0].children[0] as HTMLElement;
    this.overlayPositionBox = this.overlayRef.hostElement;

    setTimeout(() => {
      this._styleDropDown(this.dropDown);
      this._setOverlayPosition(this.dropDown, this.overlayPositionBox);
      this._openMenu();
    })
  }

  private _setVariables() {
    const config = this.menuTrigger['_getOverlayConfig']();
    this.menuTrigger['_overlayRef'] = this.menuTrigger['_overlay'].create(config);
    this.overlayRef = this.menuTrigger['_overlayRef'];
    this.overlayConf = this.overlayRef.getConfig();
    this.overlayRef.keydownEvents().subscribe();
    this.menu = this.menuTrigger.menu;
    this._setButtonVars();
  }

  private _setButtonVars() {
    this.button = this._menuButton.nativeElement;
    this.buttonWidth = this.button.getBoundingClientRect().width;
    this.buttonLeft = this.button.getBoundingClientRect().left;
    this.buttonBottom = this.button.getBoundingClientRect().bottom;
  }

  private _overrideMatMenu() {
    let strat = this.overlayConf.positionStrategy as FlexibleConnectedPositionStrategy;
    this.menuTrigger['_setPosition'](strat);
    strat.positionChanges.subscribe(() => {
      this._setButtonVars();
      this._setOverlayPosition(this.dropDown, this.overlayPositionBox);
    })
    this.overlayConf.hasBackdrop = this.menu.hasBackdrop == null ?
      !this.menuTrigger.triggersSubmenu() : this.menu.hasBackdrop;
    this.overlayRef.attach(this.menuTrigger['_getPortal']());

    if (this.menu.lazyContent) {
      this.menu.lazyContent.attach()
    }

    this.menuTrigger['_closeSubscription'] = this.menuTrigger['_menuClosingActions']().subscribe(() => {
      this.menuTrigger.closeMenu();
    });
    this.menuTrigger['_initMenu']();
  }

  private _styleDropDown(dropDown: HTMLElement) {
    this._renderer.setStyle(this._renderer.parentNode(dropDown), 'transform-origin', 'center top 0px');
  }

  private _setOverlayPosition(dropDown: HTMLElement, overlayPositionBox: HTMLElement) {
    let dropDownleft = ((this.buttonWidth / 2 + this.buttonLeft) - dropDown.offsetWidth / 2);

    this._renderer.setStyle(overlayPositionBox, 'top', this.buttonBottom + 5 + 'px');
    this._renderer.setStyle(overlayPositionBox, 'left', dropDownleft + 'px');
    this._renderer.setStyle(overlayPositionBox, 'height', '100%');
  }

  private _openMenu() {
    this.menuTrigger.menu['_startAnimation']();
  }
}

这篇关于中心垫菜单,中心垫菜单覆盖到按钮的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆