@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).
415 lines (414 loc) • 18.1 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, ChangeDetectorRef, EventEmitter, Input, Output, TemplateRef, Renderer2, NgZone, ElementRef } from '@angular/core';
import { Subscription } from 'rxjs';
import { CalendarViewEnum } from './models/view.enum';
import { BusViewService } from './services/bus-view.service';
import { DisabledDatesService } from './services/disabled-dates.service';
import { IntlService } from '@progress/kendo-angular-intl';
import { cloneDate, weekInYear } from '@progress/kendo-date-math';
import { getToday, last, setTime, stringifyClassObject } from '../util';
import { closestInScope } from '../common/dom-queries';
import { isPresent } from '../common/utils';
import { KForOf } from './for.directive';
import { NgIf, NgTemplateOutlet, NgClass } from '@angular/common';
import * as i0 from "@angular/core";
import * as i1 from "./services/bus-view.service";
import * as i2 from "@progress/kendo-angular-intl";
import * as i3 from "./services/disabled-dates.service";
/**
* @hidden
*/
export class ViewComponent {
bus;
intl;
cdr;
element;
zone;
renderer;
disabledDatesService;
allowReverse;
showOtherMonthDays;
direction = 'vertical';
isActive = true;
activeView;
cellUID;
focusedDate;
viewDate;
activeRangeEnd;
selectionRange;
min;
max;
selectedDates = [];
get weekNumber() {
return this.showWeekNumbers && this.activeView === CalendarViewEnum.month;
}
set weekNumber(showWeekNumbers) {
this.showWeekNumbers = showWeekNumbers;
}
viewIndex;
templateRef;
weekNumberTemplateRef;
headerTitle;
cellClick = new EventEmitter();
weekNumberCellClick = new EventEmitter();
cellEnter = new EventEmitter();
cellLeave = new EventEmitter();
focusedCellId = new EventEmitter();
get ariaHidden() {
return this.headerTitle === this.title ? null : true;
}
colSpan = 0;
data;
service;
title;
subscriptions = new Subscription();
showWeekNumbers;
domEvents = [];
currentCellIndex;
constructor(bus, intl, cdr, element, zone, renderer, disabledDatesService) {
this.bus = bus;
this.intl = intl;
this.cdr = cdr;
this.element = element;
this.zone = zone;
this.renderer = renderer;
this.disabledDatesService = disabledDatesService;
this.subscriptions.add(this.intl.changes.subscribe(this.intlChange.bind(this)));
this.subscriptions.add(this.disabledDatesService.changes.subscribe(this.disabledDatesChange.bind(this)));
}
ngOnInit() {
if (this.element) {
this.zone.runOutsideAngular(() => {
this.bindEvents();
});
}
}
ngOnChanges(changes) {
this.service = this.bus.service(this.activeView);
if (!this.service) {
return;
}
this.colSpan = this.service.rowLength({ prependCell: this.weekNumber });
this.title = this.service.title(this.viewDate);
this.updateData();
if (changes.activeView) {
this.currentCellIndex = null;
}
}
ngOnDestroy() {
this.subscriptions.unsubscribe();
this.domEvents.forEach(unsubscribeCallback => unsubscribeCallback());
}
isHorizontal() {
return this.direction === 'horizontal';
}
isMonthView() {
return this.activeView === CalendarViewEnum.month;
}
shouldRenderCellContent(cellCtx) {
return isPresent(cellCtx) && (!cellCtx.isOtherMonth || (cellCtx.isOtherMonth && this.showOtherMonthDays));
}
firstDate(rowCtx) {
const ctx = this.firstWeekDateContext(rowCtx);
return ctx ? ctx.value : null;
}
getWeekNumber(date) {
if (!this.weekNumber) {
return null;
}
return weekInYear(date, this.intl.firstDay());
}
getWeekNumberContext(rowCtx) {
const ctx = this.firstWeekDateContext(rowCtx);
if (!this.weekNumber || !ctx) {
return null;
}
const weekNumber = weekInYear(ctx.value, this.intl.firstDay()).toString();
return {
formattedValue: weekNumber,
id: null,
isFocused: false,
isSelected: false,
isWeekend: false,
title: weekNumber,
value: cloneDate(ctx.value)
};
}
getStyles(context) {
if (!context.isOtherMonth && this.isActive && context.isFocused) {
this.focusedCellId.emit(context.id);
}
const { isRangeEnd, isRangeStart } = context;
const isEndActive = this.activeRangeEnd === 'end' && isRangeEnd;
const isStartActive = this.activeRangeEnd === 'start' && isRangeStart;
return stringifyClassObject({
'k-range-end': !context.isOtherMonth && isRangeEnd,
'k-range-mid': !context.isOtherMonth && context.isRangeMid,
'k-range-split-end': !context.isOtherMonth && context.isRangeSplitEnd,
'k-range-split-start': !context.isOtherMonth && context.isRangeSplitStart,
'k-range-start': !context.isOtherMonth && isRangeStart,
'k-active': isStartActive || isEndActive,
'k-focus': !context.isOtherMonth && this.isActive && context.isFocused,
'k-selected': !context.isOtherMonth && (context.isSelected || isRangeStart || isRangeEnd),
'k-today': !context.isOtherMonth && context.isToday,
'k-weekend': context.isWeekend,
'k-disabled': context.isDisabled,
'k-other-month': context.isOtherMonth
});
}
tableCellIndex(rowIndex, cellIndex) {
return `${rowIndex}:${cellIndex}`;
}
handleWeekNumberClick(week) {
const availableDates = week.filter(day => day).map(item => item.value).filter(date => !this.disabledDatesService.isDateDisabled(date));
this.weekNumberCellClick.emit(availableDates);
}
getMonthLabel(date) {
return this.activeView === 1 ? this.intl.formatDate(date, 'MMMM') : null;
}
firstWeekDateContext(rowCtx) {
if (!this.weekNumber) {
return null;
}
let idx = 0;
let ctx = this.shouldRenderCellContent(rowCtx[idx]) ? rowCtx[idx] : null;
while (!ctx && idx < rowCtx.length) {
const cellCtx = rowCtx[++idx];
ctx = this.shouldRenderCellContent(cellCtx) ? cellCtx : null;
}
return ctx;
}
updateData() {
const time = last(this.selectedDates) || getToday();
const viewDate = setTime(this.viewDate, time);
this.data = this.service.data({
cellUID: this.cellUID,
focusedDate: this.focusedDate,
isActiveView: !this.bus.canMoveDown(this.activeView),
max: this.max,
min: this.min,
selectedDates: this.selectedDates,
selectionRange: this.selectionRange,
viewDate: viewDate,
isDateDisabled: this.disabledDatesService.isDateDisabled,
direction: this.direction,
allowReverse: this.allowReverse
});
}
intlChange() {
this.updateData();
this.cdr.markForCheck();
}
disabledDatesChange() {
this.updateData();
this.cdr.markForCheck();
}
bindEvents() {
const element = this.element.nativeElement;
this.domEvents.push(this.renderer.listen(element, 'mouseover', this.cellMouseoverHandler.bind(this)), this.renderer.listen(element, 'mouseleave', this.mouseLeaveHandler.bind(this)), this.renderer.listen(element, 'click', this.clickHandler.bind(this)));
}
clickHandler(args) {
const cell = this.closestCell(args);
if (!cell) {
return;
}
const index = cell.getAttribute('data-cell-index');
const cellContext = this.cellByIndex(index);
if (!cellContext.isDisabled) {
const { ctrlKey, metaKey, shiftKey } = args;
this.cellClick.emit({
date: cellContext.value,
modifiers: { ctrlKey, metaKey, shiftKey }
});
}
}
mouseLeaveHandler() {
if (this.currentCellIndex) {
this.emitCellLeave();
}
}
cellMouseoverHandler(args) {
const cell = this.closestCell(args);
if (cell) {
const index = cell.getAttribute('data-cell-index');
if (this.currentCellIndex && this.currentCellIndex !== index) {
this.emitCellLeave();
}
const value = this.cellByIndex(index).value;
this.cellEnter.emit(value);
this.currentCellIndex = index;
}
else if (this.currentCellIndex) {
this.emitCellLeave();
}
}
closestCell(eventArgs) {
return closestInScope(eventArgs.target, node => node.hasAttribute('data-cell-index'), this.element.nativeElement);
}
emitCellLeave() {
const item = this.cellByIndex(this.currentCellIndex);
if (item) {
this.cellLeave.emit(item.value);
}
this.currentCellIndex = null;
}
cellByIndex(index) {
const [rowIndex, cellIndex] = index.split(':');
return this.data[rowIndex][cellIndex];
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ViewComponent, deps: [{ token: i1.BusViewService }, { token: i2.IntlService }, { token: i0.ChangeDetectorRef }, { token: i0.ElementRef }, { token: i0.NgZone }, { token: i0.Renderer2 }, { token: i3.DisabledDatesService }], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: ViewComponent, isStandalone: true, selector: "[kendoCalendarView]", inputs: { allowReverse: "allowReverse", showOtherMonthDays: "showOtherMonthDays", direction: "direction", isActive: "isActive", activeView: "activeView", cellUID: "cellUID", focusedDate: "focusedDate", viewDate: "viewDate", activeRangeEnd: "activeRangeEnd", selectionRange: "selectionRange", min: "min", max: "max", selectedDates: "selectedDates", weekNumber: "weekNumber", viewIndex: "viewIndex", templateRef: "templateRef", weekNumberTemplateRef: "weekNumberTemplateRef", headerTitle: "headerTitle" }, outputs: { cellClick: "cellClick", weekNumberCellClick: "weekNumberCellClick", cellEnter: "cellEnter", cellLeave: "cellLeave", focusedCellId: "focusedCellId" }, usesOnChanges: true, ngImport: i0, template: `
<ng-template #emptyCell><td class="k-empty k-calendar-td" role="gridcell"> </td></ng-template>
<tr *ngIf="!isHorizontal()" class="k-calendar-tr" role="row" [attr.aria-hidden]="ariaHidden"><th class="k-calendar-caption" scope="col" [colSpan]="colSpan">{{title}}</th></tr>
<tr *kFor="let row of data; let rowIndex = index" class="k-calendar-tr" role="row">
<ng-template [ngIf]="weekNumber">
<td
class="k-alt k-calendar-td"
role="gridcell"
*ngIf="firstDate(row); else emptyCell"
(click)="handleWeekNumberClick(row)"
>
<ng-template [ngIf]="!weekNumberTemplateRef">
{{getWeekNumber(firstDate(row))}}
</ng-template>
<ng-template
[ngIf]="weekNumberTemplateRef"
[ngTemplateOutlet]="weekNumberTemplateRef"
[ngTemplateOutletContext]="{
$implicit: firstDate(row),
cellContext: getWeekNumberContext(row)
}"
></ng-template>
</td>
</ng-template>
<ng-container *kFor="let cell of row; let cellIndex = index">
<td class="k-calendar-td"
*ngIf="shouldRenderCellContent(cell); else emptyCell"
role="gridcell"
[attr.id]="cell.id"
[attr.data-cell-index]="tableCellIndex(rowIndex, cellIndex)"
[attr.aria-selected]="cell.isSelected || cell.isRangeStart || cell.isRangeMid || cell.isRangeEnd"
[attr.aria-disabled]="cell.isDisabled"
[attr.aria-label]="getMonthLabel(cell.value)"
[ngClass]="getStyles(cell)"
[title]="cell.title"
>
<span class="k-link">
<ng-template [ngIf]="!templateRef">{{cell.formattedValue}}</ng-template>
<ng-template
*ngIf="templateRef"
[ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{ $implicit: cell.value, cellContext: cell }"
></ng-template>
</span>
</td>
</ng-container>
</tr>
`, isInline: true, dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: KForOf, selector: "[kFor][kForOf]", inputs: ["kForOf", "kForTrackBy", "kForTemplate"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: ViewComponent, decorators: [{
type: Component,
args: [{
// eslint-disable-next-line
selector: '[kendoCalendarView]',
template: `
<ng-template #emptyCell><td class="k-empty k-calendar-td" role="gridcell"> </td></ng-template>
<tr *ngIf="!isHorizontal()" class="k-calendar-tr" role="row" [attr.aria-hidden]="ariaHidden"><th class="k-calendar-caption" scope="col" [colSpan]="colSpan">{{title}}</th></tr>
<tr *kFor="let row of data; let rowIndex = index" class="k-calendar-tr" role="row">
<ng-template [ngIf]="weekNumber">
<td
class="k-alt k-calendar-td"
role="gridcell"
*ngIf="firstDate(row); else emptyCell"
(click)="handleWeekNumberClick(row)"
>
<ng-template [ngIf]="!weekNumberTemplateRef">
{{getWeekNumber(firstDate(row))}}
</ng-template>
<ng-template
[ngIf]="weekNumberTemplateRef"
[ngTemplateOutlet]="weekNumberTemplateRef"
[ngTemplateOutletContext]="{
$implicit: firstDate(row),
cellContext: getWeekNumberContext(row)
}"
></ng-template>
</td>
</ng-template>
<ng-container *kFor="let cell of row; let cellIndex = index">
<td class="k-calendar-td"
*ngIf="shouldRenderCellContent(cell); else emptyCell"
role="gridcell"
[attr.id]="cell.id"
[attr.data-cell-index]="tableCellIndex(rowIndex, cellIndex)"
[attr.aria-selected]="cell.isSelected || cell.isRangeStart || cell.isRangeMid || cell.isRangeEnd"
[attr.aria-disabled]="cell.isDisabled"
[attr.aria-label]="getMonthLabel(cell.value)"
[ngClass]="getStyles(cell)"
[title]="cell.title"
>
<span class="k-link">
<ng-template [ngIf]="!templateRef">{{cell.formattedValue}}</ng-template>
<ng-template
*ngIf="templateRef"
[ngTemplateOutlet]="templateRef"
[ngTemplateOutletContext]="{ $implicit: cell.value, cellContext: cell }"
></ng-template>
</span>
</td>
</ng-container>
</tr>
`,
standalone: true,
imports: [NgIf, KForOf, NgTemplateOutlet, NgClass]
}]
}], ctorParameters: function () { return [{ type: i1.BusViewService }, { type: i2.IntlService }, { type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i0.NgZone }, { type: i0.Renderer2 }, { type: i3.DisabledDatesService }]; }, propDecorators: { allowReverse: [{
type: Input
}], showOtherMonthDays: [{
type: Input
}], direction: [{
type: Input
}], isActive: [{
type: Input
}], activeView: [{
type: Input
}], cellUID: [{
type: Input
}], focusedDate: [{
type: Input
}], viewDate: [{
type: Input
}], activeRangeEnd: [{
type: Input
}], selectionRange: [{
type: Input
}], min: [{
type: Input
}], max: [{
type: Input
}], selectedDates: [{
type: Input
}], weekNumber: [{
type: Input
}], viewIndex: [{
type: Input
}], templateRef: [{
type: Input
}], weekNumberTemplateRef: [{
type: Input
}], headerTitle: [{
type: Input
}], cellClick: [{
type: Output
}], weekNumberCellClick: [{
type: Output
}], cellEnter: [{
type: Output
}], cellLeave: [{
type: Output
}], focusedCellId: [{
type: Output
}] } });