UNPKG

@progress/kendo-angular-scheduler

Version:

Kendo UI Scheduler Angular - Outlook or Google-style angular scheduler calendar. Full-featured and customizable embedded scheduling from the creator developers trust for professional UI components.

433 lines (432 loc) 22.3 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import { ChangeDetectorRef, Component, ElementRef, Input, NgZone, Renderer2, ViewChild, } from '@angular/core'; import { ScrollbarWidthService } from '@progress/kendo-angular-common'; import { ViewContextService } from '../view-context.service'; import { ViewStateService } from '../view-state.service'; import { IntlService } from '@progress/kendo-angular-intl'; import { PDFService } from '../../pdf/pdf.service'; import { LocalizationService } from '@progress/kendo-angular-l10n'; import { addDays, addYears, toLocalDate } from '@progress/kendo-date-math'; import { TooltipDirective } from '@progress/kendo-angular-tooltip'; import { formatEventTime, toUTCDate } from '../utils'; import { caretAltLeftIcon, caretAltRightIcon } from '@progress/kendo-svg-icons'; import { FocusService } from '../../navigation'; import { MultiViewCalendarComponent, MonthCellTemplateDirective } from '@progress/kendo-angular-dateinputs'; import { BaseView } from '../common/base-view'; import { createTasks, noop, yearEnd, yearStart } from './utils'; import { MonthSlotService } from '../month/month-slot.service'; import { IconWrapperComponent } from '@progress/kendo-angular-icons'; import { NgIf, NgFor, NgClass, NgStyle } from '@angular/common'; import { take } from 'rxjs/operators'; import * as i0 from "@angular/core"; import * as i1 from "@progress/kendo-angular-l10n"; import * as i2 from "../../navigation"; import * as i3 from "@progress/kendo-angular-intl"; import * as i4 from "../view-context.service"; import * as i5 from "../view-state.service"; import * as i6 from "../../pdf/pdf.service"; import * as i7 from "../month/month-slot.service"; import * as i8 from "@progress/kendo-angular-common"; const today = new Date(Date.now()); const getDateAttribute = (element) => element?.querySelector('span>span[date]')?.getAttribute('date'); /** * @hidden */ export class YearViewInternalComponent extends BaseView { localization; focusService; intl; /** * Calculates the next or previous range to be displayed */ newRange; /** * Determines the displayed date range and formats the selected date */ dateRangeFn; calendar; tooltip; get arrowIcons() { return !this.localization.rtl ? ['caret-alt-left', 'caret-alt-right'] : ['caret-alt-right', 'caret-alt-left']; } get arrowSVGIcons() { return !this.localization.rtl ? [this.caretAltLeftIcon, this.caretAltRightIcon] : [this.caretAltRightIcon, this.caretAltLeftIcon]; } caretAltLeftIcon = caretAltLeftIcon; caretAltRightIcon = caretAltRightIcon; eventsPerSelectedDay = []; days = []; focusedDate; currentTd; tds = []; isTooltipClicked = false; constructor(localization, focusService, intl, viewContext, viewState, zone, renderer, pdfService, element, slotService, scrollBarWidthService, changeDetector) { super(viewContext, viewState, intl, slotService, zone, renderer, element, pdfService, localization, changeDetector, scrollBarWidthService); this.localization = localization; this.focusService = focusService; this.intl = intl; } ngAfterViewInit() { this.updateTds(); super.ngAfterViewInit(); this.focusedDate = new Date(this.selectedDate.getFullYear(), today.getMonth(), today.getDate()); } getSlotClass(date) { if (this.slotClass) { return this.slotClass({ start: date, end: addDays(date, 1), events: this.eventsPerDay(date), }); } } getEventClasses(item, resources, isAllDay) { if (this.eventClass) { return this.eventClass({ event: item.event, resources, isAllDay, }); } } onBlur() { if (!this.isTooltipClicked) { this.tooltip.hide(); } this.isTooltipClicked = false; } getDate(element) { return new Date(getDateAttribute(element.nativeElement)); } eventTitle(event) { const startTime = toLocalDate(event.startTime); const endTime = toLocalDate(event.endTime); const time = formatEventTime(startTime, endTime, event.isAllDay, this.intl.localeId); return `${time}, ${event.event.title}`; } onClick(event) { if (event.target.tagName === 'SPAN') { const clickedDate = getDateAttribute(event.target.closest('td.k-calendar-td')); if (clickedDate === getDateAttribute(this.currentTd)) { this.tooltip.show(this.currentTd); } } else { this.tooltip.hide(); } } onMouseDown() { this.isTooltipClicked = true; } onKeydown(event) { if (event.key === 'Backspace' || event.key === 'Delete') { this.tooltip.hide(); } if (event.key === 'Enter' && !this.tooltip.popupRef) { this.tooltip.show(this.currentTd); return; } if (event.key === 'Enter' && this.tooltip.popupRef && (getDateAttribute(this.currentTd) === this.calendar.focusedDate.toString())) { this.navigateToDay(new Date(getDateAttribute(this.currentTd))); } } navigateToDay(date) { this.tooltip.hide(); this.zone.run(() => { this.viewState.navigateTo({ viewName: 'day', date: new Date(date) }); }); } eventsPerDay(date) { return this.tasksPerDay(date)?.map((task) => task.event) || []; } tasksPerDay(date) { if (this.resources?.length > 0) { return this.tasks?.filter((event) => event.resources.length > 0 && event.startTime.getUTCDate() === date.getDate() && event.startTime.getUTCMonth() === date.getMonth()); } else { return this.tasks?.filter((event) => event.startTime.getUTCDate() === date.getDate() && event.startTime.getUTCMonth() === date.getMonth()); } } onValueChange(date) { this.eventsPerSelectedDay = this.tasksPerDay(date); this.currentTd = this.tds.find((td) => getDateAttribute(td) === date.toString()); if (this.tooltip.popupRef) { this.tooltip.hide(); } } hasEvent(date) { return this.tasksPerDay(date).length > 0; } createPDFElement() { const element = this.element.nativeElement.cloneNode(true); element.style.width = `${this.element.nativeElement.offsetWidth}px`; element.querySelector('.k-scheduler-layout').style.height = 'auto'; this.pdfService.elementReady.emit({ element: element, }); } onSelectDate(date) { const year = date.getFullYear(); const start = yearStart(year); this.focusedDate = new Date(year, start.getMonth(), today.getDate()); this.selectedDate = start; const dateRange = this.dateRange(date); this.viewState.notifyDateRange(dateRange); this.days = this.createDaySlots(dateRange); if (this.calendar) { this.calendar.min = start; this.calendar.max = yearEnd(start.getFullYear()); // wait for the view to update to get the new tds this.zone.onStable.pipe(take(1)).subscribe(() => this.updateTds()); } } onAction(e) { const now = this.selectedDate; if (e.type === 'next' || e.type === 'prev') { const offset = e.type === 'next' ? 1 : -1; const next = addYears(now, offset); this.viewState.notifyNextDate(next); this.calendar.min = next; this.calendar.max = yearEnd(next.getFullYear()); this.focusedDate = new Date(next.getFullYear(), today.getMonth(), today.getDate()); } } createTasks(items, dateRange) { this.days = this.createDaySlots(dateRange); return createTasks(dateRange.start, dateRange.end, items, this.days); } reflow() { this.updateContentHeight(); const content = this.content.nativeElement; if (this.contentHeight === 'auto') { // bigger size changes cause the table to overflow the container and in horizontal scrollbars // this changes the table and slots size during rendering before the browser re-adjusts the 100% table width content.style.overflow = 'visible'; } if (this.contentHeight === 'auto') { content.style.overflow = ''; } } dateRange(date = this.selectedDate) { return this.dateRangeFn(date); } onTasksChange() { this.items.next(this.tasks); } slotByIndex = (slotIndex, _args) => noop(slotIndex); dragHintSize = (startSlot, _endSlot) => noop(startSlot); dragRanges = (slot) => noop(slot); slotByPosition = (x, _y, _container) => noop(x); createDaySlots({ start }) { const days = []; const monthsPerYear = 12; let date = start; for (let idx = 0; idx < monthsPerYear; idx++) { const monthTotalDays = this.getLastDayOfMonth(date.getFullYear(), date.getMonth()); for (let dayIdx = 0; dayIdx < monthTotalDays; dayIdx++) { days.push(date); const nextDay = addDays(date, 1); date = nextDay; } } return days; } getLastDayOfMonth(year, month) { return new Date(year, month + 1, 0).getDate(); } cachedTds = []; updateTds() { this.cachedTds.forEach((td) => { this.renderer.setAttribute(td, 'class', 'k-calendar-td'); }); this.cachedTds = []; this.tds = Array.from(this.calendar.element.nativeElement.querySelectorAll('.k-calendar-td:not(.k-empty)')); if (this.calendar.value instanceof Date && this.calendar.value.getFullYear() === this.calendar.min?.getFullYear()) { this.currentTd = this.tds.find((td) => getDateAttribute(td) === this.calendar.value.toString()); } if (this.slotClass) { this.tds.forEach((td) => { const date = toUTCDate(new Date(getDateAttribute(td))); const userClass = this.getSlotClass(date); if (userClass) { this.renderer.addClass(td, userClass); this.cachedTds.push(td); } }); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: YearViewInternalComponent, deps: [{ token: i1.LocalizationService }, { token: i2.FocusService }, { token: i3.IntlService }, { token: i4.ViewContextService }, { token: i5.ViewStateService }, { token: i0.NgZone }, { token: i0.Renderer2 }, { token: i6.PDFService }, { token: i0.ElementRef }, { token: i7.MonthSlotService }, { token: i8.ScrollbarWidthService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: YearViewInternalComponent, isStandalone: true, selector: "year-view-internal", inputs: { newRange: "newRange", dateRangeFn: "dateRangeFn" }, providers: [MonthSlotService], viewQueries: [{ propertyName: "calendar", first: true, predicate: MultiViewCalendarComponent, descendants: true }, { propertyName: "tooltip", first: true, predicate: TooltipDirective, descendants: true }], usesInheritance: true, ngImport: i0, template: ` <div #content class="k-scheduler-layout k-scheduler-layout-flex k-scheduler-yearview"> <div class="k-scheduler-body"> <kendo-multiviewcalendar [showOtherMonthDays]="false" [showCalendarHeader]="false" [showViewHeader]="true" [views]="12" [focusedDate]="focusedDate" kendoTooltip filter=".k-calendar-td" showOn="none" [tooltipTemplate]="template" position="right" tooltipContentClass="k-scheduler-tooltip" [tooltipWidth]="220" [collision]="{ horizontal: 'flip', vertical: 'flip' }" (valueChange)="onValueChange($event)" (blur)="onBlur()" > <ng-template kendoCalendarMonthCellTemplate let-date let-context="cellContext"> <span *ngIf="!context.isOtherMonth" [attr.date]="date">{{ date.getDate() }}</span> <span *ngIf="!context.isOtherMonth && hasEvent(date)" class="k-day-indicator"></span> </ng-template> </kendo-multiviewcalendar> </div> </div> <ng-template #template let-anchor> <div class="k-tooltip-title k-text-center" (click)="navigateToDay(getDate(anchor))" (mousedown)="onMouseDown()" > <div class="k-month">{{ intl.formatDate(getDate(anchor), 'MMM') }}</div> <div class="k-link k-day k-text-primary">{{ intl.formatDate(getDate(anchor), 'dd') }}</div> </div> <div class="k-tooltip-events-container" (mousedown)="onMouseDown()"> <div class="k-tooltip-events"> <div *ngFor="let event of eventsPerSelectedDay" class="k-tooltip-event k-event" [title]="eventTitle(event)" [ngClass]="getEventClasses(event, event.resources)" [ngStyle]="getEventStyles(event, event.resources[0], event.isAllDay)" > <kendo-icon-wrapper *ngIf="event.tail || event.mid" [name]="arrowIcons[0]" [svgIcon]="arrowSVGIcons[0]" > </kendo-icon-wrapper> <div class="k-event-title k-text-ellipsis">{{ event.event.title }}</div> <span class="k-spacer"></span> <span class="k-event-time" *ngIf="(event.isMultiDay && event.head && !event.isAllDay) || !event.isMultiDay" >{{ intl.formatDate(event.start, 't') }}</span > <kendo-icon-wrapper *ngIf="event.head || event.mid" [name]="arrowIcons[1]" [svgIcon]="arrowSVGIcons[1]" > </kendo-icon-wrapper> </div> </div> </div> <div *ngIf="eventsPerSelectedDay.length === 0" class="k-no-data k-text-center"> {{ localization.get('yearViewNoEvents') }} </div> </ng-template> `, isInline: true, dependencies: [{ kind: "component", type: MultiViewCalendarComponent, selector: "kendo-multiviewcalendar", inputs: ["showOtherMonthDays", "showCalendarHeader", "size", "id", "focusedDate", "footer", "min", "max", "rangeValidation", "disabledDatesRangeValidation", "selection", "allowReverse", "value", "disabled", "tabindex", "tabIndex", "weekDaysFormat", "isActive", "disabledDates", "activeView", "bottomView", "topView", "showViewHeader", "animateNavigation", "weekNumber", "activeRangeEnd", "selectionRange", "views", "orientation", "cellTemplate", "monthCellTemplate", "yearCellTemplate", "decadeCellTemplate", "centuryCellTemplate", "weekNumberTemplate", "footerTemplate", "headerTitleTemplate", "headerTemplate"], outputs: ["activeViewChange", "navigate", "cellEnter", "cellLeave", "valueChange", "rangeSelectionChange", "blur", "focus", "focusCalendar", "onClosePopup", "onTabPress", "onShiftTabPress"], exportAs: ["kendo-multiviewcalendar"] }, { kind: "directive", type: TooltipDirective, selector: "[kendoTooltip]", inputs: ["filter", "position", "titleTemplate", "showOn", "showAfter", "callout", "closable", "offset", "tooltipWidth", "tooltipHeight", "tooltipClass", "tooltipContentClass", "collision", "closeTitle", "tooltipTemplate"], exportAs: ["kendoTooltip"] }, { kind: "directive", type: MonthCellTemplateDirective, selector: "[kendoCalendarMonthCellTemplate]" }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: IconWrapperComponent, selector: "kendo-icon-wrapper", inputs: ["name", "svgIcon", "innerCssClass", "customFontClass", "size"], exportAs: ["kendoIconWrapper"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: YearViewInternalComponent, decorators: [{ type: Component, args: [{ selector: 'year-view-internal', providers: [MonthSlotService], template: ` <div #content class="k-scheduler-layout k-scheduler-layout-flex k-scheduler-yearview"> <div class="k-scheduler-body"> <kendo-multiviewcalendar [showOtherMonthDays]="false" [showCalendarHeader]="false" [showViewHeader]="true" [views]="12" [focusedDate]="focusedDate" kendoTooltip filter=".k-calendar-td" showOn="none" [tooltipTemplate]="template" position="right" tooltipContentClass="k-scheduler-tooltip" [tooltipWidth]="220" [collision]="{ horizontal: 'flip', vertical: 'flip' }" (valueChange)="onValueChange($event)" (blur)="onBlur()" > <ng-template kendoCalendarMonthCellTemplate let-date let-context="cellContext"> <span *ngIf="!context.isOtherMonth" [attr.date]="date">{{ date.getDate() }}</span> <span *ngIf="!context.isOtherMonth && hasEvent(date)" class="k-day-indicator"></span> </ng-template> </kendo-multiviewcalendar> </div> </div> <ng-template #template let-anchor> <div class="k-tooltip-title k-text-center" (click)="navigateToDay(getDate(anchor))" (mousedown)="onMouseDown()" > <div class="k-month">{{ intl.formatDate(getDate(anchor), 'MMM') }}</div> <div class="k-link k-day k-text-primary">{{ intl.formatDate(getDate(anchor), 'dd') }}</div> </div> <div class="k-tooltip-events-container" (mousedown)="onMouseDown()"> <div class="k-tooltip-events"> <div *ngFor="let event of eventsPerSelectedDay" class="k-tooltip-event k-event" [title]="eventTitle(event)" [ngClass]="getEventClasses(event, event.resources)" [ngStyle]="getEventStyles(event, event.resources[0], event.isAllDay)" > <kendo-icon-wrapper *ngIf="event.tail || event.mid" [name]="arrowIcons[0]" [svgIcon]="arrowSVGIcons[0]" > </kendo-icon-wrapper> <div class="k-event-title k-text-ellipsis">{{ event.event.title }}</div> <span class="k-spacer"></span> <span class="k-event-time" *ngIf="(event.isMultiDay && event.head && !event.isAllDay) || !event.isMultiDay" >{{ intl.formatDate(event.start, 't') }}</span > <kendo-icon-wrapper *ngIf="event.head || event.mid" [name]="arrowIcons[1]" [svgIcon]="arrowSVGIcons[1]" > </kendo-icon-wrapper> </div> </div> </div> <div *ngIf="eventsPerSelectedDay.length === 0" class="k-no-data k-text-center"> {{ localization.get('yearViewNoEvents') }} </div> </ng-template> `, standalone: true, imports: [MultiViewCalendarComponent, TooltipDirective, MonthCellTemplateDirective, NgIf, NgFor, NgClass, NgStyle, IconWrapperComponent] }] }], ctorParameters: function () { return [{ type: i1.LocalizationService }, { type: i2.FocusService }, { type: i3.IntlService }, { type: i4.ViewContextService }, { type: i5.ViewStateService }, { type: i0.NgZone }, { type: i0.Renderer2 }, { type: i6.PDFService }, { type: i0.ElementRef }, { type: i7.MonthSlotService }, { type: i8.ScrollbarWidthService }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { newRange: [{ type: Input }], dateRangeFn: [{ type: Input }], calendar: [{ type: ViewChild, args: [MultiViewCalendarComponent] }], tooltip: [{ type: ViewChild, args: [TooltipDirective] }] } });