@progress/kendo-angular-gantt 0.3.0-dev.202201111723 → 0.3.0-dev.202201131518
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/js/kendo-angular-gantt.js +2 -2
- package/dist/cdn/main.js +4 -10
- package/dist/es/common/touch-enabled.js +9 -0
- package/dist/es/dependencies/utils.js +34 -0
- package/dist/es/dragging/dependency-drag-create.directive.js +347 -0
- package/dist/es/dragging/drag-validation-tooltip.component.js +27 -0
- package/dist/es/editing/{util.js → utils.js} +0 -0
- package/dist/es/gantt.component.js +125 -7
- package/dist/es/gantt.module.js +23 -6
- package/dist/es/index.js +4 -0
- package/dist/es/main.js +1 -0
- package/dist/es/models/events/dependency-add-event.interface.js +4 -0
- package/dist/es/models/view-item.interface.js +4 -0
- package/dist/es/package-metadata.js +1 -1
- package/dist/es/rendering/gantt-milestone-task.component.js +7 -4
- package/dist/es/rendering/gantt-summary-task.component.js +7 -4
- package/dist/es/rendering/gantt-task-base.js +30 -19
- package/dist/es/rendering/gantt-task.component.js +8 -6
- package/dist/es/rendering/gantt-tasks-table-body.component.js +5 -1
- package/dist/es/scrolling/drag-scroll-settings.js +20 -0
- package/dist/es/scrolling/timeline-scroll.directive.js +89 -0
- package/dist/es/scrolling/timeline-scroll.service.js +39 -0
- package/dist/es/scrolling/utils.js +80 -0
- package/dist/es/timeline/gantt-timeline.component.js +45 -3
- package/dist/es/utils.js +143 -12
- package/dist/es2015/common/touch-enabled.d.ts +9 -0
- package/dist/es2015/common/touch-enabled.js +9 -0
- package/dist/es2015/dependencies/utils.d.ts +15 -0
- package/dist/es2015/dependencies/utils.js +34 -0
- package/dist/es2015/dragging/dependency-drag-create.directive.d.ts +72 -0
- package/dist/es2015/dragging/dependency-drag-create.directive.js +324 -0
- package/dist/es2015/dragging/drag-validation-tooltip.component.d.ts +29 -0
- package/dist/es2015/dragging/drag-validation-tooltip.component.js +76 -0
- package/dist/es2015/editing/{util.d.ts → utils.d.ts} +0 -0
- package/dist/es2015/editing/{util.js → utils.js} +0 -0
- package/dist/es2015/gantt.component.d.ts +39 -3
- package/dist/es2015/gantt.component.js +117 -6
- package/dist/es2015/gantt.module.js +23 -6
- package/dist/es2015/index.d.ts +4 -0
- package/dist/es2015/index.js +4 -0
- package/dist/es2015/index.metadata.json +1 -1
- package/dist/es2015/main.d.ts +1 -0
- package/dist/es2015/main.js +1 -0
- package/dist/es2015/models/events/dependency-add-event.interface.d.ts +26 -0
- package/dist/es2015/models/events/dependency-add-event.interface.js +4 -0
- package/dist/es2015/models/models.d.ts +2 -0
- package/dist/es2015/models/view-item.interface.d.ts +35 -0
- package/dist/es2015/models/view-item.interface.js +4 -0
- package/dist/es2015/package-metadata.js +1 -1
- package/dist/es2015/rendering/gantt-milestone-task.component.d.ts +2 -1
- package/dist/es2015/rendering/gantt-milestone-task.component.js +18 -5
- package/dist/es2015/rendering/gantt-summary-task.component.d.ts +2 -1
- package/dist/es2015/rendering/gantt-summary-task.component.js +18 -5
- package/dist/es2015/rendering/gantt-task-base.d.ts +11 -5
- package/dist/es2015/rendering/gantt-task-base.js +30 -19
- package/dist/es2015/rendering/gantt-task.component.d.ts +2 -1
- package/dist/es2015/rendering/gantt-task.component.js +19 -7
- package/dist/es2015/rendering/gantt-tasks-table-body.component.d.ts +1 -0
- package/dist/es2015/rendering/gantt-tasks-table-body.component.js +10 -3
- package/dist/es2015/scrolling/drag-scroll-settings.d.ts +47 -0
- package/dist/es2015/scrolling/drag-scroll-settings.js +20 -0
- package/dist/es2015/scrolling/timeline-scroll.directive.d.ts +24 -0
- package/dist/es2015/scrolling/timeline-scroll.directive.js +78 -0
- package/dist/es2015/scrolling/timeline-scroll.service.d.ts +20 -0
- package/dist/es2015/scrolling/timeline-scroll.service.js +44 -0
- package/dist/es2015/scrolling/utils.d.ts +29 -0
- package/dist/es2015/scrolling/utils.js +80 -0
- package/dist/es2015/timeline/gantt-timeline.component.d.ts +25 -2
- package/dist/es2015/timeline/gantt-timeline.component.js +56 -3
- package/dist/es2015/utils.d.ts +70 -8
- package/dist/es2015/utils.js +143 -12
- package/dist/fesm2015/index.js +1337 -372
- package/dist/fesm5/index.js +1206 -298
- package/dist/npm/common/touch-enabled.js +11 -0
- package/dist/npm/dependencies/utils.js +34 -0
- package/dist/npm/dragging/dependency-drag-create.directive.js +349 -0
- package/dist/npm/dragging/drag-validation-tooltip.component.js +29 -0
- package/dist/npm/editing/{util.js → utils.js} +0 -0
- package/dist/npm/gantt.component.js +127 -9
- package/dist/npm/gantt.module.js +22 -5
- package/dist/npm/index.js +8 -0
- package/dist/npm/main.js +2 -0
- package/dist/npm/models/events/dependency-add-event.interface.js +6 -0
- package/dist/npm/models/view-item.interface.js +6 -0
- package/dist/npm/package-metadata.js +1 -1
- package/dist/npm/rendering/gantt-milestone-task.component.js +6 -3
- package/dist/npm/rendering/gantt-summary-task.component.js +6 -3
- package/dist/npm/rendering/gantt-task-base.js +30 -19
- package/dist/npm/rendering/gantt-task.component.js +7 -5
- package/dist/npm/rendering/gantt-tasks-table-body.component.js +5 -1
- package/dist/npm/scrolling/drag-scroll-settings.js +22 -0
- package/dist/npm/scrolling/timeline-scroll.directive.js +91 -0
- package/dist/npm/scrolling/timeline-scroll.service.js +41 -0
- package/dist/npm/scrolling/utils.js +83 -0
- package/dist/npm/timeline/gantt-timeline.component.js +44 -2
- package/dist/npm/utils.js +143 -12
- package/dist/systemjs/kendo-angular-gantt.js +1 -1
- package/package.json +5 -4
package/dist/es/utils.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*-------------------------------------------------------------------------------------------*/
|
|
5
5
|
import { isDocumentAvailable, closestInScope, matchesClasses } from '@progress/kendo-angular-common';
|
|
6
6
|
import { addDays, addWeeks, cloneDate, firstDayInWeek } from '@progress/kendo-date-math';
|
|
7
|
+
import { DependencyType } from './models/dependency-type.enum';
|
|
7
8
|
/**
|
|
8
9
|
* @hidden
|
|
9
10
|
*/
|
|
@@ -110,24 +111,43 @@ export var isNumber = function (contender) { return typeof contender === 'number
|
|
|
110
111
|
* @hidden
|
|
111
112
|
*/
|
|
112
113
|
export var isString = function (contender) { return typeof contender === 'string'; };
|
|
114
|
+
/**
|
|
115
|
+
* @hidden
|
|
116
|
+
*
|
|
117
|
+
* Gets the closest timeline task wrapper element from an event target.
|
|
118
|
+
* Restricts the search up to the provided parent element from the second param.
|
|
119
|
+
*/
|
|
120
|
+
export var getClosestTaskWrapper = function (element, parentScope) {
|
|
121
|
+
return closestInScope(element, matchesClasses('k-task-wrap'), parentScope);
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* @hidden
|
|
125
|
+
*
|
|
126
|
+
* Checks whether the queried item or its parent items has a `k-task-wrap` selector.
|
|
127
|
+
* Restricts the search up to the provided parent element from the second param.
|
|
128
|
+
*/
|
|
129
|
+
export var isTaskWrapper = function (contender, parentScope) {
|
|
130
|
+
var taskWrapper = closestInScope(contender, matchesClasses('k-task-wrap'), parentScope);
|
|
131
|
+
return isPresent(taskWrapper);
|
|
132
|
+
};
|
|
113
133
|
/**
|
|
114
134
|
* @hidden
|
|
115
135
|
*
|
|
116
136
|
* Gets the closest timeline task element from an event target.
|
|
117
|
-
* Restricts the search up to the provided
|
|
137
|
+
* Restricts the search up to the provided parent element from the second param.
|
|
118
138
|
*/
|
|
119
|
-
export var getClosestTask = function (element,
|
|
120
|
-
return closestInScope(element, matchesClasses('k-task'),
|
|
139
|
+
export var getClosestTask = function (element, parentScope) {
|
|
140
|
+
return closestInScope(element, matchesClasses('k-task'), parentScope);
|
|
121
141
|
};
|
|
122
142
|
/**
|
|
123
143
|
* @hidden
|
|
124
144
|
*
|
|
125
145
|
* Gets the closest timeline task element index from an event target.
|
|
126
146
|
* Uses the `data-task-index` attribute assigned to each task.
|
|
127
|
-
* Restricts the search up to the provided
|
|
147
|
+
* Restricts the search up to the provided parent element from the second param.
|
|
128
148
|
*/
|
|
129
|
-
export var getClosestTaskIndex = function (element,
|
|
130
|
-
var task = closestInScope(element, matchesClasses('k-task'),
|
|
149
|
+
export var getClosestTaskIndex = function (element, parentScope) {
|
|
150
|
+
var task = closestInScope(element, matchesClasses('k-task-wrap'), parentScope);
|
|
131
151
|
if (!isPresent(task)) {
|
|
132
152
|
return null;
|
|
133
153
|
}
|
|
@@ -137,19 +157,130 @@ export var getClosestTaskIndex = function (element, gantt) {
|
|
|
137
157
|
* @hidden
|
|
138
158
|
*
|
|
139
159
|
* Checks whether the queried item or its parent items has a `k-task` selector.
|
|
140
|
-
* Restricts the search up to the provided
|
|
160
|
+
* Restricts the search up to the provided parent element from the second param.
|
|
141
161
|
*/
|
|
142
|
-
export var isTask = function (contender,
|
|
143
|
-
var task = closestInScope(contender, matchesClasses('k-task'),
|
|
162
|
+
export var isTask = function (contender, parentScope) {
|
|
163
|
+
var task = closestInScope(contender, matchesClasses('k-task'), parentScope);
|
|
144
164
|
return isPresent(task);
|
|
145
165
|
};
|
|
146
166
|
/**
|
|
147
167
|
* @hidden
|
|
148
168
|
*
|
|
149
169
|
* Checks whether the queried item or its parent items has a `k-task-actions` selector - used for the clear button.
|
|
150
|
-
* Restricts the search up to the provided
|
|
170
|
+
* Restricts the search up to the provided parent element from the second param.
|
|
151
171
|
*/
|
|
152
|
-
export var isClearButton = function (contender,
|
|
153
|
-
var clearButtonContainer = closestInScope(contender, matchesClasses('k-task-actions'),
|
|
172
|
+
export var isClearButton = function (contender, parentScope) {
|
|
173
|
+
var clearButtonContainer = closestInScope(contender, matchesClasses('k-task-actions'), parentScope);
|
|
154
174
|
return isPresent(clearButtonContainer);
|
|
155
175
|
};
|
|
176
|
+
/**
|
|
177
|
+
* @hidden
|
|
178
|
+
*
|
|
179
|
+
* Checks whether the queried item has a `k-task-dot` selector - used for the dependency drag clues.
|
|
180
|
+
*/
|
|
181
|
+
export var isDependencyDragClue = function (element) {
|
|
182
|
+
if (!isPresent(element)) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
return element.classList.contains('k-task-dot');
|
|
186
|
+
};
|
|
187
|
+
/**
|
|
188
|
+
* @hidden
|
|
189
|
+
*
|
|
190
|
+
* Checks whether the queried item has a `k-task-dot` & `k-task-start` selector - used for the dependency drag start clues.
|
|
191
|
+
*/
|
|
192
|
+
export var isDependencyDragStartClue = function (element) {
|
|
193
|
+
if (!isPresent(element)) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
return element.classList.contains('k-task-dot') && element.classList.contains('k-task-start');
|
|
197
|
+
};
|
|
198
|
+
/**
|
|
199
|
+
* @hidden
|
|
200
|
+
*
|
|
201
|
+
* Gets the `DependencyType` for an attempted dependency create from the provided two elements.
|
|
202
|
+
* The two linked drag clue HTML elements are used to extract this data (via their CSS classes).
|
|
203
|
+
*/
|
|
204
|
+
export var getDependencyTypeFromTargetTasks = function (fromTaskClue, toTaskClue) {
|
|
205
|
+
if (!isDependencyDragClue(fromTaskClue) || !isDependencyDragClue(toTaskClue)) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
var fromTaskType = isDependencyDragStartClue(fromTaskClue) ? 'S' : 'F';
|
|
209
|
+
var toTaskType = isDependencyDragStartClue(toTaskClue) ? 'S' : 'F';
|
|
210
|
+
var dependencyTypeName = "" + fromTaskType + toTaskType;
|
|
211
|
+
switch (dependencyTypeName) {
|
|
212
|
+
case 'FF': return DependencyType.FF;
|
|
213
|
+
case 'FS': return DependencyType.FS;
|
|
214
|
+
case 'SF': return DependencyType.SF;
|
|
215
|
+
case 'SS': return DependencyType.SS;
|
|
216
|
+
default: return null;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
/**
|
|
220
|
+
* @hidden
|
|
221
|
+
*
|
|
222
|
+
* Checks whether the two provided drag clues belong to the same task element.
|
|
223
|
+
*/
|
|
224
|
+
export var sameTaskClues = function (fromTaskClue, toTaskClue, parentScope) {
|
|
225
|
+
if (!isPresent(fromTaskClue) || !isPresent(toTaskClue)) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
var fromTaskWrapper = getClosestTaskWrapper(fromTaskClue, parentScope);
|
|
229
|
+
var toTaskWrapper = getClosestTaskWrapper(toTaskClue, parentScope);
|
|
230
|
+
return fromTaskWrapper === toTaskWrapper;
|
|
231
|
+
};
|
|
232
|
+
/**
|
|
233
|
+
* @hidden
|
|
234
|
+
*
|
|
235
|
+
* Fits a contender number between a min and max range.
|
|
236
|
+
* If the contender is below the min value, the min value is returned.
|
|
237
|
+
* If the contender is above the max value, the max value is returned.
|
|
238
|
+
*/
|
|
239
|
+
export var fitToRange = function (contender, min, max) {
|
|
240
|
+
if (contender > max) {
|
|
241
|
+
return max;
|
|
242
|
+
}
|
|
243
|
+
else if (contender < min) {
|
|
244
|
+
return min;
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
return contender;
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
/**
|
|
251
|
+
* @hidden
|
|
252
|
+
*
|
|
253
|
+
* Checks whether either of the two provided tasks is a parent of the other.
|
|
254
|
+
*/
|
|
255
|
+
export var areParentChild = function (taskA, taskB) {
|
|
256
|
+
var parentChildRelationship = false;
|
|
257
|
+
var taskAParent = taskA;
|
|
258
|
+
while (isPresent(taskAParent) && isPresent(taskAParent.data)) {
|
|
259
|
+
if (taskAParent.data === taskB.data) {
|
|
260
|
+
parentChildRelationship = true;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
taskAParent = taskAParent.parent;
|
|
264
|
+
}
|
|
265
|
+
var taskBParent = taskB;
|
|
266
|
+
while (!parentChildRelationship && isPresent(taskBParent) && isPresent(taskBParent.data)) {
|
|
267
|
+
if (taskBParent.data === taskA.data) {
|
|
268
|
+
parentChildRelationship = true;
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
taskBParent = taskBParent.parent;
|
|
272
|
+
}
|
|
273
|
+
return parentChildRelationship;
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
* @hidden
|
|
277
|
+
*
|
|
278
|
+
* Extracts an element from the provided client coords.
|
|
279
|
+
* Using the `event.target` is not reliable under mobile devices with the current implementation of the draggable, so use this instead.
|
|
280
|
+
*/
|
|
281
|
+
export var elementFromPoint = function (clientX, clientY) {
|
|
282
|
+
if (!isDocumentAvailable()) {
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
return document.elementFromPoint(clientX, clientY);
|
|
286
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**-----------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright © 2021 Progress Software Corporation. All rights reserved.
|
|
3
|
+
* Licensed under commercial license. See LICENSE.md in the project root for more information
|
|
4
|
+
*-------------------------------------------------------------------------------------------*/
|
|
5
|
+
import { InjectionToken } from '@angular/core';
|
|
6
|
+
/**
|
|
7
|
+
* @hidden
|
|
8
|
+
*/
|
|
9
|
+
export declare const TOUCH_ENABLED: InjectionToken<boolean>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**-----------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright © 2021 Progress Software Corporation. All rights reserved.
|
|
3
|
+
* Licensed under commercial license. See LICENSE.md in the project root for more information
|
|
4
|
+
*-------------------------------------------------------------------------------------------*/
|
|
5
|
+
import { InjectionToken } from '@angular/core';
|
|
6
|
+
/**
|
|
7
|
+
* @hidden
|
|
8
|
+
*/
|
|
9
|
+
export const TOUCH_ENABLED = new InjectionToken('gantt-touch-enabled');
|
|
@@ -36,3 +36,18 @@ export declare const getElementRect: (element: HTMLElement, relativeContainer: H
|
|
|
36
36
|
* @hidden
|
|
37
37
|
*/
|
|
38
38
|
export declare const dependencyCoordinates: (from: Rectangle, to: Rectangle, rowHeight: number, type: DependencyType, minDistanceBeforeTurn: number, arrowSize: number) => Position[];
|
|
39
|
+
/**
|
|
40
|
+
* @hidden
|
|
41
|
+
*
|
|
42
|
+
* Translates the provided client `left` and `top` coords to coords relative to the provided container.
|
|
43
|
+
* https://developer.mozilla.org/en-US/docs/Web/CSS/CSSOM_View/Coordinate_systems#standard_cssom_coordinate_systems
|
|
44
|
+
*/
|
|
45
|
+
export declare const clientToOffsetCoords: (clientLeft: number, clientTop: number, offsetContainer: Element) => Position;
|
|
46
|
+
/**
|
|
47
|
+
* @hidden
|
|
48
|
+
*
|
|
49
|
+
* Retrieves the `left` and `top` values of the center of the provided element.
|
|
50
|
+
* The retrieved values are relative to the current viewport (client values).
|
|
51
|
+
* https://developer.mozilla.org/en-US/docs/Web/CSS/CSSOM_View/Coordinate_systems#standard_cssom_coordinate_systems
|
|
52
|
+
*/
|
|
53
|
+
export declare const getElementClientCenterCoords: (element: Element) => Position;
|
|
@@ -13,6 +13,9 @@ export const getOffsetRelativeToParent = (element, targetParent) => {
|
|
|
13
13
|
top: 0,
|
|
14
14
|
left: 0
|
|
15
15
|
};
|
|
16
|
+
if (!targetParent.contains(element)) {
|
|
17
|
+
return offset;
|
|
18
|
+
}
|
|
16
19
|
let offsetParent = element;
|
|
17
20
|
while (offsetParent && offsetParent !== targetParent) {
|
|
18
21
|
offset.top += offsetParent.offsetTop;
|
|
@@ -162,4 +165,35 @@ const getArrowEast = (top, left, arrowSize) => {
|
|
|
162
165
|
return points;
|
|
163
166
|
};
|
|
164
167
|
const ɵ2 = getArrowEast;
|
|
168
|
+
/**
|
|
169
|
+
* @hidden
|
|
170
|
+
*
|
|
171
|
+
* Translates the provided client `left` and `top` coords to coords relative to the provided container.
|
|
172
|
+
* https://developer.mozilla.org/en-US/docs/Web/CSS/CSSOM_View/Coordinate_systems#standard_cssom_coordinate_systems
|
|
173
|
+
*/
|
|
174
|
+
export const clientToOffsetCoords = (clientLeft, clientTop, offsetContainer) => {
|
|
175
|
+
// client (viewport) coordinates of the target container
|
|
176
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect#value
|
|
177
|
+
const offsetContainerClientRect = offsetContainer.getBoundingClientRect();
|
|
178
|
+
return {
|
|
179
|
+
left: clientLeft - offsetContainerClientRect.left + offsetContainer.scrollLeft,
|
|
180
|
+
top: clientTop - offsetContainerClientRect.top + offsetContainer.scrollTop
|
|
181
|
+
};
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* @hidden
|
|
185
|
+
*
|
|
186
|
+
* Retrieves the `left` and `top` values of the center of the provided element.
|
|
187
|
+
* The retrieved values are relative to the current viewport (client values).
|
|
188
|
+
* https://developer.mozilla.org/en-US/docs/Web/CSS/CSSOM_View/Coordinate_systems#standard_cssom_coordinate_systems
|
|
189
|
+
*/
|
|
190
|
+
export const getElementClientCenterCoords = (element) => {
|
|
191
|
+
// client (viewport) coordinates of the targeted element
|
|
192
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect#value
|
|
193
|
+
const { left, top, width, height } = element.getBoundingClientRect();
|
|
194
|
+
return {
|
|
195
|
+
left: left + (width / 2),
|
|
196
|
+
top: top + (height / 2)
|
|
197
|
+
};
|
|
198
|
+
};
|
|
165
199
|
export { ɵ0, ɵ1, ɵ2 };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**-----------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright © 2021 Progress Software Corporation. All rights reserved.
|
|
3
|
+
* Licensed under commercial license. See LICENSE.md in the project root for more information
|
|
4
|
+
*-------------------------------------------------------------------------------------------*/
|
|
5
|
+
import { AfterViewInit, NgZone, OnDestroy, Renderer2 } from '@angular/core';
|
|
6
|
+
import { PopupService } from '@progress/kendo-angular-popup';
|
|
7
|
+
import { GanttComponent } from '../gantt.component';
|
|
8
|
+
import { MappingService } from '../common/mapping.service';
|
|
9
|
+
import { TimelineScrollService } from '../scrolling/timeline-scroll.service';
|
|
10
|
+
/**
|
|
11
|
+
* A directive which enables the creation of new dependencies via dragging.
|
|
12
|
+
*/
|
|
13
|
+
export declare class DependencyDragCreateDirective implements AfterViewInit, OnDestroy {
|
|
14
|
+
private gantt;
|
|
15
|
+
private zone;
|
|
16
|
+
private renderer;
|
|
17
|
+
private mapper;
|
|
18
|
+
private popupService;
|
|
19
|
+
private timelineScrollService;
|
|
20
|
+
/**
|
|
21
|
+
* Specifies whether the validation tooltip will be displayed during drag operations.
|
|
22
|
+
*
|
|
23
|
+
* @default true
|
|
24
|
+
*/
|
|
25
|
+
displayValidationTooltip: boolean;
|
|
26
|
+
private readonly container;
|
|
27
|
+
private readonly polyline;
|
|
28
|
+
private readonly popupContainer;
|
|
29
|
+
/**
|
|
30
|
+
* Points to the drag clue where the dragging started (the FROM task).
|
|
31
|
+
* Used to attach/detach classes to the target element and its task wrapper parent element.
|
|
32
|
+
*/
|
|
33
|
+
private fromTaskClue;
|
|
34
|
+
/**
|
|
35
|
+
* The drag start element coords will be the same through the enitre dragging session, so compute them on press and cache them.
|
|
36
|
+
*/
|
|
37
|
+
private polylineStartCoords;
|
|
38
|
+
/**
|
|
39
|
+
* Stored during dragging to be consumed by the container scroll subscription.
|
|
40
|
+
* The scroll event doesn't expose the current pointer position, so it has to be stored separately.
|
|
41
|
+
*/
|
|
42
|
+
private currentPointerClientCoords;
|
|
43
|
+
private scrollListenerDisposer;
|
|
44
|
+
private dragPopup;
|
|
45
|
+
private dragSubscriptions;
|
|
46
|
+
constructor(gantt: GanttComponent, zone: NgZone, renderer: Renderer2, mapper: MappingService, popupService: PopupService, timelineScrollService: TimelineScrollService);
|
|
47
|
+
ngAfterViewInit(): void;
|
|
48
|
+
ngOnDestroy(): void;
|
|
49
|
+
private subscribeDraggable;
|
|
50
|
+
private unsubscribeDraggable;
|
|
51
|
+
private handlePress;
|
|
52
|
+
private handleDrag;
|
|
53
|
+
private handleRelease;
|
|
54
|
+
private updatePolyline;
|
|
55
|
+
private clearPolyline;
|
|
56
|
+
private assignDragStartClasses;
|
|
57
|
+
private removeDragStartClasses;
|
|
58
|
+
private scrollPointIntoView;
|
|
59
|
+
private cancelScroll;
|
|
60
|
+
private addScrollListener;
|
|
61
|
+
private removeScrollListener;
|
|
62
|
+
private openDragPopup;
|
|
63
|
+
private updateDragPopup;
|
|
64
|
+
private closeDragPopup;
|
|
65
|
+
private extractTaskName;
|
|
66
|
+
private getTooltipContext;
|
|
67
|
+
/**
|
|
68
|
+
* Restricts the popup position to not go below the scroll height or width of the container.
|
|
69
|
+
* Flips the position of the popup when there's not enough vertical space in the visible part of the container to render the popup.
|
|
70
|
+
*/
|
|
71
|
+
private normalizePopupPosition;
|
|
72
|
+
}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**-----------------------------------------------------------------------------------------
|
|
2
|
+
* Copyright © 2021 Progress Software Corporation. All rights reserved.
|
|
3
|
+
* Licensed under commercial license. See LICENSE.md in the project root for more information
|
|
4
|
+
*-------------------------------------------------------------------------------------------*/
|
|
5
|
+
import * as tslib_1 from "tslib";
|
|
6
|
+
import { Directive, Input, NgZone, Renderer2 } from '@angular/core';
|
|
7
|
+
import { isDocumentAvailable } from '@progress/kendo-angular-common';
|
|
8
|
+
import { PopupService } from '@progress/kendo-angular-popup';
|
|
9
|
+
import { GanttComponent } from '../gantt.component';
|
|
10
|
+
import { MappingService } from '../common/mapping.service';
|
|
11
|
+
import { TimelineScrollService } from '../scrolling/timeline-scroll.service';
|
|
12
|
+
import { DragValidationTooltipComponent } from './drag-validation-tooltip.component';
|
|
13
|
+
import { clientToOffsetCoords, getElementClientCenterCoords } from '../dependencies/utils';
|
|
14
|
+
import { isPresent, isDependencyDragClue, getClosestTaskWrapper, getDependencyTypeFromTargetTasks, getClosestTaskIndex, sameTaskClues, isTaskWrapper, fitToRange } from '../utils';
|
|
15
|
+
import { elementFromPoint } from '../utils';
|
|
16
|
+
/**
|
|
17
|
+
* When added to the .k-task-dot, the element will be kept with hover styles.
|
|
18
|
+
* Used for the drag clue from which the dragging has started.
|
|
19
|
+
*/
|
|
20
|
+
const DRAG_CLUE_HOVER_CLASS = 'k-state-hover';
|
|
21
|
+
/**
|
|
22
|
+
* Add the selection disabling class to the enitre container.
|
|
23
|
+
* Otherwise existing selection on a given task text prevents dragging the clue even if the clue has `user-select: none` styles.
|
|
24
|
+
*/
|
|
25
|
+
const USER_SELECT_NONE_CLASS = 'k-user-select-none';
|
|
26
|
+
/**
|
|
27
|
+
* When added to the .k-task-wrap, the containing .k-task-dot elements will be kept visible even when not hovered.
|
|
28
|
+
* Used for the drag clue from which the dragging has started.
|
|
29
|
+
*/
|
|
30
|
+
const TASK_WRAPPER_DRAG_CLASS = 'k-origin';
|
|
31
|
+
/**
|
|
32
|
+
* Use 20px margin between the pointer and the popup.
|
|
33
|
+
* Could be made user-configurable if there's demand.
|
|
34
|
+
*/
|
|
35
|
+
const DEFAULT_POPUP_VERTICAL_MARGIN = 20;
|
|
36
|
+
/**
|
|
37
|
+
* A directive which enables the creation of new dependencies via dragging.
|
|
38
|
+
*/
|
|
39
|
+
let DependencyDragCreateDirective = class DependencyDragCreateDirective {
|
|
40
|
+
constructor(gantt, zone, renderer, mapper, popupService, timelineScrollService) {
|
|
41
|
+
this.gantt = gantt;
|
|
42
|
+
this.zone = zone;
|
|
43
|
+
this.renderer = renderer;
|
|
44
|
+
this.mapper = mapper;
|
|
45
|
+
this.popupService = popupService;
|
|
46
|
+
this.timelineScrollService = timelineScrollService;
|
|
47
|
+
/**
|
|
48
|
+
* Specifies whether the validation tooltip will be displayed during drag operations.
|
|
49
|
+
*
|
|
50
|
+
* @default true
|
|
51
|
+
*/
|
|
52
|
+
this.displayValidationTooltip = true;
|
|
53
|
+
this.gantt.renderDependencyDragClues = true;
|
|
54
|
+
}
|
|
55
|
+
get container() {
|
|
56
|
+
if (!isPresent(this.gantt.timeline) || !isPresent(this.gantt.timeline.timelineContent)) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return this.gantt.timeline.timelineContent.nativeElement;
|
|
60
|
+
}
|
|
61
|
+
get polyline() {
|
|
62
|
+
if (!isPresent(this.gantt.timeline) || !isPresent(this.gantt.timeline.dependencyDragCreatePolyline)) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return this.gantt.timeline.dependencyDragCreatePolyline.nativeElement;
|
|
66
|
+
}
|
|
67
|
+
get popupContainer() {
|
|
68
|
+
if (!isPresent(this.gantt.timeline) || !isPresent(this.gantt.timeline.dragPopupContainer)) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return this.gantt.timeline.dragPopupContainer;
|
|
72
|
+
}
|
|
73
|
+
ngAfterViewInit() {
|
|
74
|
+
this.subscribeDraggable();
|
|
75
|
+
this.addScrollListener();
|
|
76
|
+
}
|
|
77
|
+
ngOnDestroy() {
|
|
78
|
+
this.unsubscribeDraggable();
|
|
79
|
+
this.removeScrollListener();
|
|
80
|
+
this.fromTaskClue = null;
|
|
81
|
+
this.cancelScroll();
|
|
82
|
+
this.closeDragPopup();
|
|
83
|
+
}
|
|
84
|
+
subscribeDraggable() {
|
|
85
|
+
this.dragSubscriptions = this.gantt.timeline.timelineContainerPress
|
|
86
|
+
.subscribe(this.handlePress.bind(this));
|
|
87
|
+
this.dragSubscriptions.add(this.gantt.timeline.timelineContainerDrag
|
|
88
|
+
.subscribe(this.handleDrag.bind(this)));
|
|
89
|
+
this.dragSubscriptions.add(this.gantt.timeline.timelineContainerRelease
|
|
90
|
+
.subscribe(this.handleRelease.bind(this)));
|
|
91
|
+
}
|
|
92
|
+
unsubscribeDraggable() {
|
|
93
|
+
if (isPresent(this.dragSubscriptions)) {
|
|
94
|
+
this.dragSubscriptions.unsubscribe();
|
|
95
|
+
this.dragSubscriptions = null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
handlePress({ clientX, clientY }) {
|
|
99
|
+
// using `originalEvent.target` is not reliable under mobile devices with the current implementation of the draggable, so use this instead
|
|
100
|
+
const target = elementFromPoint(clientX, clientY);
|
|
101
|
+
if (isDependencyDragClue(target)) {
|
|
102
|
+
this.fromTaskClue = target;
|
|
103
|
+
this.assignDragStartClasses(this.fromTaskClue);
|
|
104
|
+
// use the center of the target clue as polyline starting point
|
|
105
|
+
const dragClueCenterCoords = getElementClientCenterCoords(this.fromTaskClue);
|
|
106
|
+
// the polyline uses `position: aboslute`, so translate the client coordinates to offset coordinates (`left` and `top` relative to the timeline container)
|
|
107
|
+
this.polylineStartCoords = clientToOffsetCoords(dragClueCenterCoords.left, dragClueCenterCoords.top, this.container);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
handleDrag({ clientX, clientY }) {
|
|
111
|
+
if (isPresent(this.fromTaskClue)) {
|
|
112
|
+
// the polyline uses `position: aboslute`, so translate the client coordinates to offset coordinates (`left` and `top` relative to the timeline container)
|
|
113
|
+
const pointerOffsetCoords = clientToOffsetCoords(clientX, clientY, this.container);
|
|
114
|
+
// the start coords are calculated just once per drag session in handlePress
|
|
115
|
+
// use the current drag coords as polyline end coords
|
|
116
|
+
this.updatePolyline(this.polylineStartCoords, pointerOffsetCoords);
|
|
117
|
+
this.currentPointerClientCoords = { left: clientX, top: clientY };
|
|
118
|
+
if (this.gantt.dragScrollSettings.enabled) {
|
|
119
|
+
// use client coordinates for scroll trigger
|
|
120
|
+
this.scrollPointIntoView(this.currentPointerClientCoords);
|
|
121
|
+
}
|
|
122
|
+
if (this.displayValidationTooltip) {
|
|
123
|
+
this.updateDragPopup(pointerOffsetCoords);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
handleRelease({ clientX, clientY }) {
|
|
128
|
+
if (!isPresent(this.fromTaskClue)) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
// using `originalEvent.target` is not reliable under mobile devices with the current implementation of the draggable, so use this instead
|
|
132
|
+
const target = elementFromPoint(clientX, clientY);
|
|
133
|
+
if (isDependencyDragClue(target) && !sameTaskClues(this.fromTaskClue, target, this.container)) {
|
|
134
|
+
this.zone.run(() => {
|
|
135
|
+
const fromTaskClue = this.fromTaskClue;
|
|
136
|
+
const toTaskClue = target;
|
|
137
|
+
const fromTask = this.gantt.renderedTreeListItems[getClosestTaskIndex(fromTaskClue, this.container)];
|
|
138
|
+
const toTask = this.gantt.renderedTreeListItems[getClosestTaskIndex(toTaskClue, this.container)];
|
|
139
|
+
const dependencyType = getDependencyTypeFromTargetTasks(fromTaskClue, toTaskClue);
|
|
140
|
+
const { fromId, toId, type } = this.mapper.dependencyFields;
|
|
141
|
+
this.gantt.dependencyAdd.emit({
|
|
142
|
+
fromTask: fromTask,
|
|
143
|
+
toTask: toTask,
|
|
144
|
+
type: dependencyType,
|
|
145
|
+
isValid: this.gantt.validateNewDependency({
|
|
146
|
+
[fromId]: this.mapper.extractFromTask(fromTask, 'id'),
|
|
147
|
+
[toId]: this.mapper.extractFromTask(toTask, 'id'),
|
|
148
|
+
[type]: dependencyType
|
|
149
|
+
})
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
this.clearPolyline();
|
|
154
|
+
this.removeDragStartClasses(this.fromTaskClue);
|
|
155
|
+
this.fromTaskClue = null;
|
|
156
|
+
this.cancelScroll();
|
|
157
|
+
this.closeDragPopup();
|
|
158
|
+
}
|
|
159
|
+
updatePolyline(start, end) {
|
|
160
|
+
const points = `${start.left},${start.top} ${end.left},${end.top}`;
|
|
161
|
+
this.renderer.setAttribute(this.polyline, 'points', points);
|
|
162
|
+
}
|
|
163
|
+
clearPolyline() {
|
|
164
|
+
this.renderer.removeAttribute(this.polyline, 'points');
|
|
165
|
+
}
|
|
166
|
+
assignDragStartClasses(dragClue) {
|
|
167
|
+
if (!isPresent(dragClue)) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
this.renderer.addClass(this.container, USER_SELECT_NONE_CLASS);
|
|
171
|
+
this.renderer.addClass(dragClue, DRAG_CLUE_HOVER_CLASS);
|
|
172
|
+
const taskWrapper = getClosestTaskWrapper(dragClue, this.container);
|
|
173
|
+
if (isPresent(taskWrapper)) {
|
|
174
|
+
this.renderer.addClass(taskWrapper, TASK_WRAPPER_DRAG_CLASS);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
removeDragStartClasses(dragClue) {
|
|
178
|
+
if (!isPresent(dragClue)) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
this.renderer.removeClass(this.container, USER_SELECT_NONE_CLASS);
|
|
182
|
+
this.renderer.removeClass(dragClue, DRAG_CLUE_HOVER_CLASS);
|
|
183
|
+
const taskWrapper = getClosestTaskWrapper(dragClue, this.container);
|
|
184
|
+
if (isPresent(taskWrapper)) {
|
|
185
|
+
this.renderer.removeClass(taskWrapper, TASK_WRAPPER_DRAG_CLASS);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
scrollPointIntoView({ left, top }) {
|
|
189
|
+
this.timelineScrollService.requestScrollCancel();
|
|
190
|
+
this.timelineScrollService.requestHorizontalScroll(left);
|
|
191
|
+
this.timelineScrollService.requestVerticalScroll(top);
|
|
192
|
+
}
|
|
193
|
+
cancelScroll() {
|
|
194
|
+
this.timelineScrollService.requestScrollCancel();
|
|
195
|
+
}
|
|
196
|
+
addScrollListener() {
|
|
197
|
+
if (!isDocumentAvailable()) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
this.zone.runOutsideAngular(() => this.scrollListenerDisposer = this.renderer.listen(this.container, 'scroll', () => {
|
|
201
|
+
// update the polyline only if we're currently dragging
|
|
202
|
+
if (isPresent(this.fromTaskClue) && isPresent(this.currentPointerClientCoords)) {
|
|
203
|
+
const { left, top } = this.currentPointerClientCoords;
|
|
204
|
+
const pointerOffsetCoords = clientToOffsetCoords(left, top, this.container);
|
|
205
|
+
this.updatePolyline(this.polylineStartCoords, pointerOffsetCoords);
|
|
206
|
+
if (this.displayValidationTooltip) {
|
|
207
|
+
this.updateDragPopup(pointerOffsetCoords);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
removeScrollListener() {
|
|
213
|
+
if (isPresent(this.scrollListenerDisposer)) {
|
|
214
|
+
this.scrollListenerDisposer();
|
|
215
|
+
this.scrollListenerDisposer = null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
openDragPopup() {
|
|
219
|
+
if (isPresent(this.dragPopup)) {
|
|
220
|
+
this.closeDragPopup();
|
|
221
|
+
}
|
|
222
|
+
this.dragPopup = this.popupService.open({
|
|
223
|
+
animate: false,
|
|
224
|
+
content: DragValidationTooltipComponent,
|
|
225
|
+
appendTo: this.popupContainer,
|
|
226
|
+
positionMode: 'absolute',
|
|
227
|
+
popupClass: 'k-popup-transparent'
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
updateDragPopup(pointerOffsetPosition) {
|
|
231
|
+
if (!isPresent(this.dragPopup)) {
|
|
232
|
+
this.openDragPopup();
|
|
233
|
+
}
|
|
234
|
+
const tooltip = this.dragPopup.content.instance;
|
|
235
|
+
const { fromTaskName, toTaskName, isValid, showValidityStatus } = this.getTooltipContext();
|
|
236
|
+
if (tooltip.fromTaskName !== fromTaskName ||
|
|
237
|
+
tooltip.toTaskName !== toTaskName ||
|
|
238
|
+
tooltip.isValid !== isValid ||
|
|
239
|
+
tooltip.showValidityStatus !== showValidityStatus) {
|
|
240
|
+
tooltip.fromTaskName = fromTaskName;
|
|
241
|
+
tooltip.toTaskName = toTaskName;
|
|
242
|
+
tooltip.isValid = isValid;
|
|
243
|
+
tooltip.showValidityStatus = showValidityStatus;
|
|
244
|
+
this.dragPopup.content.changeDetectorRef.detectChanges();
|
|
245
|
+
}
|
|
246
|
+
this.dragPopup.popup.instance.offset = this.normalizePopupPosition(pointerOffsetPosition);
|
|
247
|
+
this.dragPopup.popup.changeDetectorRef.detectChanges();
|
|
248
|
+
}
|
|
249
|
+
closeDragPopup() {
|
|
250
|
+
if (isPresent(this.dragPopup)) {
|
|
251
|
+
this.dragPopup.close();
|
|
252
|
+
this.dragPopup = null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
extractTaskName(target) {
|
|
256
|
+
if (!isTaskWrapper(target, this.container)) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
const taskIndex = getClosestTaskIndex(target, this.container);
|
|
260
|
+
const task = this.gantt.renderedTreeListItems[taskIndex];
|
|
261
|
+
const taskName = this.mapper.extractFromTask(task, 'title');
|
|
262
|
+
return taskName;
|
|
263
|
+
}
|
|
264
|
+
getTooltipContext() {
|
|
265
|
+
const fromTaskName = this.extractTaskName(this.fromTaskClue);
|
|
266
|
+
const currentPointerTarget = elementFromPoint(this.currentPointerClientCoords.left, this.currentPointerClientCoords.top);
|
|
267
|
+
const toTaskName = isTaskWrapper(currentPointerTarget, this.container) && !sameTaskClues(this.fromTaskClue, currentPointerTarget, this.container) ?
|
|
268
|
+
this.extractTaskName(currentPointerTarget) :
|
|
269
|
+
'';
|
|
270
|
+
const showValidityStatus = isDependencyDragClue(currentPointerTarget) && !sameTaskClues(this.fromTaskClue, currentPointerTarget, this.container);
|
|
271
|
+
const { fromId, toId, type } = this.mapper.dependencyFields;
|
|
272
|
+
return {
|
|
273
|
+
fromTaskName,
|
|
274
|
+
toTaskName,
|
|
275
|
+
showValidityStatus,
|
|
276
|
+
isValid: showValidityStatus && this.gantt.validateNewDependency({
|
|
277
|
+
[fromId]: this.mapper.extractFromTask(this.gantt.renderedTreeListItems[getClosestTaskIndex(this.fromTaskClue, this.container)], 'id'),
|
|
278
|
+
[toId]: this.mapper.extractFromTask(this.gantt.renderedTreeListItems[getClosestTaskIndex(currentPointerTarget, this.container)], 'id'),
|
|
279
|
+
[type]: getDependencyTypeFromTargetTasks(this.fromTaskClue, currentPointerTarget)
|
|
280
|
+
})
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Restricts the popup position to not go below the scroll height or width of the container.
|
|
285
|
+
* Flips the position of the popup when there's not enough vertical space in the visible part of the container to render the popup.
|
|
286
|
+
*/
|
|
287
|
+
normalizePopupPosition(pointerOffsetPosition) {
|
|
288
|
+
let top = pointerOffsetPosition.top + DEFAULT_POPUP_VERTICAL_MARGIN;
|
|
289
|
+
const containerClientBottom = this.container.clientHeight + this.container.scrollTop;
|
|
290
|
+
const popupHeight = this.dragPopup.popupElement.querySelector('.k-tooltip').clientHeight;
|
|
291
|
+
const enoughSpaceToRender = top < containerClientBottom - popupHeight;
|
|
292
|
+
// flip the popup above the pointer if there's not enough space in the bottom of the container
|
|
293
|
+
if (!enoughSpaceToRender) {
|
|
294
|
+
// margin * 2 to account for the already applied margin
|
|
295
|
+
top -= popupHeight + (DEFAULT_POPUP_VERTICAL_MARGIN * 2);
|
|
296
|
+
}
|
|
297
|
+
// center the popup horizontally according to the pointer position
|
|
298
|
+
const popupWidth = this.dragPopup.popupElement.querySelector('.k-tooltip').clientWidth;
|
|
299
|
+
const left = pointerOffsetPosition.left - popupWidth / 2;
|
|
300
|
+
// don't allow the popup to be cut out of the viewport
|
|
301
|
+
const minLeftTop = 0;
|
|
302
|
+
// restrict the popup from being positioned beyond or before the available scrollable space
|
|
303
|
+
return {
|
|
304
|
+
left: fitToRange(left, minLeftTop, this.container.scrollWidth - popupWidth),
|
|
305
|
+
top: fitToRange(top, minLeftTop, this.container.scrollHeight - popupHeight)
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
tslib_1.__decorate([
|
|
310
|
+
Input(),
|
|
311
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
312
|
+
], DependencyDragCreateDirective.prototype, "displayValidationTooltip", void 0);
|
|
313
|
+
DependencyDragCreateDirective = tslib_1.__decorate([
|
|
314
|
+
Directive({
|
|
315
|
+
selector: '[kendoGanttDependencyDragCreate]'
|
|
316
|
+
}),
|
|
317
|
+
tslib_1.__metadata("design:paramtypes", [GanttComponent,
|
|
318
|
+
NgZone,
|
|
319
|
+
Renderer2,
|
|
320
|
+
MappingService,
|
|
321
|
+
PopupService,
|
|
322
|
+
TimelineScrollService])
|
|
323
|
+
], DependencyDragCreateDirective);
|
|
324
|
+
export { DependencyDragCreateDirective };
|