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).

415 lines (414 loc) 18.1 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, 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">&nbsp;</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">&nbsp;</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 }] } });