@progress/kendo-angular-gantt 0.3.0-dev.202112141015 → 1.0.0-dev.202201191538

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.
Files changed (151) hide show
  1. package/dist/cdn/js/kendo-angular-gantt.js +2 -2
  2. package/dist/cdn/main.js +1 -23
  3. package/dist/es/common/touch-enabled.js +9 -0
  4. package/dist/es/dependencies/utils.js +40 -5
  5. package/dist/es/dragging/dependency-drag-create.directive.js +347 -0
  6. package/dist/es/dragging/drag-validation-tooltip.component.js +27 -0
  7. package/dist/es/editing/dependencies-table.component.js +131 -0
  8. package/dist/es/editing/edit-dialog.component.js +39 -8
  9. package/dist/es/editing/edit.service.js +91 -8
  10. package/dist/es/editing/task-fields.component.js +43 -0
  11. package/dist/es/editing/{util.js → utils.js} +0 -0
  12. package/dist/es/gantt.component.js +352 -44
  13. package/dist/es/gantt.module.js +35 -9
  14. package/dist/es/index.js +8 -0
  15. package/dist/es/localization/gantt-localization.service.js +26 -0
  16. package/dist/es/main.js +1 -0
  17. package/dist/es/models/dependency-type.enum.js +16 -0
  18. package/dist/es/models/events/{edit-event.interface.js → dependency-add-event.interface.js} +0 -0
  19. package/dist/es/models/events/{remove-event.interface.js → task-delete-event.interface.js} +0 -0
  20. package/dist/es/models/models.js +1 -0
  21. package/dist/{es2015/models/events/edit-event.interface.js → es/models/view-item.interface.js} +0 -0
  22. package/dist/{es2015/models/events/remove-event.interface.js → es/navigation/navigation-models.js} +0 -0
  23. package/dist/es/navigation/navigation.service.js +390 -0
  24. package/dist/es/navigation/utils.js +77 -0
  25. package/dist/es/package-metadata.js +1 -1
  26. package/dist/es/rendering/gantt-milestone-task.component.js +12 -6
  27. package/dist/es/rendering/gantt-summary-task.component.js +27 -6
  28. package/dist/es/rendering/gantt-task-base.js +84 -22
  29. package/dist/es/rendering/gantt-task.component.js +13 -8
  30. package/dist/es/rendering/gantt-tasks-table-body.component.js +13 -5
  31. package/dist/es/scrolling/drag-scroll-settings.js +20 -0
  32. package/dist/es/scrolling/timeline-scroll.directive.js +89 -0
  33. package/dist/es/scrolling/timeline-scroll.service.js +39 -0
  34. package/dist/es/scrolling/utils.js +80 -0
  35. package/dist/es/timeline/gantt-timeline.component.js +50 -4
  36. package/dist/es/toolbar/toolbar.component.js +12 -13
  37. package/dist/es/toolbar/view-selector.component.js +1 -1
  38. package/dist/es/utils.js +153 -12
  39. package/dist/es2015/common/touch-enabled.d.ts +9 -0
  40. package/dist/es2015/common/touch-enabled.js +9 -0
  41. package/dist/es2015/dependencies/utils.d.ts +15 -0
  42. package/dist/es2015/dependencies/utils.js +40 -5
  43. package/dist/es2015/dragging/dependency-drag-create.directive.d.ts +72 -0
  44. package/dist/es2015/dragging/dependency-drag-create.directive.js +324 -0
  45. package/dist/es2015/dragging/drag-validation-tooltip.component.d.ts +29 -0
  46. package/dist/es2015/dragging/drag-validation-tooltip.component.js +76 -0
  47. package/dist/es2015/editing/dependencies-table.component.d.ts +39 -0
  48. package/dist/es2015/editing/dependencies-table.component.js +160 -0
  49. package/dist/es2015/editing/edit-dialog.component.d.ts +11 -4
  50. package/dist/es2015/editing/edit-dialog.component.js +66 -36
  51. package/dist/es2015/editing/edit.service.d.ts +22 -5
  52. package/dist/es2015/editing/edit.service.js +80 -11
  53. package/dist/es2015/editing/task-fields.component.d.ts +22 -0
  54. package/dist/es2015/editing/task-fields.component.js +67 -0
  55. package/dist/es2015/editing/{util.d.ts → utils.d.ts} +2 -2
  56. package/dist/es2015/editing/{util.js → utils.js} +0 -0
  57. package/dist/es2015/gantt.component.d.ts +116 -23
  58. package/dist/es2015/gantt.component.js +329 -48
  59. package/dist/es2015/gantt.module.js +35 -9
  60. package/dist/es2015/index.d.ts +8 -0
  61. package/dist/es2015/index.js +8 -0
  62. package/dist/es2015/index.metadata.json +1 -1
  63. package/dist/es2015/{models/events/remove-event.interface.d.ts → localization/gantt-localization.service.d.ts} +6 -7
  64. package/dist/es2015/localization/gantt-localization.service.js +25 -0
  65. package/dist/es2015/main.d.ts +1 -0
  66. package/dist/es2015/main.js +1 -0
  67. package/dist/es2015/models/dependency-type.enum.d.ts +1 -1
  68. package/dist/es2015/models/dependency-type.enum.js +16 -0
  69. package/dist/es2015/models/events/dependency-add-event.interface.d.ts +26 -0
  70. package/dist/es2015/models/events/dependency-add-event.interface.js +4 -0
  71. package/dist/es2015/models/events/task-click-event.interface.d.ts +3 -3
  72. package/dist/es2015/models/events/task-delete-event.interface.d.ts +21 -0
  73. package/dist/es2015/models/events/task-delete-event.interface.js +4 -0
  74. package/dist/es2015/models/events/task-edit-event.interface.d.ts +36 -6
  75. package/dist/es2015/models/models.d.ts +4 -2
  76. package/dist/es2015/models/models.js +1 -0
  77. package/dist/es2015/models/view-item.interface.d.ts +35 -0
  78. package/dist/es2015/models/view-item.interface.js +4 -0
  79. package/dist/es2015/navigation/navigation-models.d.ts +34 -0
  80. package/dist/es2015/navigation/navigation-models.js +4 -0
  81. package/dist/es2015/navigation/navigation.service.d.ts +126 -0
  82. package/dist/es2015/navigation/navigation.service.js +355 -0
  83. package/dist/es2015/navigation/utils.d.ts +26 -0
  84. package/dist/es2015/navigation/utils.js +69 -0
  85. package/dist/es2015/package-metadata.js +1 -1
  86. package/dist/es2015/rendering/gantt-milestone-task.component.d.ts +3 -1
  87. package/dist/es2015/rendering/gantt-milestone-task.component.js +35 -8
  88. package/dist/es2015/rendering/gantt-summary-task.component.d.ts +5 -1
  89. package/dist/es2015/rendering/gantt-summary-task.component.js +47 -8
  90. package/dist/es2015/rendering/gantt-task-base.d.ts +20 -6
  91. package/dist/es2015/rendering/gantt-task-base.js +75 -22
  92. package/dist/es2015/rendering/gantt-task.component.d.ts +4 -2
  93. package/dist/es2015/rendering/gantt-task.component.js +47 -13
  94. package/dist/es2015/rendering/gantt-tasks-table-body.component.d.ts +6 -3
  95. package/dist/es2015/rendering/gantt-tasks-table-body.component.js +27 -9
  96. package/dist/es2015/scrolling/drag-scroll-settings.d.ts +47 -0
  97. package/dist/es2015/scrolling/drag-scroll-settings.js +20 -0
  98. package/dist/es2015/scrolling/scroll-sync.service.d.ts +1 -1
  99. package/dist/es2015/scrolling/timeline-scroll.directive.d.ts +24 -0
  100. package/dist/es2015/scrolling/timeline-scroll.directive.js +78 -0
  101. package/dist/es2015/scrolling/timeline-scroll.service.d.ts +20 -0
  102. package/dist/es2015/scrolling/timeline-scroll.service.js +44 -0
  103. package/dist/es2015/scrolling/utils.d.ts +29 -0
  104. package/dist/es2015/scrolling/utils.js +80 -0
  105. package/dist/es2015/timeline/gantt-timeline.component.d.ts +29 -4
  106. package/dist/es2015/timeline/gantt-timeline.component.js +67 -5
  107. package/dist/es2015/toolbar/toolbar.component.d.ts +4 -5
  108. package/dist/es2015/toolbar/toolbar.component.js +12 -13
  109. package/dist/es2015/toolbar/view-selector.component.js +3 -1
  110. package/dist/es2015/utils.d.ts +77 -8
  111. package/dist/es2015/utils.js +153 -12
  112. package/dist/fesm2015/index.js +2807 -788
  113. package/dist/fesm5/index.js +2633 -688
  114. package/dist/{es2015/models/events/edit-event.interface.d.ts → npm/common/touch-enabled.js} +4 -12
  115. package/dist/npm/dependencies/utils.js +40 -5
  116. package/dist/npm/dragging/dependency-drag-create.directive.js +349 -0
  117. package/dist/npm/dragging/drag-validation-tooltip.component.js +29 -0
  118. package/dist/npm/editing/dependencies-table.component.js +133 -0
  119. package/dist/npm/editing/edit-dialog.component.js +38 -7
  120. package/dist/npm/editing/edit.service.js +90 -7
  121. package/dist/npm/editing/task-fields.component.js +45 -0
  122. package/dist/npm/editing/{util.js → utils.js} +0 -0
  123. package/dist/npm/gantt.component.js +354 -46
  124. package/dist/npm/gantt.module.js +33 -7
  125. package/dist/npm/index.js +16 -0
  126. package/dist/npm/localization/gantt-localization.service.js +28 -0
  127. package/dist/npm/main.js +2 -0
  128. package/dist/npm/models/dependency-type.enum.js +16 -0
  129. package/dist/npm/models/events/{edit-event.interface.js → dependency-add-event.interface.js} +0 -0
  130. package/dist/npm/models/events/{remove-event.interface.js → task-delete-event.interface.js} +0 -0
  131. package/dist/npm/models/models.js +2 -0
  132. package/dist/npm/models/view-item.interface.js +6 -0
  133. package/dist/npm/navigation/navigation-models.js +6 -0
  134. package/dist/npm/navigation/navigation.service.js +392 -0
  135. package/dist/npm/navigation/utils.js +79 -0
  136. package/dist/npm/package-metadata.js +1 -1
  137. package/dist/npm/rendering/gantt-milestone-task.component.js +11 -5
  138. package/dist/npm/rendering/gantt-summary-task.component.js +26 -5
  139. package/dist/npm/rendering/gantt-task-base.js +84 -22
  140. package/dist/npm/rendering/gantt-task.component.js +12 -7
  141. package/dist/npm/rendering/gantt-tasks-table-body.component.js +13 -5
  142. package/dist/npm/scrolling/drag-scroll-settings.js +22 -0
  143. package/dist/npm/scrolling/timeline-scroll.directive.js +91 -0
  144. package/dist/npm/scrolling/timeline-scroll.service.js +41 -0
  145. package/dist/npm/scrolling/utils.js +83 -0
  146. package/dist/npm/timeline/gantt-timeline.component.js +49 -3
  147. package/dist/npm/toolbar/toolbar.component.js +10 -11
  148. package/dist/npm/toolbar/view-selector.component.js +1 -1
  149. package/dist/npm/utils.js +153 -12
  150. package/dist/systemjs/kendo-angular-gantt.js +1 -1
  151. package/package.json +23 -19
@@ -3,37 +3,802 @@
3
3
  * Licensed under commercial license. See LICENSE.md in the project root for more information
4
4
  *-------------------------------------------------------------------------------------------*/
5
5
  import { __decorate, __metadata, __param } from 'tslib';
6
- import { Input, EventEmitter, Injectable, Directive, Optional, TemplateRef, QueryList, ContentChildren, ContentChild, Component, forwardRef, SkipSelf, Host, Injector, NgZone, isDevMode, ViewChild, HostBinding, Output, Renderer2, ElementRef, ChangeDetectorRef, NgModule } from '@angular/core';
6
+ import { Injectable, NgZone, EventEmitter, ViewChild, ElementRef, ViewContainerRef, HostBinding, Input, TemplateRef, Output, Component, Renderer2, Directive, Optional, QueryList, ContentChildren, ContentChild, forwardRef, SkipSelf, Host, Injector, isDevMode, InjectionToken, Inject, ChangeDetectorRef, NgModule } from '@angular/core';
7
7
  import { ColumnBase, ColumnComponent, ColumnGroupComponent, SpanColumnComponent, TreeListComponent, DataBoundTreeComponent, ExpandableTreeComponent, FlatBindingDirective, HierarchyBindingDirective, ExpandableDirective, TreeListModule } from '@progress/kendo-angular-treelist';
8
8
  import { cloneDate, addWeeks, firstDayInWeek, addDays, getDate, lastDayOfMonth, firstDayOfMonth, addMonths, isEqual, MS_PER_HOUR, MS_PER_DAY } from '@progress/kendo-date-math';
9
- import { of, Subject, Subscription, fromEvent } from 'rxjs';
9
+ import { Subject, Subscription, fromEvent, of, forkJoin, EMPTY, isObservable } from 'rxjs';
10
10
  import { validatePackage } from '@progress/kendo-licensing';
11
- import { isDocumentAvailable, closestInScope, matchesClasses, anyChanged, hasObservers, EventsModule } from '@progress/kendo-angular-common';
11
+ import { closestInScope, matchesClasses, isDocumentAvailable, Keys, hasObservers, anyChanged, EventsModule, DraggableModule } from '@progress/kendo-angular-common';
12
+ import { map, distinctUntilChanged, take, filter, switchMap, expand, reduce } from 'rxjs/operators';
13
+ import { getter, touchEnabled } from '@progress/kendo-common';
14
+ import { LocalizationService, ComponentMessages, L10N_PREFIX } from '@progress/kendo-angular-l10n';
12
15
  import { IntlService } from '@progress/kendo-angular-intl';
13
16
  import { orderBy } from '@progress/kendo-data-query';
14
- import { getter } from '@progress/kendo-common';
15
- import { map, distinctUntilChanged, take, filter, switchMap } from 'rxjs/operators';
16
- import { LocalizationService, ComponentMessages, L10N_PREFIX } from '@progress/kendo-angular-l10n';
17
17
  import { CommonModule } from '@angular/common';
18
- import { SplitterModule } from '@progress/kendo-angular-layout';
18
+ import { FormArray, FormGroup, FormControl, Validators, ReactiveFormsModule } from '@angular/forms';
19
+ import { SplitterModule, TabStripModule } from '@progress/kendo-angular-layout';
19
20
  import { ButtonsModule } from '@progress/kendo-angular-buttons';
20
21
  import { DialogModule } from '@progress/kendo-angular-dialog';
21
- import { FormGroup, ReactiveFormsModule } from '@angular/forms';
22
22
  import { LabelModule } from '@progress/kendo-angular-label';
23
23
  import { InputsModule } from '@progress/kendo-angular-inputs';
24
24
  import { DateInputsModule } from '@progress/kendo-angular-dateinputs';
