From 6ffd738c295bb81595a0627d342fedcac2ab100b Mon Sep 17 00:00:00 2001 From: "extern.fahrenschon_philipp@allianz.com" Date: Wed, 4 Nov 2020 16:04:02 +0100 Subject: [PATCH] feat(context-menu, notification-panel): set trigger button as active (#96) --- projects/ng-aquila/src/button/button-base.ts | 15 ++- .../ng-aquila/src/button/button.component.ts | 4 +- projects/ng-aquila/src/button/button.scss | 8 +- .../src/button/icon-button.component.ts | 4 +- .../src/button/plain-button.component.scss | 4 +- .../src/button/plain-button.component.ts | 4 +- .../context-menu-trigger.directive.ts | 22 +++-- .../src/context-menu/context-menu.spec.ts | 95 +++++++++++-------- .../notification-panel-trigger.directive.ts | 10 +- .../ng-aquila/src/overlay/overlay-config.ts | 3 + projects/ng-aquila/src/overlay/overlay-ref.ts | 3 + .../ng-aquila/src/overlay/overlay-service.ts | 3 + .../ng-aquila/src/overlay/overlay.spec.ts | 21 +++- .../ng-aquila/src/overlay/position-builder.ts | 1 - projects/ng-aquila/src/overlay/public-api.ts | 1 + .../ng-aquila/src/overlay/trigger-button.ts | 13 +++ 16 files changed, 147 insertions(+), 64 deletions(-) create mode 100644 projects/ng-aquila/src/overlay/trigger-button.ts diff --git a/projects/ng-aquila/src/button/button-base.ts b/projects/ng-aquila/src/button/button-base.ts index 4df842fb0..80750e047 100644 --- a/projects/ng-aquila/src/button/button-base.ts +++ b/projects/ng-aquila/src/button/button-base.ts @@ -1,4 +1,5 @@ import { ElementRef, ChangeDetectorRef, HostBinding, Directive } from '@angular/core'; +import { NxTriggerButton } from '@aposin/ng-aquila/overlay'; /** Type of a button. */ export type NxButtonType = 'primary' | 'secondary' | 'tertiary' | 'cta' | 'emphasis'; @@ -11,7 +12,7 @@ const DEFAULT_TYPE = 'primary'; /** @docs-private */ @Directive() -export class NxButtonBase { +export class NxButtonBase implements NxTriggerButton { private _classNames: string; /** @docs-private */ @@ -50,6 +51,8 @@ export class NxButtonBase { danger: boolean = false; negative: boolean = false; block: boolean = false; + @HostBinding('class.nx-button--active') + active: boolean = false; constructor(private _changeDetectorRef: ChangeDetectorRef, private _elementRef: ElementRef) { } @@ -87,4 +90,14 @@ export class NxButtonBase { get elementRef() { return this._elementRef; } + + setTriggerActive() { + this.active = true; + this._changeDetectorRef.markForCheck(); + } + + setTriggerInactive() { + this.active = false; + this._changeDetectorRef.markForCheck(); + } } diff --git a/projects/ng-aquila/src/button/button.component.ts b/projects/ng-aquila/src/button/button.component.ts index 43e44a58f..e35afa305 100644 --- a/projects/ng-aquila/src/button/button.component.ts +++ b/projects/ng-aquila/src/button/button.component.ts @@ -1,3 +1,4 @@ +import { NxTriggerButton } from '@aposin/ng-aquila/overlay'; import { Component, ElementRef, @@ -12,7 +13,8 @@ import { NxButtonBase } from './button-base'; // tslint:disable-next-line:component-selector selector: 'button[nxButton]', changeDetection: ChangeDetectionStrategy.OnPush, - inputs: ['classNames:nxButton'] + inputs: ['classNames:nxButton'], + providers: [{provide: NxTriggerButton, useExisting: NxButtonComponent}] }) export class NxButtonComponent extends NxButtonBase { diff --git a/projects/ng-aquila/src/button/button.scss b/projects/ng-aquila/src/button/button.scss index 697a9c4b0..2bdb52854 100644 --- a/projects/ng-aquila/src/button/button.scss +++ b/projects/ng-aquila/src/button/button.scss @@ -26,7 +26,7 @@ $button-margin-bottom: nx-spacer(m); } } - &:active { + &:active, &.nx-button--active { @include var(background-color, button-#{$type}-active-background-color); @include var(color, button-#{$type}-active-text-color); @include var(border-color, button-#{$type}-active-border-color); @@ -114,7 +114,7 @@ $button-margin-bottom: nx-spacer(m); } } - &:active { + &:active, &.nx-button--active { @include var(background-color, negative-02); @include var(border-color, negative-02); @include var(color, negative-accent); @@ -154,7 +154,7 @@ $button-margin-bottom: nx-spacer(m); } } - &:active { + &:active, &.nx-button--active { @include var(background-color, negative-02); @include var(border-color, negative-02); @include var(color, button-secondary-text-color); @@ -191,7 +191,7 @@ $button-margin-bottom: nx-spacer(m); } } - &:active { + &:active, &.nx-button--active { @include var(background-color, negative-02); @include var(color, button-secondary-text-color); } diff --git a/projects/ng-aquila/src/button/icon-button.component.ts b/projects/ng-aquila/src/button/icon-button.component.ts index de68e3422..ff18e4bac 100644 --- a/projects/ng-aquila/src/button/icon-button.component.ts +++ b/projects/ng-aquila/src/button/icon-button.component.ts @@ -1,3 +1,4 @@ +import { NxTriggerButton } from '@aposin/ng-aquila/overlay'; import { Component, Input, @@ -13,7 +14,8 @@ import { NxButtonBase } from './button-base'; templateUrl: './button.html', styleUrls: ['button.scss'], changeDetection: ChangeDetectionStrategy.OnPush, - inputs: ['classNames:nxIconButton'] + inputs: ['classNames:nxIconButton'], + providers: [{provide: NxTriggerButton, useExisting: NxIconButtonComponent}] }) export class NxIconButtonComponent extends NxButtonBase { constructor(changeDetectorRef: ChangeDetectorRef, elementRef: ElementRef) { diff --git a/projects/ng-aquila/src/button/plain-button.component.scss b/projects/ng-aquila/src/button/plain-button.component.scss index 4b4c66521..6947eaa2e 100644 --- a/projects/ng-aquila/src/button/plain-button.component.scss +++ b/projects/ng-aquila/src/button/plain-button.component.scss @@ -28,7 +28,7 @@ } } - &:active { + &:active, &.nx-button--active { @include var(color, plain-button-active-color); } @@ -58,7 +58,7 @@ } } - &:active { + &:active, &.nx-button--active { @include var(color, plain-button-danger-active-color); } diff --git a/projects/ng-aquila/src/button/plain-button.component.ts b/projects/ng-aquila/src/button/plain-button.component.ts index f85b6c381..9f38f8ece 100644 --- a/projects/ng-aquila/src/button/plain-button.component.ts +++ b/projects/ng-aquila/src/button/plain-button.component.ts @@ -1,3 +1,4 @@ +import { NxTriggerButton } from '@aposin/ng-aquila/overlay'; import { Component, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; @Component({ @@ -9,7 +10,8 @@ import { Component, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/ inputs: ['classNames:nxPlainButton'], host: { '[class.nx-plain-button--danger]': 'danger' - } + }, + providers: [{provide: NxTriggerButton, useExisting: NxPlainButtonComponent}] }) export class NxPlainButtonComponent { diff --git a/projects/ng-aquila/src/context-menu/context-menu-trigger.directive.ts b/projects/ng-aquila/src/context-menu/context-menu-trigger.directive.ts index 0a22aff9c..1a5ad75f6 100644 --- a/projects/ng-aquila/src/context-menu/context-menu-trigger.directive.ts +++ b/projects/ng-aquila/src/context-menu/context-menu-trigger.directive.ts @@ -1,13 +1,14 @@ import { Direction, Directionality } from '@angular/cdk/bidi'; import { LEFT_ARROW, RIGHT_ARROW } from '@angular/cdk/keycodes'; import { + ConnectedPosition, FlexibleConnectedPositionStrategy, Overlay, OverlayConfig, OverlayRef, ScrollStrategy, - ConnectedPosition } from '@angular/cdk/overlay'; +import { normalizePassiveListenerOptions } from '@angular/cdk/platform'; import { TemplatePortal } from '@angular/cdk/portal'; import { AfterContentInit, @@ -19,14 +20,15 @@ import { Optional, Output, Self, - ViewContainerRef + ViewContainerRef, } from '@angular/core'; -import { normalizePassiveListenerOptions } from '@angular/cdk/platform'; -import { asapScheduler, merge, of as observableOf, Subscription, fromEvent, Observable } from 'rxjs'; -import { delay, filter, take, takeUntil, map } from 'rxjs/operators'; -import { NxContextMenuComponent } from './context-menu.component'; +import { NxTriggerButton } from '@aposin/ng-aquila/overlay'; +import { asapScheduler, fromEvent, merge, Observable, of as observableOf, Subscription } from 'rxjs'; +import { delay, filter, map, take, takeUntil } from 'rxjs/operators'; + import { throwNxContextMenuMissingError } from './context-menu-errors'; import { NxContextMenuItemComponent } from './context-menu-item.component'; +import { NxContextMenuComponent } from './context-menu.component'; /** Default top padding of the menu panel. */ export const MENU_PANEL_TOP_PADDING = 16; @@ -130,7 +132,8 @@ export class NxContextMenuTriggerDirective @Optional() @Self() private _contextMenuItemInstance: NxContextMenuItemComponent, - @Optional() private _dir: Directionality) { + @Optional() private _dir: Directionality, + @Optional() @Self() private _triggerButton: NxTriggerButton) { if (_contextMenuItemInstance) { _contextMenuItemInstance._triggersSubmenu = this.triggersSubmenu(); @@ -197,6 +200,11 @@ export class NxContextMenuTriggerDirective this.contextMenu._startAnimation(); } + if (this._triggerButton) { + this._triggerButton.setTriggerActive(); + this.contextMenu.closed.pipe(take(1)).subscribe(() => this._triggerButton.setTriggerInactive()); + } + this._waitForClose(); } diff --git a/projects/ng-aquila/src/context-menu/context-menu.spec.ts b/projects/ng-aquila/src/context-menu/context-menu.spec.ts index 0b957264a..c17924727 100644 --- a/projects/ng-aquila/src/context-menu/context-menu.spec.ts +++ b/projects/ng-aquila/src/context-menu/context-menu.spec.ts @@ -35,6 +35,7 @@ import { createMouseEvent, createKeyboardEvent } from '../cdk-test-utils'; +import { NxButtonComponent, NxButtonModule } from '@aposin/ng-aquila/button'; // For better readablity here, We can safely ignore some conventions in our specs // tslint:disable:component-class-suffix @@ -65,7 +66,7 @@ describe('nxContextMenu', () => { function createComponent(component: Type, providers = []): ComponentFixture { TestBed.configureTestingModule({ - imports: [NxContextMenuModule, NoopAnimationsModule, NxIconModule], + imports: [NxContextMenuModule, NoopAnimationsModule, NxIconModule, NxButtonModule], declarations: [component], providers }).compileComponents(); @@ -478,6 +479,19 @@ describe('nxContextMenu', () => { flush(); })); + it('should set trigger button active state', () => { + const fixture = createComponent(SimpleMenu); + fixture.detectChanges(); + + fixture.componentInstance.trigger.openContextMenu(); + fixture.detectChanges(); + expect(fixture.componentInstance.button.active).toBe(true); + + fixture.componentInstance.trigger.closeContextMenu(); + fixture.detectChanges(); + expect(fixture.componentInstance.button.active).toBe(false); + }); + describe('lazy rendering', () => { it('should be able to render the menu content lazily', fakeAsync(() => { const fixture = createComponent(SimpleLazyMenu); @@ -1156,6 +1170,7 @@ describe('nxContextMenu', () => { tick(500); expect(overlay.querySelectorAll('.nx-context-menu').length).toBe(0, 'Expected no open menus'); + expect(instance.rootButtonEl.active).toBe(false); })); it('should add an expand icon to the menu items that trigger a sub-menu', fakeAsync(() => { @@ -1295,55 +1310,55 @@ describe('nxContextMenu', () => { })); it('should be able to open a submenu through an item that is not a direct descendant of the panel', fakeAsync(() => { - const nestedFixture = createComponent(SubmenuDeclaredInsideParentMenu); - overlay = overlayContainerElement; + const nestedFixture = createComponent(SubmenuDeclaredInsideParentMenu); + overlay = overlayContainerElement; - nestedFixture.detectChanges(); - nestedFixture.componentInstance.rootTriggerEl.nativeElement.click(); - nestedFixture.detectChanges(); - tick(500); - expect(overlay.querySelectorAll('.nx-context-menu').length) - .toBe(1, 'Expected one open menu'); + nestedFixture.detectChanges(); + nestedFixture.componentInstance.rootTriggerEl.nativeElement.click(); + nestedFixture.detectChanges(); + tick(500); + expect(overlay.querySelectorAll('.nx-context-menu').length) + .toBe(1, 'Expected one open menu'); - dispatchMouseEvent(overlay.querySelector('.level-one-trigger'), 'mouseenter'); - nestedFixture.detectChanges(); - tick(500); + dispatchMouseEvent(overlay.querySelector('.level-one-trigger'), 'mouseenter'); + nestedFixture.detectChanges(); + tick(500); - expect(overlay.querySelectorAll('.nx-context-menu').length) - .toBe(2, 'Expected two open menus'); - })); + expect(overlay.querySelectorAll('.nx-context-menu').length) + .toBe(2, 'Expected two open menus'); + })); it('should not close when hovering over a menu item inside a sub-menu panel that is declared inside the root menu', fakeAsync(() => { - const nestedFixture = createComponent(SubmenuDeclaredInsideParentMenu); - overlay = overlayContainerElement; + const nestedFixture = createComponent(SubmenuDeclaredInsideParentMenu); + overlay = overlayContainerElement; - nestedFixture.detectChanges(); - nestedFixture.componentInstance.rootTriggerEl.nativeElement.click(); - nestedFixture.detectChanges(); - tick(500); - expect(overlay.querySelectorAll('.nx-context-menu').length) - .toBe(1, 'Expected one open menu'); + nestedFixture.detectChanges(); + nestedFixture.componentInstance.rootTriggerEl.nativeElement.click(); + nestedFixture.detectChanges(); + tick(500); + expect(overlay.querySelectorAll('.nx-context-menu').length) + .toBe(1, 'Expected one open menu'); - dispatchMouseEvent(overlay.querySelector('.level-one-trigger'), 'mouseenter'); - nestedFixture.detectChanges(); - tick(500); + dispatchMouseEvent(overlay.querySelector('.level-one-trigger'), 'mouseenter'); + nestedFixture.detectChanges(); + tick(500); - expect(overlay.querySelectorAll('.nx-context-menu').length) - .toBe(2, 'Expected two open menus'); + expect(overlay.querySelectorAll('.nx-context-menu').length) + .toBe(2, 'Expected two open menus'); - dispatchMouseEvent(overlay.querySelector('.level-two-item'), 'mouseenter'); - nestedFixture.detectChanges(); - tick(500); + dispatchMouseEvent(overlay.querySelector('.level-two-item'), 'mouseenter'); + nestedFixture.detectChanges(); + tick(500); - expect(overlay.querySelectorAll('.nx-context-menu').length) - .toBe(2, 'Expected two open menus to remain'); - })); + expect(overlay.querySelectorAll('.nx-context-menu').length) + .toBe(2, 'Expected two open menus to remain'); + })); }); }); @Component({ template: ` - { }) class SimpleMenu { @ViewChild(NxContextMenuTriggerDirective) trigger: NxContextMenuTriggerDirective; - @ViewChild('triggerEl') triggerEl: ElementRef; + @ViewChild('triggerEl', {read: ElementRef}) triggerEl: ElementRef; + @ViewChild(NxButtonComponent) button: NxButtonComponent; @ViewChild(NxContextMenuComponent) menu: NxContextMenuComponent; @ViewChildren(NxContextMenuItemComponent) items: QueryList; extraItems: string[] = []; @@ -1372,10 +1388,10 @@ class SimpleMenu { @Component({ template: ` - + #rootTriggerEl #rootTriggerButton>Toggle menu + Hello {{localValue}} {{data?.value}}{{setDialogRef(overlayRef)}}`, @@ -161,6 +175,7 @@ class ComponentWithTemplateRef { @ViewChild(TemplateRef) templateRef: TemplateRef; @ViewChild('button') trigger: ElementRef; + @ViewChild(NxButtonComponent) button: NxButtonComponent; setDialogRef(overlayRef: NxOverlayRef): string { this.overlayRef = overlayRef; @@ -185,7 +200,7 @@ export class TestRootComponent { class PlainComponent { } @NgModule({ - imports: [NxOverlayModule], + imports: [NxOverlayModule, NxButtonModule], exports: [ComponentWithTemplateRef, PlainComponent], declarations: [ComponentWithTemplateRef, PlainComponent], entryComponents: [ diff --git a/projects/ng-aquila/src/overlay/position-builder.ts b/projects/ng-aquila/src/overlay/position-builder.ts index 791368ec7..e8b2e2556 100644 --- a/projects/ng-aquila/src/overlay/position-builder.ts +++ b/projects/ng-aquila/src/overlay/position-builder.ts @@ -235,7 +235,6 @@ export class NxOverlayPositionBuilder { private _resolveFallbacks(fallbacks: NxOverlayDirection[], config: NxOverlayConfig) { const [generalDirection, addition] = this._splitDirection(config.direction); return fallbacks.reduce((resolved, direction) => { - console.log(direction); if (direction === 'top' || direction === 'bottom') { if (addition) { // if we have something like bottom-start we want to do bottom-end first diff --git a/projects/ng-aquila/src/overlay/public-api.ts b/projects/ng-aquila/src/overlay/public-api.ts index a5a6129cb..1e1a7ddb0 100644 --- a/projects/ng-aquila/src/overlay/public-api.ts +++ b/projects/ng-aquila/src/overlay/public-api.ts @@ -4,3 +4,4 @@ export * from './overlay-container.component'; export * from './overlay-config'; export * from './overlay-ref'; export * from './position-builder'; +export * from './trigger-button'; diff --git a/projects/ng-aquila/src/overlay/trigger-button.ts b/projects/ng-aquila/src/overlay/trigger-button.ts new file mode 100644 index 000000000..b905b84bd --- /dev/null +++ b/projects/ng-aquila/src/overlay/trigger-button.ts @@ -0,0 +1,13 @@ +import { Directive } from '@angular/core'; + +/** + * Interface for any kind of button that should be active while + * an overlay is open. + */ +@Directive() +export abstract class NxTriggerButton { + /** Applies active styles to the button. */ + abstract setTriggerActive(): void; + /** Applies or unsets inactive styles to the button. */ + abstract setTriggerInactive(): void; +}