UNPKG

@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
/**----------------------------------------------------------------------------------------- * 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`&mdash;Controls the popup animation. By default, the open and close animations are enabled. * - `appendTo: 'root' | 'component' | ViewContainerRef`&mdash;Controls the popup container. By default, the popup will be appended to the root component. * - `popupClass: String`&mdash;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" [