UNPKG

@progress/kendo-angular-gantt

Version:
312 lines (311 loc) 10.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 { 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 = '&nbsp;'; 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); };