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,300 lines (1,284 loc) 66 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, ChangeDetectorRef, ChangeDetectionStrategy, ElementRef, EventEmitter, HostBinding, Input, Output, NgZone, TemplateRef, ViewChild, ViewContainerRef, Renderer2, 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, isEqual } from '@progress/kendo-date-math'; import { hasObservers, isControlRequired, KendoInput, Keys, MultiTabStop, ResizeSensorComponent, EventsOutsideAngularDirective } from '@progress/kendo-angular-common'; import { AdaptiveService } from '@progress/kendo-angular-utils'; import { validatePackage } from '@progress/kendo-licensing'; import { packageMetadata } from '../package-metadata'; import { MIDNIGHT_DATE, MIN_TIME, MAX_TIME } from '../defaults'; import { IntlService } from '@progress/kendo-angular-intl'; import { PickerService } from '../common/picker.service'; import { requiresZoneOnBlur, currentFocusTarget, attributeNames } from '../common/utils'; import { TIME_PART } from './models/time-part.default'; import { TimeSelectorComponent } from './timeselector.component'; import { DateInputComponent } from '../dateinput/dateinput.component'; import { PreventableEvent } from '../preventable-event'; import { noop, setTime, isWindowAvailable, getFillModeClass, getSizeClass, getRoundedClass, DEFAULT_FILL_MODE, DEFAULT_ROUNDED, DEFAULT_SIZE } from '../util'; import { timeRangeValidator } from '../validators/time-range.validator'; import { fromEvent } from 'rxjs'; import { incompleteDateValidator } from '../validators/incomplete-date.validator'; import { BusViewService } from '../calendar/services/bus-view.service'; import { checkIcon, clockIcon } from '@progress/kendo-svg-icons'; import { ActionSheetComponent, ActionSheetTemplateDirective } from '@progress/kendo-angular-navigation'; import { TimeSelectorCustomMessagesComponent } from './localization/timeselector-custom-messages.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 { TimePickerLocalizedMessagesDirective } from './localization/timepicker-localized-messages.directive'; import { touchEnabled } from '@progress/kendo-common'; import * as i0 from "@angular/core"; import * as i1 from "../calendar/services/bus-view.service"; import * as i2 from "@progress/kendo-angular-l10n"; import * as i3 from "@progress/kendo-angular-popup"; import * as i4 from "../common/picker.service"; import * as i5 from "@progress/kendo-angular-intl"; import * as i6 from "@progress/kendo-angular-utils"; const VALUE_DOC_LINK = 'https://www.telerik.com/kendo-angular-ui/components/dateinputs/timepicker/#toc-integration-with-json'; const INTL_DATE_FORMAT = 'https://github.com/telerik/kendo-intl/blob/master/docs/date-formatting/index.md'; const formatRegExp = new RegExp(`${TIME_PART.hour}|${TIME_PART.minute}|${TIME_PART.second}|${TIME_PART.millisecond}|${TIME_PART.dayperiod}|literal`); const ACCEPT_BUTTON_SELECTOR = '.k-button.k-time-accept'; const CANCEL_BUTTON_SELECTOR = '.k-button.k-time-cancel'; /** * Represents the [Kendo UI TimePicker component for Angular](slug:overview_timepicker). */ export class TimePickerComponent extends MultiTabStop { bus; zone; localization; cdr; popupService; wrapper; renderer; injector; pickerService; intl; adaptiveService; /** * @hidden */ clockIcon = clockIcon; container; popupTemplate; toggleButton; actionSheet; /** * @hidden */ focusableId; /** * Sets or gets the `disabled` property of the TimePicker and * determines whether the component is active * ([see example]({% slug disabled_timepicker %})). * To learn how to disable the component in reactive forms, refer to the article on [Forms Support](slug:formssupport_timepicker#toc-managing-the-timepicker-disabled-state-in-reactive-forms). */ disabled = false; /** * Sets the read-only state of the TimePicker * ([see example]({% slug readonly_timepicker %}#toc-read-only-timepicker)). * * @default false */ readonly = false; /** * Sets the read-only state of the TimePicker input field * ([see example]({% slug readonly_timepicker %}#toc-read-only-input)). * * > Note that if you set the [`readonly`]({% slug api_dateinputs_timepickercomponent %}#toc-readonly) property value to `true`, * the input will be rendered in a read-only state regardless of the `readOnlyInput` value. */ readOnlyInput = false; /** * If set to `true`, renders a clear button after the input text or TimePicker value has been changed. * Clicking this button resets the value of the component to `null` and triggers the `valueChange` event. * @default false */ clearButton = false; /** * Specifies the time format that is used to display the input value * ([see example]({% slug formats_timepicker %})). */ format = 't'; /** * Defines the descriptions of the format sections in the input field. * For more information, refer to the article on * [placeholders]({% slug placeholders_timepicker %}). * * @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-timepicker formatPlaceholder="wide"></kendo-timepicker> * </div> * * <div class="col-xs-12 col-md-6 example-col"> * <p>Narrow-length format description:</p> * <kendo-timepicker formatPlaceholder="narrow"></kendo-timepicker> * </div> * * <div class="col-xs-12 col-md-6 example-col"> * <p>Short-length format description:</p> * <kendo-timepicker formatPlaceholder="short"></kendo-timepicker> * </div> * * <div class="col-xs-12 col-md-6 example-col"> * <p>Display defined format:</p> * <kendo-timepicker format="HH:mm:ss" formatPlaceholder="formatPattern"></kendo-timepicker> * </div> * * <div class="col-xs-12 col-md-6 example-col"> * <p>Custom defined format descriptions</p> * <kendo-timepicker format="HH:mm:ss" * [formatPlaceholder]="{ hour: 'H', minute: 'm', second: 's' }" * ></kendo-timepicker> * </div> * </div> * ` * }) * export class AppComponent { } * ``` */ formatPlaceholder; /** * Specifies the hint the TimePicker displays when its value is `null`. * For more information, refer to the article on * [placeholders]({% slug placeholders_timepicker %}). * * @example * ```ts * _@Component({ * selector: 'my-app', * template: ` * <kendo-timepicker placeholder="Enter start..."></kendo-timepicker> * ` * }) * export class AppComponent { } * ``` */ placeholder = null; /** * Specifies the smallest valid time value * ([see example]({% slug timeranges_timepicker %})). */ set min(min) { this._min = cloneDate(min || MIN_TIME); } get min() { return this._min; } /** * Specifies the biggest valid time value * ([see example]({% slug timeranges_timepicker %})). */ set max(max) { this._max = cloneDate(max || MAX_TIME); } get max() { return this._max; } /** * Determines whether the built-in validation for incomplete dates is to be enforced when a form is being validated. */ incompleteDateValidation = false; /** * 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; /** * Determines whether to display the **Cancel** button in the popup. */ cancelButton = true; /** * Determines whether to display the **Now** button in the popup. * * > If the current time is out of range or the incremental step is greater than `1`, the **Now** button will be hidden. */ nowButton = true; /** * Configures the incremental steps of the TimePicker. * For more information, refer to the article on * [incremental steps]({% slug incrementalsteps_timepicker %}). * * > If the incremental step is greater than `1`, the **Now** button will be hidden. * * @example * ```ts * _@Component({ * selector: 'my-app', * template: ` * <kendo-timepicker format="HH:mm:ss" [steps]="steps"></kendo-timepicker> * ` * }) * class AppComponent { * public steps = { hour: 2, minute: 15, second: 15, millisecond: 10 }; * } * ``` * */ set steps(steps) { this._steps = steps || {}; } get steps() { return this._steps; } /** * Configures the popup of the TimePicker. * * 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 `tabindex` property of the TimePicker. */ tabindex = 0; /** * @hidden */ set tabIndex(tabIndex) { this.tabindex = tabIndex; } get tabIndex() { return this.tabindex; } /** * Sets the title of the input element of the TimePicker 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). * By default, subtitle is not rendered. */ adaptiveSubtitle; /** * Determines whether the built-in min or max validators are enforced when a form is being validated. * * @default true */ rangeValidation = true; /** * Enables or disables the adaptive mode. By default the adaptive rendering is disabled. */ adaptiveMode = 'none'; /** * Specifies the value of the TimePicker 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; } /** * 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.wrapper.nativeElement, getFillModeClass('input', newFillMode)); this.renderer.addClass(this.toggleButton.nativeElement, getFillModeClass('button', newFillMode)); this.renderer.addClass(this.toggleButton.nativeElement, `k-button-${newFillMode}-base`); } this._fillMode = newFillMode; } get fillMode() { return this._fillMode; } /** * Sets the HTML attributes of the inner focusable input element. Attributes which are essential for certain component functionalities cannot be changed. */ inputAttributes; /** * Fires each time the user selects a new value * ([see example](slug:events_timepicker)). */ valueChange = new EventEmitter(); /** * Fires each time the user focuses the input element * ([see example](slug:events_timepicker)). */ onFocus = new EventEmitter(); /** * Fires each time the input element gets blurred * ([see example](slug:events_timepicker)). */ onBlur = new EventEmitter(); /** * Fires each time the popup is about to open * ([see example](slug:events_timepicker)). * 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_timepicker)). * 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 `k-timepicker-popup-${this.bus.calendarId}-`; } popupRef; /** * @hidden */ checkIcon = checkIcon; 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.toggleTimeSelector(show); }); } else { this.toggleTimeSelector(show); } } get dateInput() { return this.pickerService.input; } get timeSelector() { return this.pickerService.timeSelector; } /** * @hidden */ get isControlRequired() { return isControlRequired(this.control); } /** * @hidden */ windowSize; get adaptiveAcceptButton() { return this.actionSheet.element.nativeElement.querySelector(ACCEPT_BUTTON_SELECTOR); } get adaptiveCancelButton() { return this.actionSheet.element.nativeElement.querySelector(CANCEL_BUTTON_SELECTOR); } get inputElement() { return this.wrapper.nativeElement.querySelector('input'); } onControlChange = noop; onControlTouched = noop; onValidatorChange = noop; resolvedPromise = Promise.resolve(null); timeRangeValidateFn = noop; incompleteValidator = noop; _min = cloneDate(MIN_TIME); _max = cloneDate(MAX_TIME); _popupSettings = { animate: true }; _show = false; _steps = {}; _value = null; _active = false; localizationChangeSubscription; pickerSubscriptions; windowBlurSubscription; control; domEvents = []; _size = DEFAULT_SIZE; _rounded = DEFAULT_ROUNDED; _fillMode = DEFAULT_FILL_MODE; constructor(bus, zone, localization, cdr, popupService, wrapper, renderer, injector, pickerService, intl, adaptiveService) { super(); this.bus = bus; 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.intl = intl; 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.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; } /** * @hidden */ ngAfterViewInit() { this.setComponentClasses(); this.windowSize = this.adaptiveService.size; } /** * @hidden */ ngOnChanges(changes) { if (changes.min || changes.max || changes.rangeValidation || changes.incompleteDateValidation) { this.timeRangeValidateFn = this.rangeValidation ? timeRangeValidator(this.min, this.max) : noop; this.incompleteValidator = this.incompleteDateValidation ? incompleteDateValidator() : noop; this.onValidatorChange(); } if (changes.format) { this.verifyFormat(); } if (!this.focusableId || changes.focusableId) { this.focusableId = this.dateInput?.focusableId; } } /** * @hidden */ ngOnDestroy() { 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(); } /** * @hidden */ handleKeydown(event) { const { altKey, keyCode } = event; if (keyCode === Keys.Escape) { this.focusInput(); this.show = false; hasObservers(this.escape) && this.escape.emit(); this.cdr.detectChanges(); return; } if (altKey) { if (keyCode === Keys.ArrowUp) { event.preventDefault(); this.focusInput(); this.show = false; this.cdr.detectChanges(); } if (keyCode === Keys.ArrowDown && !this.show) { event.preventDefault(); this.show = true; } } } /** * @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.timeRangeValidateFn(control) || this.incompleteValidator(control, this.dateInput && this.dateInput.isDateIncomplete); } /** * @hidden */ registerOnValidatorChange(fn) { this.onValidatorChange = fn; } /** * Focuses the TimePicker component. * * @example * ```ts * _@Component({ * selector: 'my-app', * template: ` * <button (click)="timepicker.focus()">Focus time picker</button> * <kendo-timepicker #timepicker></kendo-timepicker> * ` * }) * export class AppComponent { } * ``` */ focus() { this.dateInput.focus(); } /** * Blurs the TimePicker component. */ blur() { (this.timeSelector || 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.toggleTimeSelector((show === undefined) ? !this.show : show); }); } /** * Indicates whether the component is currently open. That is when the popup or actionSheet is open. */ get isOpen() { return this.show; } /** * @hidden */ get appendTo() { const { appendTo } = this.popupSettings; if (!appendTo || appendTo === 'root') { return undefined; } return appendTo === 'component' ? this.container : appendTo; } /** * @hidden */ handleChange(value) { if (isEqual(this.value, value)) { if (this.show) { this.focusInput(); this.show = false; } if (this.incompleteDateValidation) { this.onControlChange(cloneDate(value)); } return; } this.value = cloneDate(value); this.zone.run(() => { if (this.show) { this.focusInput(); this.show = false; } this.dateInput.showClearButton = true; this.onControlChange(cloneDate(value)); this.valueChange.emit(cloneDate(value)); }); } /** * @hidden */ handleActionSheetAccept() { this.timeSelector.handleAccept(); } /** * @hidden */ handleActionSheetCollapse() { // If not handled, the actionsheet expanded state does not get updated when overlay is clicked this.cdr.markForCheck(); } /** * @hidden */ handleReject() { this.focusInput(); this.show = false; } /** * @hidden */ handleInputChange(value) { const val = this.dateInput.formatSections['date'] ? value : this.mergeTime(value); this.handleChange(val); } /** * @hidden */ handleDateInputClick() { this.windowSize = this.adaptiveService.size; if (this.isAdaptive) { this.show = true; } } /** * @hidden */ onTabOutNow() { if (!this.timeSelector.showNowButton) { this.cancelButton ? this.timeSelector.cancel.nativeElement.focus() : this.timeSelector.accept.nativeElement.focus(); } } /** * @hidden */ handleMousedown(args) { args.preventDefault(); } /** * @hidden */ handleIconClick(event) { if (this.disabled || this.readonly) { return; } event.preventDefault(); this.focusInput(); //XXX: explicit call handleFocus handler here //due to async IE focus event this.handleFocus(); this.show = !this.show; this.cdr.detectChanges(); } /** * @hidden */ get popupClasses() { return [ 'k-timepicker-popup' ].concat(this.popupSettings.popupClass || []); } /** * @hidden */ get isAdaptiveModeEnabled() { return this.adaptiveMode === 'auto'; } /** * @hidden */ get isAdaptive() { return this.isAdaptiveModeEnabled && this.windowSize !== 'large'; } /** * @hidden */ normalizeTime(date) { return setTime(MIDNIGHT_DATE, date); } /** * @hidden */ mergeTime(value) { return this.value && value ? setTime(this.value, value) : value; } /** * @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 */ onTabOutLastPart() { this.renderer.removeClass(this.timeSelector.timeListWrappers.last.nativeElement, 'k-focus'); if (this.isAdaptive) { this.cancelButton ? this.adaptiveCancelButton.focus() : this.adaptiveAcceptButton.focus(); } else { this.timeSelector.accept.nativeElement.focus(); } } /** * @hidden */ onTabOutFirstPart() { this.renderer.removeClass(this.timeSelector.timeListWrappers.first.nativeElement, 'k-focus'); if (this.timeSelector.showNowButton) { this.timeSelector.now.nativeElement.focus(); } else { this.cancelButton ? this.timeSelector.cancel.nativeElement.focus() : this.timeSelector.accept.nativeElement.focus(); } } toggleTimeSelector(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(); } toggleActionSheet(show) { if (show === this._show) { return; } if (show && !this.isOpen) { this.actionSheet.toggle(); this.renderer.setAttribute(this.actionSheet.element.nativeElement, 'id', this.popupUID); this.renderer.setAttribute(this.dateInput?.inputElement, attributeNames.ariaControls, this.popupUID); } else if (!show && this.isOpen) { // Need to disable the pointer events to avoid triggering focus on the timelist when acionsheet close down animation starts this.renderer.setStyle(this.timeSelector.element.nativeElement, 'pointer-events', 'none'); if (this.dateInput) { this.renderer.removeAttribute(this.dateInput.inputElement, attributeNames.ariaControls); } this.actionSheet.toggle(); this.dateInput.focus(); } this._show = show; } togglePopup(show) { if (show === this.isOpen) { return; } this._show = show; this.cdr.markForCheck(); 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.popupRef.popupElement.setAttribute('id', this.popupUID); this.renderer.setAttribute(this.dateInput?.inputElement, attributeNames.ariaControls, this.popupUID); this.popupRef.popupAnchorViewportLeave.subscribe(() => this.show = false); } else { this.popupRef.close(); this.popupRef = null; if (this.dateInput) { this.renderer.removeAttribute(this.dateInput?.inputElement, attributeNames.ariaControls); } } } focusInput() { if (touchEnabled) { return; } this.dateInput.focus(); } toggleFocus() { if (!this.isActive) { return; } if (this.show) { if (!this.timeSelector) { this.cdr.detectChanges(); } if (this.isActive) { this.timeSelector.focus(); } } else if (!touchEnabled) { this.dateInput.focus(); } else if (!this.dateInput.isActive) { this.handleBlur(); } } 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.`); } } verifyFormat() { if (!isDevMode()) { return; } const formatContainsDateParts = this.intl.splitDateFormat(this.format).some(part => !formatRegExp.test(part.type)); if (formatContainsDateParts) { throw new Error(`Provided format is not supported. Supported specifiers are T|t|H|h|m|s|S|a. See ${INTL_DATE_FORMAT}`); } } 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)); } } handleWindowBlur() { if (!this.isOpen || this.actionSheet.expanded) { return; } this.show = false; } handleFocus() { if (this.isActive) { return; } this.isActive = true; if (hasObservers(this.onFocus)) { this.zone.run(() => { this.onFocus.emit(); }); } } handleBlur(args) { const currentTarget = args && currentFocusTarget(args); // relatedTarget || activeElement 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.timeSelector && this.timeSelector.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(); } this.cdr.detectChanges(); } blurComponent() { this.isActive = false; // order is important ¯\_(ツ)_/¯ this.show = false; this.onControlTouched(); this.onBlur.emit(); } 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: TimePickerComponent, deps: [{ token: i1.BusViewService }, { token: i0.NgZone }, { token: i2.LocalizationService }, { token: i0.ChangeDetectorRef }, { token: i3.PopupService }, { token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i0.Injector }, { token: i4.PickerService }, { token: i5.IntlService }, { token: i6.AdaptiveService }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: TimePickerComponent, isStandalone: true, selector: "kendo-timepicker", inputs: { focusableId: "focusableId", disabled: "disabled", readonly: "readonly", readOnlyInput: "readOnlyInput", clearButton: "clearButton", format: "format", formatPlaceholder: "formatPlaceholder", placeholder: "placeholder", min: "min", max: "max", incompleteDateValidation: "incompleteDateValidation", autoSwitchParts: "autoSwitchParts", autoSwitchKeys: "autoSwitchKeys", enableMouseWheel: "enableMouseWheel", allowCaretMode: "allowCaretMode", cancelButton: "cancelButton", nowButton: "nowButton", steps: "steps", popupSettings: "popupSettings", tabindex: "tabindex", tabIndex: "tabIndex", adaptiveTitle: "adaptiveTitle", adaptiveSubtitle: "adaptiveSubtitle", rangeValidation: "rangeValidation", adaptiveMode: "adaptiveMode", value: "value", size: "size", rounded: "rounded", fillMode: "fillMode", inputAttributes: "inputAttributes" }, outputs: { valueChange: "valueChange", onFocus: "focus", onBlur: "blur", open: "open", close: "close", escape: "escape" }, host: { properties: { "class.k-readonly": "this.readonly", "class.k-timepicker": "this.wrapperClasses", "class.k-input": "this.wrapperClasses", "class.k-disabled": "this.disabledClass" } }, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TimePickerComponent), multi: true }, { provide: NG_VALIDATORS, useExisting: forwardRef(() => TimePickerComponent), multi: true }, { provide: KendoInput, useExisting: forwardRef(() => TimePickerComponent) }, { provide: MultiTabStop, useExisting: forwardRef(() => TimePickerComponent) }, LocalizationService, BusViewService, { provide: L10N_PREFIX, useValue: 'kendo.timepicker' }, PickerService ], viewQueries: [{ propertyName: "container", first: true, predicate: ["container"], descendants: true, read: ViewContainerRef }, { propertyName: "popupTemplate", first: true, predicate: ["popupTemplate"], descendants: true }, { propertyName: "toggleButton", first: true, predicate: ["toggleButton"], descendants: true, static: true }, { propertyName: "actionSheet", first: true, predicate: ["actionSheet"], descendants: true }], exportAs: ["kendo-timepicker"], usesInheritance: true, usesOnChanges: true, ngImport: i0, template: ` <ng-container kendoTimePickerLocalizedMessages i18n-accept="kendo.timepicker.accept|The Accept button text in the timepicker component" accept="Set" i18n-acceptLabel="kendo.timepicker.acceptLabel|The label for the Accept button in the timepicker component" acceptLabel="Set time" i18n-cancel="kendo.timepicker.cancel|The Cancel button text in the timepicker component" cancel="Cancel" i18n-cancelLabel="kendo.timepicker.cancelLabel|The label for the Cancel button in the timepicker component" cancelLabel="Cancel changes" i18n-now="kendo.timepicker.now|The Now button text in the timepicker component" now="Now" i18n-nowLabel="kendo.timepicker.nowLabel|The label for the Now button in the timepicker component" nowLabel="Select now" i18n-toggle="kendo.timepicker.toggle|The label for the toggle button in the timepicker component" toggle="Toggle time list" i18n-hour="kendo.timepicker.hour|The label for the hour part in the timepicker component" hour="Hour" i18n-minute="kendo.timepicker.minute|The label for the minute part in the timepicker component" minute="Minute" i18n-second="kendo.timepicker.second|The label for the second part in the timepicker component" second="Second" i18n-millisecond="kendo.timepicker.millisecond|The label for the millisecond part in the timepicker component" millisecond="Millisecond" i18n-dayperiod="kendo.timepicker.dayperiod|The label for the dayperiod part in the timepicker component" dayperiod="Dayperiod" i18n-clearTitle="kendo.timepicker.clearTitle|The title of the clear button" clearTitle="clear" i18n-adaptiveCloseButtonTitle="kendo.timepicker.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 pickerType="timepicker" hasPopup="dialog" [isPopupOpen]="show" [disabled]="disabled" [clearButton]="clearButton" [readonly]="readonly || readOnlyInput" [role]="'combobox'" [ariaReadOnly]="readonly" [format]="format" [formatPlaceholder]="formatPlaceholder" [placeholder]="placeholder" [focusableId]="focusableId" [min]="normalizeTime(min)" [max]="normalizeTime(max)" [incompleteDateValidation]="incompleteDateValidation" [autoSwitchParts]="autoSwitchParts" [autoSwitchKeys]="autoSwitchKeys" [enableMouseWheel]="enableMouseWheel" [allowCaretMode]="allowCaretMode" fillMode="none" rounded="none" size="none" [steps]="steps" [tabindex]="!show ? tabindex : -1" [isRequired]="isControlRequired" [title]="adaptiveTitle" [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" tabindex="-1" class="k-input-button k-button k-icon-button" [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="clock" [svgIcon]="clockIcon" innerCssClass="k-button-icon" > </kendo-icon-wrapper> </button> <ng-template #popupTemplate> <ng-container *ngTemplateOutlet="timeSelectorTemplate"></ng-container> </ng-template> <ng-container #container></ng-container> <kendo-resize-sensor *ngIf="isAdaptiveModeEnabled" (resize)="onResize()"></kendo-resize-sensor> <kendo-actionsheet #actionSheet (overlayClick)="show=false" (collapse)="handleActionSheetCollapse()" [titleId]="focusableId" [cssClass]="{ 'k-adaptive-actionsheet': true, 'k-actionsheet-fullscreen': windowSize === 'small', 'k-actionsheet-bottom': windowSize === 'medium' }" [cssStyle]="{ height: windowSize === 'small' ? '100vh' : '60vh' }" > <ng-template kendoActionSheetTemplate> <div class="k-actionsheet-titlebar"> <div class="k-actionsheet-titlebar-group"> <div class="k-actionsheet-title"> <div class="k-text-center" *ngIf="adaptiveTitle">{{ adaptiveTitle }}</div> <div class="k-actionsheet-subtitle k-text-center" *ngIf="adaptiveSubtitle">{{ adaptiveSubtitle }}</div> </div> <div class="k-actionsheet-actions"> <button kendoButton type="button" icon="check" [attr.title]="localization.get('adaptiveCloseButtonTitle')" [svgIcon]="checkIcon" fillMode="flat" size="large" [tabIndex]="-1" aria-hidden="true" (click)="show = false"> </button> </div> </div> </div> <div class="k-actionsheet-content"> <ng-container *ngTemplateOutlet="timeSelectorTemplate"></ng-container> </div> <div class="k-actions k-actions-stretched k-actions-horizontal k-actionsheet-footer"> <button kendoButton type="button" (click)="handleReject()" size="large" class="k-time-cancel" [attr.title]="localization.get('cancelLabel')" [attr.aria-label]="localization.get('cancelLabel')" > {{localization.get('cancel')}} </button> <button kendoButton type="button" (click)="handleActionSheetAccept()" size="large" class="k-time-accept" themeColor="primary" [attr.title]="localization.get('acceptLabel')" [attr.aria-label]="localization.get('acceptLabel')" > {{localization.get('accept')}} </button> </div> </ng-template> </kendo-actionsheet> <ng-template #timeSelectorTemplate> <kendo-timeselector #timeSelector [class.k-timeselector-lg]="isAdaptive" [cancelButton]="cancelButton && !this.isAdaptive" [setButton]="!isAdaptive" [nowButton]="nowButton" [format]="format" [min]="min" [max]="max" [steps]="steps" [value]="value" [isAdaptiveEnabled]="isAdaptiveModeEnabled" [kendoEventsOutsideAngular]="{ keydown: handleKeydown, mousedown: handleMousedown }" [scope]="this" (valueChange)="handleChange($event)" (valueReject)="handleReject()" (tabOutLastPart)="onTabOutLastPart()" (tabOutFirstPart)="onTabOutFirstPart()" (tabOutNow)="onTabOutNow()" > <kendo-timeselector-messages [acceptLabel]="localization.get('acceptLabel')" [accept]="localization.get('accept')" [cancelLabel]="localization.get('cancelLabel')" [cancel]="localization.get('cancel')" [nowLabel]="localization.get('nowLabel')" [now]="localization.get('now')" [hour]="localization.get('hour')" [minute]="localization.get('minute')" [second]="localization.get('second')" [millisecond]="localization.get('millisecond')" [dayperiod]="localization.get('dayperiod')" > </kendo-timeselector-messages> </kendo-timeselector> </ng-template> `, isInline: true, dependencies: [{ kind: "directive", type: TimePickerLocalizedMessagesDirective, selector: "[kendoTimePickerLocalizedMessages]" }, { kind: "component", type: DateInputComponent, selector: "kendo-dateinput", inputs: ["focusableId", "pickerType", "clearButton", "disabled", "readonly", "title", "tabindex", "role", "ariaReadOnly", "tabIndex", "isRequired", "format", "formatPlaceholder", "placeholder", "steps", "max", "min", "rangeValidation", "autoCorrectParts", "autoSwitchParts", "autoSwitchKeys", "allowCaretMode", "autoFill", "incompleteDateValidation", "twoDigitYearMax", "enableMouseWheel", "value", "spinners", "isPopupOpen", "hasPopup", "size", "rounded", "fillMode", "inputAttributes"], outputs: ["valueChange", "valueUpdate", "focus", "blur"], exportAs: ["kendo-dateinput"] }, { kind: "component", type: DateInputCustomMessagesComponent, selector: "kendo-dateinput-messages" }, { kind: "directive", type: EventsOutsideAngularDirective, selector: "[kendoEventsOutsideAngular]", inputs: ["kendoEventsOutsideAngular", "scope"] }, { kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "comp