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.

1,186 lines (1,152 loc) 97.5 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, ContentChild, ContentChildren, EventEmitter, HostBinding, Input, isDevMode, NgZone, Output, QueryList, ViewChild, ViewContainerRef, ChangeDetectorRef, ChangeDetectionStrategy, ElementRef, Renderer2 } from '@angular/core'; import { L10N_PREFIX, LocalizationService } from '@progress/kendo-angular-l10n'; import { ResizeSensorComponent, isDocumentAvailable, shouldShowValidationUI, getLicenseMessage, anyChanged, isChanged, hasObservers, WatermarkOverlayComponent } from '@progress/kendo-angular-common'; import { getDate, ZonedDate, Day } from '@progress/kendo-date-math'; import { take } from 'rxjs/operators'; import { validatePackage } from '@progress/kendo-licensing'; import { packageMetadata } from './package-metadata'; import { DateChangeEvent, NavigateEvent, VIEW_EVENT_MAP } from './events'; import { ToolbarTemplateDirective } from './toolbar/toolbar-template.directive'; import { ToolbarService } from './toolbar/toolbar.service'; import { SchedulerView } from './types'; import { AgendaDateTemplateDirective } from './views/templates/agenda-date-template.directive'; import { AgendaTimeTemplateDirective } from './views/templates/agenda-time-template.directive'; import { AllDayEventTemplateDirective } from './views/templates/all-day-event-template.directive'; import { AllDaySlotTemplateDirective } from './views/templates/all-day-slot-template.directive'; import { DateHeaderTemplateDirective } from './views/templates/date-header-template.directive'; import { EventTemplateDirective } from './views/templates/event-template.directive'; import { GroupHeaderTemplateDirective } from './views/templates/group-header-template.directive'; import { MajorTimeHeaderTemplateDirective } from './views/templates/major-time-header-template.directive'; import { MinorTimeHeaderTemplateDirective } from './views/templates/minor-time-header-template.directive'; import { MonthDaySlotTemplateDirective } from './views/templates/month-day-slot-template.directive'; import { MultiWeekDaySlotTemplateDirective } from './views/templates/multi-week-day-slot-template.directive'; import { TimeSlotTemplateDirective } from './views/templates/time-slot-template.directive'; import { ViewContextService } from './views/view-context.service'; import { ViewStateService } from './views/view-state.service'; import { EditService } from './editing/edit.service'; import { EditDialogTemplateDirective } from './editing/edit-dialog-template.directive'; import { FormGroup, FormControl } from '@angular/forms'; import { LocalDataChangesService } from './editing/local-data-changes.service'; import { DialogsService } from './editing/dialogs.service'; import { SchedulerLocalizationService } from './localization/scheduler-localization.service'; import { defaultModelFields } from './common/default-model-fields'; import { readEvent, isRecurrenceMaster, copyResources, isPresent } from './common/util'; import { IntlService } from '@progress/kendo-angular-intl'; import { PDFService } from './pdf/pdf.service'; import { PDFExportEvent } from './pdf/pdf-export-event'; import { LoadingComponent } from './loading.component'; import { FocusService } from './navigation'; import { DomEventsService } from './views/common/dom-events.service'; import { closest } from './common/dom-queries'; import { alwaysFalse } from './views/utils'; import { SlotDragEvent } from './events/slot-drag-event'; import { SlotDragEndEvent } from './events/slot-drag-end-event'; import { EditDialogComponent } from './editing/edit-dialog.component'; import { NgTemplateOutlet, NgIf } from '@angular/common'; import { ToolbarComponent } from './toolbar/toolbar.component'; import { LocalizedMessagesDirective } from './localization/localized-messages.directive'; import * as i0 from "@angular/core"; import * as i1 from "./views/view-context.service"; import * as i2 from "./views/view-state.service"; import * as i3 from "./editing/edit.service"; import * as i4 from "./editing/dialogs.service"; import * as i5 from "@progress/kendo-angular-intl"; import * as i6 from "./pdf/pdf.service"; import * as i7 from "@progress/kendo-angular-l10n"; import * as i8 from "./views/common/dom-events.service"; import * as i9 from "./navigation"; const todayDate = () => getDate(new Date()); const DAYS_IN_WEEK = 7; /** * Represents the [Kendo UI Scheduler component for Angular](slug:overview_scheduler). * * @example * ```html * <kendo-scheduler [kendoSchedulerBinding]="events" [selectedDate]="selectedDate"> * <kendo-scheduler-day-view></kendo-scheduler-day-view> * </kendo-scheduler> * ``` * * @remarks * Supported children components are: {@link AgendaViewComponent}, * {@link WeekViewComponent}, {@link WorkWeekViewComponent}, {@link YearViewComponent}, * {@link SchedulerCustomMessagesComponent}, {@link TimelineMonthViewComponent}, * {@link TimelineViewComponent}, {@link TimelineWeekViewComponent}, {@link DayViewComponent}, * {@link MonthViewComponent}, {@link MultiDayViewComponent}, {@link MultiWeekViewComponent}, * {@link PDFComponent}, {@link TimeZoneEditorComponent}, {@link RecurrenceEditorComponent} */ export class SchedulerComponent { wrapper; viewContext; viewState; editService; dialogsService; intlService; changeDetector; zone; pdfService; localization; domEvents; renderer; focusService; hostClasses = true; ariaRole = 'application'; rtl = false; get dir() { return this.direction; } /** * Sets the index of the currently selected view. The default value is `0`, which shows the first declared view. * @default 0 */ set selectedViewIndex(index) { this.viewIndex = index; this.onViewIndexChange(); } get selectedViewIndex() { return this.viewIndex; } /** * Specifies whether the Scheduler is editable. Accepts a `boolean` or `EditableSettings` object. * @default false */ editable = false; /** * Specifies whether the Scheduler's day or time slots are selectable ([see example](slug:slotselection_scheduler#custom-callback)). * When set to `true`, the Scheduler emits [`slotDragStart`]({% slug api_scheduler_schedulercomponent %}#toc-slotdragstart), [`slotDrag`]({% slug api_scheduler_schedulercomponent %}#toc-slotdrag), and [`slotDragEnd`]({% slug api_scheduler_schedulercomponent %}#toc-slotdragend) events on user interaction. * @default false */ selectable = false; /** * Sets the minimum date that you can select using the Scheduler navigation. */ min; /** * Sets the maximum date that you can select using the Scheduler navigation. */ max; /** * Sets the height of events in the **Month** and **Timeline** views, and the height of **All day** events in the **Day** and **Week** views. * @default 25 */ eventHeight = 25; /** * Sets the column width for **Timeline** views. * @default 100 */ columnWidth = 100; /** * Sets whether the view shows business hours on initialization. By default, the view shows full-day hours. Applies to **Day**, **Week**, and **Timeline** views. * @default false */ showWorkHours; /** * Sets the start time of the view. Accepts a string in `HH:mm` format. Applies to **Day**, **Week**, and **Timeline** views. * @default '00:00' */ startTime = '00:00'; /** * Sets the end time of the view. Accepts a string in `HH:mm` format. Applies to **Day**, **Week**, and **Timeline** views. * @default '00:00' */ endTime = '00:00'; /** * Sets the start time of the view when `showWorkHours` is `true`. Accepts a string in `HH:mm` format. Applies to **Day**, **Week**, and **Timeline** views. * @default '08:00' */ workDayStart = '08:00'; /** * Sets the end time of the view when `showWorkHours` is `true`. Accepts a string in `HH:mm` format. Applies to **Day**, **Week**, and **Timeline** views. * @default '17:00' */ workDayEnd = '17:00'; /** * Sets the start of the work week. Applies to **Day**, **Week**, and **Timeline** views. * @default 1 */ workWeekStart; /** * Sets the end of the work week. Applies to **Day**, **Week**, and **Timeline** views. * @default 5 */ workWeekEnd; /** * Sets the first day of the week. Applies to **Week**, **Month**, and **TimelineWeek** views. Defaults to the locale settings. */ set weekStart(value) { this._weekStart = value; } get weekStart() { if (isPresent(this._weekStart)) { return this._weekStart; } return this.intlService.firstDay(); } /** * Sets the duration (in minutes) of the time slots. Applies to **Day**, **Week**, and **Timeline** views. * @default 60 */ slotDuration = 60; /** * Sets the number of divisions for the time slots. Applies to **Day**, **Week**, and **Timeline** views. * @default 2 */ slotDivisions = 2; /** * Sets the percentage of the slot filled by events. Accepts a value between 0 and 1. Applies to **Day** and **Week** views. * @default 0.9 */ slotFill = 0.9; /** * Toggles the visibility of the all-day slot. Applies to **Day**, **Multi-Day**, **Week**, and **Work-Week** views. * @default true */ allDaySlot = true; /** * Sets the time to which the view initially scrolls. Accepts a string in `HH:mm` format or a JavaScript `Date` object. Applies to **Day**, **Week**, and **Timeline** views. * @default '08:00' */ scrollTime = this.workDayStart; /** * Defines the Scheduler groups. */ group; /** * Specifies the resources of the Scheduler. */ resources; /** * Specifies whether the Scheduler displays a loading indicator. * @default false */ loading; /** * Sets the timezone ID displayed in the Scheduler. For example, `Europe/Sofia`. * @default 'Etc/UTC' */ set timezone(value) { this._timezone = value; this.events = this.events || []; } get timezone() { return this._timezone; } /** * The currently selected view. */ selectedView; /** * Sets the array of event instances shown by the Scheduler. */ set events(value) { this._events = value; this.processEvents(value); } get events() { return this._events; } /** * Sets the currently selected date of the Scheduler. Determines the displayed period. */ set selectedDate(value) { if (!value) { return; } this._selectedDate = value; this.viewContext.notifySelectedDate(value); } get selectedDate() { return this._selectedDate; } /** * Sets the names of the model fields from which the Scheduler reads its data ([see example](slug:databinding_scheduler#binding-to-custom-object-schemas). */ set modelFields(value) { this._modelFields = { ...defaultModelFields, ...value }; } get modelFields() { return this._modelFields; } /** * Sets the Scheduler current time marker settings. * @default true */ currentTimeMarker = true; /** * Defines the settings for highlighting ongoing events in the Scheduler. * @default true */ highlightOngoingEvents = true; /** * Specifies whether to display the Scheduler toolbar. * @default true */ showToolbar = true; /** * Specifies whether to display the Scheduler footer. * @default true */ showFooter = true; /** * Defines a function that is executed for every slot in the view. * The function returns a value supported by [`ngClass`](link:site.data.urls.angular['ngclassapi']). */ slotClass; /** * Defines a function that is executed for every event in the view. * The function returns a value supported by [`ngClass`](link:site.data.urls.angular['ngclassapi']). */ eventClass; /** * Defines a function that is executed for every event in the view. * The function returns a value supported by [`ngStyle`](link:site.data.urls.angular['ngstyleapi']). */ eventStyles; /** * A callback that is executed for each slot of the Scheduler view. * If it returns `true`, the `k-selected` CSS class will be added to the cell, making it visibly selected. * @default () => false */ isSlotSelected = alwaysFalse; /** * @hidden */ selectedViewIndexChange = new EventEmitter(); /** * Emits when the Scheduler is about to perform a navigation action, such as changing the view, date, or focus. */ navigate = new EventEmitter(); /** * Emits when the displayed date range in the Scheduler changes. */ dateChange = new EventEmitter(); /** * Emits when a Scheduler view slot is clicked. */ slotClick = new EventEmitter(); /** * Fires when a Scheduler view slot is double-clicked. */ slotDblClick = new EventEmitter(); /** * Emits when the user creates a new event using the `'c'` key ([see example](slug:keyboard_navigation_scheduler)). */ create = new EventEmitter(); /** * Fires when a Scheduler event is clicked. */ eventClick = new EventEmitter(); /** * Fires when a Scheduler event is double-clicked. */ eventDblClick = new EventEmitter(); /** * Emits when you press a key on a focused Scheduler event. */ eventKeydown = new EventEmitter(); /** * Fires when the user cancels the editing by clicking the **Cancel** command button. */ cancel = new EventEmitter(); /** * Fires when the user clicks the **Save** command button to save the changes of the edited event. */ save = new EventEmitter(); /** * Fires when the user clicks the **Remove** icon of a Scheduler event. */ remove = new EventEmitter(); /** * Fires when the user starts resizing a Scheduler event. */ resizeStart = new EventEmitter(); /** * Emits while the user is resizing a Scheduler event. */ resize = new EventEmitter(); /** * Fires when the user stops resizing a Scheduler event. */ resizeEnd = new EventEmitter(); /** * Fires when the user starts dragging a Scheduler event. */ dragStart = new EventEmitter(); /** * Fires while the user is dragging a Scheduler event. */ drag = new EventEmitter(); /** * Fires when the user stops dragging a Scheduler event. */ dragEnd = new EventEmitter(); /** * Fires when the user starts drag-selecting a Scheduler slot range. */ slotDragStart = new EventEmitter(); /** * Fires while the user is drag-selecting a Scheduler slot range. */ slotDrag = new EventEmitter(); /** * Fires when the user stops drag-selecting a Scheduler slot range. */ slotDragEnd = new EventEmitter(); /** * Fires when the user clicks the **PDF export** command button. */ pdfExport = new EventEmitter(); /** * Fires when the Scheduler is resized horizontally. */ schedulerResize = new EventEmitter(); /** * @hidden */ dragEndConfirmed = new EventEmitter(); /** * @hidden */ resizeEndConfirmed = new EventEmitter(); /** * @hidden */ removeConfirmed = new EventEmitter(); /** * @hidden */ editDialogTemplate; /** * @hidden */ toolbarTemplate; /** * @hidden */ dateRangeStream; /** * @hidden */ selectedDateStream; /** * @hidden */ views; /** * @hidden */ resizeSensor; /** * @hidden */ confirmationDialogContainerRef; /** * @hidden */ loadingComponent; /** * @hidden */ allDayEventTemplate; /** * @hidden */ eventTemplate; /** * @hidden */ timeSlotTemplate; /** * @hidden */ minorTimeHeaderTemplate; /** * @hidden */ majorTimeHeaderTemplate; /** * @hidden */ monthDaySlotTemplate; /** * @hidden */ multiWeekDaySlotTemplate; /** * @hidden */ dateHeaderTemplate; /** * @hidden */ allDaySlotTemplate; /** * @hidden */ groupHeaderTemplate; /** * @hidden */ agendaDateTemplate; /** * @hidden */ agendaTimeTemplate; /** * @hidden */ showLicenseWatermark = false; /** * @hidden */ licenseMessage; /** * @hidden */ get viewToolbar() { return this.viewState?.toolbarVisibilityByView.get(this.selectedView); } direction; subs; viewIndex = 0; _selectedDate; _events; _timezone = ''; _modelFields = defaultModelFields; viewItems; detachElementEventHandlers; _weekStart; constructor(wrapper, viewContext, viewState, editService, dialogsService, intlService, changeDetector, zone, pdfService, localization, domEvents, renderer, focusService) { this.wrapper = wrapper; this.viewContext = viewContext; this.viewState = viewState; this.editService = editService; this.dialogsService = dialogsService; this.intlService = intlService; this.changeDetector = changeDetector; this.zone = zone; this.pdfService = pdfService; this.localization = localization; this.domEvents = domEvents; this.renderer = renderer; this.focusService = focusService; const isValid = validatePackage(packageMetadata); this.licenseMessage = getLicenseMessage(packageMetadata); this.showLicenseWatermark = shouldShowValidationUI(isValid); this.dateRangeStream = viewState.dateRange; this.selectedDateStream = viewContext.selectedDate; } ngOnInit() { if (!this.selectedDate) { this.selectedDate = todayDate(); } } ngAfterContentInit() { if (isDevMode() && this.views.length === 0) { throw new Error('No views declared for <kendo-scheduler>. Please, declare at least one view.'); } this.subs = this.views.changes.subscribe(() => this.resetViewIndex()); this.subs.add(this.intlService.changes.subscribe(this.intlChange.bind(this))); this.subs.add(this.viewState.nextDate.subscribe(nextDate => { this.selectedDate = nextDate; })); this.subs.add(this.viewState.dateRange.subscribe(dateRange => { const isEmpty = dateRange.start.getTime() === 0; if (!isEmpty) { const args = new DateChangeEvent(this, this.selectedDate, dateRange); this.dateChange.emit(args); } })); this.subs.add(this.viewState.navigate.subscribe(({ viewName, date }) => { const views = this.views.toArray(); const view = views.find(v => v.name === viewName); const args = new NavigateEvent(this, { type: 'show-date', view: view || { name: viewName, title: viewName }, date: date }); this.navigate.next(args); if (view && !args.isDefaultPrevented()) { const index = views.indexOf(view); this.selectedView = view; this.setViewIndex(index); this.selectedDate = date; } })); this.subs.add(this.viewState.viewEvent.subscribe(({ name, args }) => { const emitter = this[name]; const confirmedEmitter = this[`${name}Confirmed`]; if (hasObservers(emitter) || (confirmedEmitter && hasObservers(confirmedEmitter))) { this.zone.run(() => { const eventInstance = new VIEW_EVENT_MAP[name](this, args); emitter.emit(eventInstance); args.prevented = eventInstance.prevented; if (confirmedEmitter && !args.prevented) { confirmedEmitter.emit(eventInstance); } }); } })); this.subs.add(this.viewState.layoutEnd.subscribe(() => { if (this.resizeSensor) { this.resizeSensor.acceptSize(); } })); this.subs.add(this.viewState.slotSelectionStart .subscribe((e) => { if (hasObservers(this.slotDragStart)) { this.zone.run(() => { this.slotDragStart.emit(Object.assign(e, { sender: this })); }); } })); this.subs.add(this.viewState.slotSelectionDrag .subscribe((value) => { if (hasObservers(this.slotDrag)) { this.zone.run(() => { this.slotDrag.emit(new SlotDragEvent(this, value)); }); } })); this.subs.add(this.viewState.slotSelectionEnd .subscribe((value) => { if (hasObservers(this.slotDragEnd)) { this.zone.run(() => { this.slotDragEnd.emit(new SlotDragEndEvent(this, value)); }); } })); this.onViewIndexChange(); this.notifyOptionsChange = this.notifyOptionsChange.bind(this); this.subs.add(this.allDayEventTemplate.changes.subscribe(this.notifyOptionsChange)); this.subs.add(this.eventTemplate.changes.subscribe(this.notifyOptionsChange)); this.subs.add(this.timeSlotTemplate.changes.subscribe(this.notifyOptionsChange)); this.subs.add(this.timeSlotTemplate.changes.subscribe(this.notifyOptionsChange)); this.subs.add(this.minorTimeHeaderTemplate.changes.subscribe(this.notifyOptionsChange)); this.subs.add(this.majorTimeHeaderTemplate.changes.subscribe(this.notifyOptionsChange)); this.subs.add(this.monthDaySlotTemplate.changes.subscribe(this.notifyOptionsChange)); this.subs.add(this.multiWeekDaySlotTemplate.changes.subscribe(this.notifyOptionsChange)); this.subs.add(this.dateHeaderTemplate.changes.subscribe(this.notifyOptionsChange)); this.subs.add(this.allDaySlotTemplate.changes.subscribe(this.notifyOptionsChange)); this.subs.add(this.groupHeaderTemplate.changes.subscribe(this.notifyOptionsChange)); this.subs.add(this.agendaDateTemplate.changes.subscribe(this.notifyOptionsChange)); this.subs.add(this.agendaTimeTemplate.changes.subscribe(this.notifyOptionsChange)); this.subs.add(this.views.changes.subscribe(() => { this.changeDetector.markForCheck(); })); //this.editService.timezone = this.timezone; this.attachEditHandlers(); this.dialogsService.container = this.confirmationDialogContainerRef; this.notifyOptionsChange(); this.subs.add(this.pdfService.exportClick.subscribe(() => { const args = new PDFExportEvent(); this.pdfExport.emit(args); if (!args.isDefaultPrevented()) { this.saveAsPDF(); } })); this.subs.add(this.pdfService.done.subscribe(() => { this.loadingComponent.toggle(false); })); this.subs.add(this.localization.changes.subscribe(({ rtl }) => { this.rtl = rtl; this.direction = this.rtl ? 'rtl' : 'ltr'; })); this.subs.add(this.viewState.optionsChange.subscribe(() => { this.changeDetector.markForCheck(); })); this.attachElementEventHandlers(); } ngOnChanges(changes) { if (isChanged('resources', changes) && !isChanged('events', changes) && this.viewItems) { this.viewItems.forEach(item => { copyResources(item.event, this.resources); }); } if (anyChanged([ 'group', 'resources', 'min', 'max', 'showWorkHours', 'startTime', 'scrollTime', 'endTime', 'eventHeight', 'workDayStart', 'workDayEnd', 'workWeekStart', 'workWeekEnd', 'weekStart', 'slotDuration', 'slotDivisions', 'editable', 'timezone', 'slotClass', 'slotFill', 'columnWidth', 'eventClass', 'eventStyles', 'isSlotSelected', 'selectable', 'allDaySlot', 'showToolbar', 'showFooter' ], changes)) { this.notifyOptionsChange(changes); } } ngOnDestroy() { if (this.subs) { this.subs.unsubscribe(); } if (this.detachElementEventHandlers) { this.detachElementEventHandlers(); } } /** * @hidden */ onResize(_event) { this.viewContext.notifyResize(); } /** * @hidden */ onNavigationAction(action) { const args = new NavigateEvent(this, action); this.navigate.next(args); if (args.isDefaultPrevented()) { return true; } if (action.type === 'view-change') { const views = this.views.toArray(); const index = views.indexOf(action.view); this.selectedView = action.view; this.setViewIndex(index); } else if (action.type === 'select-date') { if (this.isInRange(action.date)) { this.selectedDate = action.date; } } else if (action.type === 'today') { const date = new Date(); if (this.isInRange(date)) { this.selectedDate = date; } } else if (action.type === 'toggle-business-hours') { this.viewState.toggleWorkHours.next(null); } else if (action.type === 'focus-toolbar') { this.focusService.focusToolbar(); } else { this.viewContext.notifyAction(action); } } /** * @hidden */ onToolbarWidthChange(width) { this.schedulerResize.emit(width); } /** * Creates a popup editor for the new event. * * @param group - The [`FormGroup`](https://angular.io/docs/ts/latest/api/forms/index/FormGroup-class.html) that describes * the edit form. If called with a data item, the parameter will build the `FormGroup` from the data item fields. */ addEvent(group) { const isFormGroup = group instanceof FormGroup; if (!isFormGroup) { const createControl = (source) => (acc, key) => { acc[key] = new FormControl(source[key]); return acc; }; const fields = Object.keys(group).reduce(createControl(group), {}); group = new FormGroup(fields); } this.editService.addEvent(group); } /** * Switches the specified event in edit mode. * * @param dataItem - The event that will be switched to edit mode. * @param options - An object which contains the form `group` that will be bound in the edit dialog and the current edit `mode`. * */ editEvent(dataItem, options = {}) { const { group, mode } = options; this.editService.editEvent(dataItem, group, mode); } /** * Closes the event editor, if open. */ closeEvent() { this.editService.close(); } /** * Returns a flag which indicates if an event is currently edited. * * @return {boolean} - A flag which indicates if an event is currently edited. */ isEditing() { return this.editService.isEditing(); } /** * Opens the built-in confirmation dialog for defining the edit mode * that will be used when the user edits or removes a recurring event. * * @param operation - The type of operation that will be confirmed. Has to be either **Edit** or **Remove**. * * @returns the result from the confirmation dialog. */ openRecurringConfirmationDialog(operation) { return this.dialogsService.openRecurringConfirmationDialog(operation); } /** * Opens the built-in removal confirmation dialog. * * @return the result from the confirmation dialog. */ openRemoveConfirmationDialog() { return this.dialogsService.openRemoveConfirmationDialog(); } /** * Saves the current view as PDF. */ saveAsPDF() { this.loadingComponent.toggle(true); this.zone.runOutsideAngular(() => { // wait a tick in order for the loading element style to be updated by the browser. // if the export is synchronous, the browser will not update the element before the export is finished. setTimeout(() => { this.pdfService.save(); }, 0); }); } /** * Scrolls the view to the specified time. */ scrollToTime(time) { this.viewContext.notifyAction({ type: 'scroll-time', time: time }); } /** * Returns the current view slot that matches the passed document position. * * @param x - The x document position. * @param y - The y document position. * * @return {SchedulerSlot} - The slot. */ slotByPosition(x, y) { return this.viewContext.executeMethod('slotByPosition', { x, y }); } /** * Returns the event associated with the specified DOM element, if any. * * @param element - The DOM element document position. * @return the event instance, if found. */ eventFromElement(element) { return this.viewContext.executeMethod('eventFromElement', { element }); } /** * Gets the currently active event, if any. * The active event is the event that can currently receive focus. */ get activeEvent() { const activeElement = this.focusService.activeElement; if (activeElement && activeElement.nativeElement.matches('.k-event, [data-task-index]')) { return this.eventFromElement(activeElement.nativeElement); } } /** * Focuses the next event or an event at a specified relative position. * The `options` parameter can be used to set a positive or negative offset * that is relative to the currently focused event ([see example](slug:keyboard_navigation_scheduler#toc-event-navigation-with-the-tab-key)). * A `nowrap` flag toggles the wrapping to the first or to the last item. * * @param position The relative position of the event to focus. * @returns `true` if the focused event changed. Otherwise, returns `false`. */ focusNext(position) { const changed = this.focusService.focusNext(position); this.zone.onStable.pipe(take(1)).subscribe(() => this.focusService.focus()); return changed; } /** * Focuses the previous event or an event at a specified relative position. * The `options` parameter can be used to set a positive or negative offset * that is relative to the currently focused event ([see example](slug:keyboard_navigation_scheduler#toc-event-navigation-with-the-tab-key)). * A `nowrap` flag toggles the wrapping to the first or to the last item. * * @param position The relative position of the event to focus. * @returns `true` if the focused event changed. Otherwise, returns `false`. */ focusPrev(position) { const prevPosition = { offset: -1, ...position }; const changed = this.focusService.focusNext(prevPosition); this.zone.onStable.pipe(take(1)).subscribe(() => this.focusService.focus()); return changed; } /** * Focuses the last focused scheduler element or the Scheduler element, if no events are available. */ focus() { this.zone.onStable.pipe(take(1)).subscribe(() => this.focusService.focus()); } /** * @hidden */ get toolbarVisibilityState() { return isPresent(this.viewToolbar) ? this.viewToolbar : this.showToolbar; } isInRange(date) { return (!this.min || this.min <= date) && (!this.max || date <= this.max); } notifyOptionsChange(changes) { const workweek = this.workWeek; this.viewContext.notifyOptionsChange({ group: this.group, resources: this.resources, allDayEventTemplate: this.allDayEventTemplate ? this.allDayEventTemplate.first : null, eventTemplate: this.eventTemplate ? this.eventTemplate.first : null, timeSlotTemplate: this.timeSlotTemplate ? this.timeSlotTemplate.first : null, minorTimeHeaderTemplate: this.minorTimeHeaderTemplate ? this.minorTimeHeaderTemplate.first : null, majorTimeHeaderTemplate: this.majorTimeHeaderTemplate ? this.majorTimeHeaderTemplate.first : null, monthDaySlotTemplate: this.monthDaySlotTemplate ? this.monthDaySlotTemplate.first : null, multiWeekDaySlotTemplate: this.multiWeekDaySlotTemplate ? this.multiWeekDaySlotTemplate.first : null, dateHeaderTemplate: this.dateHeaderTemplate ? this.dateHeaderTemplate.first : null, allDaySlotTemplate: this.allDaySlotTemplate ? this.allDaySlotTemplate.first : null, groupHeaderTemplate: this.groupHeaderTemplate ? this.groupHeaderTemplate.first : null, agendaDateTemplate: this.agendaDateTemplate ? this.agendaDateTemplate.first : null, agendaTimeTemplate: this.agendaTimeTemplate ? this.agendaTimeTemplate.first : null, min: this.min, max: this.max, showWorkHours: this.showWorkHours, startTime: this.startTime, scrollTime: this.scrollTime, endTime: this.endTime, workDayStart: this.workDayStart, workDayEnd: this.workDayEnd, workWeekStart: workweek.start, workWeekEnd: workweek.end, weekStart: this.weekStart, allDaySlot: this.allDaySlot, slotDuration: this.slotDuration, slotDivisions: this.slotDivisions, eventHeight: this.eventHeight, editable: this.editable, selectable: this.selectable, timezone: this.timezone, currentTimeMarker: this.currentTimeMarker, highlightOngoingEvents: this.highlightOngoingEvents, showToolbar: this.showToolbar, showFooter: this.showFooter, slotClass: this.slotClass, slotFill: this.slotFill, columnWidth: this.columnWidth, eventClass: this.eventClass, eventStyles: this.eventStyles, isSlotSelected: this.isSlotSelected, changes: changes }); } get workWeek() { const { start, end } = this.intlService.weekendRange(); const workWeekStart = isPresent(this.workWeekStart) ? this.workWeekStart : (end + 1) % DAYS_IN_WEEK; const weekEnd = start > 0 ? start - 1 : Day.Saturday; const workWeekEnd = isPresent(this.workWeekEnd) ? this.workWeekEnd : weekEnd; return { start: workWeekStart, end: workWeekEnd }; } resetViewIndex() { const index = this.selectedViewIndex; const newIndex = Math.max(0, Math.min(index, this.views.length - 1)); this.setViewIndex(newIndex); this.onViewIndexChange(); } onViewIndexChange() { if (!this.views) { return; } const views = this.views.toArray(); const selectedView = views[this.viewIndex]; if (selectedView) { this.selectedView = selectedView; } } setViewIndex(newIndex) { const changed = this.selectedViewIndex !== newIndex; if (changed) { this.selectedViewIndex = newIndex; this.selectedViewIndexChange.emit(newIndex); } return changed; } processEvents(dataItems) { const timezone = this.timezone; const fields = this.modelFields; const items = dataItems .map(dataItem => readEvent(dataItem, fields, this.resources)) .filter(event => !isRecurrenceMaster(event)) .map(event => { const start = ZonedDate.fromLocalDate(event.start, timezone); const end = ZonedDate.fromLocalDate(event.end, timezone); return { start, end, event }; }); this.viewItems = items; this.viewContext.notifyItems(items); } attachEditHandlers() { if (!this.editService) { return; } this.subs.add(this.editService.changes.subscribe(this.emitCRUDEvent.bind(this))); } emitCRUDEvent(args) { Object.assign(args, { sender: this }); switch (args.action) { case 'cancel': this.cancel.emit(args); break; case 'save': this.save.emit(args); break; default: break; } } intlChange() { const currentView = this.selectedView; this.selectedView = null; if (!isPresent(this.workWeekStart) || !isPresent(this.workWeekEnd)) { this.notifyOptionsChange(); } this.changeDetector.detectChanges(); this.selectedView = currentView; if (NgZone.isInAngularZone()) { this.changeDetector.markForCheck(); } else { this.changeDetector.detectChanges(); } } attachElementEventHandlers() { if (!isDocumentAvailable()) { return; } const wrapper = this.wrapper.nativeElement; this.zone.runOutsideAngular(() => { const windowBlurSubscription = this.renderer.listen('window', 'blur', (args) => { this.domEvents.windowBlur.emit(args); }); const clickSubscription = this.renderer.listen(wrapper, 'click', (args) => { this.domEvents.click.emit(args); }); const keydownSubscription = this.renderer.listen(wrapper, 'keydown', args => { this.domEvents.keydown.emit(args); }); let focused = false; const focusInSubscription = this.renderer.listen(wrapper, 'focusin', (args) => { this.domEvents.focus.emit(args); if (!focused) { this.domEvents.focusIn.emit(args); this.renderer.addClass(this.wrapper.nativeElement, 'k-focus'); focused = true; } }); const focusOutSubscription = this.renderer.listen(wrapper, 'focusout', (args) => { const next = args.relatedTarget || document.activeElement; const outside = !closest(next, (node) => node === wrapper); if (outside) { this.domEvents.focusOut.emit(args); this.renderer.removeClass(this.wrapper.nativeElement, 'k-focus'); focused = false; } }); this.detachElementEventHandlers = () => { windowBlurSubscription(); clickSubscription(); keydownSubscription(); focusInSubscription(); focusOutSubscription(); }; }); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: SchedulerComponent, deps: [{ token: i0.ElementRef }, { token: i1.ViewContextService }, { token: i2.ViewStateService }, { token: i3.EditService }, { token: i4.DialogsService }, { token: i5.IntlService }, { token: i0.ChangeDetectorRef }, { token: i0.NgZone }, { token: i6.PDFService }, { token: i7.LocalizationService }, { token: i8.DomEventsService }, { token: i0.Renderer2 }, { token: i9.FocusService }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.2.12", type: SchedulerComponent, isStandalone: true, selector: "kendo-scheduler", inputs: { selectedViewIndex: "selectedViewIndex", editable: "editable", selectable: "selectable", min: "min", max: "max", eventHeight: "eventHeight", columnWidth: "columnWidth", showWorkHours: "showWorkHours", startTime: "startTime", endTime: "endTime", workDayStart: "workDayStart", workDayEnd: "workDayEnd", workWeekStart: "workWeekStart", workWeekEnd: "workWeekEnd", weekStart: "weekStart", slotDuration: "slotDuration", slotDivisions: "slotDivisions", slotFill: "slotFill", allDaySlot: "allDaySlot", scrollTime: "scrollTime", group: "group", resources: "resources", loading: "loading", timezone: "timezone", events: "events", selectedDate: "selectedDate", modelFields: "modelFields", currentTimeMarker: "currentTimeMarker", highlightOngoingEvents: "highlightOngoingEvents", showToolbar: "showToolbar", showFooter: "showFooter", slotClass: "slotClass", eventClass: "eventClass", eventStyles: "eventStyles", isSlotSelected: "isSlotSelected" }, outputs: { selectedViewIndexChange: "selectedViewIndexChange", navigate: "navigate", dateChange: "dateChange", slotClick: "slotClick", slotDblClick: "slotDblClick", create: "create", eventClick: "eventClick", eventDblClick: "eventDblClick", eventKeydown: "eventKeydown", cancel: "cancel", save: "save", remove: "remove", resizeStart: "resizeStart", resize: "resize", resizeEnd: "resizeEnd", dragStart: "dragStart", drag: "drag", dragEnd: "dragEnd", slotDragStart: "slotDragStart", slotDrag: "slotDrag", slotDragEnd: "slotDragEnd", pdfExport: "pdfExport", schedulerResize: "schedulerResize" }, host: { properties: { "class.k-scheduler": "this.hostClasses", "attr.role": "this.ariaRole", "class.k-rtl": "this.rtl", "attr.dir": "this.dir" } }, providers: [ EditService, DialogsService, DomEventsService, LocalDataChangesService, FocusService, SchedulerLocalizationService, { provide: LocalizationService, useExisting: SchedulerLocalizationService }, { provide: L10N_PREFIX, useValue: 'kendo.scheduler' }, ToolbarService, ViewContextService, ViewStateService, PDFService ], queries: [{ propertyName: "editDialogTemplate", first: true, predicate: EditDialogTemplateDirective, descendants: true }, { propertyName: "toolbarTemplate", first: true, predicate: ToolbarTemplateDirective, descendants: true }, { propertyName: "views", predicate: SchedulerView }, { propertyName: "allDayEventTemplate", predicate: AllDayEventTemplateDirective }, { propertyName: "eventTemplate", predicate: EventTemplateDirective }, { propertyName: "timeSlotTemplate", predicate: TimeSlotTemplateDirective }, { propertyName: "minorTimeHeaderTemplate", predicate: MinorTimeHeaderTemplateDirective }, { propertyName: "majorTimeHeaderTemplate", predicate: MajorTimeHeaderTemplateDirective }, { propertyName: "monthDaySlotTemplate", predicate: MonthDaySlotTemplateDirective }, { propertyName: "multiWeekDaySlotTemplate", predicate: MultiWeekDaySlotTemplateDirective }, { propertyName: "dateHeaderTemplate", predicate: DateHeaderTemplateDirective }, { propertyName: "allDaySlotTemplate", predicate: AllDaySlotTemplateDirective }, { propertyName: "groupHeaderTemplate", predicate: GroupHeaderTemplateDirective }, { propertyName: "agendaDateTemplate", predicate: AgendaDateTemplateDirective }, { propertyName: "agendaTimeTemplate", predicate: AgendaTimeTemplateDirective }], viewQueries: [{ propertyName: "resizeSensor", first: true, predicate: ResizeSensorComponent, descendants: true, static: true }, { propertyName: "confirmationDialogContainerRef", first: true, predicate: ["confirmationDialogContainer"], descendants: true, read: ViewContainerRef, static: true }, { propertyName: "loadingComponent", first: true, predicate: LoadingComponent, descendants: true, static: true }], usesOnChanges: true, ngImport: i0, template: ` <ng-container kendoSchedulerLocalizedMessages i18n-allEvents="kendo.scheduler.allEvents|The All events text displayed in the timeline views when there is no vertical grouping." allEvents="All events" i18n-allDay="kendo.scheduler.allDay|The all day text displayed in the day and week views." allDay="all day" i18n-dateHeader="kendo.scheduler.dateHeader|The date header text displayed in the agenda view." dateHeader="Date" i18n-timeHeader="kendo.scheduler.timeHeader|The time header text displayed in the agenda view." timeHeader="Time" i18n-deleteTitle="kendo.scheduler.deleteTitle|The delete icon title." deleteTitle="Delete" i18n-eventHeader="kendo.scheduler.eventHeader|The event header text displayed in the agenda view." eventHeader="Event" i18n-nextTitle="kendo.scheduler.nextTitle|The title of the navigation next button." nextTitle="Next" i18n-previousTitle="kendo.scheduler.previousTitle|The title of the navigation previous button." previousTitle="Previous" i18n-showFullDay="kendo.scheduler.showFullDay|The text of the show full day button displayed in the footer of the day, week and timeline views." showFullDay="Show full day" i18n-showWorkDay="kendo.scheduler.showWorkDay|The text of the show work day button displayed in the footer of the day, week and timeline views." showWorkDay="Show business hours" i18n-today="kendo.scheduler.today|The today button text displayed in the navigation." today="Today" i18n-calendarToday="kendo.scheduler.calendarToday|The text of today's date in the header of the Calendar." calendarToday="TODAY" i18n-dayViewTitle="kendo.scheduler.dayViewTitle|The day view title." dayViewTitle="Day" i18n-multiDayViewTitle="kendo.scheduler.multiDayViewTitle|The multi day view title." multiDayViewTitle="Multi-Day" i18n-weekViewTitle="kendo.scheduler.weekViewTitle|The week view title." weekViewTitle="Week" i18n-workWeekViewTitle="kendo.scheduler.workWeekViewTitle|The work week view title." workWeekViewTitle="Work Week" i18n-monthViewTitle="kendo.scheduler.monthViewTitle|The month view title." monthViewTitle="Month" i18n-multiWeekViewTitle="kendo.scheduler.multiWeekViewTitle|The multi week view title." multiWeekViewTitle="Multi-Week" i18n-timelineViewTitle="kendo.scheduler.timelineViewTitle|The timeline view title." timelineViewTitle="Timeline" i18n-timelineWeekViewTitle="kendo.scheduler.timelineWeekViewTitle|The timeline week view title." timelineWeekViewTitle="Timeline Week" i18n-timelineMonthViewTitle="kendo.scheduler.timelineMonthViewTitle|The timeline month view title." timelineMonthViewTitle="Timeline Month" i18n-agendaViewTitle="kendo.scheduler.agendaViewTitle|The agenda view title." agendaViewTitle="Agenda" i18n-yearViewTitle="kendo.scheduler.yearViewTitle|The year view title." yearViewTitle="Year" i18n-yearViewNoEvents="kendo.scheduler.yearViewNoEvents|The year view no events message." yearViewNoEvents="No events on this date." i18n-cancel="kendo.scheduler.cancel|The text similar to 'Cancel' displayed in the Scheduler." cancel="Cancel" i18n-save="kendo.scheduler.save|The text similar to 'Save' displayed in the Scheduler." save="Save" i18n-editorEventTitle="kendo.scheduler.editorEventTitle|The text similar to 'Title' displayed in the Scheduler event editor." editorEventTitle='Title' i18n-editorEventStart="kendo.scheduler.editorEventStart|The text similar to 'Start' displayed in the Scheduler event editor." editorEventStart="Start" i18n-editorEventStartTimeZone="kendo.scheduler.editorEventStartTimeZone|The text similar to 'Start Time Zone' displayed in the Scheduler event editor." editorEventStartTimeZone="Start Time Zone" i18n-editorEventEnd="kendo.scheduler.editorEventEnd|The text similar to 'End' displayed in the Scheduler event editor." editorEventEnd="End" i18n-editorEventEndTimeZone="kendo.scheduler.editorEventEndTimeZone|The text similar to 'End Time Zone' displayed in the Scheduler event editor." editorEventEndTimeZone="End Time Zone" i18n-editorEventAllDay="kendo.scheduler.editorEventAllDay|The text similar to 'All Day event' displayed in the Scheduler event editor." editorEventAllDay="All Day Event" i18n-editorEventDescription="kendo.scheduler.editorEventDescription|The text similar to 'Description' displayed in the Scheduler event editor." editorEventDescription="Description" i18n-editorEventSeparateTimeZones="kendo.scheduler.editorEventSeparateTimeZones|The text similar to 'Use separate Start and End Time Zones' displayed in the Scheduler event editor."