diff --git a/.changeset/rude-pants-own.md b/.changeset/rude-pants-own.md new file mode 100644 index 000000000..e5ef9cc72 --- /dev/null +++ b/.changeset/rude-pants-own.md @@ -0,0 +1,5 @@ +--- +'@alauda/ui': patch +--- + +chore: optimize drawer diff --git a/src/drawer/component/drawer-ref.ts b/src/drawer/component/drawer-ref.ts deleted file mode 100644 index ce7f5766c..000000000 --- a/src/drawer/component/drawer-ref.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { ComponentType } from '@angular/cdk/portal'; -import { TemplateRef } from '@angular/core'; -import { Observable } from 'rxjs'; - -export enum DrawerSize { - Small = 'small', - Medium = 'medium', - Big = 'big', -} - -export abstract class DrawerRef, R = any> { - abstract afterClosed: Observable; - abstract afterOpen: Observable; - abstract dispose(result?: R): void; - abstract open(): void; - abstract componentInstance: T | null; - - abstract title?: string | TemplateRef; - abstract footer?: string | TemplateRef; - abstract size?: DrawerSize; - abstract offsetY?: string; - abstract visible?: boolean; - abstract hideOnClickOutside?: boolean; - abstract showClose?: boolean; - abstract drawerClass?: string; - abstract mask?: boolean; - abstract maskClosable?: boolean; - abstract width?: number; - abstract divider?: boolean; -} diff --git a/src/drawer/component/drawer.component.html b/src/drawer/component/drawer.component.html deleted file mode 100644 index 98e5420ae..000000000 --- a/src/drawer/component/drawer.component.html +++ /dev/null @@ -1,78 +0,0 @@ - -
-
-
-
-
-
- - {{ title }} - - - - -
- -
- -
- - - - - -
- - -
-
-
-
diff --git a/src/drawer/component/drawer.component.ts b/src/drawer/component/drawer.component.ts index 4dca1d5e5..0956c21b2 100644 --- a/src/drawer/component/drawer.component.ts +++ b/src/drawer/component/drawer.component.ts @@ -1,92 +1,34 @@ -import { - ComponentType, - Overlay, - OverlayConfig, - OverlayRef, -} from '@angular/cdk/overlay'; -import { - CdkPortalOutlet, - ComponentPortal, - TemplatePortal, - PortalModule, -} from '@angular/cdk/portal'; -import { CdkScrollable } from '@angular/cdk/scrolling'; -import { NgIf, NgClass, NgStyle, NgTemplateOutlet } from '@angular/common'; +import { ComponentType } from '@angular/cdk/overlay'; import { AfterViewInit, ChangeDetectionStrategy, - ChangeDetectorRef, Component, ContentChild, EventEmitter, - InjectionToken, - Injector, Input, OnChanges, - OnDestroy, - OnInit, Output, SimpleChanges, TemplateRef, - Type, - ViewChild, - ViewContainerRef, - ViewEncapsulation, } from '@angular/core'; -import { - Observable, - Subject, - debounceTime, - filter, - fromEvent, - takeUntil, -} from 'rxjs'; - -import { IconComponent } from '../../icon/icon.component'; -import { isTemplateRef } from '../../utils'; +import { first } from 'rxjs'; -import { DrawerRef, DrawerSize } from './drawer-ref'; +import { DrawerService } from '../drawer.service'; import { DrawerContentDirective, DrawerFooterDirective, DrawerHeaderDirective, -} from './helper-directives'; - -export const DATA = new InjectionToken('drawer-data'); - -const DRAWER_OVERLAY_CLASS = 'aui-drawer-overlay'; - -const SIZE_MAPPER = { - [DrawerSize.Small]: 400, - [DrawerSize.Medium]: 600, - [DrawerSize.Big]: 800, -}; +} from '../helper-directives'; +import { DrawerSize } from '../types'; @Component({ selector: 'aui-drawer', - templateUrl: './drawer.component.html', - styleUrls: ['./drawer.component.scss'], - encapsulation: ViewEncapsulation.None, + template: '', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [ - NgIf, - NgClass, - NgStyle, - NgTemplateOutlet, - IconComponent, - CdkScrollable, - PortalModule, - ], + providers: [DrawerService], }) -export class DrawerComponent< - T = ComponentType, - R = unknown, - D = unknown, - > - extends DrawerRef - implements OnInit, AfterViewInit, OnChanges, OnDestroy -{ +export class DrawerComponent implements AfterViewInit, OnChanges { @Input() title: string | TemplateRef; @@ -99,10 +41,11 @@ export class DrawerComponent< @Input() offsetY = '0px'; - @Input() visible: boolean; + @Input() + visible: boolean; @Input() - content: TemplateRef | ComponentType; + content: TemplateRef | ComponentType; @Input() hideOnClickOutside = false; @@ -122,102 +65,21 @@ export class DrawerComponent< @Input() divider = true; - private _value = SIZE_MAPPER[DrawerSize.Medium]; - @Input() - set width(value: number) { - this._value = value; - } - - get width() { - return this._value; - } - - get drawerClasses(): Record { - return { - 'aui-drawer': true, - hasDivider: this.divider, - ...(this.drawerClass ? { [this.drawerClass]: true } : null), - }; - } - - private readonly afterClosed$ = new Subject(); - - get afterClosed(): Observable { - return this.afterClosed$.asObservable(); - } - - private readonly afterOpen$ = new Subject(); - - get afterOpen(): Observable { - return this.afterOpen$.asObservable(); - } - @Output() - drawerViewInit = new EventEmitter(); - - @Output() readonly close = new EventEmitter(); - - @ViewChild('drawerTemplate', { static: true }) - drawerTemplate: TemplateRef; - - @ViewChild(CdkPortalOutlet, { static: false }) - bodyPortalOutlet: CdkPortalOutlet; + readonly close = new EventEmitter(); @ContentChild(DrawerHeaderDirective, { read: TemplateRef }) - titleTemplate: TemplateRef; + private readonly titleTemplate: TemplateRef; @ContentChild(DrawerContentDirective, { read: TemplateRef }) - contentTemplate: TemplateRef | ComponentType; + private readonly contentTemplateOrComponent: + | TemplateRef + | ComponentType; @ContentChild(DrawerFooterDirective, { read: TemplateRef }) - footerTemplate: TemplateRef; - - onDestroy$ = new Subject(); - - isTemplateRef = isTemplateRef; - - componentInstance: T | null = null; - - contentParams: D; - overlayRef: OverlayRef; - portal: TemplatePortal; - templateContext = {}; - get transform() { - return `translateX(${this.visible ? 0 : '100%'})`; - } + private readonly footerTemplate: TemplateRef; - constructor( - private readonly viewContainerRef: ViewContainerRef, - private readonly overlay: Overlay, - private readonly injector: Injector, - private readonly cdr: ChangeDetectorRef, - ) { - super(); - } - - ngOnInit() { - this.attachOverlay(); - this.updateBodyOverflow(); - this.templateContext = { $implicit: this.contentParams }; - - if (this.mask) { - // Issues: https://github.com/angular/components/issues/10841 - // scrollStrategy 为 Block 时,若创建 Overlay 时,高度不足以出现滚动,则 scrollStrategy 不会生效 - fromEvent(window, 'resize') - .pipe( - debounceTime(100), - filter( - () => document.documentElement.scrollHeight > window.innerHeight, - ), - takeUntil(this.onDestroy$), - ) - .subscribe(() => { - this.overlayRef.getConfig().scrollStrategy.enable(); - }); - } - - this.cdr.detectChanges(); - } + constructor(private readonly drawerService: DrawerService) {} ngOnChanges(changes: SimpleChanges): void { const { visible } = changes; @@ -227,125 +89,21 @@ export class DrawerComponent< this.open(); } else if (!visible.firstChange) { // 不希望默认关闭时,drawer 渲染后就触发 close 事件 - this.dispose(); + this.drawerService.close(); } } } ngAfterViewInit() { - this.attachBodyContent(); - setTimeout(() => { - this.drawerViewInit.emit(); - }, 0); + this.title = this.title || this.titleTemplate; + this.content = this.content || this.contentTemplateOrComponent; + this.footer = this.footer || this.footerTemplate; } - private attachOverlay() { - if (!this.overlayRef) { - this.portal = new TemplatePortal( - this.drawerTemplate, - this.viewContainerRef, - ); - this.overlayRef = this.overlay.create(this.getOverlayConfig()); - } - if (this.overlayRef) { - this.overlayRef.attach(this.portal); - this.overlayRef - .outsidePointerEvents() - .pipe(takeUntil(this.onDestroy$)) - .subscribe(event => { - // 判断鼠标点击事件的 target 是否为 overlay-container 的子节点,如果是,则不关闭 drawer。 - // 为了避免点击 drawer 里的 tooltip 后 drawer 被关闭。 - if ( - this.visible && - this.hideOnClickOutside && - event.target instanceof Node && - !this.overlayRef.hostElement?.parentNode?.contains(event.target) - ) { - event.stopPropagation(); - event.preventDefault(); - this.dispose(); - } - }); - } - } - - private getOverlayConfig(): OverlayConfig { - return new OverlayConfig({ - panelClass: DRAWER_OVERLAY_CLASS, - positionStrategy: this.overlay.position().global(), - scrollStrategy: this.mask - ? this.overlay.scrollStrategies.block() - : this.overlay.scrollStrategies.noop(), + private open() { + const ref = this.drawerService.open(this); + ref.afterClosed.pipe(first()).subscribe(res => { + this.close.emit(res); }); } - - private attachBodyContent(): void { - this.bodyPortalOutlet?.dispose(); - const content = this.content || this.contentTemplate; - if (content instanceof Type) { - const componentPortal = new ComponentPortal( - content, - null, - Injector.create({ - providers: [ - { - provide: DATA, - useValue: this.contentParams, - }, - ], - parent: this.injector, - }), - ); - const componentRef = - this.bodyPortalOutlet?.attachComponentPortal(componentPortal); - this.componentInstance = componentRef.instance; - Object.assign(componentRef.instance, this.contentParams); - componentRef.changeDetectorRef.detectChanges(); - } - } - - private updateBodyOverflow(): void { - if (this.overlayRef) { - if (this.visible) { - this.overlayRef.getConfig().scrollStrategy.enable(); - } else { - this.overlayRef.getConfig().scrollStrategy.disable(); - } - } - } - - open() { - this.visible = true; - this.afterOpen$.next(); - this.afterOpen$.complete(); - this.updateBodyOverflow(); - this.cdr.markForCheck(); - } - - dispose(result: R = null) { - this.visible = false; - this.close.emit(); - this.afterClosed$.next(result); - this.afterClosed$.complete(); - this.updateBodyOverflow(); - this.cdr.markForCheck(); - } - - private disposeOverlay(): void { - if (this.overlayRef) { - this.overlayRef.dispose(); - } - this.overlayRef = null; - } - - maskClick() { - if (this.maskClosable && this.mask) { - this.dispose(); - } - } - - ngOnDestroy(): void { - this.onDestroy$.next(); - this.disposeOverlay(); - } } diff --git a/src/drawer/component/internal/internal.component.html b/src/drawer/component/internal/internal.component.html new file mode 100644 index 000000000..c03600182 --- /dev/null +++ b/src/drawer/component/internal/internal.component.html @@ -0,0 +1,69 @@ +
+
+
+
+
+ + {{ options.title }} + + + + +
+ +
+ +
+ + + + + +
+ + +
+
+
diff --git a/src/drawer/component/drawer.component.scss b/src/drawer/component/internal/internal.component.scss similarity index 87% rename from src/drawer/component/drawer.component.scss rename to src/drawer/component/internal/internal.component.scss index c35ad832a..ef898daf7 100644 --- a/src/drawer/component/drawer.component.scss +++ b/src/drawer/component/internal/internal.component.scss @@ -1,38 +1,23 @@ -@import '../../theme/var'; -@import '../../theme/mixin'; +@import '../../../theme/var'; +@import '../../../theme/mixin'; +@import '../../../theme/motion'; $drawer: aui-drawer; -.#{$drawer}-mask { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 0; +.cdk-overlay-backdrop.cdk-overlay-backdrop-showing.aui-drawer-mask { @include modal-backdrop; - - &.isOpen { - height: 100%; - } } +@include fade-motion(aui-drawer-mask, aui-fade, 0.3s); + .#{$drawer} { position: fixed; top: 0; bottom: 0; right: 0; z-index: 9999; - transition: transform 0.3s, opacity 0.3s, box-shaow 0.3s; - @include text-set(m, main); - &.isOpen &__content { - @include theme-light { - box-shadow: -2px 0 8px 0 use-rgba(origin-shadow, 0.2); - } - @include theme-dark { - box-shadow: -2px 0 8px 0 use-rgba(origin-shadow, 0.75); - } - } + @include text-set(m, main); &__content { background-color: use-rgb(n-10); @@ -40,6 +25,13 @@ $drawer: aui-drawer; height: 100%; right: 0; width: 100%; + + @include theme-light { + box-shadow: -2px 0 8px 0 use-rgba(origin-shadow, 0.2); + } + @include theme-dark { + box-shadow: -2px 0 8px 0 use-rgba(origin-shadow, 0.75); + } } &__header { diff --git a/src/drawer/component/internal/internal.component.ts b/src/drawer/component/internal/internal.component.ts new file mode 100644 index 000000000..58248c6b3 --- /dev/null +++ b/src/drawer/component/internal/internal.component.ts @@ -0,0 +1,183 @@ +import { + animate, + AnimationEvent, + keyframes, + style, + transition, + trigger, +} from '@angular/animations'; +import { ComponentType } from '@angular/cdk/overlay'; +import { + CdkPortalOutlet, + ComponentPortal, + PortalModule, +} from '@angular/cdk/portal'; +import { CdkScrollable } from '@angular/cdk/scrolling'; +import { NgClass, NgIf, NgStyle, NgTemplateOutlet } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + InjectionToken, + Injector, + Output, + Type, + ViewChild, + ViewEncapsulation, +} from '@angular/core'; +import { Subject } from 'rxjs'; + +import { TimingFunction } from '../../../core/animation/animation-consts'; +import { IconComponent } from '../../../icon/icon.component'; +import { isTemplateRef } from '../../../utils'; +import { DrawerOptions, DrawerSize } from '../../types'; + +export const DATA = new InjectionToken('drawer-data'); + +const SIZE_MAPPER = { + [DrawerSize.Small]: 400, + [DrawerSize.Medium]: 600, + [DrawerSize.Big]: 800, +}; +export const duration = '300ms'; + +type Step = 'showStart' | 'showDone' | 'hideStart' | 'hideDone'; + +@Component({ + templateUrl: './internal.component.html', + styleUrls: ['./internal.component.scss'], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + NgIf, + NgClass, + NgStyle, + NgTemplateOutlet, + IconComponent, + CdkScrollable, + PortalModule, + ], + animations: [ + trigger('showHide', [ + transition('hide => show, void => show', [ + animate( + `${duration} ${TimingFunction.easeOut}`, + keyframes([ + style({ + opacity: 0, + transform: 'translateX(100%)', + }), + style({ + opacity: 1, + transform: 'translateX(0)', + }), + ]), + ), + ]), + transition('show => hide, show => void', [ + animate( + `${duration} ${TimingFunction.easeInOut}`, + keyframes([ + style({ + opacity: 1, + transform: 'translateX(0)', + }), + style({ + opacity: 0, + transform: 'translateX(100%)', + }), + ]), + ), + ]), + ]), + ], +}) +export class DrawerInternalComponent> { + @Output() + maskClick = new EventEmitter(); + + @Output() + closeClick = new EventEmitter(); + + @ViewChild(CdkPortalOutlet, { static: false }) + bodyPortalOutlet: CdkPortalOutlet; + + animationStep$ = new Subject(); + + options: DrawerOptions; + showHide = 'hide'; + + get drawerClasses(): Record { + return { + 'aui-drawer': true, + hasDivider: this.options.divider, + ...(this.options.drawerClass + ? { [this.options.drawerClass]: true } + : null), + }; + } + + get width() { + return ( + this.options.width || SIZE_MAPPER[this.options.size || DrawerSize.Medium] + ); + } + + isTemplateRef = isTemplateRef; + + constructor( + private readonly cdr: ChangeDetectorRef, + private readonly injector: Injector, + ) {} + + ngAfterViewInit() { + this.attachBodyContent(); + } + + private attachBodyContent(): void { + this.bodyPortalOutlet?.dispose(); + const content = this.options.content; + if (content instanceof Type) { + const componentPortal = new ComponentPortal( + content, + null, + Injector.create({ + providers: [ + { + provide: DATA, + useValue: this.options.contentParams, + }, + ], + parent: this.injector, + }), + ); + const componentRef = + this.bodyPortalOutlet?.attachComponentPortal(componentPortal); + Object.assign(componentRef.instance, this.options.contentParams); + componentRef.changeDetectorRef.detectChanges(); + } + } + + onAnimation(event: AnimationEvent) { + const { phaseName, toState } = event; + if (['show', 'hide'].includes(toState)) { + const step = [ + toState, + phaseName.charAt(0).toUpperCase() + phaseName.slice(1), + ].join('') as Step; + this.animationStep$.next(step); + } + } + + show() { + this.showHide = 'show'; + this.cdr.markForCheck(); + } + + hide() { + this.showHide = 'hide'; + this.cdr.markForCheck(); + } +} diff --git a/src/drawer/drawer-ref.ts b/src/drawer/drawer-ref.ts new file mode 100644 index 000000000..9e4f1234b --- /dev/null +++ b/src/drawer/drawer-ref.ts @@ -0,0 +1,32 @@ +import { filter, Observable, Subject } from 'rxjs'; + +import { DrawerInternalComponent } from './component/internal/internal.component'; + +export class DrawerRef { + private result: R; + + private readonly afterOpen$ = new Subject(); + private readonly afterClosed$ = new Subject(); + + get afterOpen(): Observable { + return this.afterOpen$.asObservable(); + } + + get afterClosed(): Observable { + return this.afterClosed$.asObservable(); + } + + constructor(public drawerInstance: DrawerInternalComponent) { + this.drawerInstance.animationStep$ + .pipe(filter(step => step === 'hideDone')) + .subscribe(() => { + this.afterClosed$.next(this.result); + this.afterClosed$.complete(); + }); + } + + dispose(result: R = null): void { + this.result = result; + this.drawerInstance.hide(); + } +} diff --git a/src/drawer/drawer.module.ts b/src/drawer/drawer.module.ts index 5166ce551..4b1e8801c 100644 --- a/src/drawer/drawer.module.ts +++ b/src/drawer/drawer.module.ts @@ -6,12 +6,12 @@ import { NgModule } from '@angular/core'; import { IconModule } from '../icon'; import { DrawerComponent } from './component/drawer.component'; +import { DrawerService } from './drawer.service'; import { DrawerContentDirective, DrawerFooterDirective, DrawerHeaderDirective, -} from './component/helper-directives'; -import { DrawerService } from './drawer.service'; +} from './helper-directives'; const COMMON = [ DrawerComponent, diff --git a/src/drawer/drawer.service.ts b/src/drawer/drawer.service.ts index 780b0c09f..dff416d6f 100644 --- a/src/drawer/drawer.service.ts +++ b/src/drawer/drawer.service.ts @@ -1,65 +1,142 @@ -import { Overlay, OverlayRef } from '@angular/cdk/overlay'; -import { ComponentPortal, ComponentType } from '@angular/cdk/portal'; -import { ComponentRef, Injectable, TemplateRef } from '@angular/core'; -import { Subject, takeUntil } from 'rxjs'; +import { Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; +import { ComponentPortal } from '@angular/cdk/portal'; +import { ComponentRef, Injectable } from '@angular/core'; +import { debounceTime, filter, fromEvent, Subject, takeUntil } from 'rxjs'; -import { DrawerComponent } from './component/drawer.component'; +import { DrawerInternalComponent } from './component/internal/internal.component'; +import { DrawerRef } from './drawer-ref'; +import { DrawerOptions } from './types'; -import { DrawerSize } from '.'; +const DRAWER_OVERLAY_CLASS = 'aui-drawer-overlay'; +const DRAWER_OVERLAY_BACKDROP_CLASS = 'aui-drawer-mask'; -export interface DrawerOptions { - title?: string | TemplateRef; - width?: number; - content?: ComponentType | TemplateRef; - contentParams?: D; - footer?: string | TemplateRef; - offsetY?: string; - divider?: boolean; - drawerClass?: string; - size?: DrawerSize; - visible?: boolean; - hideOnClickOutside?: boolean; - showClose?: boolean; - mask?: boolean; - maskClosable?: boolean; -} @Injectable() export class DrawerService { - private drawerRef: ComponentRef; private overlayRef: OverlayRef; - private readonly unsubscribe$ = new Subject(); + options: DrawerOptions; + onDestroy$ = new Subject(); + drawerCpt: ComponentRef; constructor(private readonly overlay: Overlay) {} - open(options: DrawerOptions) { - this.drawerRef?.instance?.dispose(); - this.createDrawer(); + open(options: DrawerOptions) { + this.disposeOverlay(); this.updateOptions(options); - return this.drawerRef?.instance; + this.createOverlay(); + return this.createDrawer(); + } + + close() { + this.drawerCpt?.instance?.hide(); } updateOptions(options: DrawerOptions): void { - Object.assign(this.drawerRef.instance, options); + this.options = options; } - private createDrawer(): void { - this.overlayRef = this.overlay.create(); - this.drawerRef = this.overlayRef.attach( - new ComponentPortal(DrawerComponent), - ); - this.drawerRef.instance.drawerViewInit - .pipe(takeUntil(this.unsubscribe$)) - .subscribe(() => { - this.drawerRef.instance.open(); + private createOverlay() { + this.overlayRef = this.overlay.create(this.getOverlayConfig()); + this.overlayRef.backdropClick().subscribe(() => { + if (this.options.maskClosable) { + this.close(); + } + }); + this.overlayRef + .outsidePointerEvents() + .pipe(takeUntil(this.onDestroy$)) + .subscribe(event => { + // 判断鼠标点击事件的 target 是否为 overlay-container 的子节点,如果是,则不关闭 drawer。 + // 为了避免点击 drawer 里的 tooltip 后 drawer 被关闭。 + if ( + this.overlayRef && + this.options.hideOnClickOutside && + event.target instanceof Node && + !this.overlayRef.hostElement?.parentNode?.contains(event.target) + ) { + event.stopPropagation(); + event.preventDefault(); + this.close(); + } }); + this.overlayRef.getConfig().scrollStrategy.enable(); + if (this.options.mask) { + // Issues: https://github.com/angular/components/issues/10841 + // scrollStrategy 为 Block 时,若创建 Overlay 时,高度不足以出现滚动,则 scrollStrategy 不会生效 + fromEvent(window, 'resize') + .pipe( + debounceTime(100), + filter( + () => document.documentElement.scrollHeight > window.innerHeight, + ), + takeUntil(this.onDestroy$), + ) + .subscribe(() => { + this.overlayRef.getConfig().scrollStrategy.enable(); + }); + } + } - this.drawerRef.instance.afterClosed - .pipe(takeUntil(this.unsubscribe$)) - .subscribe(() => { - this.overlayRef.dispose(); - this.drawerRef = null; - this.unsubscribe$.next(); - this.unsubscribe$.complete(); - }); + private createDrawer(): DrawerRef { + this.drawerCpt = this.overlayRef.attach( + new ComponentPortal(DrawerInternalComponent), + ); + this.drawerCpt.instance.options = this.options; + this.drawerCpt.instance.animationStep$.subscribe(step => { + const backdropElement = this.overlayRef.backdropElement; + if (backdropElement) { + const enters = [ + `${DRAWER_OVERLAY_BACKDROP_CLASS}-enter`, + `${DRAWER_OVERLAY_BACKDROP_CLASS}-enter-active`, + ]; + const leaves = [ + `${DRAWER_OVERLAY_BACKDROP_CLASS}-leave`, + `${DRAWER_OVERLAY_BACKDROP_CLASS}-leave-active`, + ]; + if (step === 'showStart') { + backdropElement.classList.add(...enters); + } + if (step === 'hideStart') { + backdropElement.classList.add(...leaves); + } + if (['showDone', 'hideDone'].includes(step)) { + backdropElement.classList.remove(...enters, ...leaves); + } + } + if (step === 'hideDone') { + this.disposeOverlay(); + } + }); + + const drawerRef = new DrawerRef(this.drawerCpt.instance); + + this.drawerCpt.instance.show(); + + return drawerRef; + } + + private getOverlayConfig(): OverlayConfig { + return new OverlayConfig({ + panelClass: DRAWER_OVERLAY_CLASS, + positionStrategy: this.overlay.position().global(), + scrollStrategy: this.options.mask + ? this.overlay.scrollStrategies.block() + : this.overlay.scrollStrategies.noop(), + hasBackdrop: this.options.mask, + backdropClass: DRAWER_OVERLAY_BACKDROP_CLASS, + }); + } + + private disposeOverlay(): void { + this.onDestroy$.next(); + this.onDestroy$.complete(); + if (this.overlayRef) { + this.overlayRef.getConfig().scrollStrategy.disable(); + this.overlayRef.dispose(); + } + this.overlayRef = null; + } + + ngOnDestroy(): void { + this.disposeOverlay(); } } diff --git a/src/drawer/component/helper-directives.ts b/src/drawer/helper-directives.ts similarity index 100% rename from src/drawer/component/helper-directives.ts rename to src/drawer/helper-directives.ts diff --git a/src/drawer/index.ts b/src/drawer/index.ts index 5cbffa988..b1d0a82ed 100644 --- a/src/drawer/index.ts +++ b/src/drawer/index.ts @@ -1,5 +1,6 @@ export * from './component/drawer.component'; -export * from './component/drawer-ref'; -export * from './component/helper-directives'; export * from './drawer.module'; export * from './drawer.service'; +export * from './drawer-ref'; +export * from './helper-directives'; +export * from './types'; diff --git a/src/drawer/types.ts b/src/drawer/types.ts new file mode 100644 index 000000000..8164d960f --- /dev/null +++ b/src/drawer/types.ts @@ -0,0 +1,25 @@ +import { ComponentType } from '@angular/cdk/portal'; +import { TemplateRef } from '@angular/core'; + +export enum DrawerSize { + Small = 'small', + Medium = 'medium', + Big = 'big', +} + +export interface DrawerOptions { + title?: string | TemplateRef; + content?: ComponentType | TemplateRef; + footer?: string | TemplateRef; + contentParams?: D; // 不仅作为content的参数,同时是title和footer的上下文 + width?: number; + size?: DrawerSize; // 内置的宽度尺寸,也可以使用 width 自定义 + offsetY?: string; + divider?: boolean; + drawerClass?: string; + visible?: boolean; + showClose?: boolean; + mask?: boolean; + maskClosable?: boolean; // 点击背景是否关闭抽屉 + hideOnClickOutside?: boolean; // 在抽屉外点击是否关闭抽屉,与 maskClosable 的区别是是否有 mask +} diff --git a/stories/drawer/basic-drawer.stories.ts b/stories/drawer/basic-drawer.stories.ts index 04d2528f7..c5fc884e6 100644 --- a/stories/drawer/basic-drawer.stories.ts +++ b/stories/drawer/basic-drawer.stories.ts @@ -1,4 +1,5 @@ import { FormsModule } from '@angular/forms'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; import { BasicDrawerComponent } from './basic-drawer.component'; @@ -22,6 +23,7 @@ const meta: Meta = { InputModule, FormsModule, SwitchModule, + BrowserAnimationsModule, ], }), ], diff --git a/stories/drawer/mask-drawer.stories.ts b/stories/drawer/mask-drawer.stories.ts index 73fd798f8..578d66a1c 100644 --- a/stories/drawer/mask-drawer.stories.ts +++ b/stories/drawer/mask-drawer.stories.ts @@ -1,3 +1,4 @@ +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; import { MaskDrawerComponent } from './mask-drawer.component'; @@ -10,7 +11,7 @@ const meta: Meta = { decorators: [ moduleMetadata({ declarations: [MaskDrawerComponent], - imports: [ButtonModule, DrawerModule], + imports: [ButtonModule, DrawerModule, BrowserAnimationsModule], }), ], }; diff --git a/stories/drawer/service-drawer-cpt.stories.ts b/stories/drawer/service-drawer-cpt.stories.ts index d7c881f4d..7d8157468 100644 --- a/stories/drawer/service-drawer-cpt.stories.ts +++ b/stories/drawer/service-drawer-cpt.stories.ts @@ -1,3 +1,4 @@ +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; import { @@ -13,7 +14,7 @@ const meta: Meta = { component: ServiceDrawerCptComponent, decorators: [ moduleMetadata({ - imports: [ButtonModule, DrawerModule], + imports: [ButtonModule, DrawerModule, BrowserAnimationsModule], declarations: [ ServiceDrawerCptComponent, DrawerContentComponent, diff --git a/stories/drawer/service-drawer.stories.ts b/stories/drawer/service-drawer.stories.ts index 72945e470..44e0dac6c 100644 --- a/stories/drawer/service-drawer.stories.ts +++ b/stories/drawer/service-drawer.stories.ts @@ -1,3 +1,4 @@ +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { Meta, StoryObj, moduleMetadata } from '@storybook/angular'; import { ServiceDrawerComponent } from './service-drawer.component'; @@ -10,7 +11,7 @@ const meta: Meta = { decorators: [ moduleMetadata({ declarations: [ServiceDrawerComponent], - imports: [ButtonModule, DrawerModule], + imports: [ButtonModule, DrawerModule, BrowserAnimationsModule], }), ], };