25
+ import { PopupService, PopupModule } from '@progress/kendo-angular-popup';
26
+ import { GridModule } from '@progress/kendo-angular-grid';
27
+ import { DropDownsModule } from '@progress/kendo-angular-dropdowns';
28
+
29
+ /**
30
+ * @hidden
31
+ */
32
+ const packageMetadata = {
33
+ name: '@progress/kendo-angular-gantt',
34
+ productName: 'Kendo UI for Angular',
35
+ productCodes: ['KENDOUIANGULAR', 'KENDOUICOMPLETE'],
36
+ publishDate: 1642606197,
37
+ version: '',
38
+ licensingDocsUrl: 'https://www.telerik.com/kendo-angular-ui/my-license/?utm_medium=product&utm_source=kendoangular&utm_campaign=kendo-ui-angular-purchase-license-keys-warning'
39
+ };
40
+
41
+ /**
42
+ * @hidden
43
+ */
44
+ let ScrollSyncService = class ScrollSyncService {
45
+ constructor(ngZone) {
46
+ this.ngZone = ngZone;
47
+ this.changes = new Subject();
48
+ this.elements = [];
49
+ this.subscriptions = new Subscription();
50
+ this.subscriptions.add(this.changes.subscribe(args => {
51
+ this.scroll(args);
52
+ }));
53
+ }
54
+ registerElement(el, sourceType) {
55
+ this.elements.push({ element: el, sourceType });
56
+ if (sourceType === "timeline" || sourceType === "treelist") {
57
+ this.ngZone.runOutsideAngular(() => {
58
+ const obs = fromEvent(el, 'scroll').pipe(map(({ target: { scrollTop, scrollLeft } }) => ({
59
+ scrollTop,
60
+ scrollLeft,
61
+ sourceType
62
+ })));
63
+ const comparisonFn = sourceType === 'timeline' ?
64
+ (x, y) => (x.scrollTop === y.scrollTop) && (x.scrollLeft === y.scrollLeft) :
65
+ (x, y) => (x.scrollTop === y.scrollTop);
66
+ this.subscriptions.add(obs.pipe(distinctUntilChanged(comparisonFn))
67
+ .subscribe((event) => this.changes.next(event)));
68
+ });
69
+ }
70
+ }
71
+ ngOnDestroy() {
72
+ this.subscriptions.unsubscribe();
73
+ this.elements = null;
74
+ }
75
+ syncScrollTop(sourceType, targetType) {
76
+ const source = this.elements.find(element => element.sourceType === sourceType);
77
+ const target = this.elements.find(element => element.sourceType === targetType);
78
+ // Need to wait for the splitter pane's content to be rendered
79
+ this.ngZone.onStable.pipe(take(1)).subscribe(() => target.element.scrollTop = source.element.scrollTop);
80
+ }
81
+ resetTimelineScrollLeft() {
82
+ const source = this.elements.find(element => element.sourceType === 'timeline');
83
+ source.element.scrollLeft = 0;
84
+ }
85
+ scroll({ scrollTop, scrollLeft, sourceType }) {
86
+ this.ngZone.runOutsideAngular(() => {
87
+ if (sourceType === 'timeline') {
88
+ const header = this.elements.find(element => element.sourceType === 'header').element;
89
+ header.scrollLeft = scrollLeft;
90
+ if (!this.syncingTimeline) {
91
+ this.syncingTreeList = true;
92
+ const treelist = this.elements.find(element => element.sourceType === 'treelist').element;
93
+ treelist.scrollTop = scrollTop;
94
+ }
95
+ this.syncingTimeline = false;
96
+ }
97
+ if (sourceType === 'treelist') {
98
+ if (!this.syncingTreeList) {
99
+ this.syncingTimeline = true;
100
+ const timeline = this.elements.find(element => element.sourceType === 'timeline').element;
101
+ timeline.scrollTop = scrollTop;
102
+ }
103
+ this.syncingTreeList = false;
104
+ }
105
+ });
106
+ }
107
+ };
108
+ ScrollSyncService = __decorate([
109
+ Injectable(),
110
+ __metadata("design:paramtypes", [NgZone])
111
+ ], ScrollSyncService);
112
+
113
+ /**
114
+ * @hidden
115
+ */
116
+ const DEFAULT_DEPENDENCY_MODEL_FIELDS = Object.freeze({
117
+ toId: 'toId',
118
+ fromId: 'fromId',
119
+ id: 'id',
120
+ type: 'type'
121
+ });
122
+
123
+ /**
124
+ * @hidden
125
+ */
126
+ const DEFAULT_TASK_MODEL_FIELDS = Object.freeze({
127
+ id: 'id',
128
+ start: 'start',
129
+ end: 'end',
130
+ title: 'title',
131
+ completionRatio: 'completionRatio',
132
+ children: 'children'
133
+ });
134
+
135
+ /**
136
+ * The dependency type when two tasks are connected.
137
+ *
138
+ * The supported values are:
139
+ * * `FF`—from 'finish' to 'finish'
140
+ * * `FS`—from 'finish' to 'start'
141
+ * * `SS`—from 'start' to 'start'
142
+ * * `SF`—from 'start' to 'finish'
143
+ */
144
+ var DependencyType;
145
+ (function (DependencyType) {
146
+ DependencyType[DependencyType["FF"] = 0] = "FF";
147
+ DependencyType[DependencyType["FS"] = 1] = "FS";
148
+ DependencyType[DependencyType["SF"] = 2] = "SF";
149
+ DependencyType[DependencyType["SS"] = 3] = "SS"; // task B can't start before task A starts
150
+ })(DependencyType || (DependencyType = {}));
151
+
152
+ /**
153
+ * @hidden
154
+ */
155
+ const isWorkDay = (date, start, end) => {
156
+ return date.getDay() >= start && date.getDay() <= end;
157
+ };
158
+ /**
159
+ * @hidden
160
+ */
161
+ const isWorkHour = (date, start, end) => {
162
+ return date.getHours() >= start && date.getHours() <= end;
163
+ };
164
+ /**
165
+ * @hidden
166
+ */
167
+ const isPresent = (item) => item !== null && item !== undefined;
168
+ /**
169
+ * @hidden
170
+ *
171
+ * Normalized the data to an array in case a falsy value is passed
172
+ * or a TreeListDataResult object (applicable for the data-binding directives).
173
+ */
174
+ const normalizeGanttData = (data) => {
175
+ if (!isPresent(data)) {
176
+ return [];
177
+ }
178
+ else if (Array.isArray(data.data)) {
179
+ return data.data;
180
+ }
181
+ else {
182
+ return data;
183
+ }
184
+ };
185
+ /**
186
+ * @hidden
187
+ *
188
+ * Returns a new date with the specified hours, minutes, seconds and millliseconds set.
189
+ * Only the hours are required, the rest of the params are set to `0` by default.
190
+ */
191
+ const setTime = (date, hours, minutes = 0, seconds = 0, milliseconds = 0) => {
192
+ if (!isPresent(date)) {
193
+ return null;
194
+ }
195
+ const result = cloneDate(date);
196
+ result.setHours(hours);
197
+ result.setMinutes(minutes);
198
+ result.setSeconds(seconds);
199
+ result.setMilliseconds(milliseconds);
200
+ return result;
201
+ };
202
+ /**
203
+ * @hidden
204
+ *
205
+ * Returns the last day of a week.
206
+ * @param standingPoint - Any day of the target week.
207
+ * @param firstWeekDay - The week's starting day (e.g. Monday, Tuesday, etc.)
208
+ */
209
+ const lastDayOfWeek = (standingPoint, firstWeekDay) => {
210
+ const followingWeek = addWeeks(standingPoint, 1);
211
+ const firstDayOfFollowingWeek = firstDayInWeek(followingWeek, firstWeekDay);
212
+ const lastDayOfTargetWeek = addDays(firstDayOfFollowingWeek, -1);
213
+ return lastDayOfTargetWeek;
214
+ };
215
+ /**
216
+ * Persists the intially resolved scrollbar width value.
217
+ */
218
+ let SCROLLBAR_WIDTH;
219
+ /**
220
+ * @hidden
221
+ *
222
+ * Gets the default scrollbar width accoring to the current environment.
223
+ */
224
+ const scrollbarWidth = () => {
225
+ if (!isDocumentAvailable()) {
226
+ return;
227
+ }
228
+ // calculate scrollbar width only once, then return the cached value
229
+ if (isNaN(SCROLLBAR_WIDTH)) {
230
+ const div = document.createElement('div');
231
+ div.style.cssText = 'overflow: scroll; overflow-x: hidden; zoom: 1; clear: both; display: block;';
232
+ div.innerHTML = '&nbsp;';
233
+ document.body.appendChild(div);
234
+ SCROLLBAR_WIDTH = div.offsetWidth - div.scrollWidth;
235
+ document.body.removeChild(div);
236
+ }
237
+ return SCROLLBAR_WIDTH;
238
+ };
239
+ /**
240
+ * @hidden
241
+ */
242
+ const isColumnGroup = (column) => column.isColumnGroup;
243
+ /**
244
+ * @hidden
245
+ */
246
+ const isNumber = (contender) => typeof contender === 'number' && !isNaN(contender);
247
+ /**
248
+ * @hidden
249
+ */
250
+ const isString = (contender) => typeof contender === 'string';
251
+ /**
252
+ * @hidden
253
+ *
254
+ * Gets the closest timeline task wrapper element from an event target.
255
+ * Restricts the search up to the provided parent element from the second param.
256
+ */
257
+ const getClosestTaskWrapper = (element, parentScope) => {
258
+ return closestInScope(element, matchesClasses('k-task-wrap'), parentScope);
259
+ };
260
+ /**
261
+ * @hidden
262
+ *
263
+ * Checks whether the queried item or its parent items has a `k-task-wrap` selector.
264
+ * Restricts the search up to the provided parent element from the second param.
265
+ */
266
+ const isTaskWrapper = (contender, parentScope) => {
267
+ const taskWrapper = closestInScope(contender, matchesClasses('k-task-wrap'), parentScope);
268
+ return isPresent(taskWrapper);
269
+ };
270
+ /**
271
+ * @hidden
272
+ *
273
+ * Gets the closest timeline task element index from an event target.
274
+ * Uses the `data-task-index` attribute assigned to each task.
275
+ * Restricts the search up to the provided parent element from the second param.
276
+ */
277
+ const getClosestTaskIndex = (element, parentScope) => {
278
+ const task = closestInScope(element, matchesClasses('k-task-wrap'), parentScope);
279
+ if (!isPresent(task)) {
280
+ return null;
281
+ }
282
+ return Number(task.getAttribute('data-task-index'));
283
+ };
284
+ /**
285
+ * @hidden
286
+ *
287
+ * Checks whether the queried item or its parent items has a `k-task` selector.
288
+ * Restricts the search up to the provided parent element from the second param.
289
+ */
290
+ const isTask = (contender, parentScope) => {
291
+ const task = closestInScope(contender, matchesClasses('k-task'), parentScope);
292
+ return isPresent(task);
293
+ };
294
+ /**
295
+ * @hidden
296
+ *
297
+ * Checks whether the queried item or its parent items has a `k-toolbar` selector.
298
+ * Restricts the search up to the provided parent element from the second param.
299
+ */
300
+ const isToolbar = (contender, parentScope) => {
301
+ const toolbar = closestInScope(contender, matchesClasses('k-gantt-toolbar'), parentScope);
302
+ return isPresent(toolbar);
303
+ };
304
+ /**
305
+ * @hidden
306
+ *
307
+ * Checks whether the queried item or its parent items has a `k-task-actions` selector - used for the clear button.
308
+ * Restricts the search up to the provided parent element from the second param.
309
+ */
310
+ const isClearButton = (contender, parentScope) => {
311
+ const clearButtonContainer = closestInScope(contender, matchesClasses('k-task-actions'), parentScope);
312
+ return isPresent(clearButtonContainer);
313
+ };
314
+ /**
315
+ * @hidden
316
+ *
317
+ * Checks whether the queried item has a `k-task-dot` selector - used for the dependency drag clues.
318
+ */
319
+ const isDependencyDragClue = (element) => {
320
+ if (!isPresent(element)) {
321
+ return false;
322
+ }
323
+ return element.classList.contains('k-task-dot');
324
+ };
325
+ /**
326
+ * @hidden
327
+ *
328
+ * Checks whether the queried item has a `k-task-dot` & `k-task-start` selector - used for the dependency drag start clues.
329
+ */
330
+ const isDependencyDragStartClue = (element) => {
331
+ if (!isPresent(element)) {
332
+ return false;
333
+ }
334
+ return element.classList.contains('k-task-dot') && element.classList.contains('k-task-start');
335
+ };
336
+ /**
337
+ * @hidden
338
+ *
339
+ * Gets the `DependencyType` for an attempted dependency create from the provided two elements.
340
+ * The two linked drag clue HTML elements are used to extract this data (via their CSS classes).
341
+ */
342
+ const getDependencyTypeFromTargetTasks = (fromTaskClue, toTaskClue) => {
343
+ if (!isDependencyDragClue(fromTaskClue) || !isDependencyDragClue(toTaskClue)) {
344
+ return null;
345
+ }
346
+ const fromTaskType = isDependencyDragStartClue(fromTaskClue) ? 'S' : 'F';
347
+ const toTaskType = isDependencyDragStartClue(toTaskClue) ? 'S' : 'F';
348
+ const dependencyTypeName = `${fromTaskType}${toTaskType}`;
349
+ switch (dependencyTypeName) {
350
+ case 'FF': return DependencyType.FF;
351
+ case 'FS': return DependencyType.FS;
352
+ case 'SF': return DependencyType.SF;
353
+ case 'SS': return DependencyType.SS;
354
+ default: return null;
355
+ }
356
+ };
357
+ /**
358
+ * @hidden
359
+ *
360
+ * Checks whether the two provided drag clues belong to the same task element.
361
+ */
362
+ const sameTaskClues = (fromTaskClue, toTaskClue, parentScope) => {
363
+ if (!isPresent(fromTaskClue) || !isPresent(toTaskClue)) {
364
+ return false;
365
+ }
366
+ const fromTaskWrapper = getClosestTaskWrapper(fromTaskClue, parentScope);
367
+ const toTaskWrapper = getClosestTaskWrapper(toTaskClue, parentScope);
368
+ return fromTaskWrapper === toTaskWrapper;
369
+ };
370
+ /**
371
+ * @hidden
372
+ *
373
+ * Fits a contender number between a min and max range.
374
+ * If the contender is below the min value, the min value is returned.
375
+ * If the contender is above the max value, the max value is returned.
376
+ */
377
+ const fitToRange = (contender, min, max) => {
378
+ if (!isPresent(contender) || contender < min) {
379
+ return min;
380
+ }
381
+ else if (contender > max) {
382
+ return max;
383
+ }
384
+ else {
385
+ return contender;
386
+ }
387
+ };
388
+ /**
389
+ * @hidden
390
+ *
391
+ * Checks whether either of the two provided tasks is a parent of the other.
392
+ */
393
+ const areParentChild = (taskA, taskB) => {
394
+ let parentChildRelationship = false;
395
+ let taskAParent = taskA;
396
+ while (isPresent(taskAParent) && isPresent(taskAParent.data)) {
397
+ if (taskAParent.data === taskB.data) {
398
+ parentChildRelationship = true;
399
+ break;
400
+ }
401
+ taskAParent = taskAParent.parent;
402
+ }
403
+ let taskBParent = taskB;
404
+ while (!parentChildRelationship && isPresent(taskBParent) && isPresent(taskBParent.data)) {
405
+ if (taskBParent.data === taskA.data) {
406
+ parentChildRelationship = true;
407
+ break;
408
+ }
409
+ taskBParent = taskBParent.parent;
410
+ }
411
+ return parentChildRelationship;
412
+ };
413
+ /**
414
+ * @hidden
415
+ *
416
+ * Extracts an element from the provided client coords.
417
+ * Using the `event.target` is not reliable under mobile devices with the current implementation of the draggable, so use this instead.
418
+ */
419
+ const elementFromPoint = (clientX, clientY) => {
420
+ if (!isDocumentAvailable()) {
421
+ return null;
422
+ }
423
+ return document.elementFromPoint(clientX, clientY);
424
+ };
425
+
426
+ /**
427
+ * @hidden
428
+ */
429
+ let MappingService = class MappingService {
430
+ /**
431
+ * @hidden
432
+ */
433
+ constructor() {
434
+ this._taskFields = Object.assign({}, DEFAULT_TASK_MODEL_FIELDS);
435
+ this._dependencyFields = Object.assign({}, DEFAULT_DEPENDENCY_MODEL_FIELDS);
436
+ }
437
+ /**
438
+ * Gets or sets the model fields for the task data items.
439
+ * Uses the default values for fields which are not specified.
440
+ */
441
+ set taskFields(fields) {
442
+ this._taskFields = Object.assign({}, DEFAULT_TASK_MODEL_FIELDS, fields);
443
+ }
444
+ get taskFields() {
445
+ return this._taskFields;
446
+ }
447
+ /**
448
+ * Gets or sets the model fields for the depenency data items.
449
+ * Uses the default values for fields which are not specified.
450
+ */
451
+ set dependencyFields(fields) {
452
+ this._dependencyFields = Object.assign({}, DEFAULT_DEPENDENCY_MODEL_FIELDS, fields);
453
+ }
454
+ get dependencyFields() {
455
+ return this._dependencyFields;
456
+ }
457
+ /**
458
+ * Retrieves the value for the specified task field.
459
+ * Supports nested fields as well (e.g. 'manager.id').
460
+ */
461
+ extractFromTask(dataItem, field) {
462
+ if (!isPresent(this.taskFields)) {
463
+ return null;
464
+ }
465
+ return getter(this.taskFields[field])(dataItem);
466
+ }
467
+ /**
468
+ * Retrieves the value for the specified dependency field.
469
+ * Supports nested fields as well (e.g. 'manager.id').
470
+ */
471
+ extractFromDependency(dataItem, field) {
472
+ if (!isPresent(this.dependencyFields)) {
473
+ return null;
474
+ }
475
+ return getter(this.dependencyFields[field])(dataItem);
476
+ }
477
+ };
478
+ MappingService = __decorate([
479
+ Injectable()
480
+ ], MappingService);
481
+
482
+ /**
483
+ * @hidden
484
+ */
485
+ let DependencyDomService = class DependencyDomService {
486
+ constructor(mapper) {
487
+ this.mapper = mapper;
488
+ this.notifier = new Subject();
489
+ /**
490
+ * Maps each rendered task to its HTML element.
491
+ * Uses the task ID field value as key.
492
+ */
493
+ this.tasks = new Map();
494
+ }
495
+ /**
496
+ * Emits each time some of the tasks or the view have changed.
497
+ * Fires also on the first change of the table rows and the parent container.
498
+ */
499
+ get taskChanges() {
500
+ return this.notifier.asObservable();
501
+ }
502
+ get dependencyDomArgs() {
503
+ return {
504
+ tasks: this.tasks,
505
+ contentContainer: this.contentContainer,
506
+ timelineRow: this.timelineRow
507
+ };
508
+ }
509
+ ngOnDestroy() {
510
+ this.tasks.clear();
511
+ this.tasks = null;
512
+ this.contentContainer = null;
513
+ }
514
+ registerTimelineRow(timelineRow) {
515
+ this.timelineRow = timelineRow;
516
+ this.notifyChanges();
517
+ }
518
+ registerContentContainer(contentContainer) {
519
+ this.contentContainer = contentContainer;
520
+ this.notifyChanges();
521
+ }
522
+ registerTask(task, element) {
523
+ const id = this.mapper.extractFromTask(task, 'id');
524
+ this.tasks.set(id, element);
525
+ this.notifyChanges();
526
+ }
527
+ unregisterTask(task) {
528
+ const id = this.mapper.extractFromTask(task, 'id');
529
+ this.tasks.delete(id);
530
+ this.notifyChanges();
531
+ }
532
+ /**
533
+ * Notifies all dependency directives that a change in one of the elements has occured.
534
+ */
535
+ notifyChanges() {
536
+ this.notifier.next(this.dependencyDomArgs);
537
+ }
538
+ };
539
+ DependencyDomService = __decorate([
540
+ Injectable(),
541
+ __metadata("design:paramtypes", [MappingService])
542
+ ], DependencyDomService);
543
+
544
+ /**
545
+ * @hidden
546
+ */
547
+ let GanttTimelineComponent = class GanttTimelineComponent {
548
+ constructor(scrollSyncService, dependencyDomService, renderer, zone) {
549
+ this.scrollSyncService = scrollSyncService;
550
+ this.dependencyDomService = dependencyDomService;
551
+ this.renderer = renderer;
552
+ this.zone = zone;
553
+ this.hostClass = true;
554
+ this.dependencies = [];
555
+ // as all drag-and-drop operations are on the timeline container, use a single draggable instance
556
+ this.timelineContainerPress = new EventEmitter();
557
+ this.timelineContainerDrag = new EventEmitter();
558
+ this.timelineContainerRelease = new EventEmitter();
559
+ this.subscriptions = new Subscription();
560
+ this.subscriptions.add(
561
+ // task changes indicates change in row content, number, height, etc.
562
+ this.dependencyDomService.taskChanges
563
+ .pipe(filter(args => isPresent(args.timelineRow)), switchMap(args => this.zone.onStable.pipe(take(1), map(() => args))) // ensure the content is rendered
564
+ )
565
+ .subscribe(({ timelineRow }) => {
566
+ const timelineRowHeight = isDocumentAvailable() ? timelineRow.getBoundingClientRect().height : 0;
567
+ this.renderer.setStyle(this.timelineColumns.nativeElement, 'height', `${(this.rows || []).length * timelineRowHeight}px`);
568
+ }));
569
+ }
570
+ /**
571
+ * Specifies whether the draggable will attach or detach its pointer event listeners.
572
+ */
573
+ get draggableEnabled() {
574
+ return this.renderDependencyDragClues;
575
+ }
576
+ ngAfterViewInit() {
577
+ const timelineHeader = this.timelineHeaderWrap.nativeElement;
578
+ const rightContainer = this.timelineContent.nativeElement;
579
+ this.scrollSyncService.registerElement(rightContainer, 'timeline');
580
+ this.scrollSyncService.registerElement(timelineHeader, 'header');
581
+ this.dependencyDomService.registerContentContainer(this.tasksContainer.nativeElement);
582
+ }
583
+ ngOnDestroy() {
584
+ this.subscriptions.unsubscribe();
585
+ }
586
+ isNonWorking(item) {
587
+ return item.hasOwnProperty('isWorking') && !item.isWorking;
588
+ }
589
+ };
590
+ __decorate([
591
+ ViewChild('timelineContent', { static: true }),
592
+ __metadata("design:type", ElementRef)
593
+ ], GanttTimelineComponent.prototype, "timelineContent", void 0);
594
+ __decorate([
595
+ ViewChild('timelineColumns', { static: true }),
596
+ __metadata("design:type", ElementRef)
597
+ ], GanttTimelineComponent.prototype, "timelineColumns", void 0);
598
+ __decorate([
599
+ ViewChild('timelineHeaderWrap', { static: true }),
600
+ __metadata("design:type", ElementRef)
601
+ ], GanttTimelineComponent.prototype, "timelineHeaderWrap", void 0);
602
+ __decorate([
603
+ ViewChild('tasksContainer', { static: true }),
604
+ __metadata("design:type", ElementRef)
605
+ ], GanttTimelineComponent.prototype, "tasksContainer", void 0);
606
+ __decorate([
607
+ ViewChild('dragPopupContainer', { static: false, read: ViewContainerRef }),
608
+ __metadata("design:type", ViewContainerRef)
609
+ ], GanttTimelineComponent.prototype, "dragPopupContainer", void 0);
610
+ __decorate([
611
+ ViewChild('dependencyDragCreatePolyline', { static: false }),
612
+ __metadata("design:type", ElementRef)
613
+ ], GanttTimelineComponent.prototype, "dependencyDragCreatePolyline", void 0);
614
+ __decorate([
615
+ HostBinding('class.k-gantt-timeline'),
616
+ __metadata("design:type", Boolean)
617
+ ], GanttTimelineComponent.prototype, "hostClass", void 0);
618
+ __decorate([
619
+ Input(),
620
+ __metadata("design:type", Array)
621
+ ], GanttTimelineComponent.prototype, "rows", void 0);
622
+ __decorate([
623
+ Input(),
624
+ __metadata("design:type", Array)
625
+ ], GanttTimelineComponent.prototype, "slots", void 0);
626
+ __decorate([
627
+ Input(),
628
+ __metadata("design:type", Array)
629
+ ], GanttTimelineComponent.prototype, "groupSlots", void 0);
630
+ __decorate([
631
+ Input(),
632
+ __metadata("design:type", Number)
633
+ ], GanttTimelineComponent.prototype, "tableWidth", void 0);
634
+ __decorate([
635
+ Input(),
636
+ __metadata("design:type", String)
637
+ ], GanttTimelineComponent.prototype, "activeView", void 0);
638
+ __decorate([
639
+ Input(),
640
+ __metadata("design:type", TemplateRef)
641
+ ], GanttTimelineComponent.prototype, "taskContentTemplate", void 0);
642
+ __decorate([
643
+ Input(),
644
+ __metadata("design:type", TemplateRef)
645
+ ], GanttTimelineComponent.prototype, "taskTemplate", void 0);
646
+ __decorate([
647
+ Input(),
648
+ __metadata("design:type", TemplateRef)
649
+ ], GanttTimelineComponent.prototype, "summaryTaskTemplate", void 0);
650
+ __decorate([
651
+ Input(),
652
+ __metadata("design:type", Function)
653
+ ], GanttTimelineComponent.prototype, "taskClass", void 0);
654
+ __decorate([
655
+ Input(),
656
+ __metadata("design:type", Boolean)
657
+ ], GanttTimelineComponent.prototype, "renderDependencyDragClues", void 0);
658
+ __decorate([
659
+ Input(),
660
+ __metadata("design:type", Object)
661
+ ], GanttTimelineComponent.prototype, "dragScrollSettings", void 0);
662
+ __decorate([
663
+ Input(),
664
+ __metadata("design:type", Boolean)
665
+ ], GanttTimelineComponent.prototype, "selectable", void 0);
666
+ __decorate([
667
+ Input(),
668
+ __metadata("design:type", Function)
669
+ ], GanttTimelineComponent.prototype, "isTaskSelected", void 0);
670
+ __decorate([
671
+ Input(),
672
+ __metadata("design:type", Function)
673
+ ], GanttTimelineComponent.prototype, "isExpanded", void 0);
674
+ __decorate([
675
+ Input(),
676
+ __metadata("design:type", Array)
677
+ ], GanttTimelineComponent.prototype, "dependencies", void 0);
678
+ __decorate([
679
+ Output(),
680
+ __metadata("design:type", EventEmitter)
681
+ ], GanttTimelineComponent.prototype, "timelineContainerPress", void 0);
682
+ __decorate([
683
+ Output(),
684
+ __metadata("design:type", EventEmitter)
685
+ ], GanttTimelineComponent.prototype, "timelineContainerDrag", void 0);
686
+ __decorate([
687
+ Output(),
688
+ __metadata("design:type", EventEmitter)
689
+ ], GanttTimelineComponent.prototype, "timelineContainerRelease", void 0);
690
+ GanttTimelineComponent = __decorate([
691
+ Component({
692
+ selector: 'kendo-gantt-timeline',
693
+ template: `
694
+ <div class="k-timeline k-grid k-widget">
695
+ <div class="k-grid-header">
696
+ <div #timelineHeaderWrap class="k-grid-header-wrap">
697
+ <table
698
+ role="presentation"
699
+ [style.width.px]="tableWidth"
700
+ >
701
+ <tbody
702
+ kendoGanttHeaderTableBody
703
+ [groupSlots]="groupSlots"
704
+ [slots]="slots">
705
+ </tbody>
706
+ </table>
707
+ </div>
708
+ </div>
709
+ <!-- tabindex="-1" required for https://bugzilla.mozilla.org/show_bug.cgi?id=1069739 -->
710
+ <div
711
+ #timelineContent
712
+ class="k-grid-content"
713
+ tabindex="-1"
714
+ role="tree"
715
+ aria-roledescription="Timeline"
716
+ kendoGanttTimelineScrollable
717
+ [scrollSettings]="dragScrollSettings"
718
+ kendoDraggable
719
+ [enableDrag]="draggableEnabled"
720
+ (kendoPress)="timelineContainerPress.emit($event)"
721
+ (kendoDrag)="timelineContainerDrag.emit($event)"
722
+ (kendoRelease)="timelineContainerRelease.emit($event)"
723
+ >
724
+ <div class="k-gantt-tables">
725
+ <table
726
+ class="k-gantt-rows"
727
+ [style.width.px]="tableWidth"
728
+ role="presentation"
729
+ >
730
+ <tbody>
731
+ <tr *ngFor="let item of rows; let i = index;"
732
+ [class.k-alt]="i % 2"
733
+ >
734
+ <td></td>
735
+ </tr>
736
+ </tbody>
737
+ </table>
25
738
 
26
- /**
27
- * @hidden
28
- */
29
- const packageMetadata = {
30
- name: '@progress/kendo-angular-gantt',
31
- productName: 'Kendo UI for Angular',
32
- productCodes: ['KENDOUIANGULAR', 'KENDOUICOMPLETE'],
33
- publishDate: 1639476447,
34
- version: '',
35
- licensingDocsUrl: 'https://www.telerik.com/kendo-angular-ui/my-license/?utm_medium=product&utm_source=kendoangular&utm_campaign=kendo-ui-angular-purchase-license-keys-warning'
36
- };
739
+ <table
740
+ #timelineColumns
741
+ class="k-gantt-columns"
742
+ role="presentation"
743
+ [style.width.px]="tableWidth"
744
+ >
745
+ <colgroup>
746
+ <col *ngFor="let item of slots">
747
+ </colgroup>
748
+
749
+ <tbody>
750
+ <tr>
751
+ <td *ngFor="let item of slots"
752
+ [class.k-nonwork-hour]="isNonWorking(item)"
753
+ >
754
+ </td>
755
+ </tr>
756
+ </tbody>
757
+ </table>
758
+
759
+ <table
760
+ #tasksContainer
761
+ class="k-gantt-tasks"
762
+ role="presentation"
763
+ style="border-collapse: collapse;"
764
+ [style.width.px]="tableWidth"
765
+ >
766
+ <tbody
767
+ kendoGanttTasksTableBody
768
+ [rows]="rows"
769
+ [activeView]="activeView"
770
+ [taskContentTemplate]="taskContentTemplate"
771
+ [taskTemplate]="taskTemplate"
772
+ [summaryTaskTemplate]="summaryTaskTemplate"
773
+ [taskClass]="taskClass"
774
+ [isExpanded]="isExpanded"
775
+ [selectable]="selectable"
776
+ [isTaskSelected]="isTaskSelected"
777
+ [renderDependencyDragClues]="renderDependencyDragClues"
778
+ >
779
+ </tbody>
780
+ </table>
781
+ </div>
782
+ <svg class="k-gantt-dependencies-svg">
783
+ <polyline
784
+ *ngFor="let dependency of dependencies"
785
+ kendoGanttDependency
786
+ [dependency]="dependency"
787
+ />
788
+ <polyline #dependencyDragCreatePolyline />
789
+ </svg>
790
+
791
+ <!-- placeholder for the dependency drag popup; its position is not arbitrary - the popup is intended to be absolutely positioned inside the .k-grid-content element -->
792
+ <ng-container #dragPopupContainer></ng-container>
793
+ </div>
794
+ </div>
795
+ `
796
+ }),
797
+ __metadata("design:paramtypes", [ScrollSyncService,
798
+ DependencyDomService,
799
+ Renderer2,
800
+ NgZone])
801
+ ], GanttTimelineComponent);
37
802
 
38
803
  /**
39
804
  * The base class for the column components of the Gantt.
@@ -631,140 +1396,43 @@ const taskClassCallback = () => null;
631
1396
  * @hidden
632
1397
  */
633
1398
  const isSelected = () => false;
634
-
635
- /**
636
- * @hidden
637
- */
638
- const isWorkDay = (date, start, end) => {
639
- return date.getDay() >= start && date.getDay() <= end;
640
- };
641
- /**
642
- * @hidden
643
- */
644
- const isWorkHour = (date, start, end) => {
645
- return date.getHours() >= start && date.getHours() <= end;
646
- };
647
- /**
648
- * @hidden
649
- */
650
- const isPresent = (item) => item !== null && item !== undefined;
651
- /**
652
- * @hidden
653
- *
654
- * Normalized the data to an array in case a falsy value is passed
655
- * or a TreeListDataResult object (applicable for the data-binding directives).
656
- */
657
- const normalizeGanttData = (data) => {
658
- if (!isPresent(data)) {
659
- return [];
660
- }
661
- else if (Array.isArray(data.data)) {
662
- return data.data;
663
- }
664
- else {
665
- return data;
666
- }
667
- };
668
- /**
669
- * @hidden
670
- *
671
- * Returns a new date with the specified hours, minutes, seconds and millliseconds set.
672
- * Only the hours are required, the rest of the params are set to `0` by default.
673
- */
674
- const setTime = (date, hours, minutes = 0, seconds = 0, milliseconds = 0) => {
675
- if (!isPresent(date)) {
676
- return null;
677
- }
678
- const result = cloneDate(date);
679
- result.setHours(hours);
680
- result.setMinutes(minutes);
681
- result.setSeconds(seconds);
682
- result.setMilliseconds(milliseconds);
683
- return result;
684
- };
685
- /**
686
- * @hidden
687
- *
688
- * Returns the last day of a week.
689
- * @param standingPoint - Any day of the target week.
690
- * @param firstWeekDay - The week's starting day (e.g. Monday, Tuesday, etc.)
691
- */
692
- const lastDayOfWeek = (standingPoint, firstWeekDay) => {
693
- const followingWeek = addWeeks(standingPoint, 1);
694
- const firstDayOfFollowingWeek = firstDayInWeek(followingWeek, firstWeekDay);
695
- const lastDayOfTargetWeek = addDays(firstDayOfFollowingWeek, -1);
696
- return lastDayOfTargetWeek;
697
- };
698
- /**
699
- * Persists the intially resolved scrollbar width value.
700
- */
701
- let SCROLLBAR_WIDTH;
702
- /**
703
- * @hidden
704
- *
705
- * Gets the default scrollbar width accoring to the current environment.
706
- */
707
- const scrollbarWidth = () => {
708
- if (!isDocumentAvailable()) {
709
- return;
710
- }
711
- // calculate scrollbar width only once, then return the cached value
712
- if (isNaN(SCROLLBAR_WIDTH)) {
713
- const div = document.createElement('div');
714
- div.style.cssText = 'overflow: scroll; overflow-x: hidden; zoom: 1; clear: both; display: block;';
715
- div.innerHTML = '&nbsp;';
716
- document.body.appendChild(div);
717
- SCROLLBAR_WIDTH = div.offsetWidth - div.scrollWidth;
718
- document.body.removeChild(div);
719
- }
720
- return SCROLLBAR_WIDTH;
721
- };
722
- /**
723
- * @hidden
724
- */
725
- const isColumnGroup = (column) => column.isColumnGroup;
726
- /**
727
- * @hidden
728
- */
729
- const isNumber = (contender) => typeof contender === 'number' && !isNaN(contender);
730
- /**
731
- * @hidden
732
- */
733
- const isString = (contender) => typeof contender === 'string';
734
- /**
735
- * @hidden
736
- *
737
- * Gets the closest timeline task element index from an event target.
738
- * Uses the `data-task-index` attribute assigned to each task.
739
- * Restricts the search up to the provided gantt element from the second param.
740
- */
741
- const getClosestTaskIndex = (element, gantt) => {
742
- const task = closestInScope(element, matchesClasses('k-task'), gantt);
743
- if (!isPresent(task)) {
744
- return null;
745
- }
746
- return Number(task.getAttribute('data-task-index'));
747
- };
748
- /**
749
- * @hidden
750
- *
751
- * Checks whether the queried item or its parent items has a `k-task` selector.
752
- * Restricts the search up to the provided gantt element from the second param.
753
- */
754
- const isTask = (contender, gantt) => {
755
- const task = closestInScope(contender, matchesClasses('k-task'), gantt);
756
- return isPresent(task);
757
- };
1399
+
758
1400
  /**
759
1401
  * @hidden
760
- *
761
- * Checks whether the queried item or its parent items has a `k-task-actions` selector - used for the clear button.
762
- * Restricts the search up to the provided gantt element from the second param.
763
1402
  */
764
- const isClearButton = (contender, gantt) => {
765
- const clearButtonContainer = closestInScope(contender, matchesClasses('k-task-actions'), gantt);
766
- return isPresent(clearButtonContainer);
767
- };
1403
+ class PreventableEvent {
1404
+ constructor() {
1405
+ this.prevented = false;
1406
+ }
1407
+ /**
1408
+ * Prevents the default action for a specified event.
1409
+ * In this way, the source component suppresses
1410
+ * the built-in behavior that follows the event.
1411
+ */
1412
+ preventDefault() {
1413
+ this.prevented = true;
1414
+ }
1415
+ /**
1416
+ * Returns `true` if the event was prevented
1417
+ * by any of its subscribers.
1418
+ *
1419
+ * @returns `true` if the default action was prevented.
1420
+ * Otherwise, returns `false`.
1421
+ */
1422
+ isDefaultPrevented() {
1423
+ return this.prevented;
1424
+ }
1425
+ }
1426
+
1427
+ /**
1428
+ * Called every time a user leaves an edited cell.
1429
+ */
1430
+ class CellCloseEvent extends PreventableEvent {
1431
+ constructor(options) {
1432
+ super();
1433
+ Object.assign(this, options);
1434
+ }
1435
+ }
768
1436
 
769
1437
  /**
770
1438
  * @hidden
@@ -920,84 +1588,6 @@ class TimelineBaseViewService {
920
1588
  }
921
1589
  }
922
1590
 
923
- /**
924
- * @hidden
925
- */
926
- const DEFAULT_DEPENDENCY_MODEL_FIELDS = Object.freeze({
927
- toId: 'toId',
928
- fromId: 'fromId',
929
- id: 'id',
930
- type: 'type'
931
- });
932
-
933
- /**
934
- * @hidden
935
- */
936
- const DEFAULT_TASK_MODEL_FIELDS = Object.freeze({
937
- id: 'id',
938
- start: 'start',
939
- end: 'end',
940
- title: 'title',
941
- completionRatio: 'completionRatio',
942
- children: 'children'
943
- });
944
-
945
- /**
946
- * @hidden
947
- */
948
- let MappingService = class MappingService {
949
- /**
950
- * @hidden
951
- */
952
- constructor() {
953
- this._taskFields = Object.assign({}, DEFAULT_TASK_MODEL_FIELDS);
954
- this._dependencyFields = Object.assign({}, DEFAULT_DEPENDENCY_MODEL_FIELDS);
955
- }
956
- /**
957
- * Gets or sets the model fields for the task data items.
958
- * Uses the default values for fields which are not specified.
959
- */
960
- set taskFields(fields) {
961
- this._taskFields = Object.assign({}, DEFAULT_TASK_MODEL_FIELDS, fields);
962
- }
963
- get taskFields() {
964
- return this._taskFields;
965
- }
966
- /**
967
- * Gets or sets the model fields for the depenency data items.
968
- * Uses the default values for fields which are not specified.
969
- */
970
- set dependencyFields(fields) {
971
- this._dependencyFields = Object.assign({}, DEFAULT_DEPENDENCY_MODEL_FIELDS, fields);
972
- }
973
- get dependencyFields() {
974
- return this._dependencyFields;
975
- }
976
- /**
977
- * Retrieves the value for the specified task field.
978
- * Supports nested fields as well (e.g. 'manager.id').
979
- */
980
- extractFromTask(dataItem, field) {
981
- if (!isPresent(this.taskFields)) {
982
- return null;
983
- }
984
- return getter(this.taskFields[field])(dataItem);
985
- }
986
- /**
987
- * Retrieves the value for the specified dependency field.
988
- * Supports nested fields as well (e.g. 'manager.id').
989
- */
990
- extractFromDependency(dataItem, field) {
991
- if (!isPresent(this.dependencyFields)) {
992
- return null;
993
- }
994
- return getter(this.dependencyFields[field])(dataItem);
995
- }
996
- };
997
- MappingService = __decorate([
998
- Injectable()
999
- ], MappingService);
1000
-
1001
1591
  /**
1002
1592
  * @hidden
1003
1593
  */
