@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.
138 lines (137 loc) • 6.21 kB
JavaScript
/**-----------------------------------------------------------------------------------------
* Copyright © 2025 Progress Software Corporation. All rights reserved.
* Licensed under commercial license. See LICENSE.md in the project root for more information
*-------------------------------------------------------------------------------------------*/
import { Directive, Input, Output, EventEmitter, ChangeDetectorRef } from '@angular/core';
import { Subscription } from 'rxjs';
import { intersects, isSameRange, resourcesMatch } from '../utils';
import { SchedulerComponent } from '../../scheduler.component';
import { distinctUntilChanged, filter } from 'rxjs/operators';
import { isChanged } from '@progress/kendo-angular-common';
import * as i0 from "@angular/core";
import * as i1 from "../../scheduler.component";
/**
* Represents a directive that manages the built-in slot selection in the Scheduler.
*
* Add the `kendoSchedulerSlotSelectable` directive to a `<kendo-scheduler>` instance to allow users to select time slots by clicking or dragging.
*
* The directive keeps track of the selected slot range and emits changes when the selection is updated by user interaction.
*
* @example
* ```html
* <kendo-scheduler kendoSchedulerSlotSelectable [(slotSelection)]="selectedSlot">
* </kendo-scheduler>
* ```
*
* @remarks
* Applied to: {@link SchedulerComponent}
*/
export class SlotSelectableDirective {
scheduler;
cdr;
/**
* Represents the currently selected slot range.
*/
slotSelection;
/**
* Fires when the user changes the currently selected slot range.
*/
slotSelectionChange = new EventEmitter();
/**
* @hidden
* The resources of the cell where the selection started.
* When dragging over the slots of a different resource in grouped mode, the ongoing selection will not be affected.
*/
selectionOriginResources;
selectedRange = null;
slotSelectionChangeSource = new EventEmitter();
subscriptions = new Subscription();
constructor(scheduler, cdr) {
this.scheduler = scheduler;
this.cdr = cdr;
this.scheduler.selectable = true;
this.subscriptions.add(this.slotSelectionChangeSource
.pipe(distinctUntilChanged(isSameRange)).subscribe((v) => {
this.slotSelectionChange.emit(v);
}));
const start$ = this.scheduler.slotDragStart;
const drag$ = this.scheduler.slotDrag;
const end$ = this.scheduler.slotDragEnd;
const startSource = start$.pipe(filter(e => !e.isDefaultPrevented()));
this.subscriptions.add(startSource.subscribe(e => this.initDragSelect(e)));
this.subscriptions.add(drag$.subscribe(e => this.onDrag(e)));
this.subscriptions.add(end$.subscribe(() => this.onRelease()));
}
ngOnInit() {
this.scheduler.isSlotSelected = this.isSlotSelected.bind(this);
}
ngOnChanges(changes) {
if (isChanged("slotSelection", changes, false)) {
const defaults = {
isAllDay: false,
resources: this.scheduler?.resources ? this.scheduler.resources.reduce((result, resource) => {
result.push(...resource.data);
return result;
}, []) : []
};
this.selectedRange = Object.assign(defaults, changes['slotSelection'].currentValue);
this.cdr.markForCheck();
}
}
ngOnDestroy() {
this.subscriptions.unsubscribe();
}
isSlotSelected({ start, end, isAllDay, resources }) {
if (!start || !end || !this.selectedRange) {
return false;
}
const match = resourcesMatch(this.selectedRange.resources, resources);
if (!match) {
return false; // Limit selection to the grouped resource where the drag started.
}
return this.selectedRange && isAllDay === this.selectedRange.isAllDay && this.isInRange(start, end);
}
initDragSelect({ start, end, isAllDay, resources }) {
this.selectionOriginResources = resources.slice();
this.selectedRange = { start, end, isAllDay, resources: resources.slice() };
this.cdr.markForCheck();
}
onDrag({ start, end, resources }) {
const match = resourcesMatch(this.selectionOriginResources, resources);
if (!match) {
return; // Don't change selection when dragging over a different grouped resource's cells.
}
this.selectedRange.start = start;
this.selectedRange.end = end;
this.cdr.markForCheck();
}
onRelease() {
this.selectionOriginResources = null;
if (this.selectedRange) {
this.slotSelectionChangeSource.emit(this.selectedRange);
}
}
/**
* @hidden
* Checks if the selected range contains a local date range.
*/
isInRange(start, end) {
if (!this.selectedRange) {
return;
}
return intersects(start, end, this.selectedRange.start, this.selectedRange.end);
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SlotSelectableDirective, deps: [{ token: i1.SchedulerComponent }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "18.2.14", type: SlotSelectableDirective, isStandalone: true, selector: "[kendoSchedulerSlotSelectable]", inputs: { slotSelection: "slotSelection" }, outputs: { slotSelectionChange: "slotSelectionChange" }, usesOnChanges: true, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: SlotSelectableDirective, decorators: [{
type: Directive,
args: [{
selector: '[kendoSchedulerSlotSelectable]',
standalone: true
}]
}], ctorParameters: () => [{ type: i1.SchedulerComponent }, { type: i0.ChangeDetectorRef }], propDecorators: { slotSelection: [{
type: Input
}], slotSelectionChange: [{
type: Output
}] } });