@progress/kendo-angular-dateinputs
Version:
Kendo UI for Angular Date Inputs Package - Everything you need to add date selection functionality to apps (DatePicker, TimePicker, DateInput, DateRangePicker, DateTimePicker, Calendar, and MultiViewCalendar).
1,296 lines (1,290 loc) • 74.6 kB
JavaScript
/**-----------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the project root for more information
*-------------------------------------------------------------------------------------------*/
import { Component, ChangeDetectionStrategy, ChangeDetectorRef, ElementRef, TemplateRef, EventEmitter, HostBinding, Renderer2, Input, Output, ContentChild, ViewChild, ViewContainerRef, NgZone, forwardRef, isDevMode, Injector, } from '@angular/core';
import { NG_VALUE_ACCESSOR, NG_VALIDATORS, NgControl } from '@angular/forms';
import { L10N_PREFIX, LocalizationService } from '@progress/kendo-angular-l10n';
import { PopupService } from '@progress/kendo-angular-popup';
import { cloneDate } from '@progress/kendo-date-math';
import { hasObservers, isControlRequired, KendoInput, Keys, MultiTabStop, ResizeSensorComponent, EventsOutsideAngularDirective } from '@progress/kendo-angular-common';
import { validatePackage } from '@progress/kendo-licensing';
import { packageMetadata } from '../package-metadata';
import { MIN_DATE, MAX_DATE } from '../defaults';
import { minValidator } from '../validators/min.validator';
import { maxValidator } from '../validators/max.validator';
import { PreventableEvent } from '../preventable-event';
import { CalendarViewEnum } from '../calendar/models/view.enum';
import { CellTemplateDirective } from '../calendar/templates/cell-template.directive';
import { MonthCellTemplateDirective } from '../calendar/templates/month-cell-template.directive';
import { YearCellTemplateDirective } from '../calendar/templates/year-cell-template.directive';
import { DecadeCellTemplateDirective } from '../calendar/templates/decade-cell-template.directive';
import { CenturyCellTemplateDirective } from '../calendar/templates/century-cell-template.directive';
import { WeekNumberCellTemplateDirective } from '../calendar/templates/weeknumber-cell-template.directive';
import { HeaderTitleTemplateDirective } from '../calendar/templates/header-title-template.directive';
import { NavigationItemTemplateDirective } from '../calendar/templates/navigation-item-template.directive';
import { PickerService } from '../common/picker.service';
import { DisabledDatesService } from '../calendar/services/disabled-dates.service';
import { noop, isValidRange, setTime, isWindowAvailable, isTabExitingCalendar, getSizeClass, getRoundedClass, getFillModeClass, DEFAULT_FILL_MODE, DEFAULT_ROUNDED, DEFAULT_SIZE } from '../util';
import { requiresZoneOnBlur, currentFocusTarget, attributeNames } from '../common/utils';
import { fromEvent } from 'rxjs';
import { incompleteDateValidator } from '../validators/incomplete-date.validator';
import { disabledDatesValidator } from '../validators/disabled-date.validator';
import { DateInputComponent } from '../dateinput/dateinput.component';
import { calendarIcon, checkIcon } from '@progress/kendo-svg-icons';
import { ActionSheetComponent, ActionSheetTemplateDirective } from '@progress/kendo-angular-navigation';
import { HeaderTemplateDirective } from '../calendar/templates/header-template.directive';
import { FooterTemplateDirective } from '../calendar/templates/footer-template.directive';
import { CalendarCustomMessagesComponent } from '../calendar/localization/calendar-custom-messages.component';
import { CalendarComponent } from '../calendar/calendar.component';
import { ButtonComponent } from '@progress/kendo-angular-buttons';
import { NgTemplateOutlet, NgIf } from '@angular/common';
import { IconWrapperComponent } from '@progress/kendo-angular-icons';
import { DateInputCustomMessagesComponent } from '../dateinput/localization/dateinput-custom-messages.component';
import { DatePickerLocalizedMessagesDirective } from './localization/datepicker-localized-messages.directive';
import { touchEnabled } from '@progress/kendo-common';
import { AdaptiveService } from '@progress/kendo-angular-utils';
import * as i0 from "@angular/core";
import * as i1 from "@progress/kendo-angular-l10n";
import * as i2 from "@progress/kendo-angular-popup";
import * as i3 from "../common/picker.service";
import * as i4 from "../calendar/services/disabled-dates.service";
import * as i5 from "@progress/kendo-angular-utils";
const MIN_DOC_LINK = 'https://www.telerik.com/kendo-angular-ui/components/dateinputs/api/DatePickerComponent/#toc-min';
const MAX_DOC_LINK = 'https://www.telerik.com/kendo-angular-ui/components/dateinputs/api/DatePickerComponent/#toc-max';
const VALUE_DOC_LINK = 'https://www.telerik.com/kendo-angular-ui/components/dateinputs/datepicker/#toc-using-with-json';
const DEFAULT_FORMAT = 'd';
const TWO_DIGIT_YEAR_MAX = 68;
/**
* Represents the [Kendo UI DatePicker component for Angular](slug:overview_datepicker).
*/
export class DatePickerComponent extends MultiTabStop {
zone;
localization;
cdr;
popupService;
wrapper;
renderer;
injector;
pickerService;
disabledDatesService;
adaptiveService;
/**
* @hidden
*/
calendarIcon = calendarIcon;
container;
popupTemplate;
toggleButton;
actionSheet;
/**
* @hidden
*/
focusableId;
/**
* @hidden
*/
cellTemplate;
/**
* @hidden
*/
set cellTemplateRef(template) {
this.cellTemplate = template;
}
/**
* If set to `true`, renders a clear button after the input text or DatePicker value has been changed.
* Clicking this button resets the value of the component to `null` and triggers the `valueChange` event.
* @default false
*/
clearButton = false;
/**
* Sets the HTML attributes of the inner focusable input element. Attributes which are essential for certain component functionalities cannot be changed.
*/
inputAttributes;
/**
* @hidden
*/
monthCellTemplate;
/**
* @hidden
*/
set monthCellTemplateRef(template) {
this.monthCellTemplate = template;
}
/**
* @hidden
*/
yearCellTemplate;
/**
* @hidden
*/
set yearCellTemplateRef(template) {
this.yearCellTemplate = template;
}
/**
* @hidden
*/
decadeCellTemplate;
/**
* @hidden
*/
set decadeCellTemplateRef(template) {
this.decadeCellTemplate = template;
}
/**
* @hidden
*/
centuryCellTemplate;
/**
* @hidden
*/
set centuryCellTemplateRef(template) {
this.centuryCellTemplate = template;
}
/**
* @hidden
*/
weekNumberTemplate;
/**
* @hidden
*/
set weekNumberTemplateRef(template) {
this.weekNumberTemplate = template;
}
/**
* @hidden
*/
headerTitleTemplate;
/**
* @hidden
*/
set headerTitleTemplateRef(template) {
this.headerTitleTemplate = template;
}
/**
* @hidden
*/
headerTemplate;
/**
* @hidden
*/
set headerTemplateRef(template) {
this.headerTemplate = template;
}
/**
* @hidden
*/
footerTemplate;
/**
* @hidden
*/
set footerTemplateRef(template) {
this.footerTemplate = template;
}
/**
* Toggles the visibility of the Calendar footer.
* @default false
*/
footer = false;
/**
* @hidden
*/
navigationItemTemplate;
/**
* @hidden
*/
set navigationItemTemplateRef(template) {
this.navigationItemTemplate = template;
}
/**
* Sets the format of the displayed Calendar week days' names.
* @default 'short'
*/
weekDaysFormat = "short";
/**
* Displays the days that fall out of the current month in the Calendar ([see example]({% slug viewoptions_calendar %}#toc-displaying-other-month-days)).
* The default values per Calendar type are:
* - `infinite` - false
* - `classic` - true
*/
showOtherMonthDays;
/**
* Defines the active view that the Calendar initially renders
* ([see example]({% slug viewoptions_calendar %}#toc-active-view)).
* By default, the active view is `month`.
*
* > You have to set `activeView` within the `topView`-`bottomView` range.
*/
activeView = CalendarViewEnum[CalendarViewEnum.month];
/**
* Defines the bottommost Calendar view to which the user can navigate
* ([see example](slug:datepicker_calendar_options#toc-view-selection-depth)).
*/
bottomView = CalendarViewEnum[CalendarViewEnum.month];
/**
* Defines the topmost Calendar view to which the user can navigate
* ([see example](slug:datepicker_calendar_options#toc-view-selection-depth)).
*/
topView = CalendarViewEnum[CalendarViewEnum.century];
/**
* Specifies the Calendar type.
*
* The possible values are:
* - `infinite` (default)
* - `classic`
*
*/
calendarType = 'infinite';
/**
* Determines whether to enable animation when navigating to previous/next Calendar view.
* Applies to the [`classic`]({% slug api_dateinputs_datepickercomponent %}#toc-calendartype) Calendar only.
*
* > This feature uses the [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API). In order to run the animation in browsers that do not support it, you need the `web-animations-js` polyfill.
*
* @default false
*/
animateCalendarNavigation = false;
/**
* Sets or gets the `disabled` property of the DatePicker and determines whether the component is active
* ([see example]({% slug disabled_datepicker %})).
* To learn how to disable the component in reactive forms, refer to the article on [Forms Support](slug:formssupport_datepicker#toc-managing-the-datepicker-disabled-state-in-reactive-forms).
*/
disabled = false;
/**
* Sets the read-only state of the DatePicker
* ([see example]({% slug readonly_datepicker %}#toc-read-only-datepicker)).
*
* @default false
*/
readonly = false;
/**
* Sets the read-only state of the DatePicker input field
* ([see example]({% slug readonly_datepicker %}#toc-read-only-input)).
*
* > Note that if you set the [`readonly`]({% slug api_dateinputs_datepickercomponent %}#toc-readonly) property value to `true`,
* the input will be rendered in a read-only state regardless of the `readOnlyInput` value.
*/
readOnlyInput = false;
/**
* Configures the popup options of the DatePicker.
*
* The available options are:
* - `animate: Boolean`—Controls the popup animation. By default, the open and close animations are enabled.
* - `appendTo: 'root' | 'component' | ViewContainerRef`—Controls the popup container. By default, the popup will be appended to the root component.
* - `popupClass: String`—Specifies a list of CSS classes that are used to style the popup.
*/
set popupSettings(settings) {
this._popupSettings = Object.assign({}, { animate: true }, settings);
}
get popupSettings() {
return this._popupSettings;
}
/**
* Sets or gets the `navigation` property of the Calendar
* and determines whether the navigation side-bar is displayed.
* ([see example]({% slug sidebar_datepicker %})).
*/
set navigation(state) {
this._navigation = state;
}
get navigation() {
if (this.isAdaptive) {
return;
}
return this._navigation;
}
_navigation = true;
/**
* Specifies the smallest valid date
* ([see example]({% slug dateranges_datepicker %})).
* By default, the `min` value is `1900-1-1`.
*/
min = cloneDate(MIN_DATE);
/**
* Specifies the biggest valid date
* ([see example]({% slug dateranges_datepicker %})).
* By default, the `max` value is `2099-12-31`.
*/
max = cloneDate(MAX_DATE);
/**
* Determines whether the built-in validation for incomplete dates is to be enforced when a form is being validated.
*/
incompleteDateValidation = false;
/**
* Determines whether to autocorrect invalid segments automatically.
*
* @default true
*/
autoCorrectParts = true;
/**
* Determines whether to automatically move to the next segment after the user completes the current one.
*
* @default true
*/
autoSwitchParts = true;
/**
* A string array representing custom keys, which will move the focus to the next date format segment.
*/
autoSwitchKeys = [];
/**
* Indicates whether the mouse scroll can be used to increase/decrease the time segments values.
*
* @default true
*/
enableMouseWheel = true;
/**
* Determines if the users should see a blinking caret inside the Date Input when possible.
*
* @default false
*/
allowCaretMode = false;
/**
* When enabled, the DatePicker will autofill the rest of the date to the current date when the component loses focus.
*
* @default false
*/
autoFill = false;
/**
* Specifies the focused date of the Calendar component
* ([see example](slug:datepicker_calendar_options#toc-focused-dates)).
*/
focusedDate = null;
/**
* Specifies the value of the DatePicker component.
*
* > The `value` has to be a valid
* [JavaScript `Date`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Date) instance or `null`.
*/
set value(value) {
this.verifyValue(value);
this._value = cloneDate(value);
}
get value() {
return this._value;
}
/**
* Specifies the date format that is used to display the input value
* ([see example]({% slug formats_datepicker %})).
*
* Format value options:
* - `string` - Provide a `string` if a single format is going to be used regardless whether the input is focused or blurred.
* - [`FormatSettings`]({% slug api_dateinputs_formatsettings %}) - To display different formats when the component is focused or blurred, provide a settings object with specified `inputFormat` and `displayFormat` values.
*/
format = DEFAULT_FORMAT;
/**
* The maximum year to assume to be from the current century when typing two-digit year value
* ([see example]({% slug formats_datepicker %}#toc-two-digit-year-format)).
*
* The default value is 68, indicating that typing any value less than 69
* will be assumed to be 20xx, while 69 and larger will be assumed to be 19xx.
*/
twoDigitYearMax = TWO_DIGIT_YEAR_MAX;
/**
* Defines the descriptions of the format sections in the input field.
* ([more information and examples]({% slug placeholders_datepicker %})).
*
* @example
* ```ts
* _@Component({
* selector: 'my-app',
* template: `
* <div class="row example-wrapper" [style.min-height.px]="450">
* <div class="col-xs-12 col-md-6 example-col">
* <p>Full-length format description:</p>
* <kendo-datepicker formatPlaceholder="wide"></kendo-datepicker>
* </div>
*
* <div class="col-xs-12 col-md-6 example-col">
* <p>Narrow-length format description:</p>
* <kendo-datepicker formatPlaceholder="narrow"></kendo-datepicker>
* </div>
*
* <div class="col-xs-12 col-md-6 example-col">
* <p>Short-length format description:</p>
* <kendo-datepicker formatPlaceholder="short"></kendo-datepicker>
* </div>
*
* <div class="col-xs-12 col-md-6 example-col">
* <p>Display defined format:</p>
* <kendo-datepicker format="MM/dd/yyyy" formatPlaceholder="formatPattern"></kendo-datepicker>
* </div>
*
* <div class="col-xs-12 col-md-6 example-col">
* <p>Custom defined format descriptions</p>
* <kendo-datepicker format="MM/dd/yyyy"
* [formatPlaceholder]="{ year: 'y', month: 'M', day: 'd' }"
* ></kendo-datepicker>
* </div>
* </div>
* `
* })
* export class AppComponent { }
* ```
*/
formatPlaceholder;
/**
* Specifies the hint the DatePicker displays when its value is `null`.
* ([more information and exaples]({% slug placeholders_datepicker %})).
*
* @example
* ```ts
* _@Component({
* selector: 'my-app',
* template: `
* <kendo-datepicker placeholder="Enter birth date..."></kendo-datepicker>
* `
* })
* export class AppComponent { }
* ```
*/
placeholder = null;
/**
* Sets or gets the `tabindex` property of the DatePicker.
*/
tabindex = 0;
/**
* @hidden
*/
set tabIndex(tabIndex) {
this.tabindex = tabIndex;
}
get tabIndex() {
return this.tabindex;
}
/**
* Sets the dates of the DatePicker that will be disabled
* ([see example]({% slug disabled_dates_datepicker %})).
*/
set disabledDates(value) {
this._disabledDates = value;
this.disabledDatesService.initialize(value);
}
get disabledDates() {
return this._disabledDates;
}
/**
* Sets the title of the input element of the DatePicker and the title text rendered
* in the header of the popup(action sheet). Applicable only when [`AdaptiveMode` is set to `auto`](slug:api_dateinputs_adaptivemode).
*/
adaptiveTitle = "";
/**
* Sets the subtitle text rendered in the header of the popup(action sheet).
* Applicable only when [`AdaptiveMode` is set to `auto`](slug:api_dateinputs_adaptivemode).
*/
adaptiveSubtitle = "";
/**
* Determines whether the built-in min or max validators are enforced when validating a form.
*
* @default true
*/
rangeValidation = true;
/**
* Determines whether the built-in validator for disabled
* date ranges is enforced when validating a form
* ([see example]( slug:disabled_dates_datepicker#toc-using-a-function)).
*/
disabledDatesValidation = true;
/**
* Determines whether to display a week number column in the `month` view of the Calendar
* ([see example](slug:datepicker_calendar_options#toc-week-number-column)).
*/
weekNumber = false;
/**
* Sets the size of the component.
*
* The possible values are:
* * `small`
* * `medium` (Default)
* * `large`
* * `none`
*
*/
set size(size) {
this.renderer.removeClass(this.wrapper.nativeElement, getSizeClass('input', this.size));
this.renderer.removeClass(this.toggleButton.nativeElement, getSizeClass('button', this.size));
const newSize = size ? size : DEFAULT_SIZE;
if (newSize !== 'none') {
this.renderer.addClass(this.wrapper.nativeElement, getSizeClass('input', newSize));
this.renderer.addClass(this.toggleButton.nativeElement, getSizeClass('button', newSize));
}
this._size = newSize;
}
get size() {
return this._size;
}
/**
* Sets the border radius of the component.
*
* The possible values are:
* * `small`
* * `medium` (Default)
* * `large`
* * `full`
* * `none`
*
*/
set rounded(rounded) {
this.renderer.removeClass(this.wrapper.nativeElement, getRoundedClass(this.rounded));
const newRounded = rounded ? rounded : DEFAULT_ROUNDED;
if (newRounded !== 'none') {
this.renderer.addClass(this.wrapper.nativeElement, getRoundedClass(newRounded));
}
this._rounded = newRounded;
}
get rounded() {
return this._rounded;
}
/**
* Sets the fillMode of the component.
*
* The possible values are:
* * `solid` (Default)
* * `flat`
* * `outline`
* * `none`
*
*/
set fillMode(fillMode) {
this.renderer.removeClass(this.wrapper.nativeElement, getFillModeClass('input', this.fillMode));
this.renderer.removeClass(this.toggleButton.nativeElement, getFillModeClass('button', this.fillMode));
this.renderer.removeClass(this.toggleButton.nativeElement, `k-button-${this.fillMode}-base`);
const newFillMode = fillMode ? fillMode : DEFAULT_FILL_MODE;
if (newFillMode !== 'none') {
this.renderer.addClass(this.toggleButton.nativeElement, getFillModeClass('button', newFillMode));
this.renderer.addClass(this.toggleButton.nativeElement, `k-button-${newFillMode}-base`);
this.renderer.addClass(this.wrapper.nativeElement, getFillModeClass('input', newFillMode));
}
this._fillMode = newFillMode;
}
get fillMode() {
return this._fillMode;
}
/**
* Enables or disables the adaptive mode. By default the adaptive rendering is disabled.
*/
adaptiveMode = 'none';
/**
* Fires each time the user selects a new value
* ([see example](slug:events_datepicker)).
*/
valueChange = new EventEmitter();
/**
* Fires each time the user focuses the input element
* ([see example](slug:events_datepicker)).
*/
onFocus = new EventEmitter();
/**
* Fires each time the input element gets blurred
* ([see example](slug:events_datepicker)).
*/
onBlur = new EventEmitter();
/**
* Fires each time the popup is about to open
* ([see example](slug:events_datepicker)).
* This event is preventable. If you cancel the event, the popup will remain closed.
*/
open = new EventEmitter();
/**
* Fires each time the popup is about to close
* ([see example](slug:events_datepicker)).
* This event is preventable. If you cancel the event, the popup will remain open.
*/
close = new EventEmitter();
/**
* @hidden
*/
escape = new EventEmitter();
/**
* @hidden
*/
wrapperClasses = true;
/**
* @hidden
*/
get disabledClass() {
return this.disabled;
}
get popupUID() {
return this.calendar?.popupId;
}
popupRef;
get isActive() {
return this._active;
}
set isActive(value) {
this._active = value;
if (!this.wrapper) {
return;
}
const element = this.wrapper.nativeElement;
if (value) {
this.renderer.addClass(element, 'k-focus');
}
else {
this.renderer.removeClass(element, 'k-focus');
}
}
get show() {
return this._show;
}
set show(show) {
if (show && (this.disabled || this.readonly)) {
return;
}
const skipZone = !show && (!this._show || (!hasObservers(this.close) && !hasObservers(this.open)));
if (!skipZone) {
this.zone.run(() => {
const event = new PreventableEvent();
if (!this._show && show) {
this.open.emit(event);
}
else if (this._show && !show) {
this.close.emit(event);
}
if (event.isDefaultPrevented()) {
return;
}
this.toggleCalendar(show);
});
}
else {
this.toggleCalendar(show);
}
}
/**
* @hidden
*/
checkIcon = checkIcon;
/**
* @hidden
*/
windowSize;
/**
* @hidden
*/
get isControlRequired() {
return isControlRequired(this.control);
}
_popupSettings = { animate: true };
_show = false;
_value = null;
_active = false;
_disabledDates;
onControlChange = noop;
onControlTouched = noop;
onValidatorChange = noop;
minValidateFn = noop;
maxValidateFn = noop;
disabledDatesValidateFn = noop;
incompleteValidator = noop;
resolvedPromise = Promise.resolve(null);
subscription;
pickerSubscriptions;
localizationChangeSubscription;
windowBlurSubscription;
ariaActiveDescendantSubscription;
control;
domEvents = [];
_size = DEFAULT_SIZE;
_rounded = DEFAULT_ROUNDED;
_fillMode = DEFAULT_FILL_MODE;
constructor(zone, localization, cdr, popupService, wrapper, renderer, injector, pickerService, disabledDatesService, adaptiveService) {
super();
this.zone = zone;
this.localization = localization;
this.cdr = cdr;
this.popupService = popupService;
this.wrapper = wrapper;
this.renderer = renderer;
this.injector = injector;
this.pickerService = pickerService;
this.disabledDatesService = disabledDatesService;
this.adaptiveService = adaptiveService;
validatePackage(packageMetadata);
this.pickerSubscriptions = this.pickerService.onFocus.subscribe(this.handleFocus.bind(this));
this.pickerSubscriptions.add(this.pickerService.onBlur.subscribe(this.handleBlur.bind(this)));
this.pickerSubscriptions.add(this.pickerService.sameDateSelected.subscribe(this.handleSameSelection.bind(this)));
this.pickerSubscriptions.add(this.pickerService.dateCompletenessChange.subscribe(this.handleDateCompletenessChange.bind(this)));
}
/**
* @hidden
* Used by the TextBoxContainer to determine if the component is empty.
*/
isEmpty() {
return !this.value && this.dateInput.isEmpty();
}
/**
* @hidden
*/
ngOnInit() {
this.localizationChangeSubscription = this.localization
.changes
.subscribe(() => this.cdr.markForCheck());
this.control = this.injector.get(NgControl, null);
if (this.wrapper) {
this.renderer.removeAttribute(this.wrapper.nativeElement, 'tabindex');
this.zone.runOutsideAngular(() => {
this.bindEvents();
});
}
this.focusableId = this.dateInput?.focusableId;
this.minValidateFn = this.rangeValidation ? minValidator(this.min) : noop;
this.maxValidateFn = this.rangeValidation ? maxValidator(this.max) : noop;
}
ngAfterViewInit() {
this.setComponentClasses();
this.windowSize = this.adaptiveService.size;
}
/**
* @hidden
*/
ngOnChanges(changes) {
this.verifySettings();
if (changes.min || changes.max || changes.rangeValidation || changes.disabledDatesValidation || changes.disabledDates || changes.incompleteDateValidation) {
this.minValidateFn = this.rangeValidation ? minValidator(this.min) : noop;
this.maxValidateFn = this.rangeValidation ? maxValidator(this.max) : noop;
this.disabledDatesValidateFn = this.disabledDatesValidation ? disabledDatesValidator(this.disabledDatesService.isDateDisabled) : noop;
this.incompleteValidator = this.incompleteDateValidation ? incompleteDateValidator() : noop;
this.onValidatorChange();
}
if (!this.focusableId || changes.focusableId) {
this.focusableId = this.dateInput?.focusableId;
}
}
/**
* @hidden
*/
ngOnDestroy() {
if (this.isAdaptive && this.isOpen) {
this.toggleActionSheet(false);
}
this.isActive = false;
this.show = false;
if (this.localizationChangeSubscription) {
this.localizationChangeSubscription.unsubscribe();
}
if (this.windowBlurSubscription) {
this.windowBlurSubscription.unsubscribe();
}
this.domEvents.forEach(unbindCallback => unbindCallback());
this.pickerSubscriptions.unsubscribe();
}
/**
* Indicates whether the component is currently open. That is when the popup or actionSheet is open.
*/
get isOpen() {
return this.show;
}
/**
* @hidden
*/
writeValue(value) {
this.verifyValue(value);
this.value = cloneDate(value);
this.cdr.markForCheck();
if (!value && this.dateInput) {
this.dateInput.placeholder = this.placeholder;
this.dateInput.writeValue(value);
}
}
/**
* @hidden
*/
registerOnChange(fn) {
this.onControlChange = fn;
}
/**
* @hidden
*/
registerOnTouched(fn) {
this.onControlTouched = fn;
}
/**
* @hidden
*/
setDisabledState(isDisabled) {
this.disabled = isDisabled;
this.cdr.markForCheck();
}
/**
* @hidden
*/
validate(control) {
return this.minValidateFn(control) || this.maxValidateFn(control) || this.disabledDatesValidateFn(control) || this.incompleteValidator(control, this.dateInput && this.dateInput.isDateIncomplete);
}
/**
* @hidden
*/
registerOnValidatorChange(fn) {
this.onValidatorChange = fn;
}
/**
* @hidden
*/
handleActionSheetCollapse() {
// If not handled, the actionsheet expanded state does not get updated when overlay is clicked
this.cdr.markForCheck();
}
/**
* @hidden
*/
handleActionSheetClick(e) {
e.preventDefault();
}
/**
* Focuses the DatePicker component.
*
* @example
* ```ts
* _@Component({
* selector: 'my-app',
* template: `
* <button (click)="datepicker.focus()">Focus date picker</button>
* <kendo-datepicker #datepicker></kendo-datepicker>
* `
* })
* export class AppComponent { }
* ```
*/
focus() {
this.dateInput.focus();
}
/**
* Blurs the DatePicker component.
*/
blur() {
(this.calendar || this.dateInput)['blur']();
}
/**
* Toggles the visibility of the popup or actionSheet.
* If you use the `toggle` method to show or hide the popup or actionSheet,
* the `open` and `close` events do not fire.
*
* @param show - The state of the popup.
*/
toggle(show) {
if (this.disabled || this.readonly) {
return;
}
this.resolvedPromise.then(() => {
this.toggleCalendar((show === undefined) ? !this.show : show);
});
}
/**
* @hidden
*/
handleIconClick(event) {
if (this.disabled || this.readonly) {
return;
}
event.preventDefault();
this.focusInput();
//XXX: explicitly call the handleFocus handler here
//due to async IE focus event
this.handleFocus();
this.show = !this.show;
this.cdr.markForCheck();
}
/**
* @hidden
*/
handleDateInputClick() {
this.windowSize = this.adaptiveService.size;
if (this.isAdaptive) {
this.show = true;
}
}
/**
* @hidden
*/
handleMousedown(args) {
args.preventDefault();
}
/**
* @hidden
*/
handleChange(value, isInputValueChange) {
this.value = value;
if (this.show) {
if (!isInputValueChange) {
this.focusInput();
}
this.show = false;
}
this.onControlChange(cloneDate(value));
this.valueChange.emit(cloneDate(value));
}
/**
* @hidden
*/
handleInputChange(value) {
this.handleChange(this.dateInput.formatSections['time'] ? value : this.mergeTime(value), true);
}
/**
* @hidden
*/
get popupClasses() {
return [
'k-datepicker-popup',
'k-calendar-container'
].concat(this.popupSettings.popupClass || []);
}
/**
* @hidden
*/
get appendTo() {
const { appendTo } = this.popupSettings;
if (!appendTo || appendTo === 'root') {
return undefined;
}
return appendTo === 'component' ? this.container : appendTo;
}
get dateInput() {
return this.pickerService.input;
}
get calendar() {
return this.pickerService.calendar;
}
/**
* @hidden
*/
get isAdaptiveModeEnabled() {
return this.adaptiveMode === 'auto';
}
/**
* @hidden
*/
get isAdaptive() {
return this.isAdaptiveModeEnabled && this.windowSize !== 'large';
}
/**
* @hidden
*/
onResize() {
const currentWindowSize = this.adaptiveService.size;
if (!this.isOpen || this.windowSize === currentWindowSize) {
return;
}
if (this.actionSheet.expanded) {
this.toggleActionSheet(false);
}
else {
this.togglePopup(false);
}
this.windowSize = currentWindowSize;
}
/**
* @hidden
*/
mergeTime(value) {
return this.value && value ? setTime(value, this.value) : value;
}
/**
* @hidden
*/
handleKeydown(e) {
const { altKey, shiftKey, keyCode, target } = e;
if (keyCode === Keys.Escape) {
this.dateInput.focus();
this.show = false;
hasObservers(this.escape) && this.escape.emit();
}
if (altKey) {
if (keyCode === Keys.ArrowDown && !this.show) {
this.show = true;
}
if (keyCode === Keys.ArrowUp) {
this.dateInput.focus();
this.show = false;
}
}
if (keyCode === Keys.Tab && this.show && this.calendar.isActive && isTabExitingCalendar(this.calendarType, target, shiftKey)) {
this.dateInput.focus();
this.show = false;
}
}
toggleCalendar(show) {
const previousWindowSize = this.windowSize;
this.windowSize = this.adaptiveService.size;
if (previousWindowSize !== this.windowSize && !show) {
if (previousWindowSize !== 'large') {
this.toggleActionSheet(show);
}
else {
this.togglePopup(show);
}
}
else {
if (this.isAdaptive) {
this.toggleActionSheet(show);
}
else {
this.togglePopup(show);
}
}
this.toggleFocus();
}
togglePopup(show) {
if (show === this._show) {
return;
}
this._show = show;
if (show) {
const direction = this.localization.rtl ? 'right' : 'left';
const appendToComponent = typeof this.popupSettings.appendTo === 'string' && this.popupSettings.appendTo === 'component';
this.popupRef = this.popupService.open({
anchor: this.wrapper,
anchorAlign: { vertical: 'bottom', horizontal: direction },
animate: this.popupSettings.animate,
appendTo: this.appendTo,
content: this.popupTemplate,
popupAlign: { vertical: 'top', horizontal: direction },
popupClass: this.popupClasses,
positionMode: appendToComponent ? 'fixed' : 'absolute'
});
this.setAriaActiveDescendant();
this.popupRef.popupElement.setAttribute('id', this.popupUID);
this.renderer.setAttribute(this.dateInput?.inputElement, attributeNames.ariaControls, this.popupUID);
this.subscription = this.popupRef.popupAnchorViewportLeave.subscribe(() => this.show = false);
}
else {
this.popupRef.close();
this.popupRef = null;
this.subscription.unsubscribe();
this.ariaActiveDescendantSubscription.unsubscribe();
if (this.dateInput) {
this.renderer.removeAttribute(this.dateInput.inputElement, attributeNames.ariaControls);
this.renderer.removeAttribute(this.dateInput.inputElement, attributeNames.ariaActiveDescendant);
}
this.cdr.detectChanges();
}
}
toggleActionSheet(show) {
if (show === this._show) {
return;
}
if (show && !this.isOpen) {
this.actionSheet.toggle();
this.setAriaActiveDescendant();
this.actionSheet.element.nativeElement.setAttribute('id', this.popupUID);
this.renderer.setAttribute(this.dateInput?.inputElement, attributeNames.ariaControls, this.popupUID);
}
else if (!show && this.isOpen) {
this.actionSheet.toggle();
this.ariaActiveDescendantSubscription.unsubscribe();
if (this.dateInput) {
this.renderer.removeAttribute(this.dateInput.inputElement, attributeNames.ariaActiveDescendant);
this.renderer.removeAttribute(this.dateInput.inputElement, attributeNames.ariaControls);
this.dateInput.focus();
}
}
this._show = show;
}
setAriaActiveDescendant() {
const focusedCellChangeEvent = this.calendar.type === 'infinite' ?
this.calendar.monthView.focusedCellChange :
this.calendar.multiViewCalendar.viewList.focusedCellChange;
this.ariaActiveDescendantSubscription = focusedCellChangeEvent.subscribe((id) => this.renderer.setAttribute(this.dateInput?.inputElement, attributeNames.ariaActiveDescendant, id));
}
focusInput() {
if (touchEnabled) {
return;
}
this.dateInput.focus();
}
toggleFocus() {
if (!this.isActive) {
return;
}
if (this.show) {
if (!this.calendar) {
this.cdr.detectChanges();
}
if (this.calendar) {
this.calendar.focus();
}
}
else if (!touchEnabled) {
this.dateInput.focus();
}
else if (!this.dateInput.isActive) {
this.handleBlur();
}
}
verifySettings() {
if (!isDevMode()) {
return;
}
if (!isValidRange(this.min, this.max)) {
throw new Error(`The max value should be bigger than the min. See ${MIN_DOC_LINK} and ${MAX_DOC_LINK}.`);
}
}
verifyValue(value) {
if (!isDevMode()) {
return;
}
if (value && !(value instanceof Date)) {
throw new Error(`The 'value' should be a valid JavaScript Date instance or null. Check ${VALUE_DOC_LINK} for possible resolution.`);
}
}
bindEvents() {
const element = this.wrapper.nativeElement;
this.domEvents.push(this.renderer.listen(element, 'keydown', this.handleKeydown.bind(this)));
if (isWindowAvailable()) {
this.windowBlurSubscription = fromEvent(window, 'blur').subscribe(this.handleWindowBlur.bind(this));
}
}
handleFocus() {
if (this.isActive) {
return;
}
this.isActive = true;
if (hasObservers(this.onFocus)) {
this.zone.run(() => {
this.onFocus.emit();
});
}
}
handleWindowBlur() {
if (!this.isOpen || this.actionSheet.expanded) {
return;
}
this.show = false;
}
handleBlur(args) {
const currentTarget = args && currentFocusTarget(args);
const target = args && args.target;
const isInsideActionSheet = this.actionSheet && (this.actionSheet.element.nativeElement.contains(target) || this.actionSheet.element.nativeElement.contains(currentTarget));
if (currentTarget && (this.dateInput.containsElement(currentTarget) ||
(this.calendar && this.calendar.containsElement(currentTarget)) || isInsideActionSheet)) {
return;
}
if (hasObservers(this.onBlur) || (this.show && hasObservers(this.close)) || requiresZoneOnBlur(this.control)) {
this.zone.run(() => {
this.blurComponent();
this.cdr.markForCheck();
});
}
else {
this.blurComponent();
}
}
blurComponent() {
this.isActive = false; // order is important ¯\_(ツ)_/¯
this.show = false;
this.cdr.detectChanges();
this.onControlTouched();
this.onBlur.emit();
}
handleSameSelection() {
if (this.show) {
this.focusInput();
this.show = false;
}
}
handleDateCompletenessChange() {
this.cdr.markForCheck();
this.zone.run(() => this.onValidatorChange());
}
setComponentClasses() {
if (this.size) {
this.renderer.addClass(this.wrapper.nativeElement, getSizeClass('input', this.size));
this.renderer.addClass(this.toggleButton.nativeElement, getSizeClass('button', this.size));
}
if (this.rounded) {
this.renderer.addClass(this.wrapper.nativeElement, getRoundedClass(this.rounded));
}
if (this.fillMode) {
this.renderer.addClass(this.wrapper.nativeElement, getFillModeClass('input', this.fillMode));
this.renderer.addClass(this.toggleButton.nativeElement, getFillModeClass('button', this.fillMode));
this.renderer.addClass(this.toggleButton.nativeElement, `k-button-${this.fillMode}-base`);
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: DatePickerComponent, deps: [{ token: i0.NgZone }, { token: i1.LocalizationService }, { token: i0.ChangeDetectorRef }, { token: i2.PopupService }, { token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i0.Injector }, { token: i3.PickerService }, { token: i4.DisabledDatesService }, { token: i5.AdaptiveService }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: DatePickerComponent, isStandalone: true, selector: "kendo-datepicker", inputs: { focusableId: "focusableId", cellTemplateRef: ["cellTemplate", "cellTemplateRef"], clearButton: "clearButton", inputAttributes: "inputAttributes", monthCellTemplateRef: ["monthCellTemplate", "monthCellTemplateRef"], yearCellTemplateRef: ["yearCellTemplate", "yearCellTemplateRef"], decadeCellTemplateRef: ["decadeCellTemplate", "decadeCellTemplateRef"], centuryCellTemplateRef: ["centuryCellTemplate", "centuryCellTemplateRef"], weekNumberTemplateRef: ["weekNumberTemplate", "weekNumberTemplateRef"], headerTitleTemplateRef: ["headerTitleTemplate", "headerTitleTemplateRef"], headerTemplateRef: ["headerTemplate", "headerTemplateRef"], footerTemplateRef: ["footerTemplate", "footerTemplateRef"], footer: "footer", navigationItemTemplateRef: ["navigationItemTemplate", "navigationItemTemplateRef"], weekDaysFormat: "weekDaysFormat", showOtherMonthDays: "showOtherMonthDays", activeView: "activeView", bottomView: "bottomView", topView: "topView", calendarType: "calendarType", animateCalendarNavigation: "animateCalendarNavigation", disabled: "disabled", readonly: "readonly", readOnlyInput: "readOnlyInput", popupSettings: "popupSettings", navigation: "navigation", min: "min", max: "max", incompleteDateValidation: "incompleteDateValidation", autoCorrectParts: "autoCorrectParts", autoSwitchParts: "autoSwitchParts", autoSwitchKeys: "autoSwitchKeys", enableMouseWheel: "enableMouseWheel", allowCaretMode: "allowCaretMode", autoFill: "autoFill", focusedDate: "focusedDate", value: "value", format: "format", twoDigitYearMax: "twoDigitYearMax", formatPlaceholder: "formatPlaceholder", placeholder: "placeholder", tabindex: "tabindex", tabIndex: "tabIndex", disabledDates: "disabledDates", adaptiveTitle: "adaptiveTitle", adaptiveSubtitle: "adaptiveSubtitle", rangeValidation: "rangeValidation", disabledDatesValidation: "disabledDatesValidation", weekNumber: "weekNumber", size: "size", rounded: "rounded", fillMode: "fillMode", adaptiveMode: "adaptiveMode" }, outputs: { valueChange: "valueChange", onFocus: "focus", onBlur: "blur", open: "open", close: "close", escape: "escape" }, host: { properties: { "class.k-readonly": "this.readonly", "class.k-datepicker": "this.wrapperClasses", "class.k-input": "this.wrapperClasses", "class.k-disabled": "this.disabledClass" } }, providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DatePickerComponent), multi: true },
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => DatePickerComponent), multi: true },
{ provide: KendoInput, useExisting: forwardRef(() => DatePickerComponent) },
{ provide: MultiTabStop, useExisting: forwardRef(() => DatePickerComponent) },
LocalizationService,
PickerService,
DisabledDatesService,
{
provide: L10N_PREFIX,
useValue: 'kendo.datepicker'
}
], queries: [{ propertyName: "cellTemplate", first: true, predicate: CellTemplateDirective, descendants: true }, { propertyName: "monthCellTemplate", first: true, predicate: MonthCellTemplateDirective, descendants: true }, { propertyName: "yearCellTemplate", first: true, predicate: YearCellTemplateDirective, descendants: true }, { propertyName: "decadeCellTemplate", first: true, predicate: DecadeCellTemplateDirective, descendants: true }, { propertyName: "centuryCellTemplate", first: true, predicate: CenturyCellTemplateDirective, descendants: true }, { propertyName: "weekNumberTemplate", first: true, predicate: WeekNumberCellTemplateDirective, descendants: true }, { propertyName: "headerTitleTemplate", first: true, predicate: HeaderTitleTemplateDirective, descendants: true }, { propertyName: "headerTemplate", first: true, predicate: HeaderTemplateDirective, descendants: true }, { propertyName: "footerTemplate", first: true, predicate: FooterTemplateDirective, descendants: true }, { propertyName: "navigationItemTemplate", first: true, predicate: NavigationItemTemplateDirective, descendants: true }], viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, read: ViewContainerRef, static: true }, { propertyName: "popupTemplate", first: true, predicate: ["popupTemplate"], descendants: true, static: true }, { propertyName: "toggleButton", first: true, predicate: ["toggleButton"], descendants: true, static: true }, { propertyName: "actionSheet", first: true, predicate: ["actionSheet"], descendants: true }], exportAs: ["kendo-datepicker"], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: `
<ng-container kendoDatePickerLocalizedMessages
i18n-today="kendo.datepicker.today|The label for the today button in the calendar header"
today="Today"
i18n-toggle="kendo.datepicker.toggle|The title of the toggle button in the datepicker component"
toggle="Toggle calendar"
i18n-prevButtonTitle="kendo.datepicker.prevButtonTitle|The title of the previous button in the Classic calendar"
prevButtonTitle="Navigate to previous view"
i18n-nextButtonTitle="kendo.datepicker.nextButtonTitle|The title of the next button in the Classic calendar"
nextButtonTitle="Navigate to next view"
i18n-parentViewButtonTitle="kendo.datepicker.parentViewButtonTitle|The title of the parent view button in the calendar header"
parentViewButtonTitle="Navigate to parent view"
i18n-clearTitle="kendo.datepicker.clearTitle|The title of the clear button"
clearTitle="clear"
i18n-adaptiveCloseButtonTitle="kendo.datepicker.adaptiveCloseButtonTitle|The title of the Close button of the ActionSheet that is rendered instead of the Popup when using small screen devices in adaptive mode"
adaptiveCloseButtonTitle="Close"
>
</ng-container>
<kendo-dateinput
#input
[role]="'combobox'"
pickerType="datepicker"
hasPopup="grid"
[isPopupOpen]="show"
[clearButton]="clearButton"
[disabled]="disabled"
[readonly]="readonly || readOnlyInput"
[ariaReadOnly]="readonly"
[tabindex]="tabindex"
[isRequired]="isControlRequired"
[title]="adaptiveTitle"
[focusableId]="focusableId"
[format]="format"
[twoDigitYearMax]="twoDigitYearMax"
[formatPlaceholder]="formatPlaceholder"
[placeholder]="placeholder"
[min]="min"
[max]="max"
[incompleteDateValidation]="incompleteDateValidation"
[autoCorrectParts]="autoCorrectParts"
[autoSwitchParts]="autoSwitchParts"
[autoSwitchKeys]="autoSwitchKeys"
[enableMouseWheel]="enableMouseWheel"
[allowCaretMode]="allowCaretMode"
[autoFill]="autoFill"
fillMode="none"
rounded="none"
size="none"
[inputAttributes]="inputAttributes"
[value]="value"
(valueChange)="handleInputChange($event)"
(click)="handleDateInputClick()"
>
<kendo-dateinput-messages
[clearTitle]="localization.get('clearTitle')"
>
</kendo-dateinput-messages>
</kendo-dateinput>
<button
#toggleButton
type="button"
class="k-input-button k-button k-icon-button"
[tabindex]="-1"
[attr.title]="localization.get('toggle')"
[attr.aria-label]="localization.get('toggle')"
[attr.disabled]="disabled ? '' : null"
[kendoEventsOutsideAngular]="{
click: handleIconClick,
mousedown: handleMousedown
}"
[scope]="this"
>
<kendo-icon-wrapper
name="calendar"
[