UNPKG

ng-zorro-antd

Version:

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

1,281 lines (1,266 loc) 126 kB
import { Directionality, BidiModule } from '@angular/cdk/bidi'; import { CdkOverlayOrigin, CdkConnectedOverlay, OverlayModule } from '@angular/cdk/overlay'; import { DOCUMENT, CommonModule } from '@angular/common'; import { EventEmitter, Component, ViewEncapsulation, ChangeDetectionStrategy, Input, Output, Injectable, ChangeDetectorRef, ElementRef, Inject, ViewChild, ViewChildren, ContentChild, forwardRef, Renderer2, Optional, Host, Directive, NgModule } from '@angular/core'; import { NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms'; import { NzButtonModule } from 'ng-zorro-antd/button'; import { NzNoAnimationDirective, NzNoAnimationModule } from 'ng-zorro-antd/core/no-animation'; import { NzOutletModule } from 'ng-zorro-antd/core/outlet'; import { NzOverlayModule } from 'ng-zorro-antd/core/overlay'; import { NzIconModule } from 'ng-zorro-antd/icon'; import { NzTimePickerModule } from 'ng-zorro-antd/time-picker'; import { CandyDate, normalizeRangeValue, cloneDate, wrongSortOrder } from 'ng-zorro-antd/core/time'; import { isTemplateRef, isNonEmptyString, toBoolean, valueFunctionProp, InputBoolean } from 'ng-zorro-antd/core/util'; import { DateHelperService, NzI18nService, NzI18nModule } from 'ng-zorro-antd/i18n'; import { __decorate, __metadata } from 'tslib'; import { ReplaySubject, Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { NzConfigService, WithConfig } from 'ng-zorro-antd/core/config'; import { ESCAPE } from '@angular/cdk/keycodes'; import { Platform } from '@angular/cdk/platform'; import { slideMotion } from 'ng-zorro-antd/core/animation'; import { NzResizeObserver } from 'ng-zorro-antd/core/resize-observers'; /** * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ /** * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ const PREFIX_CLASS = 'ant-picker'; const defaultDisabledTime = { nzDisabledHours() { return []; }, nzDisabledMinutes() { return []; }, nzDisabledSeconds() { return []; } }; function getTimeConfig(value, disabledTime) { let disabledTimeConfig = disabledTime ? disabledTime(value && value.nativeDate) : {}; disabledTimeConfig = Object.assign(Object.assign({}, defaultDisabledTime), disabledTimeConfig); return disabledTimeConfig; } function isTimeValidByConfig(value, disabledTimeConfig) { let invalidTime = false; if (value) { const hour = value.getHours(); const minutes = value.getMinutes(); const seconds = value.getSeconds(); const disabledHours = disabledTimeConfig.nzDisabledHours(); if (disabledHours.indexOf(hour) === -1) { const disabledMinutes = disabledTimeConfig.nzDisabledMinutes(hour); if (disabledMinutes.indexOf(minutes) === -1) { const disabledSeconds = disabledTimeConfig.nzDisabledSeconds(hour, minutes); invalidTime = disabledSeconds.indexOf(seconds) !== -1; } else { invalidTime = true; } } else { invalidTime = true; } } return !invalidTime; } function isTimeValid(value, disabledTime) { const disabledTimeConfig = getTimeConfig(value, disabledTime); return isTimeValidByConfig(value, disabledTimeConfig); } function isAllowedDate(value, disabledDate, disabledTime) { if (!value) { return false; } if (disabledDate) { if (disabledDate(value.nativeDate)) { return false; } } if (disabledTime) { if (!isTimeValid(value, disabledTime)) { return false; } } return true; } /** * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ /** * Compatible translate the moment-like format pattern to angular's pattern * Why? For now, we need to support the existing language formats in AntD, and AntD uses the default temporal syntax. * * TODO: compare and complete all format patterns * Each format docs as below: * @link https://momentjs.com/docs/#/displaying/format/ * @link https://angular.io/api/common/DatePipe#description * @param format input format pattern */ function transCompatFormat(format) { return (format && format .replace(/Y/g, 'y') // only support y, yy, yyy, yyyy .replace(/D/g, 'd')); // d, dd represent of D, DD for momentjs, others are not support } /** * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ class CalendarFooterComponent { constructor(dateHelper) { this.dateHelper = dateHelper; this.showToday = false; this.showNow = false; this.hasTimePicker = false; this.isRange = false; this.okDisabled = false; this.rangeQuickSelector = null; this.clickOk = new EventEmitter(); this.clickToday = new EventEmitter(); this.prefixCls = PREFIX_CLASS; this.isTemplateRef = isTemplateRef; this.isNonEmptyString = isNonEmptyString; this.isTodayDisabled = false; this.todayTitle = ''; } ngOnChanges(changes) { const now = new Date(); if (changes.disabledDate) { this.isTodayDisabled = !!(this.disabledDate && this.disabledDate(now)); } if (changes.locale) { // NOTE: Compat for DatePipe formatting rules const dateFormat = transCompatFormat(this.locale.dateFormat); this.todayTitle = this.dateHelper.format(now, dateFormat); } } onClickToday() { const now = new CandyDate(); this.clickToday.emit(now.clone()); // To prevent the "now" being modified from outside, we use clone } } CalendarFooterComponent.decorators = [ { type: Component, args: [{ encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, // tslint:disable-next-line:component-selector selector: 'calendar-footer', exportAs: 'calendarFooter', template: ` <div class="{{ prefixCls }}-footer"> <div *ngIf="extraFooter" class="{{ prefixCls }}-footer-extra"> <ng-container [ngSwitch]="true"> <ng-container *ngSwitchCase="isTemplateRef(extraFooter)"> <ng-container *ngTemplateOutlet="$any(extraFooter)"></ng-container> </ng-container> <ng-container *ngSwitchCase="isNonEmptyString(extraFooter)"> <span [innerHTML]="extraFooter"></span> </ng-container> </ng-container> </div> <a *ngIf="showToday" class="{{ prefixCls }}-today-btn {{ isTodayDisabled ? prefixCls + '-today-btn-disabled' : '' }}" role="button" (click)="isTodayDisabled ? null : onClickToday()" title="{{ todayTitle }}" > {{ locale.today }} </a> <ul *ngIf="hasTimePicker || rangeQuickSelector" class="{{ prefixCls }}-ranges"> <ng-container *ngTemplateOutlet="rangeQuickSelector"></ng-container> <li *ngIf="showNow" class="{{ prefixCls }}-now"> <a class="{{ prefixCls }}-now-btn" (click)="isTodayDisabled ? null : onClickToday()"> {{ locale.now }} </a> </li> <li *ngIf="hasTimePicker" class="{{ prefixCls }}-ok"> <button nz-button type="button" nzType="primary" nzSize="small" [disabled]="okDisabled" (click)="okDisabled ? null : clickOk.emit()" > {{ locale.ok }} </button> </li> </ul> </div> ` },] } ]; CalendarFooterComponent.ctorParameters = () => [ { type: DateHelperService } ]; CalendarFooterComponent.propDecorators = { locale: [{ type: Input }], showToday: [{ type: Input }], showNow: [{ type: Input }], hasTimePicker: [{ type: Input }], isRange: [{ type: Input }], okDisabled: [{ type: Input }], disabledDate: [{ type: Input }], extraFooter: [{ type: Input }], rangeQuickSelector: [{ type: Input }], clickOk: [{ type: Output }], clickToday: [{ type: Output }] }; /** * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ class DatePickerService { constructor() { this.activeInput = 'left'; this.arrowLeft = 0; this.isRange = false; this.valueChange$ = new ReplaySubject(1); this.emitValue$ = new Subject(); this.inputPartChange$ = new Subject(); } initValue() { if (this.isRange) { this.initialValue = []; } else { this.initialValue = null; } this.setValue(this.initialValue); } hasValue(value = this.value) { if (Array.isArray(value)) { return !!value[0] || !!value[1]; } else { return !!value; } } makeValue(value) { if (this.isRange) { return value ? value.map(val => new CandyDate(val)) : []; } else { return value ? new CandyDate(value) : null; } } setActiveDate(value, hasTimePicker = false, mode = 'month') { const parentPanels = { date: 'month', month: 'year', year: 'decade' }; if (this.isRange) { this.activeDate = normalizeRangeValue(value, hasTimePicker, parentPanels[mode], this.activeInput); } else { this.activeDate = cloneDate(value); } } setValue(value) { this.value = value; this.valueChange$.next(this.value); } getActiveIndex(part = this.activeInput) { return { left: 0, right: 1 }[part]; } ngOnDestroy() { this.valueChange$.complete(); this.emitValue$.complete(); this.inputPartChange$.complete(); } } DatePickerService.decorators = [ { type: Injectable } ]; /** * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ class DateRangePopupComponent { constructor(datePickerService, cdr) { this.datePickerService = datePickerService; this.cdr = cdr; this.inline = false; this.panelModeChange = new EventEmitter(); this.calendarChange = new EventEmitter(); this.resultOk = new EventEmitter(); // Emitted when done with date selecting this.dir = 'ltr'; this.prefixCls = PREFIX_CLASS; this.endPanelMode = 'date'; this.timeOptions = null; this.hoverValue = []; // Range ONLY this.checkedPartArr = [false, false]; this.destroy$ = new Subject(); this.disabledStartTime = (value) => { return this.disabledTime && this.disabledTime(value, 'start'); }; this.disabledEndTime = (value) => { return this.disabledTime && this.disabledTime(value, 'end'); }; } get hasTimePicker() { return !!this.showTime; } get hasFooter() { return this.showToday || this.hasTimePicker || !!this.extraFooter || !!this.ranges; } ngOnInit() { this.datePickerService.valueChange$.pipe(takeUntil(this.destroy$)).subscribe(() => { this.updateActiveDate(); this.cdr.markForCheck(); }); } ngOnChanges(changes) { // Parse showTime options if (changes.showTime || changes.disabledTime) { if (this.showTime) { this.buildTimeOptions(); } } if (changes.panelMode) { this.endPanelMode = this.panelMode; } if (changes.defaultPickerValue) { this.updateActiveDate(); } } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } updateActiveDate() { const activeDate = this.datePickerService.hasValue() ? this.datePickerService.value : this.datePickerService.makeValue(this.defaultPickerValue); this.datePickerService.setActiveDate(activeDate, this.hasTimePicker, this.getPanelMode(this.endPanelMode)); } init() { this.checkedPartArr = [false, false]; this.updateActiveDate(); } /** * Prevent input losing focus when click panel * @param event */ onMousedown(event) { event.preventDefault(); } onClickOk() { const inputIndex = { left: 0, right: 1 }[this.datePickerService.activeInput]; const value = this.isRange ? this.datePickerService.value[inputIndex] : this.datePickerService.value; this.changeValueFromSelect(value); this.resultOk.emit(); } onClickToday(value) { this.changeValueFromSelect(value, !this.showTime); } onCellHover(value) { if (!this.isRange) { return; } const otherInputIndex = { left: 1, right: 0 }[this.datePickerService.activeInput]; const base = this.datePickerService.value[otherInputIndex]; if (base) { if (base.isBeforeDay(value)) { this.hoverValue = [base, value]; } else { this.hoverValue = [value, base]; } } } onPanelModeChange(mode, partType) { if (this.isRange) { const index = this.datePickerService.getActiveIndex(partType); if (index === 0) { this.panelMode = [mode, this.panelMode[1]]; } else { this.panelMode = [this.panelMode[0], mode]; } } else { this.panelMode = mode; } this.panelModeChange.emit(this.panelMode); } onActiveDateChange(value, partType) { if (this.isRange) { const activeDate = []; activeDate[this.datePickerService.getActiveIndex(partType)] = value; this.datePickerService.setActiveDate(activeDate, this.hasTimePicker, this.getPanelMode(this.endPanelMode, partType)); } else { this.datePickerService.setActiveDate(value); } } onSelectTime(value, partType) { if (this.isRange) { const newValue = cloneDate(this.datePickerService.value); const index = this.datePickerService.getActiveIndex(partType); newValue[index] = this.overrideHms(value, newValue[index]); this.datePickerService.setValue(newValue); } else { const newValue = this.overrideHms(value, this.datePickerService.value); this.datePickerService.setValue(newValue); // If not select a date currently, use today } this.datePickerService.inputPartChange$.next(); this.buildTimeOptions(); } changeValueFromSelect(value, emitValue = true) { if (this.isRange) { const selectedValue = cloneDate(this.datePickerService.value); const checkedPart = this.datePickerService.activeInput; let nextPart = checkedPart; selectedValue[this.datePickerService.getActiveIndex(checkedPart)] = value; this.checkedPartArr[this.datePickerService.getActiveIndex(checkedPart)] = true; this.hoverValue = selectedValue; if (emitValue) { if (this.inline) { // For UE, Should always be reversed, and clear vaue when next part is right nextPart = this.reversedPart(checkedPart); if (nextPart === 'right') { selectedValue[this.datePickerService.getActiveIndex(nextPart)] = null; this.checkedPartArr[this.datePickerService.getActiveIndex(nextPart)] = false; } this.datePickerService.setValue(selectedValue); this.calendarChange.emit(selectedValue); if (this.isBothAllowed(selectedValue) && this.checkedPartArr[0] && this.checkedPartArr[1]) { this.clearHoverValue(); this.datePickerService.emitValue$.next(); } } else { /** * if sort order is wrong, clear the other part's value */ if (wrongSortOrder(selectedValue)) { nextPart = this.reversedPart(checkedPart); selectedValue[this.datePickerService.getActiveIndex(nextPart)] = null; this.checkedPartArr[this.datePickerService.getActiveIndex(nextPart)] = false; } this.datePickerService.setValue(selectedValue); /** * range date usually selected paired, * so we emit the date value only both date is allowed and both part are checked */ if (this.isBothAllowed(selectedValue) && this.checkedPartArr[0] && this.checkedPartArr[1]) { this.calendarChange.emit(selectedValue); this.clearHoverValue(); this.datePickerService.emitValue$.next(); } else if (this.isAllowed(selectedValue)) { nextPart = this.reversedPart(checkedPart); this.calendarChange.emit([value.clone()]); } } } else { this.datePickerService.setValue(selectedValue); } this.datePickerService.inputPartChange$.next(nextPart); } else { this.datePickerService.setValue(value); this.datePickerService.inputPartChange$.next(); if (emitValue && this.isAllowed(value)) { this.datePickerService.emitValue$.next(); } } } reversedPart(part) { return part === 'left' ? 'right' : 'left'; } getPanelMode(panelMode, partType) { if (this.isRange) { return panelMode[this.datePickerService.getActiveIndex(partType)]; } else { return panelMode; } } // Get single value or part value of a range getValue(partType) { if (this.isRange) { return (this.datePickerService.value || [])[this.datePickerService.getActiveIndex(partType)]; } else { return this.datePickerService.value; } } getActiveDate(partType) { if (this.isRange) { return this.datePickerService.activeDate[this.datePickerService.getActiveIndex(partType)]; } else { return this.datePickerService.activeDate; } } isOneAllowed(selectedValue) { const index = this.datePickerService.getActiveIndex(); const disabledTimeArr = [this.disabledStartTime, this.disabledEndTime]; return isAllowedDate(selectedValue[index], this.disabledDate, disabledTimeArr[index]); } isBothAllowed(selectedValue) { return (isAllowedDate(selectedValue[0], this.disabledDate, this.disabledStartTime) && isAllowedDate(selectedValue[1], this.disabledDate, this.disabledEndTime)); } isAllowed(value, isBoth = false) { if (this.isRange) { return isBoth ? this.isBothAllowed(value) : this.isOneAllowed(value); } else { return isAllowedDate(value, this.disabledDate, this.disabledTime); } } getTimeOptions(partType) { if (this.showTime && this.timeOptions) { return this.timeOptions instanceof Array ? this.timeOptions[this.datePickerService.getActiveIndex(partType)] : this.timeOptions; } return null; } onClickPresetRange(val) { const value = typeof val === 'function' ? val() : val; if (value) { this.datePickerService.setValue([new CandyDate(value[0]), new CandyDate(value[1])]); this.datePickerService.emitValue$.next(); } } onPresetRangeMouseLeave() { this.clearHoverValue(); } onHoverPresetRange(val) { if (typeof val !== 'function') { this.hoverValue = [new CandyDate(val[0]), new CandyDate(val[1])]; } } getObjectKeys(obj) { return obj ? Object.keys(obj) : []; } show(partType) { const hide = this.showTime && this.isRange && this.datePickerService.activeInput !== partType; return !hide; } clearHoverValue() { this.hoverValue = []; } buildTimeOptions() { if (this.showTime) { const showTime = typeof this.showTime === 'object' ? this.showTime : {}; if (this.isRange) { const value = this.datePickerService.value; this.timeOptions = [this.overrideTimeOptions(showTime, value[0], 'start'), this.overrideTimeOptions(showTime, value[1], 'end')]; } else { this.timeOptions = this.overrideTimeOptions(showTime, this.datePickerService.value); } } else { this.timeOptions = null; } } overrideTimeOptions(origin, value, partial) { let disabledTimeFn; if (partial) { disabledTimeFn = partial === 'start' ? this.disabledStartTime : this.disabledEndTime; } else { disabledTimeFn = this.disabledTime; } return Object.assign(Object.assign({}, origin), getTimeConfig(value, disabledTimeFn)); } overrideHms(newValue, oldValue) { // tslint:disable-next-line:no-parameter-reassignment newValue = newValue || new CandyDate(); // tslint:disable-next-line:no-parameter-reassignment oldValue = oldValue || new CandyDate(); return oldValue.setHms(newValue.getHours(), newValue.getMinutes(), newValue.getSeconds()); } } DateRangePopupComponent.decorators = [ { type: Component, args: [{ encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, // tslint:disable-next-line:component-selector selector: 'date-range-popup', exportAs: 'dateRangePopup', template: ` <ng-container *ngIf="isRange; else singlePanel"> <div class="{{ prefixCls }}-range-wrapper {{ prefixCls }}-date-range-wrapper"> <div class="{{ prefixCls }}-range-arrow" [style.left.px]="datePickerService?.arrowLeft"></div> <div class="{{ prefixCls }}-panel-container"> <div class="{{ prefixCls }}-panels"> <ng-container *ngTemplateOutlet="tplInnerPopup; context: { partType: 'left' }"></ng-container> <ng-container *ngTemplateOutlet="tplInnerPopup; context: { partType: 'right' }"></ng-container> </div> <ng-container *ngTemplateOutlet="tplFooter"></ng-container> </div> </div> </ng-container> <ng-template #singlePanel> <div class="{{ prefixCls }}-panel-container {{ showWeek ? prefixCls + '-week-number' : '' }} {{ hasTimePicker ? prefixCls + '-time' : '' }} {{ isRange ? prefixCls + '-range' : '' }}" > <div class="{{ prefixCls }}-panel" [class.ant-picker-panel-rtl]="dir === 'rtl'" tabindex="-1"> <!-- Single ONLY --> <ng-container *ngTemplateOutlet="tplInnerPopup"></ng-container> <ng-container *ngTemplateOutlet="tplFooter"></ng-container> </div> </div> </ng-template> <ng-template #tplInnerPopup let-partType="partType"> <div class="{{ prefixCls }}-panel" [class.ant-picker-panel-rtl]="dir === 'rtl'" [style.display]="show(partType) ? 'block' : 'none'"> <!-- TODO(@wenqi73) [selectedValue] [hoverValue] types--> <inner-popup [showWeek]="showWeek" [endPanelMode]="getPanelMode(endPanelMode, partType)" [partType]="partType" [locale]="locale!" [showTimePicker]="hasTimePicker" [timeOptions]="getTimeOptions(partType)" [panelMode]="getPanelMode(panelMode, partType)" (panelModeChange)="onPanelModeChange($event, partType)" [activeDate]="getActiveDate(partType)" [value]="getValue(partType)" [disabledDate]="disabledDate" [dateRender]="dateRender" [selectedValue]="$any(datePickerService?.value)" [hoverValue]="$any(hoverValue)" (cellHover)="onCellHover($event)" (selectDate)="changeValueFromSelect($event, !showTime)" (selectTime)="onSelectTime($event, partType)" (headerChange)="onActiveDateChange($event, partType)" ></inner-popup> </div> </ng-template> <ng-template #tplFooter> <calendar-footer *ngIf="hasFooter" [locale]="locale!" [isRange]="isRange" [showToday]="showToday" [showNow]="showNow" [hasTimePicker]="hasTimePicker" [okDisabled]="!isAllowed($any(datePickerService?.value))" [extraFooter]="extraFooter" [rangeQuickSelector]="ranges ? tplRangeQuickSelector : null" (clickOk)="onClickOk()" (clickToday)="onClickToday($event)" ></calendar-footer> </ng-template> <!-- Range ONLY: Range Quick Selector --> <ng-template #tplRangeQuickSelector> <li *ngFor="let name of getObjectKeys(ranges)" class="{{ prefixCls }}-preset" (click)="onClickPresetRange(ranges![name])" (mouseenter)="onHoverPresetRange(ranges![name])" (mouseleave)="onPresetRangeMouseLeave()" > <span class="ant-tag ant-tag-blue">{{ name }}</span> </li> </ng-template> `, host: { '(mousedown)': 'onMousedown($event)' } },] } ]; DateRangePopupComponent.ctorParameters = () => [ { type: DatePickerService }, { type: ChangeDetectorRef } ]; DateRangePopupComponent.propDecorators = { isRange: [{ type: Input }], inline: [{ type: Input }], showWeek: [{ type: Input }], locale: [{ type: Input }], disabledDate: [{ type: Input }], disabledTime: [{ type: Input }], showToday: [{ type: Input }], showNow: [{ type: Input }], showTime: [{ type: Input }], extraFooter: [{ type: Input }], ranges: [{ type: Input }], dateRender: [{ type: Input }], panelMode: [{ type: Input }], defaultPickerValue: [{ type: Input }], panelModeChange: [{ type: Output }], calendarChange: [{ type: Output }], resultOk: [{ type: Output }], dir: [{ type: Input }] }; /** * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ class NzPickerComponent { constructor(elementRef, dateHelper, cdr, platform, nzResizeObserver, datePickerService, doc) { this.elementRef = elementRef; this.dateHelper = dateHelper; this.cdr = cdr; this.platform = platform; this.nzResizeObserver = nzResizeObserver; this.datePickerService = datePickerService; this.noAnimation = false; this.isRange = false; this.open = undefined; this.disabled = false; this.inputReadOnly = false; this.inline = false; this.popupStyle = null; this.dir = 'ltr'; this.nzId = null; this.focusChange = new EventEmitter(); this.valueChange = new EventEmitter(); this.openChange = new EventEmitter(); // Emitted when overlay's open state change this.inputSize = 12; this.destroy$ = new Subject(); this.prefixCls = PREFIX_CLASS; this.activeBarStyle = {}; this.overlayOpen = false; // Available when "open"=undefined this.overlayPositions = [ { offsetX: -12, offsetY: 8, originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' }, { offsetX: -12, offsetY: -8, originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom' }, { offsetX: 12, offsetY: 8, originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top' }, { offsetX: 12, offsetY: -8, originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom' } ]; this.currentPositionX = 'start'; this.currentPositionY = 'bottom'; this.document = doc; this.origin = new CdkOverlayOrigin(this.elementRef); } get realOpenState() { // The value that really decide the open state of overlay return this.isOpenHandledByUser() ? !!this.open : this.overlayOpen; } ngOnInit() { this.inputValue = this.isRange ? ['', ''] : ''; this.datePickerService.valueChange$.pipe(takeUntil(this.destroy$)).subscribe(() => { this.updateInputValue(); }); } ngAfterViewInit() { if (this.autoFocus) { this.focus(); } if (this.isRange && this.platform.isBrowser) { this.nzResizeObserver .observe(this.elementRef) .pipe(takeUntil(this.destroy$)) .subscribe(() => { this.updateInputWidthAndArrowLeft(); }); } this.datePickerService.inputPartChange$.pipe(takeUntil(this.destroy$)).subscribe(partType => { var _a; if (partType) { this.datePickerService.activeInput = partType; } this.focus(); this.updateInputWidthAndArrowLeft(); (_a = this.panel) === null || _a === void 0 ? void 0 : _a.updateActiveDate(); }); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } ngOnChanges(changes) { var _a, _b; if (((_a = changes.format) === null || _a === void 0 ? void 0 : _a.currentValue) !== ((_b = changes.format) === null || _b === void 0 ? void 0 : _b.previousValue)) { this.inputSize = Math.max(10, this.format.length) + 2; this.updateInputValue(); } } updateInputWidthAndArrowLeft() { var _a, _b, _c; this.inputWidth = ((_b = (_a = this.rangePickerInputs) === null || _a === void 0 ? void 0 : _a.first) === null || _b === void 0 ? void 0 : _b.nativeElement.offsetWidth) || 0; const baseStyle = { position: 'absolute', width: `${this.inputWidth}px` }; this.datePickerService.arrowLeft = this.datePickerService.activeInput === 'left' ? 0 : this.inputWidth + ((_c = this.separatorElement) === null || _c === void 0 ? void 0 : _c.nativeElement.offsetWidth) || 0; if (this.dir === 'rtl') { this.activeBarStyle = Object.assign(Object.assign({}, baseStyle), { right: `${this.datePickerService.arrowLeft}px` }); } else { this.activeBarStyle = Object.assign(Object.assign({}, baseStyle), { left: `${this.datePickerService.arrowLeft}px` }); } this.panel.cdr.markForCheck(); this.cdr.markForCheck(); } getInput(partType) { var _a, _b; if (this.inline) { return undefined; } return this.isRange ? partType === 'left' ? (_a = this.rangePickerInputs) === null || _a === void 0 ? void 0 : _a.first.nativeElement : (_b = this.rangePickerInputs) === null || _b === void 0 ? void 0 : _b.last.nativeElement : this.pickerInput.nativeElement; } focus() { const activeInputElement = this.getInput(this.datePickerService.activeInput); if (this.document.activeElement !== activeInputElement) { activeInputElement === null || activeInputElement === void 0 ? void 0 : activeInputElement.focus(); } } onFocus(event, partType) { event.preventDefault(); this.focusChange.emit(true); if (partType) { this.datePickerService.inputPartChange$.next(partType); } } onBlur(event) { event.preventDefault(); this.focusChange.emit(false); } // Show overlay content showOverlay() { if (this.inline) { return; } if (!this.realOpenState && !this.disabled) { this.updateInputWidthAndArrowLeft(); this.overlayOpen = true; this.focus(); this.panel.init(); this.openChange.emit(true); this.cdr.markForCheck(); } } hideOverlay() { if (this.inline) { return; } if (this.realOpenState) { this.overlayOpen = false; this.openChange.emit(false); } } showClear() { return !this.disabled && !this.isEmptyValue(this.datePickerService.value) && !!this.allowClear; } onClickInputBox(event) { event.stopPropagation(); this.focus(); if (!this.isOpenHandledByUser()) { this.showOverlay(); } } onClickOutside(event) { if (this.elementRef.nativeElement.contains(event.target)) { return; } if (this.panel.isAllowed(this.datePickerService.value, true)) { if (Array.isArray(this.datePickerService.value) && wrongSortOrder(this.datePickerService.value)) { const index = this.datePickerService.getActiveIndex(this.datePickerService.activeInput); const value = this.datePickerService.value[index]; this.panel.changeValueFromSelect(value, true); return; } this.updateInputValue(); this.datePickerService.emitValue$.next(); } else { this.datePickerService.setValue(this.datePickerService.initialValue); this.hideOverlay(); } } onOverlayDetach() { this.hideOverlay(); } onOverlayKeydown(event) { if (event.keyCode === ESCAPE) { this.datePickerService.setValue(this.datePickerService.initialValue); } } // NOTE: A issue here, the first time position change, the animation will not be triggered. // Because the overlay's "positionChange" event is emitted after the content's full shown up. // All other components like "nz-dropdown" which depends on overlay also has the same issue. // See: https://github.com/NG-ZORRO/ng-zorro-antd/issues/1429 onPositionChange(position) { this.currentPositionX = position.connectionPair.originX; this.currentPositionY = position.connectionPair.originY; this.cdr.detectChanges(); // Take side-effects to position styles } onClickClear(event) { event.preventDefault(); event.stopPropagation(); this.datePickerService.setValue(this.isRange ? [] : null); this.datePickerService.emitValue$.next(); } updateInputValue() { const newValue = this.datePickerService.value; if (this.isRange) { this.inputValue = newValue ? newValue.map(v => this.formatValue(v)) : ['', '']; } else { this.inputValue = this.formatValue(newValue); } this.cdr.markForCheck(); } formatValue(value) { return this.dateHelper.format(value && value.nativeDate, this.format); } onInputChange(value, isEnter = false) { /** * in IE11 focus/blur will trigger ngModelChange if has placeholder * so we forbidden IE11 to open panel through input change */ if (!this.platform.TRIDENT && this.document.activeElement === this.getInput(this.datePickerService.activeInput) && !this.realOpenState) { this.showOverlay(); return; } const date = this.checkValidDate(value); if (date) { this.panel.changeValueFromSelect(date, isEnter); } } onKeyupEnter(event) { this.onInputChange(event.target.value, true); } checkValidDate(value) { const date = new CandyDate(this.dateHelper.parseDate(value, this.format)); if (!date.isValid() || value !== this.dateHelper.format(date.nativeDate, this.format)) { return null; } return date; } getPlaceholder(partType) { return this.isRange ? this.placeholder[this.datePickerService.getActiveIndex(partType)] : this.placeholder; } isEmptyValue(value) { if (value === null) { return true; } else if (this.isRange) { return !value || !Array.isArray(value) || value.every(val => !val); } else { return !value; } } // Whether open state is permanently controlled by user himself isOpenHandledByUser() { return this.open !== undefined; } } NzPickerComponent.decorators = [ { type: Component, args: [{ encapsulation: ViewEncapsulation.None, selector: '[nz-picker]', exportAs: 'nzPicker', template: ` <ng-container *ngIf="!inline; else inlineMode"> <!-- Content of single picker --> <div *ngIf="!isRange" class="{{ prefixCls }}-input"> <input #pickerInput [attr.id]="nzId" [class.ant-input-disabled]="disabled" [disabled]="disabled" [readOnly]="inputReadOnly" [(ngModel)]="inputValue" placeholder="{{ getPlaceholder() }}" [size]="inputSize" (focus)="onFocus($event)" (blur)="onBlur($event)" (ngModelChange)="onInputChange($event)" (keyup.enter)="onKeyupEnter($event)" /> <ng-container *ngTemplateOutlet="tplRightRest"></ng-container> </div> <!-- Content of range picker --> <ng-container *ngIf="isRange"> <div class="{{ prefixCls }}-input"> <ng-container *ngTemplateOutlet="tplRangeInput; context: { partType: 'left' }"></ng-container> </div> <div #separatorElement class="{{ prefixCls }}-range-separator"> <span class="{{ prefixCls }}-separator"> <ng-container *ngIf="separator; else defaultSeparator">{{ separator }}</ng-container> </span> <ng-template #defaultSeparator> <i nz-icon nzType="swap-right" nzTheme="outline"></i> </ng-template> </div> <div class="{{ prefixCls }}-input"> <ng-container *ngTemplateOutlet="tplRangeInput; context: { partType: 'right' }"></ng-container> </div> <ng-container *ngTemplateOutlet="tplRightRest"></ng-container> </ng-container> </ng-container> <!-- Input for Range ONLY --> <ng-template #tplRangeInput let-partType="partType"> <input #rangePickerInput [disabled]="disabled" [readOnly]="inputReadOnly" [size]="inputSize" (click)="onClickInputBox($event)" (blur)="onBlur($event)" (focus)="onFocus($event, partType)" (keyup.enter)="onKeyupEnter($event)" [(ngModel)]="inputValue[datePickerService.getActiveIndex(partType)]" (ngModelChange)="onInputChange($event)" placeholder="{{ getPlaceholder(partType) }}" /> </ng-template> <!-- Right operator icons --> <ng-template #tplRightRest> <div class="{{ prefixCls }}-active-bar" [ngStyle]="activeBarStyle"></div> <span *ngIf="showClear()" class="{{ prefixCls }}-clear" (click)="onClickClear($event)"> <i nz-icon nzType="close-circle" nzTheme="fill"></i> </span> <span class="{{ prefixCls }}-suffix"> <ng-container *nzStringTemplateOutlet="suffixIcon; let suffixIcon"> <i nz-icon [nzType]="suffixIcon"></i> </ng-container> </span> </ng-template> <ng-template #inlineMode> <div class="ant-picker-wrapper" [nzNoAnimation]="noAnimation" [@slideMotion]="'enter'" style="position: relative;"> <div class="{{ prefixCls }}-dropdown {{ dropdownClassName }}" [class.ant-picker-dropdown-rtl]="dir === 'rtl'" [class.ant-picker-dropdown-placement-bottomLeft]="currentPositionY === 'bottom' && currentPositionX === 'start'" [class.ant-picker-dropdown-placement-topLeft]="currentPositionY === 'top' && currentPositionX === 'start'" [class.ant-picker-dropdown-placement-bottomRight]="currentPositionY === 'bottom' && currentPositionX === 'end'" [class.ant-picker-dropdown-placement-topRight]="currentPositionY === 'top' && currentPositionX === 'end'" [class.ant-picker-dropdown-range]="isRange" [class.ant-picker-active-left]="datePickerService.activeInput === 'left'" [class.ant-picker-active-right]="datePickerService.activeInput === 'right'" [ngStyle]="popupStyle" > <!-- Compatible for overlay that not support offset dynamically and immediately --> <ng-content></ng-content> </div> </div> </ng-template> <!-- Overlay --> <ng-template cdkConnectedOverlay nzConnectedOverlay [cdkConnectedOverlayOrigin]="origin" [cdkConnectedOverlayOpen]="realOpenState" [cdkConnectedOverlayPositions]="overlayPositions" [cdkConnectedOverlayTransformOriginOn]="'.ant-picker-wrapper'" (positionChange)="onPositionChange($event)" (detach)="onOverlayDetach()" (overlayKeydown)="onOverlayKeydown($event)" (overlayOutsideClick)="onClickOutside($event)" > <ng-container *ngTemplateOutlet="inlineMode"></ng-container> </ng-template> `, animations: [slideMotion], changeDetection: ChangeDetectionStrategy.OnPush },] } ]; NzPickerComponent.ctorParameters = () => [ { type: ElementRef }, { type: DateHelperService }, { type: ChangeDetectorRef }, { type: Platform }, { type: NzResizeObserver }, { type: DatePickerService }, { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT,] }] } ]; NzPickerComponent.propDecorators = { noAnimation: [{ type: Input }], isRange: [{ type: Input }], open: [{ type: Input }], disabled: [{ type: Input }], inputReadOnly: [{ type: Input }], inline: [{ type: Input }], placeholder: [{ type: Input }], allowClear: [{ type: Input }], autoFocus: [{ type: Input }], format: [{ type: Input }], separator: [{ type: Input }], popupStyle: [{ type: Input }], dropdownClassName: [{ type: Input }], suffixIcon: [{ type: Input }], dir: [{ type: Input }], nzId: [{ type: Input }], focusChange: [{ type: Output }], valueChange: [{ type: Output }], openChange: [{ type: Output }], cdkConnectedOverlay: [{ type: ViewChild, args: [CdkConnectedOverlay, { static: false },] }], separatorElement: [{ type: ViewChild, args: ['separatorElement', { static: false },] }], pickerInput: [{ type: ViewChild, args: ['pickerInput', { static: false },] }], rangePickerInputs: [{ type: ViewChildren, args: ['rangePickerInput',] }], panel: [{ type: ContentChild, args: [DateRangePopupComponent,] }] }; /** * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://github.com/NG-ZORRO/ng-zorro-antd/blob/master/LICENSE */ const POPUP_STYLE_PATCH = { position: 'relative' }; // Aim to override antd's style to support overlay's position strategy (position:absolute will cause it not working beacuse the overlay can't get the height/width of it's content) const NZ_CONFIG_MODULE_NAME = 'datePicker'; /** * The base picker for all common APIs */ class NzDatePickerComponent { constructor(nzConfigService, datePickerService, i18n, cdr, renderer, elementRef, dateHelper, directionality, noAnimation) { this.nzConfigService = nzConfigService; this.datePickerService = datePickerService; this.i18n = i18n; this.cdr = cdr; this.renderer = renderer; this.elementRef = elementRef; this.dateHelper = dateHelper; this.directionality = directionality; this.noAnimation = noAnimation; this._nzModuleName = NZ_CONFIG_MODULE_NAME; this.isRange = false; // Indicate whether the value is a range value this.focused = false; this.dir = 'ltr'; this.panelMode = 'date'; this.destroyed$ = new Subject(); this.isCustomPlaceHolder = false; this.isCustomFormat = false; this.showTime = false; // --- Common API this.nzAllowClear = true; this.nzAutoFocus = false; this.nzDisabled = false; this.nzBorderless = false; this.nzInputReadOnly = false; this.nzInline = false; this.nzPlaceHolder = ''; this.nzPopupStyle = POPUP_STYLE_PATCH; this.nzSize = 'default'; this.nzShowToday = true; this.nzMode = 'date'; this.nzShowNow = true; this.nzDefaultPickerValue = null; this.nzSeparator = undefined; this.nzSuffixIcon = 'calendar'; this.nzId = null; // TODO(@wenqi73) The PanelMode need named for each pickers and export this.nzOnPanelChange = new EventEmitter(); this.nzOnCalendarChange = new EventEmitter(); this.nzOnOk = new EventEmitter(); this.nzOnOpenChange = new EventEmitter(); // ------------------------------------------------------------------------ // | Control value accessor implements // ------------------------------------------------------------------------ // NOTE: onChangeFn/onTouchedFn will not be assigned if user not use as ngModel this.onChangeFn = () => void 0; this.onTouchedFn = () => void 0; // TODO: move to host after View Engine deprecation this.elementRef.nativeElement.classList.add('ant-picker'); } get nzShowTime() { return this.showTime; } set nzShowTime(value) { this.showTime = typeof value === 'object' ? value : toBoolean(value); } ngOnInit() { var _a; // Subscribe the every locale change if the nzLocale is not handled by user if (!this.nzLocale) { this.i18n.localeChange.pipe(takeUntil(this.destroyed$)).subscribe(() => this.setLocale()); } // Default value this.datePickerService.isRange = this.isRange; this.datePickerService.initValue(); this.datePickerService.emitValue$.pipe(takeUntil(this.destroyed$)).subscribe(_ => { var _a, _b, _c, _d; const value = this.datePickerService.value; this.datePickerService.initialValue = cloneDate(value); if (this.isRange) { const vAsRange = value; if (vAsRange.length) { this.onChangeFn([(_b = (_a = vAsRange[0]) === null || _a === void 0 ? void 0 : _a.nativeDate) !== null && _b !== void 0 ? _b : null, (_d = (_c = vAsRange[1]) === null || _c === void 0 ? void 0 : _c.nativeDate) !== null && _d !== void 0 ? _d : null]); } else { this.onChangeFn([]); } } else { if (value) { this.onChangeFn(value.nativeDate); } else { this.onChangeFn(null); } } this.onTouchedFn(); // When value emitted, overlay will be closed this.close(); }); this.setModeAndFormat(); (_a = this.directionality.change) === null || _a === void 0 ? void 0 : _a.pipe(takeUntil(this.destroyed$)).subscribe((direction) => { this.dir = direction; this.cdr.detectChanges(); }); this.dir = this.directionality.value; } ngOnChanges(changes) { var _a, _b; if (changes.nzPopupStyle) { // Always assign the popup style patch this.nzPopupStyle = this.nzPopupStyle ? Object.assign(Object.assign({}, this.nzPopupStyle), POPUP_STYLE_PATCH) : POPUP_STYLE_PATCH; } // Mark as customized placeholder by user once nzPlaceHolder assigned at the first time if ((_a = changes.nzPlaceHolder) === null || _a === void 0 ? void 0 : _a.currentValue) { this.isCustomPlaceHolder = true; } if ((_b = changes.nzFormat) === null || _b === void 0 ? void 0 : _b.currentValue) { this.isCustomFormat = true; } if (changes.nzLocale) { // The nzLocale is currently handled by user this.setDefaultPlaceHolder(); } if (changes.nzRenderExtraFooter) { this.extraFooter = valueFunctionProp(this.nzRenderExtraFooter); } if (changes.nzMode) { this.setDe