@@ -1187,171 +1777,567 @@ TimelineViewService = __decorate([
1187
1777
  /**
1188
1778
  * @hidden
1189
1779
  */
1190
- let ScrollSyncService = class ScrollSyncService {
1191
- constructor(ngZone) {
1192
- this.ngZone = ngZone;
1193
- this.changes = new Subject();
1194
- this.elements = [];
1195
- this.subscriptions = new Subscription();
1196
- this.subscriptions.add(this.changes.subscribe(args => {
1197
- this.scroll(args);
1198
- }));
1780
+ let EditService = class EditService {
1781
+ constructor(mapper) {
1782
+ this.mapper = mapper;
1783
+ this.showEditingDialog = new Subject();
1784
+ this.taskDelete = new Subject();
1785
+ this.editEvent = new Subject();
1786
+ this.addEvent = new Subject();
1787
+ this.predecessors = [];
1788
+ this.successors = [];
1789
+ this.updatedItems = [];
1790
+ this.deletedItems = [];
1791
+ this.itemIndex = (item, data) => {
1792
+ return data.findIndex(dataItem => this.mapper.extractFromTask(dataItem, 'id') === this.mapper.extractFromTask(item, 'id'));
1793
+ };
1199
1794
  }
1200
- registerElement(el, sourceType) {
1201
- this.elements.push({ element: el, sourceType });
1202
- if (sourceType === "timeline" || sourceType === "treelist") {
1203
- this.ngZone.runOutsideAngular(() => {
1204
- const obs = fromEvent(el, 'scroll').pipe(map(({ target: { scrollTop, scrollLeft } }) => ({
1205
- scrollTop,
1206
- scrollLeft,
1207
- sourceType
1208
- })));
1209
- const comparisonFn = sourceType === 'timeline' ?
1210
- (x, y) => (x.scrollTop === y.scrollTop) && (x.scrollLeft === y.scrollLeft) :
1211
- (x, y) => (x.scrollTop === y.scrollTop);
1212
- this.subscriptions.add(obs.pipe(distinctUntilChanged(comparisonFn))
1213
- .subscribe((event) => this.changes.next(event)));
1214
- });
1215
- }
1795
+ set dependencies(items) {
1796
+ // Can this whole thing be moved to edit-dialog? Dependencies might not be needed here
1797
+ const dataItemId = this.mapper.extractFromTask(this.dataItem, 'id');
1798
+ this.predecessors = items.filter(item => this.mapper.extractFromDependency(item, 'toId') === dataItemId);
1799
+ this.successors = items.filter(item => this.mapper.extractFromDependency(item, 'fromId') === dataItemId);
1216
1800
  }
1217
- ngOnDestroy() {
1218
- this.subscriptions.unsubscribe();
1219
- this.elements = null;
1801
+ get dependencies() {
1802
+ return [...this.predecessors, ...this.successors];
1220
1803
  }
1221
- syncScrollTop(sourceType, targetType) {
1222
- const source = this.elements.find(element => element.sourceType === sourceType);
1223
- const target = this.elements.find(element => element.sourceType === targetType);
1224
- // Need to wait for the splitter pane's content to be rendered
1225
- this.ngZone.onStable.pipe(take(1)).subscribe(() => target.element.scrollTop = source.element.scrollTop);
1804
+ createEditDialog(dataItem, taskFormGroup, dependencies) {
1805
+ this.dataItem = dataItem;
1806
+ this.taskFormGroup = taskFormGroup;
1807
+ this.dependencies = dependencies;
1808
+ this.showEditingDialog.next(true);
1226
1809
  }
1227
- resetTimelineScrollLeft() {
1228
- const source = this.elements.find(element => element.sourceType === 'timeline');
1229
- source.element.scrollLeft = 0;
1810
+ closeEditDialog() {
1811
+ this.showEditingDialog.next(false);
1812
+ this.dataItem = undefined;
1813
+ this.taskFormGroup = undefined;
1814
+ this.dependencies = [];
1815
+ this.updatedItems = [];
1816
+ this.deletedItems = [];
1230
1817
  }
1231
- scroll({ scrollTop, scrollLeft, sourceType }) {
1232
- this.ngZone.runOutsideAngular(() => {
1233
- if (sourceType === 'timeline') {
1234
- const header = this.elements.find(element => element.sourceType === 'header').element;
1235
- header.scrollLeft = scrollLeft;
1236
- if (!this.syncingTimeline) {
1237
- this.syncingTreeList = true;
1238
- const treelist = this.elements.find(element => element.sourceType === 'treelist').element;
1239
- treelist.scrollTop = scrollTop;
1240
- }
1241
- this.syncingTimeline = false;
1818
+ triggerEditEvent(editResultType) {
1819
+ this.editEvent.next({
1820
+ taskFormGroup: this.taskFormGroup,
1821
+ dataItem: this.dataItem,
1822
+ dependencies: {
1823
+ createdItems: this.getCreatedDependencies(),
1824
+ updatedItems: this.updatedItems,
1825
+ deletedItems: this.deletedItems
1826
+ },
1827
+ editResultType
1828
+ });
1829
+ }
1830
+ updateDependencies(item) {
1831
+ if (!this.isNew(item)) {
1832
+ // update
1833
+ const index = this.itemIndex(item, this.updatedItems);
1834
+ if (index !== -1) {
1835
+ this.updatedItems.splice(index, 1, item);
1242
1836
  }
1243
- if (sourceType === 'treelist') {
1244
- if (!this.syncingTreeList) {
1245
- this.syncingTimeline = true;
1246
- const timeline = this.elements.find(element => element.sourceType === 'timeline').element;
1247
- timeline.scrollTop = scrollTop;
1248
- }
1249
- this.syncingTreeList = false;
1837
+ else {
1838
+ this.updatedItems.push(item);
1250
1839
  }
1251
- });
1840
+ }
1841
+ }
1842
+ getCreatedDependencies() {
1843
+ return this.dependencies.filter(item => this.mapper.extractFromDependency(item, 'id') === null);
1844
+ }
1845
+ deleteDependency(item) {
1846
+ const updatedIndex = this.itemIndex(item, this.updatedItems);
1847
+ if (updatedIndex !== -1) {
1848
+ this.updatedItems.splice(updatedIndex, 1);
1849
+ }
1850
+ if (!this.isNew(item)) {
1851
+ this.deletedItems.push(item);
1852
+ }
1853
+ }
1854
+ loadTasks(initialValues, isInitializer = true) {
1855
+ return forkJoin(initialValues.map(v => this.getElementById(v))).pipe(map((value) => value.reduce((acc, item) => acc = acc.concat(normalizeGanttData(item)), [])), expand(values => {
1856
+ if (values.some(el => this.hasChildren(el))) {
1857
+ return this.loadTasks(values, false);
1858
+ }
1859
+ return EMPTY;
1860
+ }), reduce((acc, values) => acc.concat(values), isInitializer ? [...initialValues] : []));
1861
+ }
1862
+ getElementById(item) {
1863
+ const children = this.fetchChildren(item);
1864
+ if (isObservable(children)) {
1865
+ return children;
1866
+ }
1867
+ return of(children);
1868
+ }
1869
+ isNew(item) {
1870
+ return !isPresent(this.mapper.extractFromDependency(item, 'id'));
1252
1871
  }
1253
1872
  };
1254
- ScrollSyncService = __decorate([
1873
+ EditService = __decorate([
1255
1874
  Injectable(),
1256
- __metadata("design:paramtypes", [NgZone])
1257
- ], ScrollSyncService);
1875
+ __metadata("design:paramtypes", [MappingService])
1876
+ ], EditService);
1877
+
1878
+ /**
1879
+ * @hidden
1880
+ *
1881
+ * Notifies the timeline-scroll.directive to scroll into view to requested coordinates.
1882
+ * The scrolling is performed based on client (viewport) coordinates.
1883
+ */
1884
+ let TimelineScrollService = class TimelineScrollService {
1885
+ /**
1886
+ * @hidden
1887
+ *
1888
+ * Notifies the timeline-scroll.directive to scroll into view to requested coordinates.
1889
+ * The scrolling is performed based on client (viewport) coordinates.
1890
+ */
1891
+ constructor() {
1892
+ this.horizontalScroll = new Subject();
1893
+ this.verticalScroll = new Subject();
1894
+ this.scrollCancel = new Subject();
1895
+ }
1896
+ ngOnDestroy() {
1897
+ this.horizontalScroll.complete();
1898
+ this.verticalScroll.complete();
1899
+ this.scrollCancel.complete();
1900
+ }
1901
+ requestHorizontalScroll(clientTop) {
1902
+ this.horizontalScroll.next(clientTop);
1903
+ }
1904
+ requestVerticalScroll(clientLeft) {
1905
+ this.verticalScroll.next(clientLeft);
1906
+ }
1907
+ requestScrollCancel() {
1908
+ this.scrollCancel.next();
1909
+ }
1910
+ };
1911
+ TimelineScrollService = __decorate([
1912
+ Injectable()
1913
+ ], TimelineScrollService);
1914
+
1915
+ /**
1916
+ * @hidden
1917
+ *
1918
+ * Needed to keep the Gantt's LocalizationService reference and be able to use it component's inside the TabStrip
1919
+ */
1920
+ let GanttLocalizationService = class GanttLocalizationService {
1921
+ constructor(localizationService) {
1922
+ this.localizationService = localizationService;
1923
+ }
1924
+ get(token) {
1925
+ return this.localizationService.get(token);
1926
+ }
1927
+ };
1928
+ GanttLocalizationService = __decorate([
1929
+ Injectable(),
1930
+ __metadata("design:paramtypes", [LocalizationService])
1931
+ ], GanttLocalizationService);
1932
+
1933
+ // TODO: add those keys to `import { Keys } from '@progress/kendo-angular-common';`
1934
+ var NumpadKeys;
1935
+ (function (NumpadKeys) {
1936
+ NumpadKeys[NumpadKeys["Digit1"] = 97] = "Digit1";
1937
+ NumpadKeys[NumpadKeys["Digit2"] = 98] = "Digit2";
1938
+ NumpadKeys[NumpadKeys["Digit3"] = 99] = "Digit3";
1939
+ NumpadKeys[NumpadKeys["Digit4"] = 100] = "Digit4";
1940
+ })(NumpadKeys || (NumpadKeys = {}));
1941
+ /**
1942
+ * @hidden
1943
+ */
1944
+ const isArrowUpDownKey = (keyCode) => [
1945
+ Keys.ArrowUp,
1946
+ Keys.ArrowDown
1947
+ ].some(arrowKeyCode => keyCode === arrowKeyCode);
1948
+ /**
1949
+ * @hidden
1950
+ */
1951
+ const isNavigationKey = (keyCode) => [
1952
+ Keys.ArrowUp,
1953
+ Keys.ArrowDown,
1954
+ Keys.Home,
1955
+ Keys.End
1956
+ ].some(navigationKeyCode => keyCode === navigationKeyCode);
1957
+ /**
1958
+ * @hidden
1959
+ */
1960
+ const isExpandCollapseKey = (keyCode, altKey) => {
1961
+ return altKey && [
1962
+ Keys.ArrowLeft,
1963
+ Keys.ArrowRight
1964
+ ].some(navigationKeyCode => keyCode === navigationKeyCode);
1965
+ };
1966
+ /**
1967
+ * @hidden
1968
+ */
1969
+ const isViewDigitKey = (keyCode) => [
1970
+ Keys.Digit1,
1971
+ NumpadKeys.Digit1,
1972
+ Keys.Digit2,
1973
+ NumpadKeys.Digit2,
1974
+ Keys.Digit3,
1975
+ NumpadKeys.Digit3,
1976
+ Keys.Digit4,
1977
+ NumpadKeys.Digit4
1978
+ ].some(digitKeyCode => keyCode === digitKeyCode);
1979
+ /**
1980
+ * @hidden
1981
+ *
1982
+ * Returns the corresponding view index for the pressed digit key (Digit 1 => 0, Digit 2 => 1, etc.).
1983
+ */
1984
+ const getIndexFromViewDigitKeyCode = (keyCode) => {
1985
+ switch (keyCode) {
1986
+ case NumpadKeys.Digit1:
1987
+ case Keys.Digit1: return 0;
1988
+ case NumpadKeys.Digit2:
1989
+ case Keys.Digit2: return 1;
1990
+ case NumpadKeys.Digit3:
1991
+ case Keys.Digit3: return 2;
1992
+ case NumpadKeys.Digit4:
1993
+ case Keys.Digit4: return 3;
1994
+ default: return null;
1995
+ }
1996
+ };
1258
1997
 
1259
1998
  /**
1260
1999
  * @hidden
1261
2000
  */
1262
- let DependencyDomService = class DependencyDomService {
1263
- constructor(mapper) {
1264
- this.mapper = mapper;
1265
- this.notifier = new Subject();
2001
+ let NavigationService = class NavigationService {
2002
+ constructor(zone, renderer, scrollSyncService) {
2003
+ this.zone = zone;
2004
+ this.renderer = renderer;
2005
+ this.scrollSyncService = scrollSyncService;
1266
2006
  /**
1267
- * Maps each rendered task to its HTML element.
1268
- * Uses the task ID field value as key.
2007
+ * Notifies when the tasks' focused and interactive (tabindex) state has changed.
2008
+ *
2009
+ * All tasks are rendered with tabindex="-1".
2010
+ * When one is clicked, or when some navigation key keyboard key is pressed, it should be focused, assigned the focus class, and its tabindex updated to 0.
2011
+ * All other tasks should get -1 tabindex and have the focus class removed from them.
1269
2012
  */
1270
- this.tasks = new Map();
2013
+ this.taskStatusChanges = new Subject();
2014
+ /**
2015
+ * Keeps track of whether the Timeline part is focused.
2016
+ * Used when the index of the task elements change (tasks are changed, pushed to, spliced from, etc.)
2017
+ * and their status should be updated accordingly.
2018
+ */
2019
+ this.isTimelineFocused = false;
2020
+ /**
2021
+ * Keeps track of which part has last been focused.
2022
+ * Used when calling `gantt.focus()` to determine which part of the component should receive focus.
2023
+ */
2024
+ this.treeListLastActive = false;
2025
+ /**
2026
+ * Keeps track of which part has last been focused.
2027
+ * Used when calling `gantt.focus()` to determine which part of the component should receive focus.
2028
+ */
2029
+ this.timelineLastActive = false;
2030
+ this._enabled = false;
2031
+ this._activeTimelineIndex = 0;
2032
+ this._activeTreeListCell = { rowIndex: 0, colIndex: 0 };
1271
2033
  }
1272
2034
  /**
1273
- * Emits each time some of the tasks or the view have changed.
1274
- * Fires also on the first change of the table rows and the parent container.
2035
+ * Specifies whether navigation is enabled.
1275
2036
  */
1276
- get taskChanges() {
1277
- return this.notifier.asObservable();
2037
+ get enabled() {
2038
+ return this._enabled;
1278
2039
  }
1279
- get dependencyDomArgs() {
2040
+ /**
2041
+ * Used to retrieve read-only data about the currently active task.
2042
+ */
2043
+ get activeTask() {
1280
2044
  return {
1281
- tasks: this.tasks,
1282
- contentContainer: this.contentContainer,
1283
- timelineRow: this.timelineRow
2045
+ activeIndex: this.activeTimelineIndex,
2046
+ isFocused: this.isTimelineFocused
1284
2047
  };
1285
2048
  }
2049
+ /**
2050
+ * Persists the expected TreeList focused cell coords.
2051
+ * When the tasks in the Timeline are navigated through, the expected TreeList focus target should also change,
2052
+ * in order to allow back-tabbing from the Timeline to the same row in the TreeList.
2053
+ */
2054
+ set activeTreeListCell(cell) {
2055
+ this._activeTreeListCell = cell;
2056
+ }
2057
+ get activeTreeListCell() {
2058
+ const firstAvailableIndex = 0;
2059
+ const lastAvailableRowIndex = this.treeListHeaderRowsCount + this.gantt.treeList.view.data.length - 1;
2060
+ const rowIndex = fitToRange(this._activeTreeListCell.rowIndex, firstAvailableIndex, lastAvailableRowIndex);
2061
+ const lastAvailableColIndex = this.gantt.columns.length;
2062
+ const colIndex = fitToRange(this._activeTreeListCell.colIndex, firstAvailableIndex, lastAvailableColIndex);
2063
+ return { rowIndex, colIndex };
2064
+ }
2065
+ /**
2066
+ * Persists the expected Timeline focused task index.
2067
+ * When the cells in the TreeList are navigated through, the expected Timeline focus target should also change,
2068
+ * in order to allow tabbing from the TreeList to the same row in the Timeline.
2069
+ */
2070
+ set activeTimelineIndex(index) {
2071
+ this._activeTimelineIndex = index;
2072
+ }
2073
+ get activeTimelineIndex() {
2074
+ const firstAvailableIndex = 0;
2075
+ const lastAvailableIndex = this.gantt.treeList.view.data.length - 1;
2076
+ return fitToRange(this._activeTimelineIndex, firstAvailableIndex, lastAvailableIndex);
2077
+ }
2078
+ /**
2079
+ * The TreeList row index takes into account the header and filter rows.
2080
+ * Used when translating Timeline task indices to TreeList row indices.
2081
+ */
2082
+ get treeListHeaderRowsCount() {
2083
+ // captures nested group header rows + filter row if we start supporting it at some point
2084
+ return this.treeListElement.querySelectorAll('.k-grid-header tr').length;
2085
+ }
2086
+ initialize({ gantt, host, treeListElement, timelineElement }) {
2087
+ // no private property setters in TypeScript, so use a getter and a poorly named private prop for this value
2088
+ this._enabled = true;
2089
+ this.gantt = gantt;
2090
+ this.host = host;
2091
+ this.treeListElement = treeListElement;
2092
+ this.timelineElement = timelineElement;
2093
+ // TODO: fix in the splitter package and remove
2094
+ // move the splitbar HTML element between the two panes to keep the visial tabbing order in tact
2095
+ const splitbar = this.host.querySelector('.k-splitbar');
2096
+ if (isPresent(splitbar) && isPresent(splitbar.previousElementSibling) && isPresent(splitbar.after)) {
2097
+ splitbar.after(splitbar.previousElementSibling);
2098
+ }
2099
+ this.zone.runOutsideAngular(() => {
2100
+ this.eventListenerDisposers = [
2101
+ this.renderer.listen(this.host, 'keydown', this.handleKeydown.bind(this)),
2102
+ this.renderer.listen(this.treeListElement, 'mousedown', this.focusTreeList.bind(this)),
2103
+ this.renderer.listen(this.treeListElement, 'focusin', this.handleTreeListFocusIn.bind(this)),
2104
+ this.renderer.listen(this.timelineElement, 'mousedown', this.handleTimelineMousedown.bind(this)),
2105
+ this.renderer.listen(this.timelineElement, 'focusin', this.handleTimelineFocusIn.bind(this)),
2106
+ this.renderer.listen(this.timelineElement, 'focusout', this.handleTimelineFocusOut.bind(this))
2107
+ ];
2108
+ });
2109
+ }
1286
2110
  ngOnDestroy() {
1287
- this.tasks.clear();
1288
- this.tasks = null;
1289
- this.contentContainer = null;
2111
+ if (isPresent(this.eventListenerDisposers)) {
2112
+ this.eventListenerDisposers.forEach(removeListener => removeListener());
2113
+ this.eventListenerDisposers = null;
2114
+ }
2115
+ this.gantt = null;
2116
+ this.host = null;
2117
+ this.treeListElement = null;
2118
+ this.timelineElement = null;
1290
2119
  }
1291
- registerTimelineRow(timelineRow) {
1292
- this.timelineRow = timelineRow;
1293
- this.notifyChanges();
2120
+ /**
2121
+ * Focuses either the last active TreeList cell, or the last active Timeline task,
2122
+ * dependening on which of the two last held focus.
2123
+ *
2124
+ * Focuses the first TreeList cell by default.
2125
+ */
2126
+ focusLastActiveItem() {
2127
+ if (this.gantt.data.length === 0 || (!this.treeListLastActive && !this.timelineLastActive)) {
2128
+ this.focusCell(0, 0);
2129
+ }
2130
+ else if (this.treeListLastActive) {
2131
+ const { rowIndex, colIndex } = this.activeTreeListCell;
2132
+ this.gantt.treeList.focusCell(rowIndex, colIndex);
2133
+ }
2134
+ else if (this.timelineLastActive) {
2135
+ this.focusTask(this.activeTimelineIndex);
2136
+ }
1294
2137
  }
1295
- registerContentContainer(contentContainer) {
1296
- this.contentContainer = contentContainer;
1297
- this.notifyChanges();
2138
+ /**
2139
+ * Focuses the targeted TreeList cell regardless of the last peresisted target.
2140
+ */
2141
+ focusCell(rowIndex, colIndex) {
2142
+ this.activeTreeListCell = { rowIndex, colIndex };
2143
+ this.activeTimelineIndex = rowIndex - this.treeListHeaderRowsCount;
2144
+ this.gantt.treeList.focusCell(this.activeTreeListCell.rowIndex, this.activeTreeListCell.colIndex);
1298
2145
  }
1299
- registerTask(task, element) {
1300
- const id = this.mapper.extractFromTask(task, 'id');
1301
- this.tasks.set(id, element);
1302
- this.notifyChanges();
2146
+ /**
2147
+ * Focuses the targeted Timeline task regardless of the last peresisted target.
2148
+ */
2149
+ focusTask(index) {
2150
+ this.activeTimelineIndex = index;
2151
+ this.isTimelineFocused = true;
2152
+ this.activeTreeListCell = {
2153
+ rowIndex: index + this.treeListHeaderRowsCount,
2154
+ colIndex: this.activeTreeListCell.colIndex
2155
+ };
2156
+ this.notifyTaskStatusChange();
1303
2157
  }
1304
- unregisterTask(task) {
1305
- const id = this.mapper.extractFromTask(task, 'id');
1306
- this.tasks.delete(id);
1307
- this.notifyChanges();
2158
+ /**
2159
+ * Updates the focus target flags and notifies the active task to update its focused state.
2160
+ */
2161
+ handleTimelineFocusIn({ target }) {
2162
+ this.treeListLastActive = false;
2163
+ this.timelineLastActive = true;
2164
+ this.isTimelineFocused = true;
2165
+ if (isTask(target, this.timelineElement)) {
2166
+ this.notifyTaskStatusChange();
2167
+ }
1308
2168
  }
1309
2169
  /**
1310
- * Notifies all dependency directives that a change in one of the elements has occured.
2170
+ * Updates the timeline focus state flag and notifies the active task to update its focused state.
1311
2171
  */
1312
- notifyChanges() {
1313
- this.notifier.next(this.dependencyDomArgs);
2172
+ handleTimelineFocusOut({ relatedTarget }) {
2173
+ this.isTimelineFocused = this.timelineElement.contains(relatedTarget);
2174
+ // update the task element only if the new focus target is not in the Timeline - focus change between tasks is handled in the focusin handler
2175
+ if (!isTask(relatedTarget, this.timelineElement)) {
2176
+ this.notifyTaskStatusChange();
2177
+ }
1314
2178
  }
1315
- };
1316
- DependencyDomService = __decorate([
1317
- Injectable(),
1318
- __metadata("design:paramtypes", [MappingService])
1319
- ], DependencyDomService);
1320
-
1321
- /**
1322
- * @hidden
1323
- */
1324
- let EditService = class EditService {
1325
2179
  /**
1326
- * @hidden
2180
+ * Updates the focus target flags and corrects the TreeList focus target if needed.
2181
+ * As the TreeList will keep its last focused cell with tabindex="0",
2182
+ * this methods forcefully focuses the correct cell,
2183
+ * when navigating in the Timeline has updated the expected TreeList focus target.
1327
2184
  */
1328
- constructor() {
1329
- this.showEditingDialog = new Subject();
1330
- this.showConfirmationDialog = new Subject();
1331
- this.editEvent = new Subject();
1332
- this.addEvent = new Subject();
2185
+ handleTreeListFocusIn(event) {
2186
+ this.treeListLastActive = true;
2187
+ this.timelineLastActive = false;
2188
+ // if the previous focus target was in the TreeList, rely on its component navigation and just record the focused item index
2189
+ if (this.treeListElement.contains(event.relatedTarget)) {
2190
+ const { colIndex, rowIndex } = this.gantt.treeList.activeCell;
2191
+ this.activeTreeListCell = { colIndex, rowIndex };
2192
+ }
2193
+ else {
2194
+ // if the previous focus target was outside the TreeList, ensure the expected focus coords are used
2195
+ const { rowIndex, colIndex } = this.activeTreeListCell;
2196
+ this.gantt.treeList.focusCell(rowIndex, colIndex); // activates the target cell even if it has tabindex="-1"
2197
+ }
2198
+ this.activeTimelineIndex = this.gantt.treeList.activeCell.dataRowIndex;
2199
+ this.notifyTaskStatusChange();
2200
+ if (this.gantt.treeList.activeCell.dataRowIndex >= 0) {
2201
+ this.scrollHorizontallyToTask(this.activeTimelineIndex);
2202
+ this.scrollSyncService.syncScrollTop('treelist', 'timeline');
2203
+ }
1333
2204
  }
1334
- createEditDialog(dataItem, formGroup) {
1335
- this.dataItem = dataItem;
1336
- this.formGroup = formGroup;
1337
- this.showEditingDialog.next(true);
2205
+ handleKeydown(event) {
2206
+ const { keyCode, target, altKey } = event;
2207
+ const isTimelineActive = this.timelineElement.contains(target);
2208
+ if (isTimelineActive) {
2209
+ if (isArrowUpDownKey(keyCode)) {
2210
+ const direction = keyCode === Keys.ArrowUp ? -1 : 1;
2211
+ this.activeTimelineIndex = this.activeTimelineIndex + direction;
2212
+ this.activeTreeListCell = {
2213
+ rowIndex: this.activeTimelineIndex + this.treeListHeaderRowsCount,
2214
+ colIndex: this.activeTreeListCell.colIndex
2215
+ };
2216
+ }
2217
+ else if (keyCode === Keys.Home) {
2218
+ this.activeTimelineIndex = 0;
2219
+ this.activeTreeListCell = {
2220
+ rowIndex: this.activeTimelineIndex + this.treeListHeaderRowsCount,
2221
+ colIndex: this.activeTreeListCell.colIndex
2222
+ };
2223
+ }
2224
+ else if (keyCode === Keys.End) {
2225
+ const lastAvailableIndex = this.gantt.treeList.view.data.length - 1;
2226
+ this.activeTimelineIndex = lastAvailableIndex;
2227
+ this.activeTreeListCell = {
2228
+ rowIndex: this.activeTimelineIndex + this.treeListHeaderRowsCount,
2229
+ colIndex: this.activeTreeListCell.colIndex
2230
+ };
2231
+ }
2232
+ if (isNavigationKey(keyCode)) {
2233
+ this.scrollHorizontallyToTask(this.activeTimelineIndex);
2234
+ this.scrollSyncService.syncScrollTop('timeline', 'treelist');
2235
+ this.notifyTaskStatusChange();
2236
+ event.preventDefault();
2237
+ }
2238
+ if (keyCode === Keys.Space && hasObservers(this.gantt.selectionChange)) {
2239
+ const task = this.gantt.renderedTreeListItems[this.activeTimelineIndex];
2240
+ const selectionAction = this.gantt.getSelectionAction(event, task);
2241
+ if (isPresent(task) && !this.gantt.isSameSelection(selectionAction, task)) {
2242
+ this.zone.run(() => this.gantt.notifySelectionChange(task, selectionAction));
2243
+ }
2244
+ event.preventDefault();
2245
+ }
2246
+ if (keyCode === Keys.Enter && hasObservers(this.gantt.taskClick)) {
2247
+ const task = this.gantt.renderedTreeListItems[this.activeTimelineIndex];
2248
+ if (isPresent(task)) {
2249
+ this.zone.run(() => this.gantt.notifyTaskClick(event, task, this.activeTimelineIndex));
2250
+ }
2251
+ event.preventDefault();
2252
+ }
2253
+ if (isExpandCollapseKey(keyCode, altKey)) {
2254
+ const task = this.gantt.renderedTreeListItems[this.activeTimelineIndex];
2255
+ if (isPresent(task) && this.gantt.hasChildren(task)) {
2256
+ const shouldExpand = keyCode === Keys.ArrowRight;
2257
+ const isExpanded = this.gantt.isExpanded(task);
2258
+ const sameState = shouldExpand === isExpanded;
2259
+ if (!sameState) {
2260
+ this.zone.run(() => {
2261
+ const expandEvent = { dataItem: task };
2262
+ // order is not arbitrary
2263
+ // the TreeList emits the individual events first, then the combined `expandStateChange` event
2264
+ const individualEmitter = shouldExpand ? this.gantt.rowExpand : this.gantt.rowCollapse;
2265
+ individualEmitter.emit(expandEvent);
2266
+ this.gantt.expandStateChange.emit(Object.assign({}, expandEvent, { expand: shouldExpand }));
2267
+ this.gantt.updateView();
2268
+ this.scrollHorizontallyToTask(this.activeTimelineIndex);
2269
+ });
2270
+ }
2271
+ }
2272
+ event.preventDefault();
2273
+ }
2274
+ }
2275
+ const isTreeListActive = this.treeListElement.contains(target);
2276
+ if (keyCode === Keys.Delete && (isTimelineActive || isTreeListActive) && hasObservers(this.gantt.taskDelete)) {
2277
+ const taskIndex = isTreeListActive ?
2278
+ this.gantt.treeList.activeCell.dataRowIndex :
2279
+ this.activeTimelineIndex;
2280
+ const task = this.gantt.renderedTreeListItems[taskIndex];
2281
+ if (isPresent(task)) {
2282
+ this.zone.run(() => this.gantt.notifyTaskDelete(task));
2283
+ }
2284
+ }
2285
+ if (isViewDigitKey(keyCode) && !isToolbar(target, this.host) && !this.gantt.isInEditMode) {
2286
+ const targetViewIndex = getIndexFromViewDigitKeyCode(keyCode);
2287
+ const availableViews = this.gantt.views.toArray();
2288
+ const targetView = availableViews[targetViewIndex];
2289
+ if (isPresent(targetView) && targetView.type !== this.gantt.activeView) {
2290
+ this.zone.run(() => this.gantt.changeActiveView(targetView.type));
2291
+ }
2292
+ }
1338
2293
  }
1339
- closeEditDialog() {
1340
- this.showEditingDialog.next(false);
1341
- this.dataItem = undefined;
1342
- this.formGroup = undefined;
2294
+ /**
2295
+ * Filters for task mousedown in the Timeline.
2296
+ */
2297
+ handleTimelineMousedown({ target }) {
2298
+ if (isTask(target, this.host) && !isClearButton(target, this.host)) {
2299
+ const taskIndex = getClosestTaskIndex(target, this.host);
2300
+ this.focusTask(taskIndex);
2301
+ }
1343
2302
  }
1344
- triggerEditEvent(editResultType) {
1345
- this.editEvent.next({
1346
- formGroup: this.formGroup,
1347
- dataItem: this.dataItem,
1348
- editResultType
1349
- });
2303
+ /**
2304
+ * Scrolls horizontally to the beginning of the target task if the beginning of its content is not in the viewport.
2305
+ */
2306
+ scrollHorizontallyToTask(index) {
2307
+ const task = this.timelineElement.querySelectorAll('.k-task-wrap').item(index);
2308
+ if (!isPresent(task)) {
2309
+ return;
2310
+ }
2311
+ // scroll horizontally to the item if less than 200px from the beginning of its content are visible
2312
+ const targetVisibleWidth = 200;
2313
+ const isScrollBeforeTask = (this.timelineElement.clientWidth + this.timelineElement.scrollLeft) < (task.offsetLeft + targetVisibleWidth);
2314
+ const isScrollAfterTask = this.timelineElement.scrollLeft > task.offsetLeft;
2315
+ if (isScrollBeforeTask || isScrollAfterTask) {
2316
+ this.timelineElement.scrollLeft = task.offsetLeft;
2317
+ }
2318
+ }
2319
+ /**
2320
+ * Focus the TreeList on TreeList mousedown.
2321
+ * A nasty hack to trick `handleTreeListFocusIn` into regarding the previous focus target as again the TreeList.
2322
+ * Otherwise cell clicks are wrongly overwritten in `handleTreeListFocusIn` and the click focus target is not respected.
2323
+ */
2324
+ focusTreeList() {
2325
+ this.gantt.treeList.focus();
2326
+ }
2327
+ /**
2328
+ * Fires the `taskStatusChanges` event with active and focused status retrieved from
2329
+ * `this.activeTimelineIndex` and `this.isTimelineFocused`.
2330
+ */
2331
+ notifyTaskStatusChange() {
2332
+ this.taskStatusChanges.next(this.activeTask);
1350
2333
  }
1351
2334
  };
1352
- EditService = __decorate([
1353
- Injectable()
1354
- ], EditService);
2335
+ NavigationService = __decorate([
2336
+ Injectable(),
2337
+ __metadata("design:paramtypes", [NgZone,
2338
+ Renderer2,
2339
+ ScrollSyncService])
2340
+ ], NavigationService);
1355
2341
 
1356
2342
  /**
1357
2343
  * @hidden
@@ -1655,6 +2641,12 @@ const mapPath = (item) => ({
1655
2641
  var GanttComponent_1;
1656
2642
  const TREELIST_GROUP_COLUMNS_CLASS = 'k-gantt-treelist-nested-columns';
1657
2643
  const DEFAULT_VIEW = 'week';
2644
+ const DEFAULT_DRAG_SCROLL_SETTINGS = {
2645
+ enabled: true,
2646
+ step: 3,
2647
+ interval: 1,
2648
+ threshold: 10
2649
+ };
1658
2650
  /**
1659
2651
  * Represents the Kendo UI Gantt component for Angular.
1660
2652
  *
@@ -1727,7 +2719,7 @@ const DEFAULT_VIEW = 'week';
1727
2719
  * ```
1728
2720
  */
1729
2721
  let GanttComponent = GanttComponent_1 = class GanttComponent {
1730
- constructor(timelineViewService, scrollSyncService, renderer, mapper, optionChangesService, dependencyDomService, editService, localizationService, hostElement, zone) {
2722
+ constructor(timelineViewService, scrollSyncService, renderer, mapper, optionChangesService, dependencyDomService, editService, localizationService, hostElement, zone, navigationService) {
1731
2723
  this.timelineViewService = timelineViewService;
1732
2724
  this.scrollSyncService = scrollSyncService;
1733
2725
  this.renderer = renderer;
@@ -1738,13 +2730,33 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
1738
2730
  this.localizationService = localizationService;
1739
2731
  this.hostElement = hostElement;
1740
2732
  this.zone = zone;
2733
+ this.navigationService = navigationService;
2734
+ /**
2735
+ * @hidden
2736
+ */
2737
+ this.roleDescription = 'Gantt Chart';
2738
+ /**
2739
+ * @hidden
2740
+ */
2741
+ this.role = 'application';
1741
2742
  this.hostClasses = true;
1742
2743
  /**
1743
- * Provides a callback that determines if the given task is selected ([see example]({% slug selection_gantt %}#toc-custom-selection))
2744
+ * Specifies a callback that determines if the given task is selected ([see example]({% slug selection_gantt %}#toc-custom-selection)).
1744
2745
  *
1745
2746
  * > The [`selectable`]({% slug api_gantt_ganttcomponent %}#toc-selectable) prop has to be set to `true` in order for this callback to be executed.
1746
2747
  */
1747
2748
  this.isSelected = isSelected;
2749
+ /**
2750
+ * Specifies a callback that determines if a new dependency is valid.
2751
+ * Used when evaluating if an attempt to create a new dependency will result in a valid link between the two tasks
2752
+ * [see example]({% slug editing_drag_create_dependencies_gantt %}#toc-validation).
2753
+ *
2754
+ * By defalut, dependencies are deemed invalid when:
2755
+ * - The two tasks are in a parent-child relationship.
2756
+ * - The two tasks are already dependent on one another. Only one dependency is allowed per pair.
2757
+ * - The start or end times of the two tasks are incompatible with the attempted dependency type.
2758
+ */
2759
+ this.validateNewDependency = this.defaultValidateNewDependencyCallback.bind(this);
1748
2760
  /**
1749
2761
  * Fires when the Gantt selection is changed through user interaction.
1750
2762
  *
@@ -1760,14 +2772,6 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
1760
2772
  * > When applied, the [`SelectableDirective`]({% slug api_gantt_selectabledirective %}) sets `selectable` to `true` internally.
1761
2773
  */
1762
2774
  this.selectable = false;
1763
- /**
1764
- * Gets or sets the callback function that retrieves the child items for a particular item.
1765
- */
1766
- this.fetchChildren = fetchChildren;
1767
- /**
1768
- * Gets or sets the callback function that indicates if a particular item has child items.
1769
- */
1770
- this.hasChildren = hasChildren;
1771
2775
  /**
1772
2776
  * Defines the dependencies that will be drawn between the rendered tasks.
1773
2777
  *
@@ -1806,6 +2810,12 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
1806
2810
  * The end of the work week (index based).
1807
2811
  */
1808
2812
  this.workWeekEnd = 5;
2813
+ /**
2814
+ * If set to `true`, the user can use dedicated shortcuts to interact with the Gantt.
2815
+ * By default, navigation is disabled for the TreeList and Timeline parts of the component,
2816
+ * ([see example]({% slug keyboard_navigation_gantt %})).
2817
+ */
2818
+ this.navigable = false;
1809
2819
  /**
1810
2820
  * Indicates whether the Gantt columns will be resized during initialization so that they fit their headers and row content.
1811
2821
  * Columns with autoSize set to false are excluded.
@@ -1849,7 +2859,8 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
1849
2859
  */
1850
2860
  this.cellClose = new EventEmitter();
1851
2861
  /**
1852
- * Fires when the end user clicks the `Delete` button in the task editing dialog or the task delete icon.
2862
+ * Fires when the end user clicks the `Delete` button in the task editing dialog,
2863
+ * the task delete icon, or presses the `Delete` key on the keyboard when a task in the timeline is focused.
1853
2864
  * Use the event handler to open a confirmation dialog when necessary.
1854
2865
  */
1855
2866
  this.taskDelete = new EventEmitter();
@@ -1873,6 +2884,11 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
1873
2884
  * Fires when the user adds a task.
1874
2885
  */
1875
2886
  this.taskAdd = new EventEmitter();
2887
+ /**
2888
+ * Fires when the user adds a dependency via dragging
2889
+ * [see example]({% slug editing_drag_create_dependencies_gantt %}#toc-basic-concepts).
2890
+ */
2891
+ this.dependencyAdd = new EventEmitter();
1876
2892
  /**
1877
2893
  * Fires when the sorting of the Gantt is changed.
1878
2894
  * You have to handle the event yourself and sort the data.
@@ -1929,6 +2945,15 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
1929
2945
  * Fires when a task is clicked.
1930
2946
  */
1931
2947
  this.taskClick = new EventEmitter();
2948
+ /**
2949
+ * @hidden
2950
+ *
2951
+ * Specifies whether the dependency drag clues will be rendered.
2952
+ * Set internally by the dependency-drag-create directive.
2953
+ *
2954
+ * @default false
2955
+ */
2956
+ this.renderDependencyDragClues = false;
1932
2957
  /**
1933
2958
  * @hidden
1934
2959
  *
@@ -1945,6 +2970,7 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
1945
2970
  this.showConfirmationDialog = false;
1946
2971
  this._columns = new QueryList();
1947
2972
  this._data = [];
2973
+ this._dragScrollSettings = Object.assign({}, DEFAULT_DRAG_SCROLL_SETTINGS);
1948
2974
  this._timelinePaneOptions = Object.assign({}, DEFAULT_TIMELINE_PANE_SETTINGS);
1949
2975
  this._treeListPaneOptions = Object.assign({}, DEFAULT_TREELIST_PANE_SETTINGS);
1950
2976
  this._rowClass = rowClassCallback;
@@ -1955,6 +2981,8 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
1955
2981
  addTaskTool: 'none',
1956
2982
  viewSelectorTool: 'top'
1957
2983
  };
2984
+ this._fetchChildren = fetchChildren;
2985
+ this._hasChildren = hasChildren;
1958
2986
  this.rtl = false;
1959
2987
  this.optionChangesSubscriptions = new Subscription();
1960
2988
  this.editServiceSubscription = new Subscription();
@@ -1967,16 +2995,24 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
1967
2995
  }));
1968
2996
  this.editService.getSelectedItem = this.getFirstSelectedItem.bind(this);
1969
2997
  this.editServiceSubscription.add(this.editService.showEditingDialog.subscribe(show => this.showEditingDialog = show));
1970
- this.editServiceSubscription.add(this.editService.showConfirmationDialog.subscribe(() => this.taskDelete.emit()));
2998
+ this.editServiceSubscription.add(this.editService.taskDelete.subscribe(task => {
2999
+ if (hasObservers(this.taskDelete)) {
3000
+ this.zone.run(() => this.notifyTaskDelete(task));
3001
+ }
3002
+ }));
1971
3003
  this.editServiceSubscription.add(this.editService.editEvent.subscribe(args => {
1972
3004
  this[args.editResultType].emit({
1973
- formGroup: args.formGroup,
3005
+ taskFormGroup: args.taskFormGroup,
1974
3006
  item: getEditItem(args.dataItem, this.treeList.view.data, this.mapper),
3007
+ dependencies: args.dependencies,
1975
3008
  sender: this
1976
3009
  });
1977
3010
  this.showConfirmationDialog = this.showEditingDialog = false;
1978
- this.editService.dataItem = this.editService.formGroup = null;
3011
+ this.editService.dataItem = this.editService.taskFormGroup = null;
1979
3012
  this.updateView();
3013
+ if (this.navigable) {
3014
+ this.focus();
3015
+ }
1980
3016
  }));
1981
3017
  this.editServiceSubscription.add(this.editService.addEvent.subscribe(args => {
1982
3018
  const selectedItem = this.getFirstSelectedItem();
@@ -2000,6 +3036,12 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
2000
3036
  set toolbarTemplate(customToolbarTemplate) {
2001
3037
  this._customToolbarTemplate = customToolbarTemplate;
2002
3038
  }
3039
+ get hostRoleDescriptionAttr() {
3040
+ return this.roleDescription;
3041
+ }
3042
+ get hostRoleAttr() {
3043
+ return this.role;
3044
+ }
2003
3045
  get dir() {
2004
3046
  return this.direction;
2005
3047
  }
@@ -2082,6 +3124,26 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
2082
3124
  get toolbarSettings() {
2083
3125
  return this._toolbarSettings;
2084
3126
  }
3127
+ /**
3128
+ * Gets or sets the callback function that retrieves the child items for a particular item.
3129
+ */
3130
+ set fetchChildren(fn) {
3131
+ this._fetchChildren = fn;
3132
+ this.editService.fetchChildren = fn;
3133
+ }
3134
+ get fetchChildren() {
3135
+ return this._fetchChildren;
3136
+ }
3137
+ /**
3138
+ * Gets or sets the callback function that indicates if a particular item has child items.
3139
+ */
3140
+ set hasChildren(fn) {
3141
+ this._hasChildren = fn;
3142
+ this.editService.hasChildren = fn;
3143
+ }
3144
+ get hasChildren() {
3145
+ return this._hasChildren;
3146
+ }
2085
3147
  /**
2086
3148
  * The options of the timeline splitter pane. By default the pane is `collapsible`,
2087
3149
  * `resizable`, not `collapsed`, and its `size` is `'50%'`.
@@ -2137,6 +3199,17 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
2137
3199
  get taskIdField() {
2138
3200
  return this.mapper.taskFields.id;
2139
3201
  }
3202
+ /**
3203
+ * Specifies the settings for auto-scrolling during dragging
3204
+ * when the pointer moves outside of the container bounderies
3205
+ * [see example]({% slug editing_drag_create_dependencies_gantt %}#toc-auto-scrolling).
3206
+ */
3207
+ set dragScrollSettings(settings) {
3208
+ this._dragScrollSettings = Object.assign({}, DEFAULT_DRAG_SCROLL_SETTINGS, settings);
3209
+ }
3210
+ get dragScrollSettings() {
3211
+ return this._dragScrollSettings;
3212
+ }
2140
3213
  /**
2141
3214
  * @hidden
2142
3215
  */
@@ -2146,6 +3219,15 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
2146
3219
  }
2147
3220
  return this.treeList.view.data.map(item => item.data);
2148
3221
  }
3222
+ /**
3223
+ * @hidden
3224
+ */
3225
+ get viewItems() {
3226
+ if (!isPresent(this.treeList)) {
3227
+ return [];
3228
+ }
3229
+ return this.treeList.view.data;
3230
+ }
2149
3231
  /**
2150
3232
  * @hidden
2151
3233
  */
@@ -2192,8 +3274,8 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
2192
3274
  /**
2193
3275
  * @hidden
2194
3276
  */
2195
- get editDialogFormGroup() {
2196
- return this.editService.formGroup;
3277
+ get isInEditMode() {
3278
+ return this.showEditingDialog || this.showConfirmationDialog || this.treeList.isEditing();
2197
3279
  }
2198
3280
  ngOnChanges(changes) {
2199
3281
  if (anyChanged(['data', 'activeView', 'workWeekStart', 'workWeekEnd', 'workDayStart', 'workDayEnd'], changes)) {
@@ -2202,6 +3284,14 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
2202
3284
  }
2203
3285
  ngAfterViewInit() {
2204
3286
  this.updateTreeListMargin();
3287
+ if (this.navigable) {
3288
+ this.navigationService.initialize({
3289
+ gantt: this,
3290
+ host: this.hostElement.nativeElement,
3291
+ treeListElement: this.treeList.wrapper.nativeElement,
3292
+ timelineElement: this.timeline.timelineContent.nativeElement
3293
+ });
3294
+ }
2205
3295
  const leftContainer = this.treeList.wrapper.nativeElement.querySelector('kendo-treelist-list > div');
2206
3296
  this.scrollSyncService.registerElement(leftContainer, 'treelist');
2207
3297
  }
@@ -2219,6 +3309,34 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
2219
3309
  this.localizationSubscription.unsubscribe();
2220
3310
  }
2221
3311
  }
3312
+ /**
3313
+ * Focuses the last active cell or task in the Gantt.
3314
+ * If no item has previously been focused, the first cell of the TreeList part will receive focus,
3315
+ * ([see example]({% slug keyboard_navigation_gantt %}#toc-controlling-the-focus)).
3316
+ */
3317
+ focus() {
3318
+ if (this.navigable) {
3319
+ this.navigationService.focusLastActiveItem();
3320
+ }
3321
+ }
3322
+ /**
3323
+ * Focuses the targeted cell in the TreeList part of the component,
3324
+ * ([see example]({% slug keyboard_navigation_gantt %}#toc-controlling-the-focus)).
3325
+ */
3326
+ focusCell(rowIndex, colIndex) {
3327
+ if (this.navigable) {
3328
+ this.navigationService.focusCell(rowIndex, colIndex);
3329
+ }
3330
+ }
3331
+ /**
3332
+ * Focuses the targeted task in the Timeline part of the component,
3333
+ * ([see example]({% slug keyboard_navigation_gantt %}#toc-controlling-the-focus)).
3334
+ */
3335
+ focusTask(taskIndex) {
3336
+ if (this.navigable) {
3337
+ this.navigationService.focusTask(taskIndex);
3338
+ }
3339
+ }
2222
3340
  /**
2223
3341
  * Applies the minimum possible width for the specified column,
2224
3342
  * so that the whole text fits without wrapping. This method expects the Gantt
@@ -2283,7 +3401,10 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
2283
3401
  */
2284
3402
  editTask(dataItem, formGroup) {
2285
3403
  if (!this.showEditingDialog) {
2286
- this.editService.createEditDialog(dataItem, formGroup);
3404
+ const taskId = this.mapper.extractFromTask(dataItem, 'id');
3405
+ const dependencies = this.dependencies.filter(item => this.mapper.extractFromDependency(item, 'toId') === taskId
3406
+ || this.mapper.extractFromDependency(item, 'fromId') === taskId);
3407
+ this.editService.createEditDialog(dataItem, formGroup, dependencies);
2287
3408
  }
2288
3409
  }
2289
3410
  /**
@@ -2300,6 +3421,15 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
2300
3421
  openConfirmationDialog() {
2301
3422
  this.showConfirmationDialog = true;
2302
3423
  }
3424
+ /**
3425
+ * @hidden
3426
+ */
3427
+ handleConfirmationDialogClose() {
3428
+ this.showConfirmationDialog = false;
3429
+ if (this.navigable) {
3430
+ this.focus();
3431
+ }
3432
+ }
2303
3433
  /**
2304
3434
  * Opens a cell for editing.
2305
3435
  */
@@ -2384,7 +3514,7 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
2384
3514
  if (hasObservers(this.taskClick)) {
2385
3515
  const taskIndex = getClosestTaskIndex(target, gantt);
2386
3516
  const task = this.renderedTreeListItems[taskIndex];
2387
- this.zone.run(() => this.emitTaskClick(event, task, taskIndex));
3517
+ this.zone.run(() => this.notifyTaskClick(event, task, taskIndex));
2388
3518
  }
2389
3519
  }
2390
3520
  /**
@@ -2402,8 +3532,8 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
2402
3532
  if ((hasObservers(this.selectionChange) && !this.isSameSelection(selectionAction, task)) ||
2403
3533
  hasObservers(this.taskClick)) {
2404
3534
  this.zone.run(() => {
2405
- this.emitSelectionChange(task, selectionAction);
2406
- this.emitTaskClick(event, task, taskIndex);
3535
+ this.notifySelectionChange(task, selectionAction);
3536
+ this.notifyTaskClick(event, task, taskIndex);
2407
3537
  });
2408
3538
  }
2409
3539
  }
@@ -2440,7 +3570,7 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
2440
3570
  }
2441
3571
  const task = event.items.map(item => item.dataItem)[0]; // single selection only currently available
2442
3572
  const action = event.action;
2443
- this.emitSelectionChange(task, action);
3573
+ this.notifySelectionChange(task, action);
2444
3574
  }
2445
3575
  /**
2446
3576
  * @hidden
@@ -2491,7 +3621,7 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
2491
3621
  dataItem: task,
2492
3622
  originalEvent: event,
2493
3623
  sender: this,
2494
- rowIndex: taskIndex,
3624
+ index: taskIndex,
2495
3625
  type: 'dblclick'
2496
3626
  }));
2497
3627
  }
@@ -2502,16 +3632,35 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
2502
3632
  getText(token) {
2503
3633
  return this.localizationService.get(token);
2504
3634
  }
2505
- emitTaskClick(event, dataItem, itemIndex) {
3635
+ /**
3636
+ * @hidden
3637
+ */
3638
+ changeActiveView(view) {
3639
+ if (view !== this.activeView) {
3640
+ this.activeView = view;
3641
+ this.loadTimelineData();
3642
+ this.scrollSyncService.resetTimelineScrollLeft();
3643
+ this.activeViewChange.emit(view);
3644
+ }
3645
+ }
3646
+ /**
3647
+ * @hidden
3648
+ */
3649
+ notifyTaskClick(event, dataItem, itemIndex) {
3650
+ // simulates the TreeList `cellClick` event triggered by enter press (type: 'click')
3651
+ const type = event instanceof KeyboardEvent ? 'click' : event.type;
2506
3652
  this.taskClick.emit({
2507
3653
  originalEvent: event,
2508
3654
  dataItem: dataItem,
2509
- rowIndex: itemIndex,
2510
- type: event.type,
3655
+ index: itemIndex,
3656
+ type: type,
2511
3657
  sender: this
2512
3658
  });
2513
3659
  }
2514
- emitSelectionChange(dataItem, action) {
3660
+ /**
3661
+ * @hidden
3662
+ */
3663
+ notifySelectionChange(dataItem, action) {
2515
3664
  if (this.isSameSelection(action, dataItem)) {
2516
3665
  return;
2517
3666
  }
@@ -2522,6 +3671,29 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
2522
3671
  });
2523
3672
  this.treeList.updateView();
2524
3673
  }
3674
+ /**
3675
+ * @hidden
3676
+ */
3677
+ notifyTaskDelete(task) {
3678
+ this.editService.dataItem = task;
3679
+ this.taskDelete.emit({
3680
+ item: getEditItem(task, this.treeList.view.data, this.mapper),
3681
+ sender: this
3682
+ });
3683
+ }
3684
+ /**
3685
+ * @hidden
3686
+ */
3687
+ isSameSelection(action, dataItem) {
3688
+ return action === 'select' && this.isSelected(dataItem);
3689
+ }
3690
+ /**
3691
+ * @hidden
3692
+ */
3693
+ getSelectionAction({ ctrlKey, metaKey }, dataItem) {
3694
+ const shouldToggleSelection = ctrlKey || metaKey;
3695
+ return (shouldToggleSelection && this.isSelected(dataItem)) ? 'remove' : 'select';
3696
+ }
2525
3697
  updateTreeListGroupClass(columns = this.columns) {
2526
3698
  if (!isPresent(this.treeList)) {
2527
3699
  return;
@@ -2548,23 +3720,65 @@ let GanttComponent = GanttComponent_1 = class GanttComponent {
2548
3720
  }
2549
3721
  return this.views.find(view => view.type === this.activeView);
2550
3722
  }
2551
- isSameSelection(action, dataItem) {
2552
- return action === 'select' && this.isSelected(dataItem);
2553
- }
2554
- getSelectionAction({ ctrlKey, metaKey }, dataItem) {
2555
- const shouldToggleSelection = ctrlKey || metaKey;
2556
- return (shouldToggleSelection && this.isSelected(dataItem)) ? 'remove' : 'select';
2557
- }
2558
3723
  getFirstSelectedItem() {
2559
3724
  const isSelectedCallback = this.isSelected || isSelected;
2560
3725
  const loadedItems = this.renderedTreeListItems || [];
2561
3726
  return loadedItems.find(isSelectedCallback);
2562
3727
  }
3728
+ defaultValidateNewDependencyCallback(dependency) {
3729
+ const fromTaskId = this.mapper.extractFromDependency(dependency, 'fromId');
3730
+ const toTaskId = this.mapper.extractFromDependency(dependency, 'toId');
3731
+ const fromTask = this.treeList.view.data.find(task => this.mapper.extractFromTask(task.data, 'id') === fromTaskId);
3732
+ const toTask = this.treeList.view.data.find(task => this.mapper.extractFromTask(task.data, 'id') === toTaskId);
3733
+ // mark as invalid if the attempted dependency is lacking valid from- and to-tasks
3734
+ // or when the from- and to-tasks are actually the same task
3735
+ if (!isPresent(fromTask) || !isPresent(fromTask.data) ||
3736
+ !isPresent(toTask) || !isPresent(toTask.data) ||
3737
+ fromTask.data === toTask.data) {
3738
+ return false;
3739
+ }
3740
+ const tasksDependentOnOneAnother = this.dependencies.some(current => {
3741
+ const currentFromId = this.mapper.extractFromDependency(current, 'fromId');
3742
+ const currentToId = this.mapper.extractFromDependency(current, 'toId');
3743
+ return (fromTaskId === currentFromId && toTaskId === currentToId) ||
3744
+ (toTaskId === currentFromId && fromTaskId === currentToId);
3745
+ });
3746
+ // mark as invalid if the attempted dependency is trying to connect already dependent tasks
3747
+ // mark as invalid if the two tasks are in parent-child relationship
3748
+ if (tasksDependentOnOneAnother || areParentChild(fromTask, toTask)) {
3749
+ return false;
3750
+ }
3751
+ const fromTaskStart = this.mapper.extractFromTask(fromTask.data, 'start');
3752
+ const fromTaskEnd = this.mapper.extractFromTask(fromTask.data, 'end');
3753
+ const toTaskStart = this.mapper.extractFromTask(toTask.data, 'start');
3754
+ const toTaskEnd = this.mapper.extractFromTask(toTask.data, 'end');
3755
+ // if the two tasks are available to be connected via a dependency,
3756
+ // check if their start and end time allow for the attempted dependency type
3757
+ switch (this.mapper.extractFromDependency(dependency, 'type')) {
3758
+ // finish to finish (FF) — the from-task ends before the to-task can end
3759
+ case DependencyType.FF:
3760
+ return fromTaskEnd <= toTaskEnd;
3761
+ // finish to start (FS) — the from-task ends before the to-task can begin
3762
+ case DependencyType.FS:
3763
+ return fromTaskEnd <= toTaskStart;
3764
+ // start to finish (SF) — the from-task begins before the to-task can end
3765
+ case DependencyType.SF:
3766
+ return fromTaskStart <= toTaskEnd;
3767
+ // start to start (SS) — the from-task begins before the to-task can begin
3768
+ case DependencyType.SS:
3769
+ return fromTaskStart <= toTaskStart;
3770
+ default: return false;
3771
+ }
3772
+ }
2563
3773
  };
2564
3774
  __decorate([
2565
3775
  ViewChild(TreeListComponent, { static: true }),
2566
3776
  __metadata("design:type", TreeListComponent)
2567
3777
  ], GanttComponent.prototype, "treeList", void 0);
3778
+ __decorate([
3779
+ ViewChild(GanttTimelineComponent, { static: false }),
3780
+ __metadata("design:type", GanttTimelineComponent)
3781
+ ], GanttComponent.prototype, "timeline", void 0);
2568
3782
  __decorate([
2569
3783
  ContentChild(GanttTaskContentTemplateDirective, { static: true }),
2570
3784
  __metadata("design:type", GanttTaskContentTemplateDirective)
@@ -2581,6 +3795,24 @@ __decorate([
2581
3795
  ContentChildren(ToolbarTemplateDirective),
2582
3796
  __metadata("design:type", QueryList)
2583
3797
  ], GanttComponent.prototype, "toolbarTemplateChildren", void 0);
3798
+ __decorate([
3799
+ Input('aria-roledescription'),
3800
+ __metadata("design:type", String)
3801
+ ], GanttComponent.prototype, "roleDescription", void 0);
3802
+ __decorate([
3803
+ HostBinding('attr.aria-roledescription'),
3804
+ __metadata("design:type", String),
3805
+ __metadata("design:paramtypes", [])
3806
+ ], GanttComponent.prototype, "hostRoleDescriptionAttr", null);
3807
+ __decorate([
3808
+ Input('role'),
3809
+ __metadata("design:type", String)
3810
+ ], GanttComponent.prototype, "role", void 0);
3811
+ __decorate([
3812
+ HostBinding('attr.role'),
3813
+ __metadata("design:type", String),
3814
+ __metadata("design:paramtypes", [])
3815
+ ], GanttComponent.prototype, "hostRoleAttr", null);
2584
3816
  __decorate([
2585
3817
  HostBinding('class.k-gantt'),
2586
3818
  __metadata("design:type", Boolean)
@@ -2623,6 +3855,10 @@ __decorate([
2623
3855
  Input(),
2624
3856
  __metadata("design:type", Function)
2625
3857
  ], GanttComponent.prototype, "isSelected", void 0);
3858
+ __decorate([
3859
+ Input(),
3860
+ __metadata("design:type", Function)
3861
+ ], GanttComponent.prototype, "validateNewDependency", void 0);
2626
3862
  __decorate([
2627
3863
  Output(),
2628
3864
  __metadata("design:type", EventEmitter)
@@ -2638,12 +3874,14 @@ __decorate([
2638
3874
  ], GanttComponent.prototype, "toolbarSettings", null);
2639
3875
  __decorate([
2640
3876
  Input(),
2641
- __metadata("design:type", Function)
2642
- ], GanttComponent.prototype, "fetchChildren", void 0);
3877
+ __metadata("design:type", Function),
3878
+ __metadata("design:paramtypes", [Function])
3879
+ ], GanttComponent.prototype, "fetchChildren", null);
2643
3880
  __decorate([
2644
3881
  Input(),
2645
- __metadata("design:type", Function)
2646
- ], GanttComponent.prototype, "hasChildren", void 0);
3882
+ __metadata("design:type", Function),
3883
+ __metadata("design:paramtypes", [Function])
3884
+ ], GanttComponent.prototype, "hasChildren", null);
2647
3885
  __decorate([
2648
3886
  Input(),
2649
3887
  __metadata("design:type", Array)
@@ -2680,6 +3918,10 @@ __decorate([
2680
3918
  Input(),
2681
3919
  __metadata("design:type", Number)
2682
3920
  ], GanttComponent.prototype, "workWeekEnd", void 0);
3921
+ __decorate([
3922
+ Input(),
3923
+ __metadata("design:type", Boolean)
3924
+ ], GanttComponent.prototype, "navigable", void 0);
2683
3925
  __decorate([
2684
3926
  Input(),
2685
3927
  __metadata("design:type", Object),
@@ -2720,6 +3962,11 @@ __decorate([
2720
3962
  Input(),
2721
3963
  __metadata("design:type", Boolean)
2722
3964
  ], GanttComponent.prototype, "columnsResizable", void 0);
3965
+ __decorate([
3966
+ Input(),
3967
+ __metadata("design:type", Object),
3968
+ __metadata("design:paramtypes", [Object])
3969
+ ], GanttComponent.prototype, "dragScrollSettings", null);
2723
3970
  __decorate([
2724
3971
  Output(),
2725
3972
  __metadata("design:type", EventEmitter)
@@ -2760,6 +4007,10 @@ __decorate([
2760
4007
  Output(),
2761
4008
  __metadata("design:type", EventEmitter)
2762
4009
  ], GanttComponent.prototype, "taskAdd", void 0);
4010
+ __decorate([
4011
+ Output(),
4012
+ __metadata("design:type", EventEmitter)
4013
+ ], GanttComponent.prototype, "dependencyAdd", void 0);
2763
4014
  __decorate([
2764
4015
  Output(),
2765
4016
  __metadata("design:type", EventEmitter)
@@ -2817,6 +4068,7 @@ GanttComponent = GanttComponent_1 = __decorate([
2817
4068
  selector: 'kendo-gantt',
2818
4069
  exportAs: 'kendoGantt',
2819
4070
  providers: [
4071
+ GanttLocalizationService,
2820
4072
  LocalizationService,
2821
4073
  {
2822
4074
  provide: DataBoundTreeComponent,
@@ -2834,7 +4086,9 @@ GanttComponent = GanttComponent_1 = __decorate([
2834
4086
  DependencyDomService,
2835
4087
  MappingService,
2836
4088
  OptionChangesService,
2837
- EditService
4089
+ EditService,
4090
+ TimelineScrollService,
4091
+ NavigationService
2838
4092
  ],
2839
4093
  template: `
2840
4094
  <ng-container kendoGanttLocalizedMessages
@@ -3061,6 +4315,7 @@ GanttComponent = GanttComponent_1 = __decorate([
3061
4315
  [data]="data"
3062
4316
  [hasChildren]="hasChildren"
3063
4317
  [fetchChildren]="fetchChildren"
4318
+ [navigable]="navigable"
3064
4319
  [isExpanded]="isExpanded"
3065
4320
  [autoSize]="columnsAutoSize"
3066
4321
  [columnMenu]="columnMenu"
@@ -3149,7 +4404,9 @@ GanttComponent = GanttComponent_1 = __decorate([
3149
4404
  [scrollable]="false">
3150
4405
  <kendo-gantt-timeline
3151
4406
  *ngIf="views && views.length"
3152
- [rows]="renderedTreeListItems"
4407
+ [renderDependencyDragClues]="renderDependencyDragClues"
4408
+ [dragScrollSettings]="dragScrollSettings"
4409
+ [rows]="viewItems"
3153
4410
  [slots]="timelineSlots"
3154
4411
  [groupSlots]="timelineGroupSlots"
3155
4412
  [tableWidth]="tableWidth"
@@ -3159,7 +4416,8 @@ GanttComponent = GanttComponent_1 = __decorate([
3159
4416
  [summaryTaskTemplate]="summaryTaskTemplate?.templateRef"
3160
4417
  [taskClass]="taskClass"
3161
4418
  [dependencies]="dependencies"
3162
- [hasChildren]="hasChildren"
4419
+ [isExpanded]="isExpanded"
4420
+ [selectable]="selectable"
3163
4421
  [isTaskSelected]="isTaskSelected"
3164
4422
  [kendoEventsOutsideAngular]="{
3165
4423
  click: handleTimelineClick,
@@ -3178,18 +4436,21 @@ GanttComponent = GanttComponent_1 = __decorate([
3178
4436
  [showViewSelector]="toolbarSettings.viewSelectorTool === 'bottom' || toolbarSettings.viewSelectorTool === 'both'"
3179
4437
  class="k-gantt-footer k-toolbar k-gantt-toolbar"
3180
4438
  position="bottom"></kendo-gantt-toolbar>
3181
- <kendo-gantt-edit-dialog *ngIf="showEditingDialog" [formGroup]="editDialogFormGroup"></kendo-gantt-edit-dialog>
4439
+ <kendo-gantt-edit-dialog
4440
+ *ngIf="showEditingDialog"
4441
+ [data]="data">
4442
+ </kendo-gantt-edit-dialog>
3182
4443
  <kendo-dialog
3183
4444
  *ngIf="showConfirmationDialog"
3184
4445
  [width]="575"
3185
4446
  [height]="170"
3186
4447
  [title]="getText('confirmationDialogTitle')"
3187
- (close)="showConfirmationDialog = false;">
3188
- <span>{{getText('confirmationDialogContent')}}</span>
4448
+ (close)="handleConfirmationDialogClose()">
4449
+ <span>{{ getText('confirmationDialogContent') }}</span>
3189
4450
  <kendo-dialog-actions layout="normal">
3190
4451
  <kendo-treelist-spacer></kendo-treelist-spacer>
3191
4452
  <button kendoButton [primary]="true" (click)="handleDeleteConfirmation()">{{ getText('deleteButtonText') }}</button>
3192
- <button kendoButton (click)="showConfirmationDialog = false;">{{ getText('cancelButtonText') }}</button>
4453
+ <button kendoButton (click)="handleConfirmationDialogClose()">{{ getText('cancelButtonText') }}</button>
3193
4454
  </kendo-dialog-actions>
3194
4455
  </kendo-dialog>
3195
4456
  `
@@ -3203,205 +4464,14 @@ GanttComponent = GanttComponent_1 = __decorate([
3203
4464
  EditService,
3204
4465
  LocalizationService,
3205
4466
  ElementRef,
3206
- NgZone])
4467
+ NgZone,
4468
+ NavigationService])
3207
4469
  ], GanttComponent);
3208
4470
 
3209
4471
  /**
3210
4472
  * @hidden
3211
4473
  */
3212
- let GanttTimelineComponent = class GanttTimelineComponent {
3213
- constructor(scrollSyncService, dependencyDomService, renderer, zone) {
3214
- this.scrollSyncService = scrollSyncService;
3215
- this.dependencyDomService = dependencyDomService;
3216
- this.renderer = renderer;
3217
- this.zone = zone;
3218
- this.hostClass = true;
3219
- this.dependencies = [];
3220
- this.subscriptions = new Subscription();
3221
- this.subscriptions.add(
3222
- // task changes indicates change in row content, number, height, etc.
3223
- this.dependencyDomService.taskChanges
3224
- .pipe(filter(args => isPresent(args.timelineRow)), switchMap(args => this.zone.onStable.pipe(take(1), map(() => args))) // ensure the content is rendered
3225
- )
3226
- .subscribe(({ timelineRow }) => {
3227
- const timelineRowHeight = isDocumentAvailable() ? timelineRow.getBoundingClientRect().height : 0;
3228
- this.renderer.setStyle(this.timelineColumns.nativeElement, 'height', `${(this.rows || []).length * timelineRowHeight}px`);
3229
- }));
3230
- }
3231
- ngAfterViewInit() {
3232
- const timelineHeader = this.timelineHeaderWrap.nativeElement;
3233
- const rightContainer = this.timelineContent.nativeElement;
3234
- this.scrollSyncService.registerElement(rightContainer, 'timeline');
3235
- this.scrollSyncService.registerElement(timelineHeader, 'header');
3236
- this.dependencyDomService.registerContentContainer(this.tasksContainer.nativeElement);
3237
- }
3238
- ngOnDestroy() {
3239
- this.subscriptions.unsubscribe();
3240
- }
3241
- isNonWorking(item) {
3242
- return item.hasOwnProperty('isWorking') && !item.isWorking;
3243
- }
3244
- };
3245
- __decorate([
3246
- ViewChild('timelineContent', { static: true }),
3247
- __metadata("design:type", ElementRef)
3248
- ], GanttTimelineComponent.prototype, "timelineContent", void 0);
3249
- __decorate([
3250
- ViewChild('timelineColumns', { static: true }),
3251
- __metadata("design:type", ElementRef)
3252
- ], GanttTimelineComponent.prototype, "timelineColumns", void 0);
3253
- __decorate([
3254
- ViewChild('timelineHeaderWrap', { static: true }),
3255
- __metadata("design:type", ElementRef)
3256
- ], GanttTimelineComponent.prototype, "timelineHeaderWrap", void 0);
3257
- __decorate([
3258
- ViewChild('tasksContainer', { static: true }),
3259
- __metadata("design:type", ElementRef)
3260
- ], GanttTimelineComponent.prototype, "tasksContainer", void 0);
3261
- __decorate([
3262
- HostBinding('class.k-gantt-timeline'),
3263
- __metadata("design:type", Boolean)
3264
- ], GanttTimelineComponent.prototype, "hostClass", void 0);
3265
- __decorate([
3266
- Input(),
3267
- __metadata("design:type", Array)
3268
- ], GanttTimelineComponent.prototype, "rows", void 0);
3269
- __decorate([
3270
- Input(),
3271
- __metadata("design:type", Array)
3272
- ], GanttTimelineComponent.prototype, "slots", void 0);
3273
- __decorate([
3274
- Input(),
3275
- __metadata("design:type", Array)
3276
- ], GanttTimelineComponent.prototype, "groupSlots", void 0);
3277
- __decorate([
3278
- Input(),
3279
- __metadata("design:type", Number)
3280
- ], GanttTimelineComponent.prototype, "tableWidth", void 0);
3281
- __decorate([
3282
- Input(),
3283
- __metadata("design:type", String)
3284
- ], GanttTimelineComponent.prototype, "activeView", void 0);
3285
- __decorate([
3286
- Input(),
3287
- __metadata("design:type", TemplateRef)
3288
- ], GanttTimelineComponent.prototype, "taskContentTemplate", void 0);
3289
- __decorate([
3290
- Input(),
3291
- __metadata("design:type", TemplateRef)
3292
- ], GanttTimelineComponent.prototype, "taskTemplate", void 0);
3293
- __decorate([
3294
- Input(),
3295
- __metadata("design:type", TemplateRef)
3296
- ], GanttTimelineComponent.prototype, "summaryTaskTemplate", void 0);
3297
- __decorate([
3298
- Input(),
3299
- __metadata("design:type", Function)
3300
- ], GanttTimelineComponent.prototype, "taskClass", void 0);
3301
- __decorate([
3302
- Input(),
3303
- __metadata("design:type", Function)
3304
- ], GanttTimelineComponent.prototype, "isTaskSelected", void 0);
3305
- __decorate([
3306
- Input(),
3307
- __metadata("design:type", Function)
3308
- ], GanttTimelineComponent.prototype, "hasChildren", void 0);
3309
- __decorate([
3310
- Input(),
3311
- __metadata("design:type", Array)
3312
- ], GanttTimelineComponent.prototype, "dependencies", void 0);
3313
- GanttTimelineComponent = __decorate([
3314
- Component({
3315
- selector: 'kendo-gantt-timeline',
3316
- template: `
3317
- <div class="k-timeline k-grid k-widget">
3318
- <div class="k-grid-header">
3319
- <div #timelineHeaderWrap class="k-grid-header-wrap">
3320
- <table
3321
- role="presentation"
3322
- [style.width.px]="tableWidth"
3323
- >
3324
- <tbody
3325
- kendoGanttHeaderTableBody
3326
- [groupSlots]="groupSlots"
3327
- [slots]="slots">
3328
- </tbody>
3329
- </table>
3330
- </div>
3331
- </div>
3332
- <div #timelineContent class="k-grid-content">
3333
- <div class="k-gantt-tables">
3334
- <table
3335
- class="k-gantt-rows"
3336
- [style.width.px]="tableWidth"
3337
- role="presentation"
3338
- >
3339
- <tbody>
3340
- <tr *ngFor="let item of rows; let i = index;"
3341
- [class.k-alt]="i % 2"
3342
- >
3343
- <td></td>
3344
- </tr>
3345
- </tbody>
3346
- </table>
3347
-
3348
- <table
3349
- #timelineColumns
3350
- class="k-gantt-columns"
3351
- role="presentation"
3352
- [style.width.px]="tableWidth"
3353
- >
3354
- <colgroup>
3355
- <col *ngFor="let item of slots">
3356
- </colgroup>
3357
-
3358
- <tbody>
3359
- <tr>
3360
- <td *ngFor="let item of slots"
3361
- [class.k-nonwork-hour]="isNonWorking(item)"
3362
- >
3363
- </td>
3364
- </tr>
3365
- </tbody>
3366
- </table>
3367
-
3368
- <table
3369
- #tasksContainer
3370
- class="k-gantt-tasks"
3371
- role="presentation"
3372
- style="border-collapse: collapse;"
3373
- [style.width.px]="tableWidth"
3374
- >
3375
- <tbody
3376
- kendoGanttTasksTableBody
3377
- [rows]="rows"
3378
- [activeView]="activeView"
3379
- [taskContentTemplate]="taskContentTemplate"
3380
- [taskTemplate]="taskTemplate"
3381
- [summaryTaskTemplate]="summaryTaskTemplate"
3382
- [taskClass]="taskClass"
3383
- [hasChildren]="hasChildren"
3384
- [isTaskSelected]="isTaskSelected"
3385
- >
3386
- </tbody>
3387
- </table>
3388
- </div>
3389
- <svg class="k-gantt-dependencies-svg">
3390
- <polyline
3391
- *ngFor="let dependency of dependencies"
3392
- kendoGanttDependency
3393
- [dependency]="dependency"
3394
- />
3395
- </svg>
3396
- </div>
3397
- </div>
3398
- `
3399
- }),
3400
- __metadata("design:paramtypes", [ScrollSyncService,
3401
- DependencyDomService,
3402
- Renderer2,
3403
- NgZone])
3404
- ], GanttTimelineComponent);
4474
+ const TOUCH_ENABLED = new InjectionToken('gantt-touch-enabled');
3405
4475
 
3406
4476
  /**
3407
4477
  * @hidden
@@ -3418,8 +4488,8 @@ let GanttTasksTableBodyComponent = class GanttTasksTableBodyComponent {
3418
4488
  this.dependencyDomService.registerTimelineRow(timelineRow.nativeElement);
3419
4489
  }
3420
4490
  }
3421
- isMileStone(task) {
3422
- return !this.hasChildren(task) && isEqual(this.mapper.extractFromTask(task, 'start'), this.mapper.extractFromTask(task, 'end'));
4491
+ isMileStone(item) {
4492
+ return !item.hasChildren && isEqual(this.mapper.extractFromTask(item.data, 'start'), this.mapper.extractFromTask(item.data, 'end'));
3423
4493
  }
3424
4494
  };
3425
4495
  __decorate([
@@ -3427,6 +4497,10 @@ __decorate([
3427
4497
  __metadata("design:type", ElementRef),
3428
4498
  __metadata("design:paramtypes", [ElementRef])
3429
4499
  ], GanttTasksTableBodyComponent.prototype, "timelineRow", null);
4500
+ __decorate([
4501
+ Input(),
4502
+ __metadata("design:type", Boolean)
4503
+ ], GanttTasksTableBodyComponent.prototype, "selectable", void 0);
3430
4504
  __decorate([
3431
4505
  Input(),
3432
4506
  __metadata("design:type", Array)
@@ -3454,11 +4528,15 @@ __decorate([
3454
4528
  __decorate([
3455
4529
  Input(),
3456
4530
  __metadata("design:type", Function)
3457
- ], GanttTasksTableBodyComponent.prototype, "hasChildren", void 0);
4531
+ ], GanttTasksTableBodyComponent.prototype, "isExpanded", void 0);
3458
4532
  __decorate([
3459
4533
  Input(),
3460
4534
  __metadata("design:type", Function)
3461
4535
  ], GanttTasksTableBodyComponent.prototype, "isTaskSelected", void 0);
4536
+ __decorate([
4537
+ Input(),
4538
+ __metadata("design:type", Boolean)
4539
+ ], GanttTasksTableBodyComponent.prototype, "renderDependencyDragClues", void 0);
3462
4540
  GanttTasksTableBodyComponent = __decorate([
3463
4541
  Component({
3464
4542
  selector: '[kendoGanttTasksTableBody]',
@@ -3467,33 +4545,43 @@ GanttTasksTableBodyComponent = __decorate([
3467
4545
  <td>
3468
4546
  <kendo-gantt-milestone-task
3469
4547
  *ngIf="isMileStone(item); else task"
3470
- [dataItem]="item"
4548
+ [dataItem]="item.data"
4549
+ [level]="item.level"
3471
4550
  [activeView]="activeView"
3472
4551
  [taskClass]="taskClass"
4552
+ [selectable]="selectable"
3473
4553
  [isSelected]="isTaskSelected"
3474
4554
  [index]="index"
4555
+ [renderDependencyDragClues]="renderDependencyDragClues"
3475
4556
  >
3476
4557
  </kendo-gantt-milestone-task>
3477
4558
  <ng-template #task>
3478
4559
  <kendo-gantt-summary-task
3479
- *ngIf="hasChildren(item)"
3480
- [dataItem]="item"
4560
+ *ngIf="item.hasChildren"
4561
+ [dataItem]="item.data"
4562
+ [level]="item.level"
3481
4563
  [template]="summaryTaskTemplate"
3482
4564
  [activeView]="activeView"
3483
4565
  [taskClass]="taskClass"
4566
+ [selectable]="selectable"
3484
4567
  [isSelected]="isTaskSelected"
4568
+ [isExpanded]="isExpanded"
3485
4569
  [index]="index"
4570
+ [renderDependencyDragClues]="renderDependencyDragClues"
3486
4571
  >
3487
4572
  </kendo-gantt-summary-task>
3488
4573
  <kendo-gantt-task
3489
- *ngIf="!hasChildren(item)"
3490
- [dataItem]="item"
4574
+ *ngIf="!item.hasChildren"
4575
+ [dataItem]="item.data"
4576
+ [level]="item.level"
3491
4577
  [taskContentTemplate]="taskContentTemplate"
3492
4578
  [taskTemplate]="taskTemplate"
3493
4579
  [activeView]="activeView"
3494
4580
  [taskClass]="taskClass"
4581
+ [selectable]="selectable"
3495
4582
  [isSelected]="isTaskSelected"
3496
4583
  [index]="index"
4584
+ [renderDependencyDragClues]="renderDependencyDragClues"
3497
4585
  >
3498
4586
  </kendo-gantt-task>
3499
4587
  </ng-template>
@@ -3538,22 +4626,32 @@ const slotUnitDuration = {
3538
4626
  week: MS_PER_DAY,
3539
4627
  month: MS_PER_DAY * 7
3540
4628
  };
4629
+ const FOCUSED_CLASS = 'k-focus';
3541
4630
  /**
3542
4631
  * @hidden
3543
4632
  */
3544
4633
  class GanttTaskBase {
3545
4634
  constructor(mapper, // left public to be available for usage in the templates
3546
- timelineViewService, dependencyDomService, optionChangesService, cdr) {
4635
+ timelineViewService, dependencyDomService, optionChangesService, cdr, navigationService) {
3547
4636
  this.mapper = mapper;
3548
4637
  this.timelineViewService = timelineViewService;
3549
4638
  this.dependencyDomService = dependencyDomService;
3550
4639
  this.optionChangesService = optionChangesService;
3551
4640
  this.cdr = cdr;
4641
+ this.navigationService = navigationService;
3552
4642
  this.wrapperClass = true;
3553
- this.viewChangesSubscription = new Subscription();
3554
- this.viewChangesSubscription.add(this.optionChangesService.viewChanges.subscribe(() => {
3555
- this.cdr.markForCheck();
3556
- }));
4643
+ this.subscriptions = new Subscription();
4644
+ this.subscriptions.add(this.optionChangesService.viewChanges
4645
+ .subscribe(() => this.cdr.markForCheck()));
4646
+ this.subscriptions.add(this.navigationService.taskStatusChanges
4647
+ .subscribe(this.updateActiveState.bind(this)));
4648
+ }
4649
+ get taskIndexAttribute() {
4650
+ return this.index;
4651
+ }
4652
+ get ariaSelected() {
4653
+ // assinging null will not render the attribute at all (desired in selectable="false" mode)
4654
+ return this.selectable ? String(this.isSelected(this.dataItem)) : null;
3557
4655
  }
3558
4656
  get slotUnitDuration() {
3559
4657
  return slotUnitDuration[this.activeView];
@@ -3561,25 +4659,8 @@ class GanttTaskBase {
3561
4659
  get viewService() {
3562
4660
  return this.timelineViewService.service(this.activeView);
3563
4661
  }
3564
- get slotWidth() {
3565
- return this.viewService.options.slotWidth;
3566
- }
3567
- ngOnChanges(changes) {
3568
- if (isPresent(changes.dataItem)) {
3569
- if (isPresent(changes.dataItem.previousValue)) {
3570
- this.dependencyDomService.unregisterTask(changes.dataItem.previousValue);
3571
- }
3572
- this.dependencyDomService.registerTask(this.dataItem, this.taskElement.nativeElement);
3573
- }
3574
- else if (isPresent(changes.activeView)) {
3575
- this.dependencyDomService.notifyChanges();
3576
- }
3577
- }
3578
- ngOnDestroy() {
3579
- if (isPresent(this.dataItem)) {
3580
- this.dependencyDomService.unregisterTask(this.dataItem);
3581
- }
3582
- this.viewChangesSubscription.unsubscribe();
4662
+ get slotWidth() {
4663
+ return this.viewService.options.slotWidth;
3583
4664
  }
3584
4665
  get taskWidth() {
3585
4666
  const itemDuration = this.mapper.extractFromTask(this.dataItem, 'end') - this.mapper.extractFromTask(this.dataItem, 'start');
@@ -3587,6 +4668,12 @@ class GanttTaskBase {
3587
4668
  const width = durationInSlotUnits * this.slotWidth;
3588
4669
  return width;
3589
4670
  }
4671
+ /**
4672
+ * The `left` style prop has to be applied to the host element (.k-task-wrap), as the drag clue elements are displayed on .k-task-wrap hover.
4673
+ * Applying the `left` offset to the inner .k-task element leaves the .k-task-wrap element rendered with an offset of 0 somewhere on the left
4674
+ * and hovering just the .k-task element doesn't expose the drag clues.
4675
+ * Additionally, positioning the entire container takes care of positioning the hints as well.
4676
+ */
3590
4677
  get taskOffset() {
3591
4678
  const timeAfterViewStart = this.mapper.extractFromTask(this.dataItem, 'start') - this.viewService.viewStart;
3592
4679
  const offsetInSlotUnits = timeAfterViewStart / this.slotUnitDuration;
@@ -3598,11 +4685,48 @@ class GanttTaskBase {
3598
4685
  // fall-back to 0 in case no completionRatio is provided
3599
4686
  return isNumber(overlayWidth) ? overlayWidth : 0;
3600
4687
  }
4688
+ ngOnChanges(changes) {
4689
+ if (isPresent(changes.dataItem)) {
4690
+ if (isPresent(changes.dataItem.previousValue)) {
4691
+ this.dependencyDomService.unregisterTask(changes.dataItem.previousValue);
4692
+ }
4693
+ this.dependencyDomService.registerTask(this.dataItem, this.taskElement.nativeElement);
4694
+ }
4695
+ else if (isPresent(changes.activeView)) {
4696
+ this.dependencyDomService.notifyChanges();
4697
+ }
4698
+ if (this.navigationService.enabled && isPresent(changes.index)) {
4699
+ this.updateActiveState(this.navigationService.activeTask);
4700
+ }
4701
+ }
4702
+ ngOnDestroy() {
4703
+ if (isPresent(this.dataItem)) {
4704
+ this.dependencyDomService.unregisterTask(this.dataItem);
4705
+ }
4706
+ this.subscriptions.unsubscribe();
4707
+ }
4708
+ updateActiveState({ activeIndex, isFocused }) {
4709
+ const isActive = activeIndex === this.index;
4710
+ const tabindex = isActive ? '0' : '-1';
4711
+ this.taskElement.nativeElement.setAttribute('tabindex', tabindex);
4712
+ if (isActive && isFocused) {
4713
+ this.taskElement.nativeElement.focus();
4714
+ this.taskElement.nativeElement.classList.add(FOCUSED_CLASS);
4715
+ }
4716
+ else {
4717
+ this.taskElement.nativeElement.classList.remove(FOCUSED_CLASS);
4718
+ }
4719
+ }
3601
4720
  }
3602
4721
  __decorate([
3603
4722
  HostBinding('class.k-task-wrap'),
3604
4723
  __metadata("design:type", Boolean)
3605
4724
  ], GanttTaskBase.prototype, "wrapperClass", void 0);
4725
+ __decorate([
4726
+ HostBinding('attr.data-task-index'),
4727
+ __metadata("design:type", Number),
4728
+ __metadata("design:paramtypes", [])
4729
+ ], GanttTaskBase.prototype, "taskIndexAttribute", null);
3606
4730
  __decorate([
3607
4731
  ViewChild('task', { static: true }),
3608
4732
  __metadata("design:type", ElementRef)
@@ -3615,6 +4739,18 @@ __decorate([
3615
4739
  Input(),
3616
4740
  __metadata("design:type", Number)
3617
4741
  ], GanttTaskBase.prototype, "index", void 0);
4742
+ __decorate([
4743
+ Input(),
4744
+ __metadata("design:type", Number)
4745
+ ], GanttTaskBase.prototype, "level", void 0);
4746
+ __decorate([
4747
+ Input(),
4748
+ __metadata("design:type", Boolean)
4749
+ ], GanttTaskBase.prototype, "renderDependencyDragClues", void 0);
4750
+ __decorate([
4751
+ Input(),
4752
+ __metadata("design:type", Boolean)
4753
+ ], GanttTaskBase.prototype, "selectable", void 0);
3618
4754
  __decorate([
3619
4755
  Input(),
3620
4756
  __metadata("design:type", Function)
@@ -3627,19 +4763,25 @@ __decorate([
3627
4763
  Input(),
3628
4764
  __metadata("design:type", Function)
3629
4765
  ], GanttTaskBase.prototype, "taskClass", void 0);
4766
+ __decorate([
4767
+ HostBinding('style.left.px'),
4768
+ __metadata("design:type", Number),
4769
+ __metadata("design:paramtypes", [])
4770
+ ], GanttTaskBase.prototype, "taskOffset", null);
3630
4771
 
3631
4772
  var GanttTaskComponent_1;
3632
4773
  /**
3633
4774
  * @hidden
3634
4775
  */
3635
4776
  let GanttTaskComponent = GanttTaskComponent_1 = class GanttTaskComponent extends GanttTaskBase {
3636
- constructor(mapper, timelineViewService, dependencyDomService, optionChangesService, cdr, editService) {
3637
- super(mapper, timelineViewService, dependencyDomService, optionChangesService, cdr);
4777
+ constructor(editService, touchEnabled$$1, mapper, timelineViewService, dependencyDomService, optionChangesService, cdr, navigationService) {
4778
+ super(mapper, timelineViewService, dependencyDomService, optionChangesService, cdr, navigationService);
3638
4779
  this.editService = editService;
4780
+ this.touchEnabled = touchEnabled$$1;
3639
4781
  }
3640
4782
  onTaskDelete() {
3641
4783
  this.editService.dataItem = this.dataItem;
3642
- this.editService.showConfirmationDialog.next();
4784
+ this.editService.taskDelete.next(this.dataItem);
3643
4785
  }
3644
4786
  };
3645
4787
  __decorate([
@@ -3663,17 +4805,19 @@ GanttTaskComponent = GanttTaskComponent_1 = __decorate([
3663
4805
  <div
3664
4806
  #task
3665
4807
  class="k-task k-task-single"
4808
+ role="treeitem"
3666
4809
  [ngClass]="taskClass(dataItem)"
3667
4810
  [style.width.px]="taskWidth"
3668
- [style.left.px]="taskOffset"
3669
4811
  [attr.title]="mapper.extractFromTask(dataItem, 'title')"
3670
- [attr.data-task-index]="index"
3671
4812
  [class.k-state-selected]="isSelected(dataItem)"
4813
+ [attr.aria-selected]="ariaSelected"
4814
+ [attr.aria-level]="level + 1"
3672
4815
  >
3673
4816
  <ng-container *ngIf="!taskTemplate">
3674
4817
  <div
3675
4818
  class="k-task-complete"
3676
4819
  [style.width.px]="completionOverlayWidth"
4820
+ aria-hidden="true"
3677
4821
  >
3678
4822
  </div>
3679
4823
  <div class="k-task-content">
@@ -3688,10 +4832,17 @@ GanttTaskComponent = GanttTaskComponent_1 = __decorate([
3688
4832
  >
3689
4833
  </ng-template>
3690
4834
  </div>
3691
- <span class="k-task-actions">
4835
+ <span
4836
+ class="k-task-actions"
4837
+ aria-hidden="true"
4838
+ >
3692
4839
  <span
3693
- (click)="onTaskDelete()"
3694
- class="k-link k-task-delete">
4840
+ class="k-link k-task-delete"
4841
+ [kendoEventsOutsideAngular]="{
4842
+ click: onTaskDelete
4843
+ }"
4844
+ [scope]="this"
4845
+ >
3695
4846
  <span class="k-icon k-i-close"></span>
3696
4847
  </span>
3697
4848
  </span>
@@ -3707,14 +4858,36 @@ GanttTaskComponent = GanttTaskComponent_1 = __decorate([
3707
4858
  >
3708
4859
  </ng-template>
3709
4860
  </div>
3710
- `
4861
+ <ng-container *ngIf="renderDependencyDragClues">
4862
+ <div
4863
+ class="k-task-dot k-task-start k-touch-action-none"
4864
+ [class.k-display-block]="touchEnabled"
4865
+ >
4866
+ </div>
4867
+ <div
4868
+ class="k-task-dot k-task-end k-touch-action-none"
4869
+ [class.k-display-block]="touchEnabled"
4870
+ >
4871
+ </div>
4872
+ </ng-container>
4873
+ `,
4874
+ styles: [`
4875
+ .k-task.k-focus {
4876
+ box-shadow: 0 0 4px 3px grey;
4877
+ outline: none;
4878
+ }
4879
+ .k-task.k-focus.k-state-selected {
4880
+ box-shadow: 0 0 4px 3px #ffaea8;
4881
+ }
4882
+ `]
3711
4883
  }),
3712
- __metadata("design:paramtypes", [MappingService,
4884
+ __param(1, Inject(TOUCH_ENABLED)),
4885
+ __metadata("design:paramtypes", [EditService, Boolean, MappingService,
3713
4886
  TimelineViewService,
3714
4887
  DependencyDomService,
3715
4888
  OptionChangesService,
3716
4889
  ChangeDetectorRef,
3717
- EditService])
4890
+ NavigationService])
3718
4891
  ], GanttTaskComponent);
3719
4892
 
3720
4893
  var GanttSummaryTaskComponent_1;
@@ -3722,10 +4895,17 @@ var GanttSummaryTaskComponent_1;
3722
4895
  * @hidden
3723
4896
  */
3724
4897
  let GanttSummaryTaskComponent = GanttSummaryTaskComponent_1 = class GanttSummaryTaskComponent extends GanttTaskBase {
3725
- constructor(mapper, timelineViewService, dependencyDomService, optionChangesService, cdr) {
3726
- super(mapper, timelineViewService, dependencyDomService, optionChangesService, cdr);
4898
+ constructor(touchEnabled$$1, mapper, timelineViewService, dependencyDomService, optionChangesService, cdr, navigationService) {
4899
+ super(mapper, timelineViewService, dependencyDomService, optionChangesService, cdr, navigationService);
4900
+ this.touchEnabled = touchEnabled$$1;
3727
4901
  this.summaryWrapperClass = true;
3728
4902
  }
4903
+ get ariaExpanded() {
4904
+ // if no callback is provided, all child items are displayed and the item is regarded as expanded
4905
+ // replicates the TreeList aria-expanded behavior
4906
+ const isExpanded = !isPresent(this.isExpanded) || this.isExpanded(this.dataItem);
4907
+ return String(isExpanded);
4908
+ }
3729
4909
  };
3730
4910
  __decorate([
3731
4911
  HostBinding('class.k-summary-wrap'),
@@ -3735,6 +4915,10 @@ __decorate([
3735
4915
  Input(),
3736
4916
  __metadata("design:type", TemplateRef)
3737
4917
  ], GanttSummaryTaskComponent.prototype, "template", void 0);
4918
+ __decorate([
4919
+ Input(),
4920
+ __metadata("design:type", Function)
4921
+ ], GanttSummaryTaskComponent.prototype, "isExpanded", void 0);
3738
4922
  GanttSummaryTaskComponent = GanttSummaryTaskComponent_1 = __decorate([
3739
4923
  Component({
3740
4924
  selector: 'kendo-gantt-summary-task',
@@ -3747,13 +4931,15 @@ GanttSummaryTaskComponent = GanttSummaryTaskComponent_1 = __decorate([
3747
4931
  template: `
3748
4932
  <div
3749
4933
  #task
4934
+ role="treeitem"
3750
4935
  class="k-task k-task-summary"
3751
4936
  [ngClass]="taskClass(dataItem)"
3752
4937
  [style.width.px]="taskWidth"
3753
- [style.left.px]="taskOffset"
3754
4938
  [attr.title]="mapper.extractFromTask(dataItem, 'title')"
3755
- [attr.data-task-index]="index"
3756
4939
  [class.k-state-selected]="isSelected(dataItem)"
4940
+ [attr.aria-selected]="ariaSelected"
4941
+ [attr.aria-expanded]="ariaExpanded"
4942
+ [attr.aria-level]="level + 1"
3757
4943
  >
3758
4944
  <div *ngIf="!template; else summaryTemplate"
3759
4945
  class="k-task-summary-progress"
@@ -3774,13 +4960,36 @@ GanttSummaryTaskComponent = GanttSummaryTaskComponent_1 = __decorate([
3774
4960
  >
3775
4961
  </ng-template>
3776
4962
  </div>
3777
- `
4963
+ <ng-container *ngIf="renderDependencyDragClues">
4964
+ <div
4965
+ class="k-task-dot k-task-start k-touch-action-none"
4966
+ [class.k-display-block]="touchEnabled"
4967
+ >
4968
+ </div>
4969
+ <div
4970
+ class="k-task-dot k-task-end k-touch-action-none"
4971
+ [class.k-display-block]="touchEnabled"
4972
+ >
4973
+ </div>
4974
+ </ng-container>
4975
+ `,
4976
+ styles: [`
4977
+ .k-task.k-focus {
4978
+ box-shadow: 0 0 4px 3px grey;
4979
+ outline: none;
4980
+ }
4981
+ .k-task.k-focus.k-state-selected {
4982
+ box-shadow: 0 0 4px 3px #ffaea8;
4983
+ }
4984
+ `]
3778
4985
  }),
3779
- __metadata("design:paramtypes", [MappingService,
4986
+ __param(0, Inject(TOUCH_ENABLED)),
4987
+ __metadata("design:paramtypes", [Boolean, MappingService,
3780
4988
  TimelineViewService,
3781
4989
  DependencyDomService,
3782
4990
  OptionChangesService,
3783
- ChangeDetectorRef])
4991
+ ChangeDetectorRef,
4992
+ NavigationService])
3784
4993
  ], GanttSummaryTaskComponent);
3785
4994
 
3786
4995
  var GanttMilestoneTaskComponent_1;
@@ -3788,8 +4997,9 @@ var GanttMilestoneTaskComponent_1;
3788
4997
  * @hidden
3789
4998
  */
3790
4999
  let GanttMilestoneTaskComponent = GanttMilestoneTaskComponent_1 = class GanttMilestoneTaskComponent extends GanttTaskBase {
3791
- constructor(mapper, timelineViewService, dependencyDomService, optionChangesService, cdr) {
3792
- super(mapper, timelineViewService, dependencyDomService, optionChangesService, cdr);
5000
+ constructor(touchEnabled$$1, mapper, timelineViewService, dependencyDomService, optionChangesService, cdr, navigationService) {
5001
+ super(mapper, timelineViewService, dependencyDomService, optionChangesService, cdr, navigationService);
5002
+ this.touchEnabled = touchEnabled$$1;
3793
5003
  this.milestoneWrapperClass = true;
3794
5004
  }
3795
5005
  };
@@ -3809,21 +5019,45 @@ GanttMilestoneTaskComponent = GanttMilestoneTaskComponent_1 = __decorate([
3809
5019
  template: `
3810
5020
  <div
3811
5021
  #task
5022
+ role="treeitem"
3812
5023
  class="k-task k-task-milestone"
3813
5024
  [ngClass]="taskClass(dataItem)"
3814
- [style.left.px]="taskOffset"
3815
5025
  [attr.title]="mapper.extractFromTask(dataItem, 'title')"
3816
5026
  [class.k-state-selected]="isSelected(dataItem)"
3817
- [attr.data-task-index]="index"
5027
+ [attr.aria-selected]="ariaSelected"
5028
+ [attr.aria-level]="level + 1"
3818
5029
  >
3819
5030
  </div>
3820
- `
5031
+ <ng-container *ngIf="renderDependencyDragClues">
5032
+ <div
5033
+ class="k-task-dot k-task-start k-touch-action-none"
5034
+ [class.k-display-block]="touchEnabled"
5035
+ >
5036
+ </div>
5037
+ <div
5038
+ class="k-task-dot k-task-end k-touch-action-none"
5039
+ [class.k-display-block]="touchEnabled"
5040
+ >
5041
+ </div>
5042
+ </ng-container>
5043
+ `,
5044
+ styles: [`
5045
+ .k-task.k-focus {
5046
+ box-shadow: 0 0 4px 3px grey;
5047
+ outline: none;
5048
+ }
5049
+ .k-task.k-focus.k-state-selected {
5050
+ box-shadow: 0 0 4px 3px #ffaea8;
5051
+ }
5052
+ `]
3821
5053
  }),
3822
- __metadata("design:paramtypes", [MappingService,
5054
+ __param(0, Inject(TOUCH_ENABLED)),
5055
+ __metadata("design:paramtypes", [Boolean, MappingService,
3823
5056
  TimelineViewService,
3824
5057
  DependencyDomService,
3825
5058
  OptionChangesService,
3826
- ChangeDetectorRef])
5059
+ ChangeDetectorRef,
5060
+ NavigationService])
3827
5061
  ], GanttMilestoneTaskComponent);
3828
5062
 
3829
5063
  /**
@@ -4019,10 +5253,10 @@ SelectableDirective = __decorate([
4019
5253
  * @hidden
4020
5254
  */
4021
5255
  let ToolbarComponent = class ToolbarComponent {
4022
- constructor(gantt, scrollSyncService) {
5256
+ constructor(gantt) {
4023
5257
  this.gantt = gantt;
4024
- this.scrollSyncService = scrollSyncService;
4025
5258
  this.context = {};
5259
+ this.role = 'toolbar';
4026
5260
  }
4027
5261
  set position(value) {
4028
5262
  this.context.position = this._position = value;
@@ -4038,13 +5272,14 @@ let ToolbarComponent = class ToolbarComponent {
4038
5272
  const templatePosition = ganttToolbarTemplate ? ganttToolbarTemplate.position : null;
4039
5273
  return ganttToolbarTemplate && (templatePosition === 'both' || templatePosition === this.position);
4040
5274
  }
4041
- onViewChange(e) {
4042
- this.gantt.activeView = e; // TODO: may be load the timeline data directly in an activeView setter?
4043
- this.gantt.loadTimelineData();
4044
- this.gantt.activeViewChange.emit(e);
4045
- this.scrollSyncService.resetTimelineScrollLeft();
5275
+ handleViewChange(view) {
5276
+ this.gantt.changeActiveView(view);
4046
5277
  }
4047
5278
  };
5279
+ __decorate([
5280
+ HostBinding('attr.role'),
5281
+ __metadata("design:type", String)
5282
+ ], ToolbarComponent.prototype, "role", void 0);
4048
5283
  __decorate([
4049
5284
  Input(),
4050
5285
  __metadata("design:type", Boolean)
@@ -4069,7 +5304,7 @@ ToolbarComponent = __decorate([
4069
5304
  *ngIf="showViewSelector"
4070
5305
  [views]="gantt.viewTypes"
4071
5306
  [activeView]="gantt.activeView"
4072
- (activeViewChange)="onViewChange($event)"></kendo-gantt-view-selector>
5307
+ (activeViewChange)="handleViewChange($event)"></kendo-gantt-view-selector>
4073
5308
  </ng-container>
4074
5309
  <ng-template
4075
5310
  *ngIf="renderTemplate"
@@ -4079,8 +5314,7 @@ ToolbarComponent = __decorate([
4079
5314
  </ng-template>
4080
5315
  `
4081
5316
  }),
4082
- __metadata("design:paramtypes", [GanttComponent,
4083
- ScrollSyncService])
5317
+ __metadata("design:paramtypes", [GanttComponent])
4084
5318
  ], ToolbarComponent);
4085
5319
 
4086
5320
  /**
@@ -4135,7 +5369,9 @@ ViewSelectorComponent = __decorate([
4135
5369
  Component({
4136
5370
  selector: 'kendo-gantt-view-selector',
4137
5371
  template: `
4138
- <select class="k-dropdown k-views-dropdown"
5372
+ <select
5373
+ class="k-dropdownlist k-picker k-rounded-md k-views-dropdown"
5374
+ aria-label="View Selector"
4139
5375
  [value]="activeView"
4140
5376
  (change)="activeViewChange.emit($event.target.value)">
4141
5377
  <option *ngFor="let view of views" [value]="view">{{getViewTypeText(view)}}</option>
@@ -4162,6 +5398,9 @@ const getOffsetRelativeToParent = (element, targetParent) => {
4162
5398
  top: 0,
4163
5399
  left: 0
4164
5400
  };
5401
+ if (!targetParent.contains(element)) {
5402
+ return offset;
5403
+ }
4165
5404
  let offsetParent = element;
4166
5405
  while (offsetParent && offsetParent !== targetParent) {
4167
5406
  offset.top += offsetParent.offsetTop;
@@ -4195,9 +5434,9 @@ const dependencyCoordinates = (from, to, rowHeight, type, minDistanceBeforeTurn,
4195
5434
  | |
4196
5435
  [[[]]]- -[[[]]]
4197
5436
  */
4198
- if (type === 0 /* FF */ || type === 3 /* SS */) {
5437
+ if (type === DependencyType.FF || type === DependencyType.SS) {
4199
5438
  // polyline start from first task
4200
- const dir = type === 3 /* SS */ ? 'left' : 'right';
5439
+ const dir = type === DependencyType.SS ? 'left' : 'right';
4201
5440
  top = from.top;
4202
5441
  left = from[dir];
4203
5442
  points.push({ top, left });
@@ -4223,9 +5462,9 @@ const dependencyCoordinates = (from, to, rowHeight, type, minDistanceBeforeTurn,
4223
5462
  |
4224
5463
  -[[[]]]
4225
5464
  */
4226
- const startDir = type === 2 /* SF */ ? 'left' : 'right';
4227
- const endDir = type === 2 /* SF */ ? 'right' : 'left';
4228
- const additionalTurn = type === 2 /* SF */
5465
+ const startDir = type === DependencyType.SF ? 'left' : 'right';
5466
+ const endDir = type === DependencyType.SF ? 'right' : 'left';
5467
+ const additionalTurn = type === DependencyType.SF
4229
5468
  ? from[startDir] - minDistanceBeforeTurn * 2 < to[endDir]
4230
5469
  : from[startDir] + minDistanceBeforeTurn * 2 > to[endDir];
4231
5470
  // polyline start from first task
@@ -4308,80 +5547,645 @@ const getArrowEast = (top, left, arrowSize) => {
4308
5547
  });
4309
5548
  return points;
4310
5549
  };
5550
+ /**
5551
+ * @hidden
5552
+ *
5553
+ * Translates the provided client `left` and `top` coords to coords relative to the provided container.
5554
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/CSSOM_View/Coordinate_systems#standard_cssom_coordinate_systems
5555
+ */
5556
+ const clientToOffsetCoords = (clientLeft, clientTop, offsetContainer) => {
5557
+ // client (viewport) coordinates of the target container
5558
+ // https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect#value
5559
+ const offsetContainerClientRect = offsetContainer.getBoundingClientRect();
5560
+ return {
5561
+ left: clientLeft - offsetContainerClientRect.left + offsetContainer.scrollLeft,
5562
+ top: clientTop - offsetContainerClientRect.top + offsetContainer.scrollTop
5563
+ };
5564
+ };
5565
+ /**
5566
+ * @hidden
5567
+ *
5568
+ * Retrieves the `left` and `top` values of the center of the provided element.
5569
+ * The retrieved values are relative to the current viewport (client values).
5570
+ * https://developer.mozilla.org/en-US/docs/Web/CSS/CSSOM_View/Coordinate_systems#standard_cssom_coordinate_systems
5571
+ */
5572
+ const getElementClientCenterCoords = (element) => {
5573
+ // client (viewport) coordinates of the targeted element
5574
+ // https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect#value
5575
+ const { left, top, width, height } = element.getBoundingClientRect();
5576
+ return {
5577
+ left: left + (width / 2),
5578
+ top: top + (height / 2)
5579
+ };
5580
+ };
5581
+
5582
+ /**
5583
+ * Defines the size of the arrow that will be drawn at the end of each Gantt dependency.
5584
+ */
5585
+ const ARROW_SIZE = 4;
5586
+ /**
5587
+ * Defines the distance the polyline will cover from the task element before making a turn.
5588
+ */
5589
+ const MIN_DISTANCE_BEFORE_TURN = 10;
5590
+ /**
5591
+ * @hidden
5592
+ */
5593
+ let GanttDependencyDirective = class GanttDependencyDirective {
5594
+ constructor(polyline, zone, renderer, mapper, dependencyDomService) {
5595
+ this.polyline = polyline;
5596
+ this.zone = zone;
5597
+ this.renderer = renderer;
5598
+ this.mapper = mapper;
5599
+ this.dependencyDomService = dependencyDomService;
5600
+ this.subscriptions = new Subscription();
5601
+ this.subscriptions.add(dependencyDomService.taskChanges
5602
+ .pipe(switchMap(changes =>
5603
+ // reacts only on the very last event emission,
5604
+ // ensures that the tasks are drawn in the DOM
5605
+ this.zone.onStable.pipe(take(1), map(() => changes))))
5606
+ .subscribe(changes => this.updatePoints(changes)));
5607
+ }
5608
+ ngOnDestroy() {
5609
+ this.subscriptions.unsubscribe();
5610
+ }
5611
+ ngOnChanges(changes) {
5612
+ if (isPresent(changes.dependency)) {
5613
+ this.updatePoints(this.dependencyDomService.dependencyDomArgs);
5614
+ }
5615
+ }
5616
+ updatePoints({ timelineRow, contentContainer, tasks }) {
5617
+ if (!isPresent(timelineRow) || !isPresent(contentContainer) ||
5618
+ !isPresent(tasks) || tasks.size === 0 ||
5619
+ !tasks.has(this.mapper.extractFromDependency(this.dependency, 'fromId')) || !tasks.has(this.mapper.extractFromDependency(this.dependency, 'toId'))) {
5620
+ this.clearPoints();
5621
+ return;
5622
+ }
5623
+ const fromCoordinates = getElementRect(tasks.get(this.mapper.extractFromDependency(this.dependency, 'fromId')), contentContainer);
5624
+ const toCoordinates = getElementRect(tasks.get(this.mapper.extractFromDependency(this.dependency, 'toId')), contentContainer);
5625
+ const timelineRowHeight = isDocumentAvailable() ? timelineRow.getBoundingClientRect().height : 0;
5626
+ const points = dependencyCoordinates(fromCoordinates, toCoordinates, timelineRowHeight, this.dependency.type, MIN_DISTANCE_BEFORE_TURN, ARROW_SIZE);
5627
+ this.drawPoints(points);
5628
+ }
5629
+ clearPoints() {
5630
+ this.renderer.setAttribute(this.polyline.nativeElement, 'points', '');
5631
+ }
5632
+ drawPoints(points) {
5633
+ if (!isPresent(points) || points.length === 0) {
5634
+ this.clearPoints();
5635
+ return;
5636
+ }
5637
+ const parsedCoords = points.map(({ left, top }) => `${left},${top}`).join(' ');
5638
+ this.renderer.setAttribute(this.polyline.nativeElement, 'points', parsedCoords);
5639
+ }
5640
+ };
5641
+ __decorate([
5642
+ Input(),
5643
+ __metadata("design:type", Object)
5644
+ ], GanttDependencyDirective.prototype, "dependency", void 0);
5645
+ GanttDependencyDirective = __decorate([
5646
+ Directive({
5647
+ selector: '[kendoGanttDependency]'
5648
+ }),
5649
+ __metadata("design:paramtypes", [ElementRef,
5650
+ NgZone,
5651
+ Renderer2,
5652
+ MappingService,
5653
+ DependencyDomService])
5654
+ ], GanttDependencyDirective);
5655
+
5656
+ /**
5657
+ * @hidden
5658
+ */
5659
+ let DragValidationTooltipComponent = class DragValidationTooltipComponent {
5660
+ /**
5661
+ * @hidden
5662
+ */
5663
+ constructor() {
5664
+ /**
5665
+ * Sets the status class of the attempted operation.
5666
+ * Note that the status will be ignored and the `neutral` status class will be rendered,
5667
+ * if the any of the fromTaskName or toTaskName are not populated.
5668
+ */
5669
+ this.isValid = false;
5670
+ }
5671
+ };
5672
+ DragValidationTooltipComponent = __decorate([
5673
+ Component({
5674
+ template: `
5675
+ <div
5676
+ class="k-tooltip k-gantt-tooltip-validation"
5677
+ [class.k-gantt-tooltip-valid]="showValidityStatus && isValid"
5678
+ [class.k-gantt-tooltip-invalid]="showValidityStatus && !isValid"
5679
+ >
5680
+ <div class="k-gantt-tooltip-validation-row">
5681
+ <span class="k-gantt-tooltip-validation-label">From:</span>
5682
+ <span class="k-gantt-tooltip-validation-value">{{ fromTaskName }}</span>
5683
+ </div>
5684
+ <div class="k-gantt-tooltip-validation-row">
5685
+ <span class="k-gantt-tooltip-validation-label">To:</span>
5686
+ <span class="k-gantt-tooltip-validation-value">{{ toTaskName }}</span>
5687
+ </div>
5688
+ </div>
5689
+ `,
5690
+ styles: [`
5691
+ .k-gantt-tooltip-validation {
5692
+ max-width: 200px;
5693
+ display: block;
5694
+ }
5695
+ .k-gantt-tooltip-validation::before {
5696
+ content: '';
5697
+ position: absolute;
5698
+ left: 0;
5699
+ top: 0;
5700
+ width: 4px;
5701
+ height: 100%;
5702
+ background: #656565;
5703
+ }
5704
+ .k-gantt-tooltip-validation.k-gantt-tooltip-valid::before {
5705
+ background: #37B400;
5706
+ }
5707
+ .k-gantt-tooltip-validation.k-gantt-tooltip-invalid::before {
5708
+ background: #F31700;
5709
+ }
5710
+ .k-gantt-tooltip-validation-row {
5711
+ white-space: nowrap;
5712
+ overflow: hidden;
5713
+ text-overflow: ellipsis;
5714
+ }
5715
+ .k-gantt-tooltip-validation-label {
5716
+ display: inline-flex;
5717
+ width: 50px;
5718
+ }
5719
+ .k-gantt-tooltip-validation-value {
5720
+ font-weight: bold;
5721
+ }
5722
+ `]
5723
+ })
5724
+ ], DragValidationTooltipComponent);
5725
+
5726
+ /**
5727
+ * When added to the .k-task-dot, the element will be kept with hover styles.
5728
+ * Used for the drag clue from which the dragging has started.
5729
+ */
5730
+ const DRAG_CLUE_HOVER_CLASS = 'k-state-hover';
5731
+ /**
5732
+ * Add the selection disabling class to the enitre container.
5733
+ * Otherwise existing selection on a given task text prevents dragging the clue even if the clue has `user-select: none` styles.
5734
+ */
5735
+ const USER_SELECT_NONE_CLASS = 'k-user-select-none';
5736
+ /**
5737
+ * When added to the .k-task-wrap, the containing .k-task-dot elements will be kept visible even when not hovered.
5738
+ * Used for the drag clue from which the dragging has started.
5739
+ */
5740
+ const TASK_WRAPPER_DRAG_CLASS = 'k-origin';
5741
+ /**
5742
+ * Use 20px margin between the pointer and the popup.
5743
+ * Could be made user-configurable if there's demand.
5744
+ */
5745
+ const DEFAULT_POPUP_VERTICAL_MARGIN = 20;
5746
+ /**
5747
+ * A directive which enables the creation of new dependencies via dragging.
5748
+ */
5749
+ let DependencyDragCreateDirective = class DependencyDragCreateDirective {
5750
+ constructor(gantt, zone, renderer, mapper, popupService, timelineScrollService) {
5751
+ this.gantt = gantt;
5752
+ this.zone = zone;
5753
+ this.renderer = renderer;
5754
+ this.mapper = mapper;
5755
+ this.popupService = popupService;
5756
+ this.timelineScrollService = timelineScrollService;
5757
+ /**
5758
+ * Specifies whether the validation tooltip will be displayed during drag operations.
5759
+ *
5760
+ * @default true
5761
+ */
5762
+ this.displayValidationTooltip = true;
5763
+ this.gantt.renderDependencyDragClues = true;
5764
+ }
5765
+ get container() {
5766
+ if (!isPresent(this.gantt.timeline) || !isPresent(this.gantt.timeline.timelineContent)) {
5767
+ return null;
5768
+ }
5769
+ return this.gantt.timeline.timelineContent.nativeElement;
5770
+ }
5771
+ get polyline() {
5772
+ if (!isPresent(this.gantt.timeline) || !isPresent(this.gantt.timeline.dependencyDragCreatePolyline)) {
5773
+ return null;
5774
+ }
5775
+ return this.gantt.timeline.dependencyDragCreatePolyline.nativeElement;
5776
+ }
5777
+ get popupContainer() {
5778
+ if (!isPresent(this.gantt.timeline) || !isPresent(this.gantt.timeline.dragPopupContainer)) {
5779
+ return null;
5780
+ }
5781
+ return this.gantt.timeline.dragPopupContainer;
5782
+ }
5783
+ ngAfterViewInit() {
5784
+ this.subscribeDraggable();
5785
+ this.addScrollListener();
5786
+ }
5787
+ ngOnDestroy() {
5788
+ this.unsubscribeDraggable();
5789
+ this.removeScrollListener();
5790
+ this.fromTaskClue = null;
5791
+ this.cancelScroll();
5792
+ this.closeDragPopup();
5793
+ }
5794
+ subscribeDraggable() {
5795
+ this.dragSubscriptions = this.gantt.timeline.timelineContainerPress
5796
+ .subscribe(this.handlePress.bind(this));
5797
+ this.dragSubscriptions.add(this.gantt.timeline.timelineContainerDrag
5798
+ .subscribe(this.handleDrag.bind(this)));
5799
+ this.dragSubscriptions.add(this.gantt.timeline.timelineContainerRelease
5800
+ .subscribe(this.handleRelease.bind(this)));
5801
+ }
5802
+ unsubscribeDraggable() {
5803
+ if (isPresent(this.dragSubscriptions)) {
5804
+ this.dragSubscriptions.unsubscribe();
5805
+ this.dragSubscriptions = null;
5806
+ }
5807
+ }
5808
+ handlePress({ clientX, clientY }) {
5809
+ // using `originalEvent.target` is not reliable under mobile devices with the current implementation of the draggable, so use this instead
5810
+ const target = elementFromPoint(clientX, clientY);
5811
+ if (isDependencyDragClue(target)) {
5812
+ this.fromTaskClue = target;
5813
+ this.assignDragStartClasses(this.fromTaskClue);
5814
+ // use the center of the target clue as polyline starting point
5815
+ const dragClueCenterCoords = getElementClientCenterCoords(this.fromTaskClue);
5816
+ // the polyline uses `position: aboslute`, so translate the client coordinates to offset coordinates (`left` and `top` relative to the timeline container)
5817
+ this.polylineStartCoords = clientToOffsetCoords(dragClueCenterCoords.left, dragClueCenterCoords.top, this.container);
5818
+ }
5819
+ }
5820
+ handleDrag({ clientX, clientY }) {
5821
+ if (isPresent(this.fromTaskClue)) {
5822
+ // the polyline uses `position: aboslute`, so translate the client coordinates to offset coordinates (`left` and `top` relative to the timeline container)
5823
+ const pointerOffsetCoords = clientToOffsetCoords(clientX, clientY, this.container);
5824
+ // the start coords are calculated just once per drag session in handlePress
5825
+ // use the current drag coords as polyline end coords
5826
+ this.updatePolyline(this.polylineStartCoords, pointerOffsetCoords);
5827
+ this.currentPointerClientCoords = { left: clientX, top: clientY };
5828
+ if (this.gantt.dragScrollSettings.enabled) {
5829
+ // use client coordinates for scroll trigger
5830
+ this.scrollPointIntoView(this.currentPointerClientCoords);
5831
+ }
5832
+ if (this.displayValidationTooltip) {
5833
+ this.updateDragPopup(pointerOffsetCoords);
5834
+ }
5835
+ }
5836
+ }
5837
+ handleRelease({ clientX, clientY }) {
5838
+ if (!isPresent(this.fromTaskClue)) {
5839
+ return;
5840
+ }
5841
+ // using `originalEvent.target` is not reliable under mobile devices with the current implementation of the draggable, so use this instead
5842
+ const target = elementFromPoint(clientX, clientY);
5843
+ if (isDependencyDragClue(target) && !sameTaskClues(this.fromTaskClue, target, this.container)) {
5844
+ this.zone.run(() => {
5845
+ const fromTaskClue = this.fromTaskClue;
5846
+ const toTaskClue = target;
5847
+ const fromTask = this.gantt.renderedTreeListItems[getClosestTaskIndex(fromTaskClue, this.container)];
5848
+ const toTask = this.gantt.renderedTreeListItems[getClosestTaskIndex(toTaskClue, this.container)];
5849
+ const dependencyType = getDependencyTypeFromTargetTasks(fromTaskClue, toTaskClue);
5850
+ const { fromId, toId, type } = this.mapper.dependencyFields;
5851
+ this.gantt.dependencyAdd.emit({
5852
+ fromTask: fromTask,
5853
+ toTask: toTask,
5854
+ type: dependencyType,
5855
+ isValid: this.gantt.validateNewDependency({
5856
+ [fromId]: this.mapper.extractFromTask(fromTask, 'id'),
5857
+ [toId]: this.mapper.extractFromTask(toTask, 'id'),
5858
+ [type]: dependencyType
5859
+ })
5860
+ });
5861
+ });
5862
+ }
5863
+ this.clearPolyline();
5864
+ this.removeDragStartClasses(this.fromTaskClue);
5865
+ this.fromTaskClue = null;
5866
+ this.cancelScroll();
5867
+ this.closeDragPopup();
5868
+ }
5869
+ updatePolyline(start, end) {
5870
+ const points = `${start.left},${start.top} ${end.left},${end.top}`;
5871
+ this.renderer.setAttribute(this.polyline, 'points', points);
5872
+ }
5873
+ clearPolyline() {
5874
+ this.renderer.removeAttribute(this.polyline, 'points');
5875
+ }
5876
+ assignDragStartClasses(dragClue) {
5877
+ if (!isPresent(dragClue)) {
5878
+ return;
5879
+ }
5880
+ this.renderer.addClass(this.container, USER_SELECT_NONE_CLASS);
5881
+ this.renderer.addClass(dragClue, DRAG_CLUE_HOVER_CLASS);
5882
+ const taskWrapper = getClosestTaskWrapper(dragClue, this.container);
5883
+ if (isPresent(taskWrapper)) {
5884
+ this.renderer.addClass(taskWrapper, TASK_WRAPPER_DRAG_CLASS);
5885
+ }
5886
+ }
5887
+ removeDragStartClasses(dragClue) {
5888
+ if (!isPresent(dragClue)) {
5889
+ return;
5890
+ }
5891
+ this.renderer.removeClass(this.container, USER_SELECT_NONE_CLASS);
5892
+ this.renderer.removeClass(dragClue, DRAG_CLUE_HOVER_CLASS);
5893
+ const taskWrapper = getClosestTaskWrapper(dragClue, this.container);
5894
+ if (isPresent(taskWrapper)) {
5895
+ this.renderer.removeClass(taskWrapper, TASK_WRAPPER_DRAG_CLASS);
5896
+ }
5897
+ }
5898
+ scrollPointIntoView({ left, top }) {
5899
+ this.timelineScrollService.requestScrollCancel();
5900
+ this.timelineScrollService.requestHorizontalScroll(left);
5901
+ this.timelineScrollService.requestVerticalScroll(top);
5902
+ }
5903
+ cancelScroll() {
5904
+ this.timelineScrollService.requestScrollCancel();
5905
+ }
5906
+ addScrollListener() {
5907
+ if (!isDocumentAvailable()) {
5908
+ return;
5909
+ }
5910
+ this.zone.runOutsideAngular(() => this.scrollListenerDisposer = this.renderer.listen(this.container, 'scroll', () => {
5911
+ // update the polyline only if we're currently dragging
5912
+ if (isPresent(this.fromTaskClue) && isPresent(this.currentPointerClientCoords)) {
5913
+ const { left, top } = this.currentPointerClientCoords;
5914
+ const pointerOffsetCoords = clientToOffsetCoords(left, top, this.container);
5915
+ this.updatePolyline(this.polylineStartCoords, pointerOffsetCoords);
5916
+ if (this.displayValidationTooltip) {
5917
+ this.updateDragPopup(pointerOffsetCoords);
5918
+ }
5919
+ }
5920
+ }));
5921
+ }
5922
+ removeScrollListener() {
5923
+ if (isPresent(this.scrollListenerDisposer)) {
5924
+ this.scrollListenerDisposer();
5925
+ this.scrollListenerDisposer = null;
5926
+ }
5927
+ }
5928
+ openDragPopup() {
5929
+ if (isPresent(this.dragPopup)) {
5930
+ this.closeDragPopup();
5931
+ }
5932
+ this.dragPopup = this.popupService.open({
5933
+ animate: false,
5934
+ content: DragValidationTooltipComponent,
5935
+ appendTo: this.popupContainer,
5936
+ positionMode: 'absolute',
5937
+ popupClass: 'k-popup-transparent'
5938
+ });
5939
+ }
5940
+ updateDragPopup(pointerOffsetPosition) {
5941
+ if (!isPresent(this.dragPopup)) {
5942
+ this.openDragPopup();
5943
+ }
5944
+ const tooltip = this.dragPopup.content.instance;
5945
+ const { fromTaskName, toTaskName, isValid, showValidityStatus } = this.getTooltipContext();
5946
+ if (tooltip.fromTaskName !== fromTaskName ||
5947
+ tooltip.toTaskName !== toTaskName ||
5948
+ tooltip.isValid !== isValid ||
5949
+ tooltip.showValidityStatus !== showValidityStatus) {
5950
+ tooltip.fromTaskName = fromTaskName;
5951
+ tooltip.toTaskName = toTaskName;
5952
+ tooltip.isValid = isValid;
5953
+ tooltip.showValidityStatus = showValidityStatus;
5954
+ this.dragPopup.content.changeDetectorRef.detectChanges();
5955
+ }
5956
+ this.dragPopup.popup.instance.offset = this.normalizePopupPosition(pointerOffsetPosition);
5957
+ this.dragPopup.popup.changeDetectorRef.detectChanges();
5958
+ }
5959
+ closeDragPopup() {
5960
+ if (isPresent(this.dragPopup)) {
5961
+ this.dragPopup.close();
5962
+ this.dragPopup = null;
5963
+ }
5964
+ }
5965
+ extractTaskName(target) {
5966
+ if (!isTaskWrapper(target, this.container)) {
5967
+ return null;
5968
+ }
5969
+ const taskIndex = getClosestTaskIndex(target, this.container);
5970
+ const task = this.gantt.renderedTreeListItems[taskIndex];
5971
+ const taskName = this.mapper.extractFromTask(task, 'title');
5972
+ return taskName;
5973
+ }
5974
+ getTooltipContext() {
5975
+ const fromTaskName = this.extractTaskName(this.fromTaskClue);
5976
+ const currentPointerTarget = elementFromPoint(this.currentPointerClientCoords.left, this.currentPointerClientCoords.top);
5977
+ const toTaskName = isTaskWrapper(currentPointerTarget, this.container) && !sameTaskClues(this.fromTaskClue, currentPointerTarget, this.container) ?
5978
+ this.extractTaskName(currentPointerTarget) :
5979
+ '';
5980
+ const showValidityStatus = isDependencyDragClue(currentPointerTarget) && !sameTaskClues(this.fromTaskClue, currentPointerTarget, this.container);
5981
+ const { fromId, toId, type } = this.mapper.dependencyFields;
5982
+ return {
5983
+ fromTaskName,
5984
+ toTaskName,
5985
+ showValidityStatus,
5986
+ isValid: showValidityStatus && this.gantt.validateNewDependency({
5987
+ [fromId]: this.mapper.extractFromTask(this.gantt.renderedTreeListItems[getClosestTaskIndex(this.fromTaskClue, this.container)], 'id'),
5988
+ [toId]: this.mapper.extractFromTask(this.gantt.renderedTreeListItems[getClosestTaskIndex(currentPointerTarget, this.container)], 'id'),
5989
+ [type]: getDependencyTypeFromTargetTasks(this.fromTaskClue, currentPointerTarget)
5990
+ })
5991
+ };
5992
+ }
5993
+ /**
5994
+ * Restricts the popup position to not go below the scroll height or width of the container.
5995
+ * Flips the position of the popup when there's not enough vertical space in the visible part of the container to render the popup.
5996
+ */
5997
+ normalizePopupPosition(pointerOffsetPosition) {
5998
+ let top = pointerOffsetPosition.top + DEFAULT_POPUP_VERTICAL_MARGIN;
5999
+ const containerClientBottom = this.container.clientHeight + this.container.scrollTop;
6000
+ const popupHeight = this.dragPopup.popupElement.querySelector('.k-tooltip').clientHeight;
6001
+ const enoughSpaceToRender = top < containerClientBottom - popupHeight;
6002
+ // flip the popup above the pointer if there's not enough space in the bottom of the container
6003
+ if (!enoughSpaceToRender) {
6004
+ // margin * 2 to account for the already applied margin
6005
+ top -= popupHeight + (DEFAULT_POPUP_VERTICAL_MARGIN * 2);
6006
+ }
6007
+ // center the popup horizontally according to the pointer position
6008
+ const popupWidth = this.dragPopup.popupElement.querySelector('.k-tooltip').clientWidth;
6009
+ const left = pointerOffsetPosition.left - popupWidth / 2;
6010
+ // don't allow the popup to be cut out of the viewport
6011
+ const minLeftTop = 0;
6012
+ // restrict the popup from being positioned beyond or before the available scrollable space
6013
+ return {
6014
+ left: fitToRange(left, minLeftTop, this.container.scrollWidth - popupWidth),
6015
+ top: fitToRange(top, minLeftTop, this.container.scrollHeight - popupHeight)
6016
+ };
6017
+ }
6018
+ };
6019
+ __decorate([
6020
+ Input(),
6021
+ __metadata("design:type", Boolean)
6022
+ ], DependencyDragCreateDirective.prototype, "displayValidationTooltip", void 0);
6023
+ DependencyDragCreateDirective = __decorate([
6024
+ Directive({
6025
+ selector: '[kendoGanttDependencyDragCreate]'
6026
+ }),
6027
+ __metadata("design:paramtypes", [GanttComponent,
6028
+ NgZone,
6029
+ Renderer2,
6030
+ MappingService,
6031
+ PopupService,
6032
+ TimelineScrollService])
6033
+ ], DependencyDragCreateDirective);
4311
6034
 
4312
6035
  /**
4313
- * Defines the size of the arrow that will be drawn at the end of each Gantt dependency.
6036
+ * @hidden
4314
6037
  */
4315
- const ARROW_SIZE = 4;
6038
+ var ScrollDirection;
6039
+ (function (ScrollDirection) {
6040
+ ScrollDirection[ScrollDirection["Backwards"] = -1] = "Backwards";
6041
+ ScrollDirection[ScrollDirection["Forward"] = 1] = "Forward";
6042
+ })(ScrollDirection || (ScrollDirection = {}));
4316
6043
  /**
4317
- * Defines the distance the polyline will cover from the task element before making a turn.
6044
+ * @hidden
4318
6045
  */
4319
- const MIN_DISTANCE_BEFORE_TURN = 10;
6046
+ var ScrollAxis;
6047
+ (function (ScrollAxis) {
6048
+ ScrollAxis["Vertical"] = "scrollTop";
6049
+ ScrollAxis["Horizontal"] = "scrollLeft";
6050
+ })(ScrollAxis || (ScrollAxis = {}));
6051
+
4320
6052
  /**
4321
6053
  * @hidden
6054
+ *
6055
+ * Checks if the beginning of the scrollable element is reached (top/left).
6056
+ * Floors the top value.
4322
6057
  */
4323
- let GanttDependencyDirective = class GanttDependencyDirective {
4324
- constructor(polyline, zone, renderer, mapper, dependencyDomService) {
4325
- this.polyline = polyline;
6058
+ const isUpperLimitReached = (element, axis) => Math.floor(element[axis]) <= 0;
6059
+ /**
6060
+ * @hidden
6061
+ *
6062
+ * Checks if the end of the scrollable element is reached (bottom/right).
6063
+ * Ceils the top value.
6064
+ */
6065
+ const isBottomLimitReached = (element, axis) => {
6066
+ const elementSize = axis === ScrollAxis.Horizontal ?
6067
+ element.scrollWidth - element.clientWidth :
6068
+ element.scrollHeight - element.clientHeight;
6069
+ return Math.ceil(element[axis]) >= elementSize;
6070
+ };
6071
+ /**
6072
+ * @hidden
6073
+ *
6074
+ * Scrolls the element in the given direction by the provided step in the provided scroll axis.
6075
+ *
6076
+ * If the targeted scroll incrementation doesn't yield any result due to device pixel ratio issues (https://github.com/dimitar-pechev/RenderingIndependentScrollOffsets#readme),
6077
+ * increments the step with 1px and again attempts to change the scrollTop of the element, until the content is actually scrolled.
6078
+ *
6079
+ * Cuts the operation short after 20 unsuccessful attempts to prevent infinite loops in possible corner-case scenarios.
6080
+ */
6081
+ const scrollElement = (element, step, direction, scrollAxis) => {
6082
+ if (!(isPresent(element) && isDocumentAvailable())) {
6083
+ return;
6084
+ }
6085
+ const initialScrollPosition = element[scrollAxis];
6086
+ let currentStep = step;
6087
+ let iterations = 0;
6088
+ while (initialScrollPosition === element[scrollAxis] &&
6089
+ !(direction === ScrollDirection.Backwards && isUpperLimitReached(element, scrollAxis)) &&
6090
+ !(direction === ScrollDirection.Forward && isBottomLimitReached(element, scrollAxis)) &&
6091
+ iterations < 20 // cut the operation short in 20 attempts - in case of a wild corner case
6092
+ ) {
6093
+ element[scrollAxis] += (currentStep * direction);
6094
+ // try with a larger step if the current one doesn't update the scroll position successfully
6095
+ currentStep += 1;
6096
+ iterations += 1;
6097
+ }
6098
+ };
6099
+ /**
6100
+ * @hidden
6101
+ *
6102
+ * As client coordinates are not restricted to the range 0px - {viewportSize}px, but can have negative starting values or ending values greater than the viewport size,
6103
+ * this function extracts the visible boundaries of the provided element - fall-backing to 0 when the top/left are below 0,
6104
+ * and fall-backing to the actual visible size of the container for bottom/right.
6105
+ */
6106
+ const getViewportBoundaries = (element) => {
6107
+ const elementRect = element.getBoundingClientRect();
6108
+ // if the beginning of the scrollable container is above/before the current viewport, fall-back to 0
6109
+ const topLimit = Math.max(elementRect.top, 0);
6110
+ const leftLimit = Math.max(elementRect.left, 0);
6111
+ // if the end of the scrollable container is beneath/after the current viewport, fall-back to its client height
6112
+ // add the distance from the start of the viewport to the beginning of the container to ensure scrolling bottom begins when the actual end of the container is reached
6113
+ const bottomLimit = topLimit + Math.min(elementRect.bottom, element.clientHeight);
6114
+ const rightLimit = leftLimit + Math.min(elementRect.right, element.clientWidth);
6115
+ return {
6116
+ top: topLimit,
6117
+ bottom: bottomLimit,
6118
+ left: leftLimit,
6119
+ right: rightLimit
6120
+ };
6121
+ };
6122
+
6123
+ /**
6124
+ * @hidden
6125
+ */
6126
+ let TimelineScrollableDirective = class TimelineScrollableDirective {
6127
+ constructor(timelineScrollableContainer, scrollService, zone) {
6128
+ this.timelineScrollableContainer = timelineScrollableContainer;
6129
+ this.scrollService = scrollService;
4326
6130
  this.zone = zone;
4327
- this.renderer = renderer;
4328
- this.mapper = mapper;
4329
- this.dependencyDomService = dependencyDomService;
4330
6131
  this.subscriptions = new Subscription();
4331
- this.subscriptions.add(dependencyDomService.taskChanges
4332
- .pipe(switchMap(changes =>
4333
- // reacts only on the very last event emission,
4334
- // ensures that the tasks are drawn in the DOM
4335
- this.zone.onStable.pipe(take(1), map(() => changes))))
4336
- .subscribe(changes => this.updatePoints(changes)));
6132
+ this.subscriptions.add(this.scrollService.horizontalScroll
6133
+ .subscribe(this.scrollHorizontallyTo.bind(this)));
6134
+ this.subscriptions.add(this.scrollService.verticalScroll
6135
+ .subscribe(this.scrollVerticallyTo.bind(this)));
6136
+ this.subscriptions.add(this.scrollService.scrollCancel
6137
+ .subscribe(this.cancelScroll.bind(this)));
4337
6138
  }
4338
6139
  ngOnDestroy() {
4339
6140
  this.subscriptions.unsubscribe();
4340
6141
  }
4341
- ngOnChanges(changes) {
4342
- if (isPresent(changes.dependency)) {
4343
- this.updatePoints(this.dependencyDomService.dependencyDomArgs);
4344
- }
4345
- }
4346
- updatePoints({ timelineRow, contentContainer, tasks }) {
4347
- if (!isPresent(timelineRow) || !isPresent(contentContainer) ||
4348
- !isPresent(tasks) || tasks.size === 0 ||
4349
- !tasks.has(this.mapper.extractFromDependency(this.dependency, 'fromId')) || !tasks.has(this.mapper.extractFromDependency(this.dependency, 'toId'))) {
4350
- this.clearPoints();
4351
- return;
4352
- }
4353
- const fromCoordinates = getElementRect(tasks.get(this.mapper.extractFromDependency(this.dependency, 'fromId')), contentContainer);
4354
- const toCoordinates = getElementRect(tasks.get(this.mapper.extractFromDependency(this.dependency, 'toId')), contentContainer);
4355
- const timelineRowHeight = isDocumentAvailable() ? timelineRow.getBoundingClientRect().height : 0;
4356
- const points = dependencyCoordinates(fromCoordinates, toCoordinates, timelineRowHeight, this.dependency.type, MIN_DISTANCE_BEFORE_TURN, ARROW_SIZE);
4357
- this.drawPoints(points);
6142
+ scrollHorizontallyTo(left) {
6143
+ this.zone.runOutsideAngular(() => {
6144
+ const container = this.timelineScrollableContainer.nativeElement;
6145
+ const visibleBoundaries = getViewportBoundaries(container);
6146
+ if (left < visibleBoundaries.left + this.scrollSettings.threshold) {
6147
+ this.horizontalScrollInterval = setInterval(() => scrollElement(container, this.scrollSettings.step, ScrollDirection.Backwards, ScrollAxis.Horizontal), this.scrollSettings.interval);
6148
+ }
6149
+ else if (left > visibleBoundaries.right - this.scrollSettings.threshold) {
6150
+ this.horizontalScrollInterval = setInterval(() => scrollElement(container, this.scrollSettings.step, ScrollDirection.Forward, ScrollAxis.Horizontal), this.scrollSettings.interval);
6151
+ }
6152
+ });
4358
6153
  }
4359
- clearPoints() {
4360
- this.renderer.setAttribute(this.polyline.nativeElement, 'points', '');
6154
+ scrollVerticallyTo(top) {
6155
+ this.zone.runOutsideAngular(() => {
6156
+ const container = this.timelineScrollableContainer.nativeElement;
6157
+ const visibleBoundaries = getViewportBoundaries(container);
6158
+ if (top < visibleBoundaries.top + this.scrollSettings.threshold) {
6159
+ this.verticalScrollInterval = setInterval(() => scrollElement(container, this.scrollSettings.step, ScrollDirection.Backwards, ScrollAxis.Vertical), this.scrollSettings.interval);
6160
+ }
6161
+ else if (top > visibleBoundaries.bottom - this.scrollSettings.threshold) {
6162
+ this.verticalScrollInterval = setInterval(() => scrollElement(container, this.scrollSettings.step, ScrollDirection.Forward, ScrollAxis.Vertical), this.scrollSettings.interval);
6163
+ }
6164
+ });
4361
6165
  }
4362
- drawPoints(points) {
4363
- if (!isPresent(points) || points.length === 0) {
4364
- this.clearPoints();
4365
- return;
6166
+ cancelScroll() {
6167
+ if (isPresent(this.verticalScrollInterval)) {
6168
+ clearInterval(this.verticalScrollInterval);
6169
+ this.verticalScrollInterval = null;
6170
+ }
6171
+ if (isPresent(this.horizontalScrollInterval)) {
6172
+ clearInterval(this.horizontalScrollInterval);
6173
+ this.horizontalScrollInterval = null;
4366
6174
  }
4367
- const parsedCoords = points.map(({ left, top }) => `${left},${top}`).join(' ');
4368
- this.renderer.setAttribute(this.polyline.nativeElement, 'points', parsedCoords);
4369
6175
  }
4370
6176
  };
4371
6177
  __decorate([
4372
6178
  Input(),
4373
6179
  __metadata("design:type", Object)
4374
- ], GanttDependencyDirective.prototype, "dependency", void 0);
4375
- GanttDependencyDirective = __decorate([
6180
+ ], TimelineScrollableDirective.prototype, "scrollSettings", void 0);
6181
+ TimelineScrollableDirective = __decorate([
4376
6182
  Directive({
4377
- selector: '[kendoGanttDependency]'
6183
+ selector: '[kendoGanttTimelineScrollable]'
4378
6184
  }),
4379
6185
  __metadata("design:paramtypes", [ElementRef,
4380
- NgZone,
4381
- Renderer2,
4382
- MappingService,
4383
- DependencyDomService])
4384
- ], GanttDependencyDirective);
6186
+ TimelineScrollService,
6187
+ NgZone])
6188
+ ], TimelineScrollableDirective);
4385
6189
 
4386
6190
  var TimelineDayViewComponent_1;
4387
6191
  let TimelineDayViewComponent = TimelineDayViewComponent_1 = class TimelineDayViewComponent extends ViewBase {
@@ -4459,25 +6263,46 @@ TimelineMonthViewComponent = TimelineMonthViewComponent_1 = __decorate([
4459
6263
  * @hidden
4460
6264
  */
4461
6265
  let EditDialogComponent = class EditDialogComponent {
4462
- constructor(mapper, editService, localizationService) {
6266
+ constructor(mapper, editService, cdr, localizationService) {
4463
6267
  this.mapper = mapper;
4464
6268
  this.editService = editService;
6269
+ this.cdr = cdr;
4465
6270
  this.localizationService = localizationService;
4466
6271
  }
6272
+ ngOnInit() {
6273
+ this.editService.loadTasks(this.data).subscribe(value => {
6274
+ this.loadedTasks = value;
6275
+ });
6276
+ }
6277
+ get predecessors() {
6278
+ return this.editService.predecessors;
6279
+ }
6280
+ set predecessors(items) {
6281
+ this.editService.predecessors = items;
6282
+ }
6283
+ get successors() {
6284
+ return this.editService.successors;
6285
+ }
6286
+ set successors(items) {
6287
+ this.editService.successors = items;
6288
+ }
4467
6289
  getText(token) {
4468
6290
  return this.localizationService.get(token);
4469
6291
  }
6292
+ getDependencyType(typeId) {
6293
+ return DependencyType[typeId];
6294
+ }
4470
6295
  handleEditingResult(editResultType) {
4471
6296
  this.editService.triggerEditEvent(editResultType);
4472
6297
  }
4473
- onTaskDelete() {
4474
- this.editService.showConfirmationDialog.next();
6298
+ handleTaskDelete() {
6299
+ this.editService.taskDelete.next(this.editService.dataItem);
4475
6300
  }
4476
6301
  };
4477
6302
  __decorate([
4478
6303
  Input(),
4479
- __metadata("design:type", FormGroup)
4480
- ], EditDialogComponent.prototype, "formGroup", void 0);
6304
+ __metadata("design:type", Array)
6305
+ ], EditDialogComponent.prototype, "data", void 0);
4481
6306
  EditDialogComponent = __decorate([
4482
6307
  Component({
4483
6308
  selector: 'kendo-gantt-edit-dialog',
@@ -4489,36 +6314,44 @@ EditDialogComponent = __decorate([
4489
6314
  (close)="handleEditingResult('cancel')">
4490
6315
  <kendo-dialog-messages
4491
6316
  [closeTitle]="getText('taskEditingDialogCloseTitle')"></kendo-dialog-messages>
4492
- <form class="k-form" [formGroup]="formGroup">
4493
- <kendo-formfield *ngIf="formGroup.contains(mapper.taskFields.title)">
4494
- <kendo-label [for]="mapper.taskFields.title" [text]="getText('titleFieldInputLabel')"></kendo-label>
4495
- <input class="k-textbox" [formControlName]="mapper.taskFields.title" />
4496
- </kendo-formfield>
4497
- <div class="k-hstack">
4498
- <kendo-formfield [style.width.%]="49" *ngIf="formGroup.contains(mapper.taskFields.start)">
4499
- <kendo-label [for]="mapper.taskFields.start" [text]="getText('startFieldInputLabel')"></kendo-label>
4500
- <kendo-datetimepicker [formControlName]="mapper.taskFields.start"></kendo-datetimepicker>
4501
- </kendo-formfield>
4502
- <kendo-treelist-spacer></kendo-treelist-spacer>
4503
- <kendo-formfield [style.width.%]="49" *ngIf="formGroup.contains(mapper.taskFields.end)">
4504
- <kendo-label [for]="mapper.taskFields.end" [text]="getText('endFieldInputLabel')"></kendo-label>
4505
- <kendo-datetimepicker [formControlName]="mapper.taskFields.end"></kendo-datetimepicker>
4506
- </kendo-formfield>
4507
- </div>
4508
- <kendo-formfield [style.width.%]="49" *ngIf="formGroup.contains(mapper.taskFields.completionRatio)">
4509
- <kendo-label [for]="mapper.taskFields.completionRatio" [text]="getText('completionRatioFieldInputLabel')"></kendo-label>
4510
- <kendo-numerictextbox
4511
- [formControlName]="mapper.taskFields.completionRatio"
4512
- [min]="0"
4513
- [max]="1"
4514
- [decimals]="2"
4515
- format="p2"
4516
- [step]="0.01"
4517
- ></kendo-numerictextbox>
4518
- </kendo-formfield>
4519
- </form>
6317
+
6318
+ <kendo-tabstrip [keepTabContent]="true" style="height: 345px;">
6319
+ <kendo-tabstrip-tab [title]="getText('taskEditingGeneralTabTitle')" [selected]="true">
6320
+ <ng-template kendoTabContent>
6321
+ <kendo-gantt-task-fields></kendo-gantt-task-fields>
6322
+ </ng-template>
6323
+ </kendo-tabstrip-tab>
6324
+ <kendo-tabstrip-tab [title]="getText('taskEditingPredecessorsTabTitle')">
6325
+ <ng-template kendoTabContent>
6326
+ <kendo-gantt-dependencies-table
6327
+ [tasks]="loadedTasks"
6328
+ [(dependencies)]="predecessors"
6329
+ dependencyType="predecessor"
6330
+ >
6331
+ </kendo-gantt-dependencies-table>
6332
+ </ng-template>
6333
+ </kendo-tabstrip-tab>
6334
+ <kendo-tabstrip-tab [title]="getText('taskEditingSuccessorsTabTitle')">
6335
+ <ng-template kendoTabContent>
6336
+ <kendo-gantt-dependencies-table
6337
+ [tasks]="loadedTasks"
6338
+ [(dependencies)]="successors"
6339
+ dependencyType="successor">
6340
+ </kendo-gantt-dependencies-table>
6341
+ </ng-template>
6342
+ </kendo-tabstrip-tab>
6343
+ </kendo-tabstrip>
6344
+
4520
6345
  <kendo-dialog-actions layout="normal">
4521
- <button kendoButton (click)="onTaskDelete()">{{ getText('deleteButtonText') }}</button>
6346
+ <button
6347
+ kendoButton
6348
+ [kendoEventsOutsideAngular]="{
6349
+ click: handleTaskDelete
6350
+ }"
6351
+ [scope]="this"
6352
+ >
6353
+ {{ getText('deleteButtonText') }}
6354
+ </button>
4522
6355
  <kendo-treelist-spacer></kendo-treelist-spacer>
4523
6356
  <button kendoButton [primary]="true" (click)="handleEditingResult('save')">{{ getText('saveButtonText') }}</button>
4524
6357
  <button kendoButton (click)="handleEditingResult('cancel')">{{ getText('cancelButtonText') }}</button>
@@ -4528,6 +6361,7 @@ EditDialogComponent = __decorate([
4528
6361
  }),
4529
6362
  __metadata("design:paramtypes", [MappingService,
4530
6363
  EditService,
6364
+ ChangeDetectorRef,
4531
6365
  LocalizationService])
4532
6366
  ], EditDialogComponent);
4533
6367
 
@@ -4954,6 +6788,213 @@ GanttAddTaskComponent = __decorate([
4954
6788
  NgZone])
4955
6789
  ], GanttAddTaskComponent);
4956
6790
 
6791
+ /**
6792
+ * @hidden
6793
+ */
6794
+ let DependenciesTableComponent = class DependenciesTableComponent {
6795
+ constructor(mapper, editService, localizationService) {
6796
+ this.mapper = mapper;
6797
+ this.editService = editService;
6798
+ this.localizationService = localizationService;
6799
+ this.dependenciesChange = new EventEmitter();
6800
+ this.selectedKeys = [];
6801
+ this.formGroups = new FormArray([]);
6802
+ this.dependencyTypes = this.getDependencyTypes();
6803
+ }
6804
+ get taskId() {
6805
+ return this.editService.dataItem.id;
6806
+ }
6807
+ // The target dependency id field
6808
+ // e.g. if Predecessors, we have the `fromId` which is the currently edited task,
6809
+ // while the `toId` is missing (needs to be selected by the user)
6810
+ get dependencyIdField() {
6811
+ return this.dependencyType === 'predecessor' ? 'fromId' : 'toId';
6812
+ }
6813
+ ngOnInit() {
6814
+ // generate the FormGroups per each Grid row
6815
+ if (this.formGroups.controls.length === 0) {
6816
+ const fields = this.mapper.dependencyFields;
6817
+ this.dependencies.forEach(item => {
6818
+ const formGroup = new FormGroup({
6819
+ [fields.id]: new FormControl(this.mapper.extractFromDependency(item, 'id')),
6820
+ [fields.fromId]: new FormControl(this.mapper.extractFromDependency(item, 'fromId'), Validators.required),
6821
+ [fields.toId]: new FormControl(this.mapper.extractFromDependency(item, 'toId'), Validators.required),
6822
+ [fields.type]: new FormControl(this.mapper.extractFromDependency(item, 'type'), Validators.required)
6823
+ });
6824
+ this.formGroups.push(formGroup);
6825
+ });
6826
+ }
6827
+ this.formGroups.valueChanges.subscribe(val => {
6828
+ this.formGroups.controls.forEach(control => {
6829
+ if (control.dirty) {
6830
+ this.editService.updateDependencies(control.value);
6831
+ }
6832
+ });
6833
+ this.dependenciesChange.emit(val);
6834
+ });
6835
+ }
6836
+ getFormControl(dataItemIndex, field) {
6837
+ // return the FormControl for the respective column editor
6838
+ return this.formGroups.controls
6839
+ .find((_control, index) => index === dataItemIndex)
6840
+ .get(this.mapper.dependencyFields[field]);
6841
+ }
6842
+ getText(token) {
6843
+ return this.localizationService.get(token);
6844
+ }
6845
+ getDependencyTypes() {
6846
+ const types = Object.keys(DependencyType)
6847
+ .filter(value => typeof DependencyType[value] === 'number')
6848
+ .map(type => {
6849
+ return {
6850
+ type,
6851
+ id: DependencyType[type]
6852
+ };
6853
+ });
6854
+ return types;
6855
+ }
6856
+ addHandler() {
6857
+ const fields = this.mapper.dependencyFields;
6858
+ const formGroup = new FormGroup({
6859
+ [fields.id]: new FormControl(),
6860
+ [fields.fromId]: new FormControl(this.dependencyIdField === 'toId' ? this.taskId : null, Validators.required),
6861
+ [fields.toId]: new FormControl(this.dependencyIdField === 'fromId' ? this.taskId : null, Validators.required),
6862
+ [fields.type]: new FormControl(null, Validators.required)
6863
+ });
6864
+ this.formGroups.push(formGroup);
6865
+ }
6866
+ removeHandler() {
6867
+ const [selectedIndex] = this.selectedKeys;
6868
+ const item = this.formGroups.at(selectedIndex).value;
6869
+ this.editService.deleteDependency(item);
6870
+ this.formGroups.removeAt(selectedIndex);
6871
+ }
6872
+ };
6873
+ __decorate([
6874
+ Input(),
6875
+ __metadata("design:type", Array)
6876
+ ], DependenciesTableComponent.prototype, "tasks", void 0);
6877
+ __decorate([
6878
+ Input(),
6879
+ __metadata("design:type", Array)
6880
+ ], DependenciesTableComponent.prototype, "dependencies", void 0);
6881
+ __decorate([
6882
+ Input(),
6883
+ __metadata("design:type", String)
6884
+ ], DependenciesTableComponent.prototype, "dependencyType", void 0);
6885
+ __decorate([
6886
+ Output(),
6887
+ __metadata("design:type", EventEmitter)
6888
+ ], DependenciesTableComponent.prototype, "dependenciesChange", void 0);
6889
+ DependenciesTableComponent = __decorate([
6890
+ Component({
6891
+ selector: 'kendo-gantt-dependencies-table',
6892
+ template: `
6893
+ <kendo-grid
6894
+ [data]="dependencies"
6895
+ [selectable]="{ mode: 'single' }"
6896
+ [(selectedKeys)]="selectedKeys"
6897
+ kendoGridSelectBy
6898
+ [height]="275"
6899
+ >
6900
+ <ng-template kendoGridToolbarTemplate>
6901
+ <button kendoButton (click)="addHandler()">
6902
+ {{ getText('taskEditingDependenciesAddButtonText') }}
6903
+ </button>
6904
+ <button kendoButton (click)="removeHandler()" [disabled]="selectedKeys.length === 0">
6905
+ {{ getText('taskEditingDependenciesRemoveButtonText') }}
6906
+ </button>
6907
+ </ng-template>
6908
+ <kendo-grid-column [title]="getText('taskEditingDependenciesGridNameColumnTitle')" [field]="dependencyIdField">
6909
+ <ng-template kendoGridCellTemplate let-dataItem="dataItem" let-column="column" let-rowIndex="rowIndex">
6910
+ <kendo-dropdownlist
6911
+ [data]="tasks"
6912
+ textField="title"
6913
+ valueField="id"
6914
+ [valuePrimitive]="true"
6915
+ [formControl]="getFormControl(rowIndex, column.field)"
6916
+ >
6917
+ </kendo-dropdownlist>
6918
+ </ng-template>
6919
+ </kendo-grid-column>
6920
+ <kendo-grid-column [title]="getText('taskEditingDependenciesGridTypeColumnTitle')" field="type">
6921
+ <ng-template kendoGridCellTemplate let-dataItem="dataItem" let-column="column" let-rowIndex="rowIndex">
6922
+ <kendo-dropdownlist
6923
+ [data]="dependencyTypes"
6924
+ textField="type"
6925
+ valueField="id"
6926
+ [valuePrimitive]="true"
6927
+ [formControl]="getFormControl(rowIndex, column.field)"
6928
+ >
6929
+ </kendo-dropdownlist>
6930
+ </ng-template>
6931
+ </kendo-grid-column>
6932
+ </kendo-grid>
6933
+ `
6934
+ }),
6935
+ __metadata("design:paramtypes", [MappingService,
6936
+ EditService,
6937
+ GanttLocalizationService])
6938
+ ], DependenciesTableComponent);
6939
+
6940
+ /**
6941
+ * @hidden
6942
+ */
6943
+ let TaskFieldsComponent = class TaskFieldsComponent {
6944
+ constructor(mapper, editService, localizationService) {
6945
+ this.mapper = mapper;
6946
+ this.editService = editService;
6947
+ this.localizationService = localizationService;
6948
+ }
6949
+ /**
6950
+ * @hidden
6951
+ */
6952
+ get formGroup() {
6953
+ return this.editService.taskFormGroup;
6954
+ }
6955
+ getText(token) {
6956
+ return this.localizationService.get(token);
6957
+ }
6958
+ };
6959
+ TaskFieldsComponent = __decorate([
6960
+ Component({
6961
+ selector: 'kendo-gantt-task-fields',
6962
+ template: `
6963
+ <form class="k-form" [formGroup]="formGroup">
6964
+ <kendo-formfield *ngIf="formGroup.contains(mapper.taskFields.title)">
6965
+ <kendo-label [for]="mapper.taskFields.title" [text]="getText('titleFieldInputLabel')"></kendo-label>
6966
+ <input kendoTextBox [formControlName]="mapper.taskFields.title" />
6967
+ </kendo-formfield>
6968
+ <div class="k-hstack">
6969
+ <kendo-formfield [style.width.%]="49" *ngIf="formGroup.contains(mapper.taskFields.start)">
6970
+ <kendo-label [for]="mapper.taskFields.start" [text]="getText('startFieldInputLabel')"></kendo-label>
6971
+ <kendo-datetimepicker [formControlName]="mapper.taskFields.start"></kendo-datetimepicker>
6972
+ </kendo-formfield>
6973
+ <kendo-treelist-spacer></kendo-treelist-spacer>
6974
+ <kendo-formfield [style.width.%]="49" *ngIf="formGroup.contains(mapper.taskFields.end)">
6975
+ <kendo-label [for]="mapper.taskFields.end" [text]="getText('endFieldInputLabel')"></kendo-label>
6976
+ <kendo-datetimepicker [formControlName]="mapper.taskFields.end"></kendo-datetimepicker>
6977
+ </kendo-formfield>
6978
+ </div>
6979
+ <kendo-formfield [style.width.%]="49" *ngIf="formGroup.contains(mapper.taskFields.completionRatio)">
6980
+ <kendo-label [for]="mapper.taskFields.completionRatio" [text]="getText('completionRatioFieldInputLabel')"></kendo-label>
6981
+ <kendo-numerictextbox
6982
+ [formControlName]="mapper.taskFields.completionRatio"
6983
+ [min]="0"
6984
+ [max]="1"
6985
+ [decimals]="2"
6986
+ format="p2"
6987
+ [step]="0.01"
6988
+ ></kendo-numerictextbox>
6989
+ </kendo-formfield>
6990
+ </form>
6991
+ `
6992
+ }),
6993
+ __metadata("design:paramtypes", [MappingService,
6994
+ EditService,
6995
+ GanttLocalizationService])
6996
+ ], TaskFieldsComponent);
6997
+
4957
6998
  const IMPORTED_MODULES = [
4958
6999
  CommonModule,
4959
7000
  ReactiveFormsModule,
@@ -4965,7 +7006,12 @@ const IMPORTED_MODULES = [
4965
7006
  TreeListModule,
4966
7007
  ButtonsModule,
4967
7008
  DialogModule,
4968
- EventsModule
7009
+ EventsModule,
7010
+ PopupModule,
7011
+ DraggableModule,
7012
+ TabStripModule,
7013
+ GridModule,
7014
+ DropDownsModule
4969
7015
  ];
4970
7016
  const DECLARATIONS = [
4971
7017
  GanttComponent,
@@ -4995,6 +7041,7 @@ const DECLARATIONS = [
4995
7041
  FooterTemplateDirective,
4996
7042
  GanttExpandableDirective,
4997
7043
  GanttDependencyDirective,
7044
+ DependencyDragCreateDirective,
4998
7045
  TimelineDayViewComponent,
4999
7046
  TimelineWeekViewComponent,
5000
7047
  TimelineMonthViewComponent,
@@ -5002,8 +7049,13 @@ const DECLARATIONS = [
5002
7049
  EditDialogComponent,
5003
7050
  CustomMessagesComponent,
5004
7051
  LocalizedMessagesDirective,
5005
- GanttAddTaskComponent
7052
+ GanttAddTaskComponent,
7053
+ DragValidationTooltipComponent,
7054
+ TimelineScrollableDirective,
7055
+ DependenciesTableComponent,
7056
+ TaskFieldsComponent
5006
7057
  ];
7058
+ const ɵ0$3 = touchEnabled;
5007
7059
  /**
5008
7060
  * Represents the [NgModule]({{ site.data.urls.angular['ngmoduleapi'] }})
5009
7061
  * definition for the Gantt component.
@@ -5042,52 +7094,19 @@ GanttModule = __decorate([
5042
7094
  imports: [...IMPORTED_MODULES],
5043
7095
  declarations: [...DECLARATIONS],
5044
7096
  exports: [...DECLARATIONS],
7097
+ entryComponents: [DragValidationTooltipComponent],
5045
7098
  providers: [{
5046
7099
  provide: L10N_PREFIX,
5047
7100
  useValue: 'kendo.gantt'
7101
+ }, {
7102
+ provide: TOUCH_ENABLED,
7103
+ useValue: ɵ0$3
5048
7104
  }]
5049
7105
  })
5050
7106
  ], GanttModule);
5051
7107
 
5052
- /**
5053
- * @hidden
5054
- */
5055
- class PreventableEvent {
5056
- constructor() {
5057
- this.prevented = false;
5058
- }
5059
- /**
5060
- * Prevents the default action for a specified event.
5061
- * In this way, the source component suppresses
5062
- * the built-in behavior that follows the event.
5063
- */
5064
- preventDefault() {
5065
- this.prevented = true;
5066
- }
5067
- /**
5068
- * Returns `true` if the event was prevented
5069
- * by any of its subscribers.
5070
- *
5071
- * @returns `true` if the default action was prevented.
5072
- * Otherwise, returns `false`.
5073
- */
5074
- isDefaultPrevented() {
5075
- return this.prevented;
5076
- }
5077
- }
5078
-
5079
- /**
5080
- * Called every time a user leaves an edited cell.
5081
- */
5082
- class CellCloseEvent extends PreventableEvent {
5083
- constructor(options) {
5084
- super();
5085
- Object.assign(this, options);
5086
- }
5087
- }
5088
-
5089
7108
  /**
5090
7109
  * Generated bundle index. Do not edit.
5091
7110
  */
5092
7111
 
5093
- export { MappingService, OptionChangesService, DependencyDomService, GanttDependencyDirective, GanttAddTaskComponent, EditDialogComponent, EditService, CustomMessagesComponent, LocalizedMessagesDirective, Messages, PreventableEvent, GanttHeaderTableBodyComponent, GanttMilestoneTaskComponent, GanttSummaryTaskComponent, GanttTaskBase, GanttTaskComponent, GanttTasksTableBodyComponent, ScrollSyncService, GanttTimelineComponent, TimelineBaseViewService, TimelineDayViewComponent, TimelineDayViewService, TimelineMonthViewComponent, TimelineMonthViewService, TimelineViewService, TimelineWeekViewComponent, TimelineWeekViewService, ViewBase, ToolbarComponent, ViewSelectorComponent, GanttComponent, GanttModule, GanttHierarchyBindingDirective, GanttFlatBindingDirective, GanttExpandableDirective, GanttTaskTemplateDirective, GanttTaskContentTemplateDirective, GanttSummaryTaskTemplateDirective, ToolbarTemplateDirective, SelectableDirective, CellCloseEvent, GanttColumnBase, GanttColumnComponent, GanttColumnGroupComponent, GanttSpanColumnComponent, CellTemplateDirective, HeaderTemplateDirective, FooterTemplateDirective, ColumnMenuTemplateDirective, FilterCellTemplateDirective, FilterMenuTemplateDirective, EditTemplateDirective };
7112
+ export { MappingService, OptionChangesService, TOUCH_ENABLED, DependencyDomService, GanttDependencyDirective, DragValidationTooltipComponent, GanttAddTaskComponent, DependenciesTableComponent, EditDialogComponent, EditService, TaskFieldsComponent, CustomMessagesComponent, GanttLocalizationService, LocalizedMessagesDirective, Messages, PreventableEvent, NavigationService, GanttHeaderTableBodyComponent, GanttMilestoneTaskComponent, GanttSummaryTaskComponent, GanttTaskBase, GanttTaskComponent, GanttTasksTableBodyComponent, ScrollSyncService, TimelineScrollableDirective, TimelineScrollService, GanttTimelineComponent, TimelineBaseViewService, TimelineDayViewComponent, TimelineDayViewService, TimelineMonthViewComponent, TimelineMonthViewService, TimelineViewService, TimelineWeekViewComponent, TimelineWeekViewService, ViewBase, ToolbarComponent, ViewSelectorComponent, GanttComponent, GanttModule, GanttHierarchyBindingDirective, GanttFlatBindingDirective, GanttExpandableDirective, DependencyDragCreateDirective, GanttTaskTemplateDirective, GanttTaskContentTemplateDirective, GanttSummaryTaskTemplateDirective, ToolbarTemplateDirective, SelectableDirective, DependencyType, CellCloseEvent, GanttColumnBase, GanttColumnComponent, GanttColumnGroupComponent, GanttSpanColumnComponent, CellTemplateDirective, HeaderTemplateDirective, FooterTemplateDirective, ColumnMenuTemplateDirective, FilterCellTemplateDirective, FilterMenuTemplateDirective, EditTemplateDirective };