ng-zorro-antd
Version:
An enterprise-class UI components based on Ant Design and Angular
1,281 lines (1,266 loc) • 126 kB
JavaScript
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