From e121d4006b6f1ac709e45a75d39722be453faa0d Mon Sep 17 00:00:00 2001 From: Elain T Date: Wed, 29 May 2024 12:09:16 -0400 Subject: [PATCH] feat(design)!: allow modal to be closed with ESC key (#2812) BREAKING CHANGE: Previously, the `DaffModalService` required a `DaffModal` as an arg to `close` (the return type of `open`). This API was probably larger than it should have been, so I trimmed it down. Most consumers won't notice a return type change, but if you do, we can reconsider this change. --- .../add-to-cart-notification.effects.ts | 4 +- .../src/basic-modal/basic-modal.component.ts | 7 ++- libs/design/modal/src/modal.module.ts | 4 ++ .../modal/src/modal/modal.component.spec.ts | 4 ++ .../design/modal/src/modal/modal.component.ts | 10 +++- .../design/modal/src/service/modal.service.ts | 46 +++++++++++-------- .../design/modal/src/specs/animations.spec.ts | 4 ++ .../modal/src/specs/dynamic-content.spec.ts | 4 ++ 8 files changed, 58 insertions(+), 25 deletions(-) diff --git a/apps/demo/src/app/cart/components/add-to-cart-notification/effects/add-to-cart-notification.effects.ts b/apps/demo/src/app/cart/components/add-to-cart-notification/effects/add-to-cart-notification.effects.ts index e8c7bf9ed7..83b06b5d15 100644 --- a/apps/demo/src/app/cart/components/add-to-cart-notification/effects/add-to-cart-notification.effects.ts +++ b/apps/demo/src/app/cart/components/add-to-cart-notification/effects/add-to-cart-notification.effects.ts @@ -13,7 +13,7 @@ import { import { DaffCartActionTypes } from '@daffodil/cart/state'; import { DaffModalService, - DaffModal, + DaffModalComponent, } from '@daffodil/design/modal'; import { @@ -25,7 +25,7 @@ import { AddToCartNotificationComponent } from '../components/add-to-cart-notifi @Injectable() export class AddToCartNotificationEffects { - private notification: DaffModal; + private notification: DaffModalComponent; constructor( private actions$: Actions, diff --git a/libs/design/modal/examples/src/basic-modal/basic-modal.component.ts b/libs/design/modal/examples/src/basic-modal/basic-modal.component.ts index 3f313b1cd3..4055c99c59 100644 --- a/libs/design/modal/examples/src/basic-modal/basic-modal.component.ts +++ b/libs/design/modal/examples/src/basic-modal/basic-modal.component.ts @@ -3,7 +3,10 @@ import { Component, } from '@angular/core'; -import { DaffModalService } from '@daffodil/design/modal'; +import { + DaffModalComponent, + DaffModalService, +} from '@daffodil/design/modal'; import { BasicModalContentComponent } from './modal-content.component'; @@ -14,7 +17,7 @@ import { BasicModalContentComponent } from './modal-content.component'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class BasicModalComponent { - modal: any; + modal: DaffModalComponent; constructor(private modalService: DaffModalService) {} diff --git a/libs/design/modal/src/modal.module.ts b/libs/design/modal/src/modal.module.ts index 23f1464d62..ed967c371d 100644 --- a/libs/design/modal/src/modal.module.ts +++ b/libs/design/modal/src/modal.module.ts @@ -8,6 +8,7 @@ import { DaffModalActionsComponent } from './modal-actions/modal-actions.compone import { DaffModalContentComponent } from './modal-content/modal-content.component'; import { DaffModalHeaderComponent } from './modal-header/modal-header.component'; import { DaffModalTitleDirective } from './modal-title/modal-title.directive'; +import { DaffModalService } from './service/modal.service'; @NgModule({ imports: [ @@ -28,5 +29,8 @@ import { DaffModalTitleDirective } from './modal-title/modal-title.directive'; DaffModalContentComponent, DaffModalActionsComponent, ], + providers: [ + DaffModalService, + ], }) export class DaffModalModule { } diff --git a/libs/design/modal/src/modal/modal.component.spec.ts b/libs/design/modal/src/modal/modal.component.spec.ts index aab0367ed9..ba1c68b03c 100644 --- a/libs/design/modal/src/modal/modal.component.spec.ts +++ b/libs/design/modal/src/modal/modal.component.spec.ts @@ -11,6 +11,7 @@ import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { DaffModalComponent } from './modal.component'; +import { DaffModalService } from '../service/modal.service'; @Component({ template: `
@@ -34,6 +35,9 @@ describe('@daffodil/design/modal | DaffModalComponent', () => { WrapperComponent, DaffModalComponent, ], + providers: [ + DaffModalService, + ], }) .compileComponents(); })); diff --git a/libs/design/modal/src/modal/modal.component.ts b/libs/design/modal/src/modal/modal.component.ts index a1e25a7370..8614b933ff 100644 --- a/libs/design/modal/src/modal/modal.component.ts +++ b/libs/design/modal/src/modal/modal.component.ts @@ -18,12 +18,14 @@ import { ElementRef, AfterContentInit, AfterViewInit, + Self, } from '@angular/core'; import { daffFocusableElementsSelector } from '@daffodil/design'; import { daffFadeAnimations } from '../animations/modal-animation'; import { getAnimationState } from '../animations/modal-animation-state'; +import { DaffModalService } from '../service/modal.service'; @Component({ selector: 'daff-modal', @@ -61,13 +63,17 @@ export class DaffModalComponent implements AfterContentInit, AfterViewInit { >(); /** - * Event fired when the backdrop is clicked. This is often used to close the modal. + * @docs-private */ - hide: EventEmitter = new EventEmitter(); + @HostListener('keydown.escape') + onEscape() { + this.modalService.close(this); + } private _focusTrap: ConfigurableFocusTrap; constructor( + private modalService: DaffModalService, private _focusTrapFactory: ConfigurableFocusTrapFactory, private _elementRef: ElementRef, ) { diff --git a/libs/design/modal/src/service/modal.service.ts b/libs/design/modal/src/service/modal.service.ts index 0a3baa18e0..ac34bb3c3e 100644 --- a/libs/design/modal/src/service/modal.service.ts +++ b/libs/design/modal/src/service/modal.service.ts @@ -9,18 +9,16 @@ import { Injectable, Type, ComponentRef, + Injector, } from '@angular/core'; import { DaffModal } from '../modal/modal'; import { DaffModalConfiguration } from '../modal/modal-config'; import { DaffModalComponent } from '../modal/modal.component'; -import { DaffModalModule } from '../modal.module'; -@Injectable({ - providedIn: DaffModalModule, -}) +@Injectable() export class DaffModalService { - private _modals: DaffModal[] = []; + private _modals: Map = new Map(); constructor(private overlay: Overlay) {} @@ -29,7 +27,16 @@ export class DaffModalService { private _attachModal( overlayRef: OverlayRef, ): ComponentRef { - const modal = overlayRef.attach(new ComponentPortal(DaffModalComponent)); + const modal = overlayRef.attach( + new ComponentPortal( + DaffModalComponent, + undefined, + Injector.create({ providers: [{ + provide: DaffModalService, + useValue: this, + }]}), + ), + ); modal.instance.open = true; return modal; } @@ -51,22 +58,21 @@ export class DaffModalService { } private _removeModal(modal: DaffModal) { - const index = this._modals.indexOf(modal); - if (index === -1) { - throw new Error( + if (!this._modals.has(modal.modal.instance)) { + throw new Error( 'The Modal that you are trying to remove does not exist.', ); - } + } - modal.overlay.dispose(); + this._modals.delete(modal.modal.instance); - this._modals = this._modals.filter(m => m !== modal); + modal.overlay.dispose(); } open( component: Type, configuration?: Partial, - ): DaffModal { + ): DaffModalComponent { const config = { ...this.defaultConfiguration, ...configuration }; const _ref = this._createOverlayRef(); const _modal = this._attachModal(_ref); @@ -77,22 +83,24 @@ export class DaffModalService { overlay: _ref, }; - this._modals.push(modal); + this._modals.set(modal.modal.instance, modal); _ref .backdropClick() .subscribe(() => config.onBackdropClicked ? config.onBackdropClicked() - : this.close(modal), + : this.close(modal.modal.instance), ); - return modal; + return modal.modal.instance; } - close(modal: DaffModal): void { - modal.modal.instance.open = false; + close(component: DaffModalComponent): void { + component.open = false; + const modal = this._modals.get(component); + modal.overlay.detachBackdrop(); - modal.modal.instance.closedAnimationCompleted.subscribe( + component.closedAnimationCompleted.subscribe( (e: AnimationEvent) => this._removeModal(modal), ); } diff --git a/libs/design/modal/src/specs/animations.spec.ts b/libs/design/modal/src/specs/animations.spec.ts index 1060d32d4f..536f707a79 100644 --- a/libs/design/modal/src/specs/animations.spec.ts +++ b/libs/design/modal/src/specs/animations.spec.ts @@ -11,6 +11,7 @@ import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { DaffModalComponent } from '../modal/modal.component'; +import { DaffModalService } from '../service/modal.service'; @Component({ template: `
@@ -36,6 +37,9 @@ describe('@daffodil/design/modal | DaffModalComponent', () => { WrapperComponent, DaffModalComponent, ], + providers: [ + DaffModalService, + ], }) .compileComponents(); })); diff --git a/libs/design/modal/src/specs/dynamic-content.spec.ts b/libs/design/modal/src/specs/dynamic-content.spec.ts index 09cc327a6c..b734c592bf 100644 --- a/libs/design/modal/src/specs/dynamic-content.spec.ts +++ b/libs/design/modal/src/specs/dynamic-content.spec.ts @@ -16,6 +16,7 @@ import { By } from '@angular/platform-browser'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { DaffModalComponent } from '../modal/modal.component'; +import { DaffModalService } from '../service/modal.service'; @Component({ template: `` }) class WrapperComponent {} @@ -45,6 +46,9 @@ describe('@daffodil/design/modal | DaffModalComponent', () => { WrapperComponent, DaffModalComponent, ], + providers: [ + DaffModalService, + ], }) .compileComponents(); }));