UNPKG

@progress/kendo-angular-gantt

Version:
240 lines (239 loc) 11.4 kB
/**----------------------------------------------------------------------------------------- * Copyright © 2025 Progress Software Corporation. All rights reserved. * Licensed under commercial license. See LICENSE.md in the project root for more information *-------------------------------------------------------------------------------------------*/ import { Injectable, NgZone } from '@angular/core'; import { Subject } from 'rxjs'; import { MappingService } from '../common/mapping.service'; import { isPresent } from '@progress/kendo-angular-common'; import * as i0 from "@angular/core"; import * as i1 from "../common/mapping.service"; /** * @hidden */ export class TaskDragService { mapper; zone; taskDraggable; drag = new Subject(); dragEnd = new Subject(); dragStart = new Subject(); tasksContainer; tasksContainerRect; scrollableContainer; currentDragTask; currentDragTaskRect; leftDragHandle; rightDragHandle; completionRatioHandle; tasks = []; editedProp; prevX; prevWidth; prevLeft; prevScrollLeft; newRange; offsetX; maxWidth; newCompletionRatio; constructor(mapper, zone) { this.mapper = mapper; this.zone = zone; } registerTask(task) { if (!this.tasks.some(t => t === task)) { this.tasks.push(task); } } onDragStart(args) { this.tasksContainerRect = this.tasksContainer.getBoundingClientRect(); this.currentDragTask = this.tasks.find(t => t.taskElement.nativeElement.contains(args.dragTarget) || t.taskElement.nativeElement.nextElementSibling === args.dragTarget); const taskEl = this.currentDragTask.taskElement.nativeElement; this.currentDragTaskRect = taskEl.getBoundingClientRect(); this.completionRatioHandle = args.dragTarget.classList.contains('k-task-draghandle'); this.leftDragHandle = args.dragTarget.classList.contains('k-resize-w'); this.rightDragHandle = args.dragTarget.classList.contains('k-resize-e'); if (this.leftDragHandle) { this.editedProp = 'start'; } else if (this.rightDragHandle) { this.editedProp = 'end'; } else { this.editedProp = 'both'; } this.dragStart.next(args); } onDrag(args) { if (this.completionRatioHandle) { // completion ratio handle drag const taskWidth = this.currentDragTaskRect.width; const scrollLeft = this.scrollableContainer?.scrollLeft; const scrollDelta = isPresent(this.prevScrollLeft) ? this.prevScrollLeft - scrollLeft : 0; const completionWidth = Math.min(Math.max(args.dragEvent.clientX - this.currentDragTaskRect.left - scrollDelta, 0), taskWidth); const newCompletionRatio = completionWidth / taskWidth; this.currentDragTask.draggedCompletionWidth = completionWidth; !this.prevScrollLeft && (this.prevScrollLeft = scrollLeft); this.newCompletionRatio = newCompletionRatio; this.drag.next({ start: this.mapper.extractFromTask(this.currentDragTask.dataItem, 'start'), end: this.mapper.extractFromTask(this.currentDragTask.dataItem, 'end'), completionRatio: newCompletionRatio, dragEvent: args, item: this.currentDragTask.dataItem, offset: this.prevX, width: this.prevWidth }); } else { // task resize or drag const marquee = this.calculateMarquee(args, this.leftDragHandle, this.rightDragHandle); if (!marquee) { return; } const { left, width } = marquee; this.newRange = this.calculateStartEnd(this.editedProp, width, left); this.drag.next({ start: this.newRange.start, end: this.newRange.end, completionRatio: this.mapper.extractFromTask(this.currentDragTask.dataItem, 'completionRatio'), dragEvent: args, item: this.currentDragTask.dataItem, offset: left, width }); } } onDragEnd(args) { if (this.completionRatioHandle) { this.dragEnd.next({ start: this.mapper.extractFromTask(this.currentDragTask.dataItem, 'start'), end: this.mapper.extractFromTask(this.currentDragTask.dataItem, 'end'), completionRatio: this.newCompletionRatio, dragEvent: args, item: this.currentDragTask.dataItem, offset: this.prevX, width: this.prevWidth }); } else { this.dragEnd.next({ start: this.newRange.start, end: this.newRange.end, completionRatio: this.mapper.extractFromTask(this.currentDragTask.dataItem, 'completionRatio'), dragEvent: args, item: this.currentDragTask.dataItem, offset: this.prevX, width: this.prevWidth }); } this.prevWidth = this.prevX = this.prevLeft = this.prevScrollLeft = this.offsetX = this.maxWidth = this.newCompletionRatio = null; this.currentDragTaskRect = this.currentDragTask = this.currentDragTask.draggedCompletionWidth = this.editedProp = this.newRange = null; this.leftDragHandle = this.rightDragHandle = this.completionRatioHandle = false; } calculateMarquee(e, leftDragHandle, rightDragHandle) { const clientX = e.dragEvent.clientX; // Update container rect when dragging outside of it to avoid miscalculations // coming from scrolling the whole page during dragging a task if (clientX < this.tasksContainerRect.x || clientX > this.tasksContainerRect.right) { this.zone.runOutsideAngular(() => { this.tasksContainerRect = this.tasksContainer.getBoundingClientRect(); this.currentDragTaskRect = this.currentDragTask.taskElement.nativeElement.getBoundingClientRect(); }); return; } const scrollLeft = this.scrollableContainer?.scrollLeft; const scrollDelta = isPresent(this.prevScrollLeft) ? this.prevScrollLeft - scrollLeft : 0; let left, width; if (leftDragHandle) { if (!isPresent(this.maxWidth)) { this.maxWidth = this.currentDragTaskRect.right - this.tasksContainerRect.x + scrollLeft; } left = Math.min(Math.max(clientX - this.tasksContainerRect.x - scrollDelta, 0), this.maxWidth); width = Math.min(Math.max(this.currentDragTaskRect.right - clientX + scrollDelta, 0), this.maxWidth); } else if (rightDragHandle) { if (!isPresent(this.maxWidth)) { this.maxWidth = this.tasksContainerRect.right - this.currentDragTaskRect.x; } left = this.prevLeft ?? (this.currentDragTaskRect.x - this.tasksContainerRect.x); width = Math.min(Math.max(clientX - this.tasksContainerRect.x - left - scrollDelta, 0), this.maxWidth); } else { // dragging the whole task if (!isPresent(this.offsetX)) { this.offsetX = e.dragEvent.offsetX; } left = Math.max(clientX - this.tasksContainerRect.x - this.offsetX - scrollDelta, 0); } const cursorLeft = clientX - this.tasksContainerRect.x; // out of limits (0, max possible width) when dragging the left handle if (this.leftDragHandle && (cursorLeft < 0 || this.maxWidth < cursorLeft)) { return { left: cursorLeft < 0 ? 0 : this.maxWidth, width }; } // out of limits (0, max possible width) when dragging right handle if (rightDragHandle && (clientX < this.currentDragTaskRect.x) || clientX > this.tasksContainerRect.right) { return { left, width: clientX < this.currentDragTaskRect.x ? 0 : this.maxWidth }; } // out of limits (start < view start || end > view end) when dragging the whole task if (!(this.leftDragHandle || this.rightDragHandle) && (left < 0 || (clientX + this.currentDragTaskRect.width - scrollDelta - this.offsetX > this.tasksContainerRect.right))) { return { left: Math.min(Math.max(left, 0), this.tasksContainerRect.width - this.currentDragTaskRect.width), width: this.currentDragTaskRect.width }; } this.prevX = clientX; this.prevLeft = left; this.prevWidth = width; !this.prevScrollLeft && (this.prevScrollLeft = scrollLeft); return { left, width: width ?? this.currentDragTaskRect.width }; } calculateStartEnd(editedProp, width, left) { const startDate = this.mapper.extractFromTask(this.currentDragTask.dataItem, 'start'); const endDate = this.mapper.extractFromTask(this.currentDragTask.dataItem, 'end'); const duration = endDate - startDate; const delta = left - this.currentDragTaskRect.x + this.tasksContainerRect.x; if (editedProp === 'both') { if (duration) { const delta = left - this.currentDragTaskRect.x + this.tasksContainerRect.left; const coef = delta / this.currentDragTask.taskWidth; const deltaMs = duration * coef; const newStart = new Date(startDate.getTime() + deltaMs); const newEnd = new Date(newStart.getTime() + duration); return { start: newStart, end: newEnd }; } else { // milestone task const initialOffset = this.currentDragTaskRect.x - this.tasksContainerRect.x; const msPerPx = (startDate - this.currentDragTask.viewService.viewStart) / initialOffset; const deltaMs = delta * msPerPx; return { start: new Date(startDate.getTime() + deltaMs), end: new Date(startDate.getTime() + deltaMs) }; } } const coef = width / this.currentDragTask.taskWidth; const newDuration = duration * coef; const newPropDate = editedProp === 'start' ? new Date(endDate - newDuration) : new Date(startDate.getTime() + newDuration); const newStart = editedProp === 'start' ? newPropDate : startDate; const newEnd = editedProp === 'end' ? newPropDate : endDate; return { start: newStart, end: newEnd }; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TaskDragService, deps: [{ token: i1.MappingService }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TaskDragService }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: TaskDragService, decorators: [{ type: Injectable }], ctorParameters: () => [{ type: i1.MappingService }, { type: i0.NgZone }] });