Центр Mat-Menu, Центр MatMenu Наложение на кнопку - PullRequest
0 голосов
/ 10 января 2019

За Выпуск материала 9631 Центрирование mat-menu на кнопке выглядит следующим образом.

проблема в том, что его поддержка отличается от спецификации и может выглядеть странно.


Мне нужна эта функциональность ... Поскольку написание моего собственного наложения CDK было бы более трудоемким, чем переопределение компонента mat-menu ... Меня не интересует воссоздание mat-menu, и мне просто нужно меню для центрирования ... Я также не заинтересован в какой-либо другой библиотеке для достижения этой цели, я хочу использовать материал mat-menu, поэтому мой вопрос заключается в следующем.

Вопрос:

Используя угловой directive, как я могу переопределить частные переменные и методы MatMenuTrigger, чтобы центрировать наложение CDK на кнопку?

enter image description here

1 Ответ

0 голосов
/ 10 января 2019

Я выбрал Директиву, потому что MatMenuTrigger - это Directive, имело смысл повторить логику, используемую источником материала.

Пока что я подошел к такому подходу, я не уверен, является ли достижение класса таким, как это, "приемлемо" само по себе, или есть какие-то негативные последствия для такого подхода.

Я открыт для конструктивного обсуждения этого подхода для любых рекомендаций или улучшений.


Stackblitz

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

По сути, я отсоединяю matMenuTrigger от кнопки, помещая ее в div, оборачивая mat-menu ... так что я могу программно открывать меню из директивы, а не кнопки.

  • Я также назначаю menuTrigger для templateRef на div и передаю его в качестве входных данных моему селектору center-mat-menu
<button mat-button [center-mat-menu]="menuTrigger">Menu</button>
<div #menuTrigger="matMenuTrigger" [matMenuTriggerFor]="menu">
 <mat-menu #menu="matMenu">

Оттуда я создаю слушателя через @HostListener

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

А затем повторить логику директивы MatMenuTrigger для управления размещением, инициализации меню и запуска открытой анимации при нажатии.

Это в основном репликация метода openMenu() в источнике menu-trigger.ts, за исключением того, что я манипулирую левым и верхним стилями после инициализации меню и перед тем, как вызвать this.menuTrigger.menu['_startAnimation']();, чтобы открыть меню.

меню-trigger.ts

Я сохраняю размер кнопки источника в переменных и использовать эту информацию для расчета центральной точки кнопки, затем я использую это с Ширина инициализированного меню для расчета 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']();
  }
}
...