@progress/kendo-angular-gantt
Version:
Kendo UI Angular Gantt
312 lines (311 loc) • 10.3 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 { isDocumentAvailable, closestInScope, matchesClasses } from '@progress/kendo-angular-common';
import { addDays, addWeeks, cloneDate, firstDayInWeek, lastDayOfMonth } from '@progress/kendo-date-math';
import { DependencyType } from './models/dependency-type.enum';
/**
* @hidden
*/
export const isWorkDay = (date, start, end) => {
return date.getDay() >= start && date.getDay() <= end;
};
/**
* @hidden
*/
export const isWorkHour = (date, start, end) => {
return date.getHours() >= start && date.getHours() <= end;
};
/**
* @hidden
*/
export const isPresent = (item) => item !== null && item !== undefined;
/**
* @hidden
*
* Normalized the data to an array in case a falsy value is passed
* or a TreeListDataResult object (applicable for the data-binding directives).
*/
export const normalizeGanttData = (data) => {
if (!isPresent(data)) {
return [];
}
else if (Array.isArray(data.data)) {
return data.data;
}
else {
return data;
}
};
/**
* @hidden
*/
export const isArray = (value) => Array.isArray(value);
/**
* @hidden
*
* Returns a new date with the specified hours, minutes, seconds and millliseconds set.
* Only the hours are required, the rest of the params are set to `0` by default.
*/
export const setTime = (date, hours, minutes = 0, seconds = 0, milliseconds = 0) => {
if (!isPresent(date)) {
return null;
}
const result = cloneDate(date);
result.setHours(hours);
result.setMinutes(minutes);
result.setSeconds(seconds);
result.setMilliseconds(milliseconds);
return result;
};
/**
* @hidden
*
* Returns the last day of a week.
* @param standingPoint - Any day of the target week.
* @param firstWeekDay - The week's starting day (e.g. Monday, Tuesday, etc.)
*/
export const lastDayOfWeek = (standingPoint, firstWeekDay) => {
const followingWeek = addWeeks(standingPoint, 1);
const firstDayOfFollowingWeek = firstDayInWeek(followingWeek, firstWeekDay);
const lastDayOfTargetWeek = addDays(firstDayOfFollowingWeek, -1);
return lastDayOfTargetWeek;
};
/**
* @hidden
*
* Returns the total number of days in a month
*/
export const getTotalDaysInMonth = (date) => {
return lastDayOfMonth(date).getDate();
};
/**
* @hidden
*
* Returns the total number of months between two dates
* by excluding the months of the dates themselves.
*/
export const getTotalMonthsInBetween = (start, end) => {
const diff = end.getMonth() - start.getMonth() + (12 * (end.getFullYear() - start.getFullYear()));
return diff <= 1 ? 0 : diff - 1;
};
/**
* Persists the initially resolved scrollbar width value.
*/
let SCROLLBAR_WIDTH;
/**
* @hidden
*
* Gets the default scrollbar width accoring to the current environment.
*/
export const scrollbarWidth = () => {
if (!isDocumentAvailable()) {
return;
}
// calculate scrollbar width only once, then return the cached value
if (isNaN(SCROLLBAR_WIDTH)) {
const div = document.createElement('div');
div.style.cssText = 'overflow: scroll; overflow-x: hidden; zoom: 1; clear: both; display: block;';
div.innerHTML = ' ';
document.body.appendChild(div);
SCROLLBAR_WIDTH = div.offsetWidth - div.scrollWidth;
document.body.removeChild(div);
}
return SCROLLBAR_WIDTH;
};
/**
* @hidden
*/
export const isColumnGroup = (column) => column.isColumnGroup;
/**
* @hidden
*/
export const isNumber = (contender) => typeof contender === 'number' && !isNaN(contender);
/**
* @hidden
*/
export const isString = (contender) => typeof contender === 'string';
/**
* @hidden
*
* Gets the closest timeline task wrapper element from an event target.
* Restricts the search up to the provided parent element from the second param.
*/
export const getClosestTaskWrapper = (element, parentScope) => {
return closestInScope(element, matchesClasses('k-task-wrap'), parentScope);
};
/**
* @hidden
*
* Checks whether the queried item or its parent items has a `k-task-wrap` selector.
* Restricts the search up to the provided parent element from the second param.
*/
export const isTaskWrapper = (contender, parentScope) => {
const taskWrapper = closestInScope(contender, matchesClasses('k-task-wrap'), parentScope);
return isPresent(taskWrapper);
};
/**
* @hidden
*
* Gets the closest timeline task element from an event target.
* Restricts the search up to the provided parent element from the second param.
*/
export const getClosestTask = (element, parentScope) => {
return closestInScope(element, matchesClasses('k-task'), parentScope);
};
/**
* @hidden
*
* Gets the closest timeline task element index from an event target.
* Uses the `data-task-index` attribute assigned to each task.
* Restricts the search up to the provided parent element from the second param.
*/
export const getClosestTaskIndex = (element, parentScope) => {
const task = closestInScope(element, matchesClasses('k-task-wrap'), parentScope);
if (!isPresent(task)) {
return null;
}
return Number(task.getAttribute('data-task-index'));
};
/**
* @hidden
*
* Checks whether the queried item or its parent items has a `k-task` selector.
* Restricts the search up to the provided parent element from the second param.
*/
export const isTask = (contender, parentScope) => {
const task = closestInScope(contender, matchesClasses('k-task'), parentScope);
return isPresent(task);
};
/**
* @hidden
*
* Checks whether the queried item or its parent items has a `k-toolbar` selector.
* Restricts the search up to the provided parent element from the second param.
*/
export const isToolbar = (contender, parentScope) => {
const toolbar = closestInScope(contender, matchesClasses('k-gantt-toolbar'), parentScope);
return isPresent(toolbar);
};
/**
* @hidden
*
* Checks whether the queried item or its parent items has a `k-task-actions` selector - used for the clear button.
* Restricts the search up to the provided parent element from the second param.
*/
export const isClearButton = (contender, parentScope) => {
const clearButtonContainer = closestInScope(contender, matchesClasses('k-task-actions'), parentScope);
return isPresent(clearButtonContainer);
};
/**
* @hidden
*
* Checks whether the queried item has a `k-task-dot` selector - used for the dependency drag clues.
*/
export const isDependencyDragClue = (element) => {
if (!isPresent(element)) {
return false;
}
return element.classList.contains('k-task-dot');
};
/**
* @hidden
*
* Checks whether the queried item has a `k-task-dot` & `k-task-start` selector - used for the dependency drag start clues.
*/
export const isDependencyDragStartClue = (element) => {
if (!isPresent(element)) {
return false;
}
return element.classList.contains('k-task-dot') && element.classList.contains('k-task-start');
};
/**
* @hidden
*
* Gets the `DependencyType` for an attempted dependency create from the provided two elements.
* The two linked drag clue HTML elements are used to extract this data (via their CSS classes).
*/
export const getDependencyTypeFromTargetTasks = (fromTaskClue, toTaskClue) => {
if (!isDependencyDragClue(fromTaskClue) || !isDependencyDragClue(toTaskClue)) {
return null;
}
const fromTaskType = isDependencyDragStartClue(fromTaskClue) ? 'S' : 'F';
const toTaskType = isDependencyDragStartClue(toTaskClue) ? 'S' : 'F';
const dependencyTypeName = `${fromTaskType}${toTaskType}`;
switch (dependencyTypeName) {
case 'FF': return DependencyType.FF;
case 'FS': return DependencyType.FS;
case 'SF': return DependencyType.SF;
case 'SS': return DependencyType.SS;
default: return null;
}
};
/**
* @hidden
*
* Checks whether the two provided drag clues belong to the same task element.
*/
export const sameTaskClues = (fromTaskClue, toTaskClue, parentScope) => {
if (!isPresent(fromTaskClue) || !isPresent(toTaskClue)) {
return false;
}
const fromTaskWrapper = getClosestTaskWrapper(fromTaskClue, parentScope);
const toTaskWrapper = getClosestTaskWrapper(toTaskClue, parentScope);
return fromTaskWrapper === toTaskWrapper;
};
/**
* @hidden
*
* Fits a contender number between a min and max range.
* If the contender is below the min value, the min value is returned.
* If the contender is above the max value, the max value is returned.
*/
export const fitToRange = (contender, min, max) => {
if (!isPresent(contender) || contender < min) {
return min;
}
else if (contender > max) {
return max;
}
else {
return contender;
}
};
/**
* @hidden
*
* Checks whether either of the two provided tasks is a parent of the other.
*/
export const areParentChild = (taskA, taskB) => {
let parentChildRelationship = false;
let taskAParent = taskA;
while (isPresent(taskAParent) && isPresent(taskAParent.data)) {
if (taskAParent.data === taskB.data) {
parentChildRelationship = true;
break;
}
taskAParent = taskAParent.parent;
}
let taskBParent = taskB;
while (!parentChildRelationship && isPresent(taskBParent) && isPresent(taskBParent.data)) {
if (taskBParent.data === taskA.data) {
parentChildRelationship = true;
break;
}
taskBParent = taskBParent.parent;
}
return parentChildRelationship;
};
/**
* @hidden
*
* Extracts an element from the provided client coords.
* Using the `event.target` is not reliable under mobile devices with the current implementation of the draggable, so use this instead.
*/
export const elementFromPoint = (clientX, clientY) => {
if (!isDocumentAvailable()) {
return null;
}
return document.elementFromPoint(clientX, clientY);
};