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

565 lines (558 loc) 25.6 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ /* eslint-disable @angular-eslint/component-selector */ import { Component, ChangeDetectionStrategy, ChangeDetectorRef, EventEmitter, HostBinding, Input, Output, TemplateRef, ElementRef, Renderer2, HostListener } from '@angular/core'; import { cloneDate } from '@progress/kendo-date-math'; import { BusViewService } from './services/bus-view.service'; import { IntlService } from '@progress/kendo-angular-intl'; import { Action } from './models/navigation-action.enum'; import { CalendarViewEnum } from './models/view.enum'; import { MIN_DATE, MAX_DATE } from '../defaults'; import { dateInRange, hasChange, shiftWeekNames } from '../util'; import { isDocumentAvailable } from '@progress/kendo-angular-common'; import { attributeNames, isPresent } from '../common/utils'; import { KForOf } from './for.directive'; import { ViewComponent } from './view.component'; import { NgClass, NgIf, NgFor, NgTemplateOutlet } 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"; const DEFAULT_VIEWS_LENGTH = 2; /** * @hidden */ export class HorizontalViewListComponent { bus; intl; cdr; element; renderer; handleMultiViewCalendarFocus() { this.focusCalendar.emit(); } handleMultiViewCalendarBlur(event) { this.blurCalendar.emit(event); } /** * Needed for the MultiViewCalendar used in the Scheduler Year view */ showOtherMonthDays = true; cellTemplateRef; weekNumberTemplateRef; allowReverse; activeRangeEnd; activeView = CalendarViewEnum.month; cellUID; focusedDate; isActive = true; min = new Date(MIN_DATE); max = new Date(MAX_DATE); selectionRange; selectedDates = []; views = DEFAULT_VIEWS_LENGTH; showViewHeader = false; animateNavigation = false; orientation = 'horizontal'; activeDescendant; tabIndex = 0; disabled = false; id; weekDaysFormat = 'short'; get weekNumber() { return this.showWeekNumbers && this.isMonthView(); } set weekNumber(showWeekNumbers) { this.showWeekNumbers = showWeekNumbers; } cellClick = new EventEmitter(); weekNumberCellClick = new EventEmitter(); cellEnter = new EventEmitter(); cellLeave = new EventEmitter(); activeDateChange = new EventEmitter(); focusCalendar = new EventEmitter(); blurCalendar = new EventEmitter(); focusedCellChange = new EventEmitter(); getComponentClass = true; get horizontalHostClass() { return this.orientation === 'horizontal'; } get verticalHostClass() { return this.orientation === 'vertical'; } get getComponentMonthClass() { return this.activeView === CalendarViewEnum.month; } get getComponentYearClass() { return this.activeView === CalendarViewEnum.year; } get getComponentDecadeClass() { return this.activeView === CalendarViewEnum.decade; } get getComponentCenturyClass() { return this.activeView === CalendarViewEnum.century; } get role() { return this.views >= 2 ? 'grid' : null; } get tabindex() { return this.views >= 2 ? '0' : null; } get getActiveDescendant() { return this.views === 1 ? this.activeDescendant : null; } get getTabIndex() { return this.disabled || this.views >= 2 ? null : this.tabIndex; } service; weekNames = []; wideWeekNames = []; activeDate; dates = []; skip; total; nextAnimationDate; prevAnimationDate; showWeekNumbers; intlSubscription; animation; constructor(bus, intl, cdr, element, renderer) { this.bus = bus; this.intl = intl; this.cdr = cdr; this.element = element; this.renderer = renderer; this.intlSubscription = this.intl.changes.subscribe(this.intlChange.bind(this)); } ngOnChanges(changes) { this.initService(); if (this.weekNames.length === 0 || changes.weekNumber) { this.weekNames = this.getWeekNames(this.weekDaysFormat); this.wideWeekNames = this.getWeekNames('wide'); } if (changes.weekDaysFormat && this.weekNames.length !== 0 && !changes.weekNumber) { this.weekNames = this.getWeekNames(this.weekDaysFormat); } if (!this.service) { return; } this.views = this.views || DEFAULT_VIEWS_LENGTH; const focusedDate = this.focusedDate; const viewDate = this.clampDate(this.service.viewDate(focusedDate, this.max, this.views)); this.skip = this.service.skip(viewDate, this.min); this.total = this.service.total(this.min, this.max); const activeViewChanged = hasChange(changes, 'activeView'); const viewsHasChanged = this.views > 0 && hasChange(changes, 'views'); if (activeViewChanged || !this.isInDates(focusedDate) || viewsHasChanged || !this.activeDate) { this.dates = this.service.datesList(viewDate, this.getTake(this.skip)); this.activeDate = cloneDate(this.dates[0]); this.activeDateChange.emit(this.activeDate); } this.setAriaActivedescendant(); //set tabindex for MultiViewCalendar if (this.views >= 2) { this.renderer.setAttribute(this.element.nativeElement, 'tabindex', this.tabIndex.toString()); } } ngOnDestroy() { this.intlSubscription.unsubscribe(); } initService() { this.service = this.bus.service(this.activeView); } isMonthView() { return this.activeView === CalendarViewEnum.month; } getCaptionTitle(date) { return this.service.title(date); } handleClassicCalendarFocus() { this.focusCalendar.emit(); } handleClassicCalendarBlur(event) { this.blurCalendar.emit(event); } animateView(action) { const container = this.element.nativeElement; const table = container.querySelector('table'); // the whole width excluding padding/margin const initialContainerWidth = parseFloat(getComputedStyle(container).width); // table width const tableWidth = parseFloat(getComputedStyle(table).width); this.renderer.setStyle(container, 'width', `${initialContainerWidth}px`); this.renderer.setStyle(container, 'overflow', 'visible'); // initialize an additional view for the animation if (action === Action.NextView) { // animating Action.NextView requires adding an additional view before the rendered views this.nextAnimationDate = cloneDate(this.dates[0]); } else { // animating Action.PrevView requires adding an additional view after the rendered views this.prevAnimationDate = cloneDate(this.dates[this.dates.length - 1]); } // run cdr to render the additional view this.cdr.detectChanges(); container.querySelectorAll('table').forEach(table => { this.renderer.setStyle(table, 'width', `${tableWidth}px`); }); // we always slide by the width of 1 table // cross-browser compatibility is ensured by measuring the client rectangle and substracting the gap const tabRect = table.getBoundingClientRect(); const containerGap = parseFloat(getComputedStyle(container).columnGap); const start = action === Action.NextView ? 'translateX(0)' : `translateX(-${tabRect.width + containerGap}px)`; const end = action === Action.NextView ? `translateX(-${tabRect.width + containerGap}px)` : 'translateX(0)'; if (!this.animation) { this.animation = container.animate([ { transform: start }, { transform: end } ], { duration: 500, easing: 'ease-out' }); this.animation.oncancel = this.animation.onfinish = () => { // clear all inline styles this.renderer.removeStyle(container, 'width'); this.renderer.removeStyle(container, 'overflow'); container.querySelectorAll('table').forEach(table => { this.renderer.removeStyle(table, 'width'); }); // clear the animation and the animation view this.animation = null; this.nextAnimationDate = null; this.prevAnimationDate = null; // run cdr to remove additional animation view from the markup this.cdr.detectChanges(); }; } else { // if animation is already running, cancel it and show the end navigation result on multiple prev/next button clicks this.animation.cancel(); } } navigate(action) { if (this.animateNavigation && isDocumentAvailable() && isPresent(this.element.nativeElement.animate)) { this.animateView(action); } const candidate = this.move(action); const list = this.service.datesList(candidate, this.getTake(this.skip)); if (this.isListInRange(list)) { this.dates = list; } this.activeDate = cloneDate(this.dates[0]); this.focusedDate = cloneDate(candidate); this.cdr.markForCheck(); this.activeDateChange.emit(this.activeDate); return cloneDate(candidate); } canNavigate(action) { if (!this.service) { return false; } return this.isListInRange(this.service.datesList(this.move(action), this.getTake(this.skip))); } getWeekNames(type) { const weekNames = shiftWeekNames(this.intl.dateFormatNames({ nameType: type, type: 'days' }), this.intl.firstDay()); return this.weekNumber ? [''].concat(weekNames) : weekNames; } intlChange() { this.weekNames = this.getWeekNames(this.weekDaysFormat); this.wideWeekNames = this.getWeekNames('wide'); this.cdr.markForCheck(); } clampDate(value) { return dateInRange(value, this.min, this.max); } move(action) { return this.service.move(this.dates[0] || this.focusedDate, action); } isListInRange(list) { const lowerBound = this.service.beginningOfPeriod(this.min); const upperBound = this.service.beginningOfPeriod(this.service.addToDate(this.max, 1)); return lowerBound <= list[0] && list[list.length - 1] < upperBound; } isInDates(value) { return this.service.isInArray(value, this.dates); } getTake(skip) { return Math.min(this.total - skip, this.views); } setAriaActivedescendant() { if (this.views >= 2) { this.renderer.setAttribute(this.element.nativeElement, attributeNames.ariaActiveDescendant, this.activeDescendant); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HorizontalViewListComponent, deps: [{ token: i1.BusViewService }, { token: i2.IntlService }, { token: i0.ChangeDetectorRef }, { token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: HorizontalViewListComponent, isStandalone: true, selector: "kendo-calendar-horizontal", inputs: { showOtherMonthDays: "showOtherMonthDays", cellTemplateRef: "cellTemplateRef", weekNumberTemplateRef: "weekNumberTemplateRef", allowReverse: "allowReverse", activeRangeEnd: "activeRangeEnd", activeView: "activeView", cellUID: "cellUID", focusedDate: "focusedDate", isActive: "isActive", min: "min", max: "max", selectionRange: "selectionRange", selectedDates: "selectedDates", views: "views", showViewHeader: "showViewHeader", animateNavigation: "animateNavigation", orientation: "orientation", activeDescendant: "activeDescendant", tabIndex: "tabIndex", disabled: "disabled", id: "id", weekDaysFormat: "weekDaysFormat", weekNumber: "weekNumber" }, outputs: { cellClick: "cellClick", weekNumberCellClick: "weekNumberCellClick", cellEnter: "cellEnter", cellLeave: "cellLeave", activeDateChange: "activeDateChange", focusCalendar: "focusCalendar", blurCalendar: "blurCalendar", focusedCellChange: "focusedCellChange" }, host: { listeners: { "focus": "handleMultiViewCalendarFocus()", "blur": "handleMultiViewCalendarBlur($event)" }, properties: { "class.k-calendar-view": "this.getComponentClass", "class.k-align-items-start": "this.getComponentClass", "class.k-justify-content-center": "this.getComponentClass", "class.k-hstack": "this.horizontalHostClass", "class.k-vstack": "this.verticalHostClass", "class.k-calendar-monthview": "this.getComponentMonthClass", "class.k-calendar-yearview": "this.getComponentYearClass", "class.k-calendar-decadeview": "this.getComponentDecadeClass", "class.k-calendar-centuryview": "this.getComponentCenturyClass", "attr.role": "this.role", "attr.tabindex": "this.tabindex" } }, usesOnChanges: true, ngImport: i0, template: ` <ng-template #tableTemplate let-date="date" let-class="className"> <table [attr.role]="views >= 2 ? 'none' : 'grid'" class="k-calendar-table" [ngClass]="class" [attr.aria-labelledby]="id" [attr.aria-activedescendant]="getActiveDescendant" [attr.tabindex]="getTabIndex" (focus)="handleClassicCalendarFocus()" (blur)="handleClassicCalendarBlur($event)" > <caption *ngIf="showViewHeader" class="k-calendar-caption">{{ getCaptionTitle(date) }}</caption> <thead *ngIf="isMonthView()" class="k-calendar-thead" role="rowgroup"> <tr class="k-calendar-tr" role="row"> <th *ngFor="let name of weekNames; let i = index;" class="k-calendar-th" scope="col" [attr.aria-label]="wideWeekNames[i]" role="columnheader" >{{name}}</th> </tr> </thead> <tbody class="k-calendar-tbody" kendoCalendarView [allowReverse]="allowReverse" [showOtherMonthDays]="showOtherMonthDays" role="rowgroup" direction="horizontal" [activeView]="activeView" [isActive]="isActive" [min]="min" [max]="max" [cellUID]="cellUID" [focusedDate]="focusedDate" [selectedDates]="selectedDates" [selectionRange]="selectionRange" [activeRangeEnd]="activeRangeEnd" [weekNumber]="weekNumber" [templateRef]="cellTemplateRef" [weekNumberTemplateRef]="weekNumberTemplateRef" [viewDate]="date" (cellClick)="cellClick.emit($event)" (weekNumberCellClick)="weekNumberCellClick.emit($event)" (cellEnter)="cellEnter.emit($event)" (cellLeave)="cellLeave.emit($event)" (focusedCellId)="focusedCellChange.emit($event)" > </tbody> </table> </ng-template> <!-- When Next is clicked a placeholder table is rendered before the Main Table --> <ng-template *ngIf="nextAnimationDate" [ngTemplateOutlet]="tableTemplate" [ngTemplateOutletContext]="{ date: nextAnimationDate, className: 'k-pointer-events-none' }" > </ng-template> <ng-template *kFor="let date of dates" [ngTemplateOutlet]="tableTemplate" [ngTemplateOutletContext]="{ date: date }" > </ng-template> <!-- When Prev is clicked a placeholder table is rendered after the Main Table --> <ng-template *ngIf="prevAnimationDate" [ngTemplateOutlet]="tableTemplate" [ngTemplateOutletContext]="{ date: prevAnimationDate, className: 'k-pointer-events-none' }" > </ng-template> `, isInline: true, dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: ViewComponent, selector: "[kendoCalendarView]", inputs: ["allowReverse", "showOtherMonthDays", "direction", "isActive", "activeView", "cellUID", "focusedDate", "viewDate", "activeRangeEnd", "selectionRange", "min", "max", "selectedDates", "weekNumber", "viewIndex", "templateRef", "weekNumberTemplateRef", "headerTitle"], outputs: ["cellClick", "weekNumberCellClick", "cellEnter", "cellLeave", "focusedCellId"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: KForOf, selector: "[kFor][kForOf]", inputs: ["kForOf", "kForTrackBy", "kForTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: HorizontalViewListComponent, decorators: [{ type: Component, args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'kendo-calendar-horizontal', template: ` <ng-template #tableTemplate let-date="date" let-class="className"> <table [attr.role]="views >= 2 ? 'none' : 'grid'" class="k-calendar-table" [ngClass]="class" [attr.aria-labelledby]="id" [attr.aria-activedescendant]="getActiveDescendant" [attr.tabindex]="getTabIndex" (focus)="handleClassicCalendarFocus()" (blur)="handleClassicCalendarBlur($event)" > <caption *ngIf="showViewHeader" class="k-calendar-caption">{{ getCaptionTitle(date) }}</caption> <thead *ngIf="isMonthView()" class="k-calendar-thead" role="rowgroup"> <tr class="k-calendar-tr" role="row"> <th *ngFor="let name of weekNames; let i = index;" class="k-calendar-th" scope="col" [attr.aria-label]="wideWeekNames[i]" role="columnheader" >{{name}}</th> </tr> </thead> <tbody class="k-calendar-tbody" kendoCalendarView [allowReverse]="allowReverse" [showOtherMonthDays]="showOtherMonthDays" role="rowgroup" direction="horizontal" [activeView]="activeView" [isActive]="isActive" [min]="min" [max]="max" [cellUID]="cellUID" [focusedDate]="focusedDate" [selectedDates]="selectedDates" [selectionRange]="selectionRange" [activeRangeEnd]="activeRangeEnd" [weekNumber]="weekNumber" [templateRef]="cellTemplateRef" [weekNumberTemplateRef]="weekNumberTemplateRef" [viewDate]="date" (cellClick)="cellClick.emit($event)" (weekNumberCellClick)="weekNumberCellClick.emit($event)" (cellEnter)="cellEnter.emit($event)" (cellLeave)="cellLeave.emit($event)" (focusedCellId)="focusedCellChange.emit($event)" > </tbody> </table> </ng-template> <!-- When Next is clicked a placeholder table is rendered before the Main Table --> <ng-template *ngIf="nextAnimationDate" [ngTemplateOutlet]="tableTemplate" [ngTemplateOutletContext]="{ date: nextAnimationDate, className: 'k-pointer-events-none' }" > </ng-template> <ng-template *kFor="let date of dates" [ngTemplateOutlet]="tableTemplate" [ngTemplateOutletContext]="{ date: date }" > </ng-template> <!-- When Prev is clicked a placeholder table is rendered after the Main Table --> <ng-template *ngIf="prevAnimationDate" [ngTemplateOutlet]="tableTemplate" [ngTemplateOutletContext]="{ date: prevAnimationDate, className: 'k-pointer-events-none' }" > </ng-template> `, standalone: true, imports: [NgClass, NgIf, NgFor, ViewComponent, NgTemplateOutlet, KForOf] }] }], ctorParameters: function () { return [{ type: i1.BusViewService }, { type: i2.IntlService }, { type: i0.ChangeDetectorRef }, { type: i0.ElementRef }, { type: i0.Renderer2 }]; }, propDecorators: { handleMultiViewCalendarFocus: [{ type: HostListener, args: ["focus"] }], handleMultiViewCalendarBlur: [{ type: HostListener, args: ["blur", ['$event']] }], showOtherMonthDays: [{ type: Input }], cellTemplateRef: [{ type: Input }], weekNumberTemplateRef: [{ type: Input }], allowReverse: [{ type: Input }], activeRangeEnd: [{ type: Input }], activeView: [{ type: Input }], cellUID: [{ type: Input }], focusedDate: [{ type: Input }], isActive: [{ type: Input }], min: [{ type: Input }], max: [{ type: Input }], selectionRange: [{ type: Input }], selectedDates: [{ type: Input }], views: [{ type: Input }], showViewHeader: [{ type: Input }], animateNavigation: [{ type: Input }], orientation: [{ type: Input }], activeDescendant: [{ type: Input }], tabIndex: [{ type: Input }], disabled: [{ type: Input }], id: [{ type: Input }], weekDaysFormat: [{ type: Input }], weekNumber: [{ type: Input }], cellClick: [{ type: Output }], weekNumberCellClick: [{ type: Output }], cellEnter: [{ type: Output }], cellLeave: [{ type: Output }], activeDateChange: [{ type: Output }], focusCalendar: [{ type: Output }], blurCalendar: [{ type: Output }], focusedCellChange: [{ type: Output }], getComponentClass: [{ type: HostBinding, args: ["class.k-calendar-view"] }, { type: HostBinding, args: ["class.k-align-items-start"] }, { type: HostBinding, args: ["class.k-justify-content-center"] }], horizontalHostClass: [{ type: HostBinding, args: ["class.k-hstack"] }], verticalHostClass: [{ type: HostBinding, args: ["class.k-vstack"] }], getComponentMonthClass: [{ type: HostBinding, args: ["class.k-calendar-monthview"] }], getComponentYearClass: [{ type: HostBinding, args: ["class.k-calendar-yearview"] }], getComponentDecadeClass: [{ type: HostBinding, args: ["class.k-calendar-decadeview"] }], getComponentCenturyClass: [{ type: HostBinding, args: ["class.k-calendar-centuryview"] }], role: [{ type: HostBinding, args: ['attr.role'] }], tabindex: [{ type: HostBinding, args: ['attr.tabindex'] }] } });