UNPKG

ng-ytl-zorro-antd

Version:

An enterprise-class UI components based on Ant Design and Angular

358 lines (319 loc) 10.6 kB
import { AfterViewInit, Component, ComponentFactory, ComponentRef, ElementRef, EventEmitter, HostListener, Inject, Input, OnDestroy, OnInit, Output, TemplateRef, ViewChild, ViewContainerRef, ViewEncapsulation, } from '@angular/core'; import { NzLocaleService } from '../locale/index'; import { toBoolean } from '../util/convert'; import nzGlobalMonitor from '../util/nz-global-monitor'; import { NzModalSubject } from './nz-modal-subject.service'; interface Position { x: number; y: number; } @Component({ selector : 'nz-modal', viewProviders: [ NzModalSubject ], encapsulation: ViewEncapsulation.None, template : ` <div [ngClass]="_customClass"> <div [ngClass]="_maskClassMap" [style.zIndex]="_zIndex"></div> <div tabindex="-1" role="dialog" [attr.aria-labelledby]="modalId" (click)="closeFromMask($event)" [ngClass]="_wrapClass" [style.zIndex]="_zIndex" [ngStyle]="{ 'display': !_visible && !_animationStatus ? 'none' : '' }"> <div #modal_content role="document" [ngClass]="_bodyClassMap" [ngStyle]="_bodyStyleMap"> <div class="ant-modal-content"> <ng-template [ngIf]="_closable"> <button aria-label="Close" class="ant-modal-close" (click)="clickCancel($event)"> <span class="ant-modal-close-x"></span> </button> </ng-template> <div class="ant-modal-header" *ngIf="_title || _titleTpl"> <div class="ant-modal-title" [attr.id]="modalId"> <ng-template #defaultTitle>{{ _title }}</ng-template> <ng-template [ngTemplateOutlet]="_titleTpl || defaultTitle"></ng-template> </div> </div> <div class="ant-modal-body"> <ng-template #defaultContent>{{ _content }}</ng-template> <ng-template [ngTemplateOutlet]="_contentTpl || defaultContent"></ng-template> <ng-template #modal_component></ng-template> </div> <div class="ant-modal-footer" *ngIf="!_footerHide"> <ng-template #defaultFooter> <button nz-button [nzType]="'ghost'" [nzSize]="'large'" (click)="clickCancel($event)"> <span>{{ _cancelText }}</span> </button> <button nz-button [nzType]="'primary'" [nzSize]="'large'" (click)="clickOk($event)" [nzLoading]="_confirmLoading"> <span>{{ _okText }}</span> </button> </ng-template> <ng-template [ngTemplateOutlet]="_footerTpl || defaultFooter"></ng-template> </div> </div> </div> <div tabindex="0" style="width: 0px; height: 0px; overflow: hidden;">sentinel</div> </div> </div> `, styleUrls : [ './style/index.less' ] }) export class NzModalComponent implements OnInit, OnDestroy, AfterViewInit { private _confirmLoading = false; private _maskClosable = true; _footerHide = false; _closable = true; _visible = false; _prefixCls = 'ant-modal'; _maskClassMap; _bodyClassMap; _bodyStyleMap; _width = '520px'; _zIndex = 1000; _title = ''; _titleTpl: TemplateRef<void>; _content = ''; _contentTpl: TemplateRef<void>; _footerTpl: TemplateRef<void>; _okText = this._locale.translate('Modal.okText'); _cancelText = this._locale.translate('Modal.cancelText'); _style: object = {}; _wrapClass = `${this._prefixCls}-wrap`; _customClass = ''; _animationStatus = ''; _bodyComponent: ComponentFactory<void>; _componentParams: object = {}; modalId = `nzModal${nzGlobalMonitor.getGlobalCount()}`; @ViewChild('modal_content') contentEl: ElementRef; @ViewChild('modal_component', { read: ViewContainerRef }) bodyEl: ViewContainerRef; @Input() set nzVisible(value: boolean) { const visible = toBoolean(value); if (this._visible === visible) { return; } if (visible) { this.anmiateFade('enter'); this.subject.next('onShow'); // 每次触发点击事件的时候,通过全局监听的类,记录下点击的位置,计算动画的origin setTimeout(() => { this.setStyles({ x: nzGlobalMonitor.lastClickPos.x || 0, y: nzGlobalMonitor.lastClickPos.y || 0 }); }); } else { this.anmiateFade('leave'); this.subject.next('onHide'); } this._visible = visible; this.nzVisibleChange.emit(this._visible); // 设置全局的overflow样式 nzGlobalMonitor.setDocumentOverflowHidden(visible); } get nzVisible(): boolean { return this._visible; } @Input() set nzConfirmLoading(value: boolean) { this._confirmLoading = toBoolean(value); } @Input() set nzClosable(value: boolean) { this._closable = toBoolean(value); } @Input() set nzClass(value: string) { this._customClass = value; } @Input() set nzWidth(value: string | number) { this._width = typeof value === 'number' ? value + 'px' : value; } @Input() set nzZIndex(value: number) { this._zIndex = value; } @Input() set nzTitle(value: string | TemplateRef<void>) { // TODO: should guard for string instead, all types are theoretically structural if (value instanceof TemplateRef) { this._titleTpl = value; } else { this._title = value; } } @Input() set nzContent(value: string | TemplateRef<void> | ComponentFactory<void>) { if (value instanceof ComponentFactory) { // 如果容器对象已存在,则直接渲染,如果不存在,则设置到_bodyComponent,在ngAfterViewInit中执行 if (this.bodyEl) { const compRef: ComponentRef<void> = this.bodyEl.createComponent(value, null, this._vcr.injector); Object.assign(compRef.instance, this._componentParams); } else { this._bodyComponent = value; } } else if (value instanceof TemplateRef) { this._contentTpl = value; } else { this._content = value; } } @Input() set nzFooter(value: TemplateRef<void> | boolean) { if (value instanceof TemplateRef) { this._footerTpl = value; } else { this._footerHide = !toBoolean(value); } } @Input() set nzOkText(value: string) { this._okText = value; } @Input() set nzCancelText(value: string) { this._cancelText = value; } @Input() set nzMaskClosable(value: boolean) { this._maskClosable = toBoolean(value); } @Input() set nzStyle(value: object) { this._style = value; } @Input() set nzWrapClassName(value: string) { if (value) { this._wrapClass = `${this._prefixCls}-wrap ${value}`; } } @Input() set nzComponentParams(value: object) { this._componentParams = value; } @Output() nzVisibleChange: EventEmitter<boolean> = new EventEmitter(); @Output() nzOnOk: EventEmitter<MouseEvent> = new EventEmitter(); // TODO: reconsider the payload type @Output() nzOnCancel: EventEmitter<MouseEvent | KeyboardEvent> = new EventEmitter(); @HostListener('keydown.esc', [ '$event' ]) onEsc(e: KeyboardEvent): void { if (this._maskClosable) { this.clickCancel(e); } } setStyles(origin?: { x: number, y: number }): void { const el = this.contentEl.nativeElement; const transformOrigin = origin ? `${origin.x - el.offsetLeft}px ${origin.y - el.offsetTop}px 0px` : ''; // TODO: spread on literal has been disallow on latest proposal this._bodyStyleMap = { ...{ 'width' : this._width, 'transform-origin': transformOrigin }, ...this._style }; } setClassMap(): void { this._maskClassMap = { [`${this._prefixCls}-mask`] : true, [`${this._prefixCls}-mask-hidden`]: !this._visible && !this._animationStatus, 'fade-enter' : this._animationStatus === 'enter', 'fade-enter-active' : this._animationStatus === 'enter', 'fade-leave' : this._animationStatus === 'leave', 'fade-leave-active' : this._animationStatus === 'leave' }; this._bodyClassMap = { [this._prefixCls] : true, 'zoom-enter' : this._animationStatus === 'enter', 'zoom-enter-active': this._animationStatus === 'enter', 'zoom-leave' : this._animationStatus === 'leave', 'zoom-leave-active': this._animationStatus === 'leave' }; } anmiateFade(status: string): void { this._animationStatus = status; this.setClassMap(); setTimeout(_ => { this._animationStatus = ''; this.setClassMap(); this.subject.next(status === 'enter' ? 'onShown' : 'onHidden'); // modal打开后,默认焦点设置到modal上 if (status === 'enter') { this.contentEl.nativeElement.parentNode.focus(); } }, 200); } closeFromMask(e: MouseEvent): void { if (this._maskClosable && (e.target as HTMLElement).getAttribute('role') === 'dialog') { this.clickCancel(e); } } setConfirmLoading(loading: boolean): void { this.nzConfirmLoading = loading; } open(): void { this.nzVisible = true; } close(): void { this.nzVisible = false; } clickOk(e: MouseEvent): void { if (this.nzOnOk) { this.nzOnOk.emit(e); } else { this.nzVisible = false; } this.subject.next('onOk'); } clickCancel(e: MouseEvent | KeyboardEvent): void { this.nzOnCancel.emit(e); this.subject.next('onCancel'); } constructor(public subject: NzModalSubject, private _vcr: ViewContainerRef, private _locale: NzLocaleService) { this.subject.modalId = this.modalId; } ngOnInit(): void { this.setClassMap(); this.setStyles(); } ngAfterViewInit(): void { if (this._bodyComponent) { const compRef: ComponentRef<void> = this.bodyEl.createComponent(this._bodyComponent, null, this._vcr.injector); Object.assign(compRef.instance, this._componentParams); } } ngOnDestroy(): void { if (this._visible) { nzGlobalMonitor.setDocumentOverflowHidden(false); } this.subject.next('onDestroy'); this.subject.unsubscribe(); this.subject = null; } }