@limble/limble-tree 1.0.0-alpha.1 → 1.0.0-beta.1

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 (99) hide show
  1. package/README.md +450 -17
  2. package/esm2020/lib/components/branch/branch.component.mjs +7 -15
  3. package/esm2020/lib/components/dropzone/dropzone.component.mjs +2 -2
  4. package/esm2020/lib/components/root/root.component.mjs +3 -4
  5. package/esm2020/lib/core/branch-options.interface.mjs +1 -1
  6. package/esm2020/lib/core/configuration/configuration.mjs +1 -1
  7. package/esm2020/lib/core/configuration/tree-options.interface.mjs +1 -1
  8. package/esm2020/lib/core/index.mjs +1 -2
  9. package/esm2020/lib/core/relationship.interface.mjs +1 -1
  10. package/esm2020/lib/core/tree-branch/branch-controller.mjs +99 -0
  11. package/esm2020/lib/core/tree-branch/tree-branch.mjs +260 -106
  12. package/esm2020/lib/core/tree-node-base.mjs +10 -9
  13. package/esm2020/lib/core/tree-root/root-controller.mjs +42 -0
  14. package/esm2020/lib/core/tree-root/tree-root.mjs +138 -27
  15. package/esm2020/lib/core/tree-service/tree.service.mjs +7 -1
  16. package/esm2020/lib/events/drag/drag-end-event.mjs +9 -7
  17. package/esm2020/lib/events/drag/drag-start-event.mjs +2 -4
  18. package/esm2020/lib/events/drag/drop-event.mjs +2 -4
  19. package/esm2020/lib/events/general/destruction-event.mjs +10 -0
  20. package/esm2020/lib/events/general/index.mjs +2 -0
  21. package/esm2020/lib/events/index.mjs +2 -1
  22. package/esm2020/lib/events/relational/graft-event.mjs +2 -4
  23. package/esm2020/lib/events/relational/prune-event.mjs +2 -4
  24. package/esm2020/lib/events/relational/relational-tree-event.interface.mjs +1 -1
  25. package/esm2020/lib/extras/collapse/collapse.mjs +2 -2
  26. package/esm2020/lib/extras/collapse/collapse.module.mjs +2 -1
  27. package/esm2020/lib/extras/collapse/collapse.service.mjs +26 -1
  28. package/esm2020/lib/extras/drag-and-drop/drag-and-drop.mjs +6 -10
  29. package/esm2020/lib/extras/drag-and-drop/drag-and-drop.module.mjs +5 -1
  30. package/esm2020/lib/extras/drag-and-drop/drag-and-drop.service.mjs +21 -5
  31. package/esm2020/lib/extras/drag-and-drop/draggable.directive.mjs +7 -7
  32. package/esm2020/lib/extras/drag-and-drop/dragover-no-change-detect.mjs +10 -11
  33. package/esm2020/lib/extras/drag-and-drop/dropzone-renderer.mjs +9 -7
  34. package/esm2020/lib/legacy/legacy-component-obj.interface.mjs +1 -1
  35. package/esm2020/lib/legacy/legacy-tree-data.interface.mjs +1 -1
  36. package/esm2020/lib/legacy/legacy-tree-options.interface.mjs +1 -1
  37. package/esm2020/lib/legacy/legacy-tree.mjs +30 -30
  38. package/esm2020/lib/legacy/limble-tree-root/limble-tree-root.component.mjs +1 -1
  39. package/esm2020/lib/limble-tree.module.mjs +6 -2
  40. package/esm2020/lib/structure/branchable.interface.mjs +1 -1
  41. package/esm2020/lib/structure/component-container.interface.mjs +2 -0
  42. package/esm2020/lib/structure/index.mjs +2 -5
  43. package/esm2020/lib/structure/tree-branch-node.interface.mjs +1 -1
  44. package/esm2020/lib/structure/tree-event.interface.mjs +1 -1
  45. package/esm2020/lib/structure/tree-node.interface.mjs +1 -1
  46. package/esm2020/lib/structure/tree-relationship.interface.mjs +1 -1
  47. package/fesm2015/limble-limble-tree.mjs +821 -376
  48. package/fesm2015/limble-limble-tree.mjs.map +1 -1
  49. package/fesm2020/limble-limble-tree.mjs +819 -372
  50. package/fesm2020/limble-limble-tree.mjs.map +1 -1
  51. package/lib/core/branch-options.interface.d.ts +1 -1
  52. package/lib/core/configuration/configuration.d.ts +2 -2
  53. package/lib/core/configuration/tree-options.interface.d.ts +48 -29
  54. package/lib/core/index.d.ts +0 -1
  55. package/lib/core/relationship.interface.d.ts +2 -3
  56. package/lib/core/tree-branch/branch-controller.d.ts +25 -0
  57. package/lib/core/tree-branch/tree-branch.d.ts +199 -24
  58. package/lib/core/tree-node-base.d.ts +4 -5
  59. package/lib/core/tree-root/root-controller.d.ts +19 -0
  60. package/lib/core/tree-root/tree-root.d.ts +109 -14
  61. package/lib/core/tree-service/tree.service.d.ts +8 -2
  62. package/lib/events/drag/drag-end-event.d.ts +15 -13
  63. package/lib/events/drag/drag-start-event.d.ts +6 -5
  64. package/lib/events/drag/drop-event.d.ts +7 -10
  65. package/lib/events/general/destruction-event.d.ts +8 -0
  66. package/lib/events/general/index.d.ts +1 -0
  67. package/lib/events/index.d.ts +1 -0
  68. package/lib/events/relational/graft-event.d.ts +5 -6
  69. package/lib/events/relational/prune-event.d.ts +5 -6
  70. package/lib/events/relational/relational-tree-event.interface.d.ts +5 -1
  71. package/lib/extras/collapse/collapse.module.d.ts +1 -0
  72. package/lib/extras/collapse/collapse.service.d.ts +25 -0
  73. package/lib/extras/drag-and-drop/drag-and-drop.d.ts +2 -3
  74. package/lib/extras/drag-and-drop/drag-and-drop.module.d.ts +4 -0
  75. package/lib/extras/drag-and-drop/drag-and-drop.service.d.ts +22 -3
  76. package/lib/extras/drag-and-drop/draggable.directive.d.ts +4 -5
  77. package/lib/extras/drag-and-drop/dragover-no-change-detect.d.ts +7 -3
  78. package/lib/legacy/legacy-component-obj.interface.d.ts +1 -1
  79. package/lib/legacy/legacy-tree-data.interface.d.ts +1 -1
  80. package/lib/legacy/legacy-tree-options.interface.d.ts +2 -2
  81. package/lib/legacy/legacy-tree.d.ts +4 -4
  82. package/lib/legacy/limble-tree-root/limble-tree-root.component.d.ts +4 -4
  83. package/lib/limble-tree.module.d.ts +4 -0
  84. package/lib/structure/branchable.interface.d.ts +0 -1
  85. package/lib/structure/component-container.interface.d.ts +8 -0
  86. package/lib/structure/index.d.ts +1 -4
  87. package/lib/structure/tree-branch-node.interface.d.ts +3 -3
  88. package/lib/structure/tree-event.interface.d.ts +3 -3
  89. package/lib/structure/tree-node.interface.d.ts +11 -6
  90. package/lib/structure/tree-relationship.interface.d.ts +2 -2
  91. package/package.json +1 -1
  92. package/esm2020/lib/structure/container-tree-node.interface.mjs +0 -2
  93. package/esm2020/lib/structure/content-container.interface.mjs +0 -2
  94. package/esm2020/lib/structure/event-conduit.interface.mjs +0 -2
  95. package/esm2020/lib/structure/tree-root.node.interface.mjs +0 -2
  96. package/lib/structure/container-tree-node.interface.d.ts +0 -3
  97. package/lib/structure/content-container.interface.d.ts +0 -3
  98. package/lib/structure/event-conduit.interface.d.ts +0 -6
  99. package/lib/structure/tree-root.node.interface.d.ts +0 -2
@@ -1,9 +1,8 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, NgModule, EventEmitter, Directive, Input, Output, Component, NgZone, ViewContainerRef, ViewChild, ViewChildren, createComponent, EnvironmentInjector, HostListener } from '@angular/core';
3
- import { BehaviorSubject, Subject, fromEvent, merge, map, filter, first } from 'rxjs';
2
+ import { Injectable, NgModule, EventEmitter, Directive, Output, Component, Input, NgZone, ViewContainerRef, ViewChild, ViewChildren, createComponent, EnvironmentInjector, HostListener } from '@angular/core';
3
+ import { BehaviorSubject, fromEvent, merge, map, Subject, filter, first } from 'rxjs';
4
4
  import * as i1 from '@angular/common';
5
5
  import { CommonModule } from '@angular/common';
6
- import { throttleTime } from 'rxjs/operators';
7
6
 
8
7
  class TreeCollapser {
9
8
  constructor() {
@@ -27,7 +26,7 @@ class TreeCollapser {
27
26
  branch.graftTo(treeBranch);
28
27
  });
29
28
  this.tempStorage.delete(treeBranch);
30
- treeBranch.getContents().changeDetectorRef.detectChanges();
29
+ treeBranch.detectChanges();
31
30
  }
32
31
  isCollapsed(treeBranch) {
33
32
  return this.tempStorage.has(treeBranch);
@@ -38,13 +37,38 @@ class TreeCollapser {
38
37
  }
39
38
  const treeCollapser = new TreeCollapser();
40
39
 
40
+ /** A service that collapses and expands tree branches */
41
41
  class TreeCollapseService {
42
+ /**
43
+ * Causes a TreeBranch to collapse, temporarily pruning all of its children
44
+ * from the tree.
45
+ *
46
+ * @param treeBranch - The branch to collapse.
47
+ */
42
48
  collapse(treeBranch) {
43
49
  treeCollapser.collapse(treeBranch);
44
50
  }
51
+ /**
52
+ * Causes a TreeBranch to expand, restoring all of its children which were
53
+ * previously pruned by a call to `collapse()`.
54
+ *
55
+ * @param treeBranch - The branch to expand.
56
+ */
45
57
  expand(treeBranch) {
46
58
  treeCollapser.expand(treeBranch);
47
59
  }
60
+ /**
61
+ * Determines whether a TreeBranch currently has any children which are
62
+ * pruned from the tree due to a call to the `collapse()` method.
63
+ *
64
+ * @remarks
65
+ * Child branches which are pruned manually from the tree, rather than
66
+ * through the `collapse()` method, will not be considered.
67
+ *
68
+ * @param treeBranch - The branch to check.
69
+ *
70
+ * @returns `true` if the branch is currently collapsed; `false` if it is not.
71
+ */
48
72
  isCollapsed(treeBranch) {
49
73
  return treeCollapser.isCollapsed(treeBranch);
50
74
  }
@@ -55,6 +79,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
55
79
  type: Injectable
56
80
  }] });
57
81
 
82
+ /** A module containing the entities which provide collapse functionality */
58
83
  class TreeCollapseModule {
59
84
  }
60
85
  TreeCollapseModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: TreeCollapseModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
@@ -67,66 +92,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
67
92
  }]
68
93
  }] });
69
94
 
70
- class Configuration {
71
- constructor() {
72
- this.configStorage = new Map();
73
- this.configStorage = new Map();
74
- }
75
- setConfig(root, options) {
76
- this.configStorage.set(root, options);
77
- }
78
- getConfig(root) {
79
- return this.configStorage.get(root);
80
- }
81
- delete(root) {
82
- this.configStorage.delete(root);
83
- }
84
- }
85
- const config = new Configuration();
86
-
87
- class TreeError extends Error {
88
- }
89
-
90
- class DragEndEvent {
91
- constructor(source, endpoints) {
92
- this._source = source;
93
- this._oldParent = endpoints.oldParent;
94
- this._oldIndex = endpoints.oldIndex;
95
- this._newParent = endpoints.newParent;
96
- this._newIndex = endpoints.newIndex;
97
- }
98
- type() {
99
- return "drag end";
100
- }
101
- source() {
102
- return this._source;
103
- }
104
- newIndex() {
105
- return this._newIndex;
106
- }
107
- newParent() {
108
- return this._newParent;
109
- }
110
- oldIndex() {
111
- return this._oldIndex;
112
- }
113
- oldParent() {
114
- return this._oldParent;
115
- }
116
- }
117
-
118
- class DragStartEvent {
119
- constructor(source) {
120
- this._source = source;
121
- }
122
- type() {
123
- return "drag start";
124
- }
125
- source() {
126
- return this._source;
127
- }
128
- }
129
-
130
95
  function assert(condition) {
131
96
  if (condition) {
132
97
  return;
@@ -179,119 +144,20 @@ class DragState {
179
144
  }
180
145
  const dragState = new DragState();
181
146
 
182
- class DropEvent {
183
- constructor(source, parent, index) {
184
- this._source = source;
185
- this._parent = parent;
186
- this._index = index;
187
- }
188
- type() {
189
- return "drag end";
190
- }
191
- source() {
192
- return this._source;
193
- }
194
- index() {
195
- return this._index;
196
- }
197
- parent() {
198
- return this._parent;
199
- }
200
- }
201
-
202
- class DragAndDrop {
203
- constructor() {
204
- this.dragAborted$ = new Subject();
205
- }
206
- dragStart(treeBranch, event) {
207
- if (!this.draggingAllowed(treeBranch)) {
208
- event.preventDefault();
209
- return;
210
- }
211
- treeBranch.dispatch(new DragStartEvent(treeBranch));
212
- this.setDragEffects(treeBranch, event);
213
- this.watchForDragend(treeBranch, event);
214
- // We have to do a setTimeout because DOM changes are not allowed during a
215
- // dragstart event.
216
- setTimeout(() => {
217
- dragState.starting(treeBranch);
218
- treeBranch.prune();
219
- dragState.dragging();
220
- });
221
- }
222
- drop(parent, index) {
223
- const treeBranch = dragState.getDragData();
224
- if (treeBranch === undefined) {
225
- throw new TreeError("Cannot get dragged branch");
226
- }
227
- this.graftDraggedBranch(treeBranch, parent, index);
228
- treeBranch.dispatch(new DropEvent(treeBranch, parent, index));
229
- }
230
- getDragImageOffsets(event, element) {
231
- const bounds = element.getBoundingClientRect();
232
- const xOffset = event.clientX - bounds.left;
233
- const yOffset = event.clientY - bounds.top;
234
- return [xOffset, yOffset];
235
- }
236
- setDragEffects(treeBranch, event) {
237
- const dataTransfer = event.dataTransfer;
238
- if (!(dataTransfer instanceof DataTransfer)) {
239
- throw new Error("bad drag event");
240
- }
241
- const nativeElement = treeBranch.getContents().location.nativeElement;
242
- const [xOffset, yOffset] = this.getDragImageOffsets(event, nativeElement);
243
- dataTransfer.setDragImage(nativeElement, xOffset, yOffset);
244
- }
245
- watchForDragend(treeBranch, event) {
246
- const oldParent = treeBranch.parent();
247
- const oldIndex = treeBranch.index();
248
- if (oldParent === undefined || oldIndex === undefined) {
249
- throw new Error("branch must have a parent");
250
- }
251
- event.target?.addEventListener("dragend", (dragend) => {
252
- if (dragState.state() !== DragStates.Dropped) {
253
- //The drag ended but a drop never occurred, so put the dragged branch back where it started.
254
- this.dragAborted$.next(dragend);
255
- this.graftDraggedBranch(treeBranch, oldParent, oldIndex);
256
- }
257
- dragState.restart();
258
- const newParent = treeBranch.parent();
259
- assert(newParent !== undefined);
260
- const newIndex = treeBranch.index();
261
- assert(newIndex !== undefined);
262
- treeBranch.dispatch(new DragEndEvent(treeBranch, {
263
- oldParent,
264
- oldIndex,
265
- newParent,
266
- newIndex
267
- }));
268
- }, { once: true });
269
- }
270
- draggingAllowed(treeBranch) {
271
- const allowDragging = config.getConfig(treeBranch.root())?.allowDragging ??
272
- (() => true);
273
- return allowDragging(treeBranch);
274
- }
275
- graftDraggedBranch(treeBranch, parent, index) {
276
- treeBranch.graftTo(parent, index);
277
- treeBranch.getContents().location.nativeElement.style.display = "block";
278
- dragState.dropped();
279
- }
280
- }
281
- const dragAndDrop = new DragAndDrop();
282
-
147
+ /**
148
+ * Works just like Angular's built-in `(dragover)` event binding, but is much
149
+ * more performant. It throttles the event to a configurable rate (default once
150
+ * every 25ms) and runs outside of Angular's change detection.
151
+ */
283
152
  class DragoverNoChangeDetectDirective {
284
153
  constructor(ngZone, el) {
285
154
  this.ngZone = ngZone;
286
155
  this.el = el;
287
156
  this.dragoverNoChangeDetect = new EventEmitter();
288
- this.dragoverEventThrottle = 25;
289
157
  }
290
158
  ngOnInit() {
291
159
  this.ngZone.runOutsideAngular(() => {
292
- this.eventSubscription = fromEvent(this.el.nativeElement, "dragover")
293
- .pipe(throttleTime(this.dragoverEventThrottle))
294
- .subscribe(($event) => {
160
+ this.eventSubscription = fromEvent(this.el.nativeElement, "dragover").subscribe(($event) => {
295
161
  this.dragoverNoChangeDetect.emit($event);
296
162
  });
297
163
  });
@@ -303,16 +169,14 @@ class DragoverNoChangeDetectDirective {
303
169
  }
304
170
  }
305
171
  DragoverNoChangeDetectDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: DragoverNoChangeDetectDirective, deps: [{ token: i0.NgZone }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
306
- DragoverNoChangeDetectDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: DragoverNoChangeDetectDirective, isStandalone: true, selector: "[dragoverNoChangeDetect]", inputs: { dragoverEventThrottle: "dragoverEventThrottle" }, outputs: { dragoverNoChangeDetect: "dragoverNoChangeDetect" }, ngImport: i0 });
172
+ DragoverNoChangeDetectDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: DragoverNoChangeDetectDirective, isStandalone: true, selector: "[dragoverNoChangeDetect]", outputs: { dragoverNoChangeDetect: "dragoverNoChangeDetect" }, ngImport: i0 });
307
173
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: DragoverNoChangeDetectDirective, decorators: [{
308
174
  type: Directive,
309
175
  args: [{
310
176
  standalone: true,
311
177
  selector: "[dragoverNoChangeDetect]"
312
178
  }]
313
- }], ctorParameters: function () { return [{ type: i0.NgZone }, { type: i0.ElementRef }]; }, propDecorators: { dragoverEventThrottle: [{
314
- type: Input
315
- }], dragoverNoChangeDetect: [{
179
+ }], ctorParameters: function () { return [{ type: i0.NgZone }, { type: i0.ElementRef }]; }, propDecorators: { dragoverNoChangeDetect: [{
316
180
  type: Output
317
181
  }] } });
318
182
 
@@ -336,10 +200,10 @@ class DropzoneComponent {
336
200
  }
337
201
  }
338
202
  DropzoneComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: DropzoneComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
339
- DropzoneComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.12", type: DropzoneComponent, isStandalone: true, selector: "dropzone", inputs: { placement: "placement" }, outputs: { dropped: "dropped" }, ngImport: i0, template: "<div\n class=\"dropzone\"\n [ngClass]=\"{ active: active }\"\n (dragenter)=\"dragenterHandler()\"\n (dragleave)=\"dragleaveHandler()\"\n (dragoverNoChangeDetect)=\"dragoverHandler($event)\"\n (drop)=\"dropHandler($event)\"\n></div>\n", styles: [".dropzone{border-radius:8px;border:1px dashed #727374;background-color:#ededed;height:20px;margin:8px 0;transition:height .2s ease-out;animation:animation .2s ease-out}.dropzone.active{border-color:#289e49;border-width:2px;background-color:#d0e8d6;height:48px}@keyframes animation{0%{height:0px;opacity:0}to{height:20px;opacity:1}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: DragoverNoChangeDetectDirective, selector: "[dragoverNoChangeDetect]", inputs: ["dragoverEventThrottle"], outputs: ["dragoverNoChangeDetect"] }] });
203
+ DropzoneComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.12", type: DropzoneComponent, isStandalone: true, selector: "dropzone", inputs: { placement: "placement" }, outputs: { dropped: "dropped" }, ngImport: i0, template: "<div\n class=\"dropzone\"\n [ngClass]=\"{ active: active }\"\n (dragenter)=\"dragenterHandler()\"\n (dragleave)=\"dragleaveHandler()\"\n (dragoverNoChangeDetect)=\"dragoverHandler($event)\"\n (drop)=\"dropHandler($event)\"\n></div>\n", styles: [".dropzone{border-radius:8px;border:1px dashed #727374;background-color:#ededed;height:36px;margin:8px 0;transition:height .3s ease-out;animation:animation .3s ease-out}.dropzone.active{border-color:#289e49;border-width:2px;background-color:#d0e8d6;height:72px}@keyframes animation{0%{height:0px;opacity:0}to{height:36px;opacity:1}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: DragoverNoChangeDetectDirective, selector: "[dragoverNoChangeDetect]", outputs: ["dragoverNoChangeDetect"] }] });
340
204
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: DropzoneComponent, decorators: [{
341
205
  type: Component,
342
- args: [{ standalone: true, selector: "dropzone", imports: [CommonModule, DragoverNoChangeDetectDirective], template: "<div\n class=\"dropzone\"\n [ngClass]=\"{ active: active }\"\n (dragenter)=\"dragenterHandler()\"\n (dragleave)=\"dragleaveHandler()\"\n (dragoverNoChangeDetect)=\"dragoverHandler($event)\"\n (drop)=\"dropHandler($event)\"\n></div>\n", styles: [".dropzone{border-radius:8px;border:1px dashed #727374;background-color:#ededed;height:20px;margin:8px 0;transition:height .2s ease-out;animation:animation .2s ease-out}.dropzone.active{border-color:#289e49;border-width:2px;background-color:#d0e8d6;height:48px}@keyframes animation{0%{height:0px;opacity:0}to{height:20px;opacity:1}}\n"] }]
206
+ args: [{ standalone: true, selector: "dropzone", imports: [CommonModule, DragoverNoChangeDetectDirective], template: "<div\n class=\"dropzone\"\n [ngClass]=\"{ active: active }\"\n (dragenter)=\"dragenterHandler()\"\n (dragleave)=\"dragleaveHandler()\"\n (dragoverNoChangeDetect)=\"dragoverHandler($event)\"\n (drop)=\"dropHandler($event)\"\n></div>\n", styles: [".dropzone{border-radius:8px;border:1px dashed #727374;background-color:#ededed;height:36px;margin:8px 0;transition:height .3s ease-out;animation:animation .3s ease-out}.dropzone.active{border-color:#289e49;border-width:2px;background-color:#d0e8d6;height:72px}@keyframes animation{0%{height:0px;opacity:0}to{height:36px;opacity:1}}\n"] }]
343
207
  }], propDecorators: { placement: [{
344
208
  type: Input
345
209
  }], dropped: [{
@@ -359,22 +223,14 @@ class BranchComponent {
359
223
  this.showLateralDropzone = false;
360
224
  }
361
225
  ngAfterViewInit() {
362
- if (this.contentContainer === undefined) {
363
- throw new TreeError("Cannot get contentContainer");
364
- }
365
- if (this.contentToHost === undefined) {
366
- throw new TreeError("'content' is a required input");
367
- }
226
+ assert(this.contentContainer !== undefined);
227
+ assert(this.contentToHost !== undefined);
368
228
  this.hostedContent = this.contentContainer.createComponent(this.contentToHost);
369
229
  this.contentCreated.emit(this.hostedContent.instance);
370
- if (this.dropzones === undefined) {
371
- throw new Error("querylist not defined");
372
- }
230
+ assert(this.dropzones !== undefined);
373
231
  const inner = this.dropzones.get(0);
374
232
  const lateral = this.dropzones.get(1);
375
- if (inner === undefined || lateral == undefined) {
376
- throw new Error("dropzones not defined");
377
- }
233
+ assert(inner !== undefined && lateral !== undefined);
378
234
  merge(inner.dropped.pipe(map(() => "inner")), lateral.dropped.pipe(map(() => "lateral"))).subscribe(this.dropped);
379
235
  this.hostedContent.changeDetectorRef.detectChanges();
380
236
  }
@@ -403,7 +259,7 @@ class BranchComponent {
403
259
  }
404
260
  }
405
261
  BranchComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: BranchComponent, deps: [{ token: i0.ApplicationRef }], target: i0.ɵɵFactoryTarget.Component });
406
- BranchComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.12", type: BranchComponent, isStandalone: true, selector: "branch", inputs: { contentToHost: "contentToHost" }, outputs: { contentCreated: "contentCreated", showDropzones: "showDropzones", dropped: "dropped" }, viewQueries: [{ propertyName: "branchesContainer", first: true, predicate: ["branchesContainer"], descendants: true, read: ViewContainerRef }, { propertyName: "contentContainer", first: true, predicate: ["contentContainer"], descendants: true, read: ViewContainerRef }, { propertyName: "dropzones", predicate: DropzoneComponent, descendants: true }], ngImport: i0, template: "<div class=\"content\" (dragoverNoChangeDetect)=\"dragoverHandler($event)\">\n <div #contentContainer></div>\n</div>\n<div class=\"branches-container\">\n <dropzone placement=\"inner\" [hidden]=\"!showInnerDropzone\"></dropzone>\n <div #branchesContainer></div>\n</div>\n<dropzone placement=\"lateral\" [hidden]=\"!showLateralDropzone\"></dropzone>\n", styles: [".branches-container{margin-left:16px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DropzoneComponent, selector: "dropzone", inputs: ["placement"], outputs: ["dropped"] }, { kind: "directive", type: DragoverNoChangeDetectDirective, selector: "[dragoverNoChangeDetect]", inputs: ["dragoverEventThrottle"], outputs: ["dragoverNoChangeDetect"] }] });
262
+ BranchComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.2.12", type: BranchComponent, isStandalone: true, selector: "branch", inputs: { contentToHost: "contentToHost" }, outputs: { contentCreated: "contentCreated", showDropzones: "showDropzones", dropped: "dropped" }, viewQueries: [{ propertyName: "branchesContainer", first: true, predicate: ["branchesContainer"], descendants: true, read: ViewContainerRef }, { propertyName: "contentContainer", first: true, predicate: ["contentContainer"], descendants: true, read: ViewContainerRef }, { propertyName: "dropzones", predicate: DropzoneComponent, descendants: true }], ngImport: i0, template: "<div class=\"content\" (dragoverNoChangeDetect)=\"dragoverHandler($event)\">\n <div #contentContainer></div>\n</div>\n<div class=\"branches-container\">\n <dropzone placement=\"inner\" [hidden]=\"!showInnerDropzone\"></dropzone>\n <div #branchesContainer></div>\n</div>\n<dropzone placement=\"lateral\" [hidden]=\"!showLateralDropzone\"></dropzone>\n", styles: [".branches-container{margin-left:16px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DropzoneComponent, selector: "dropzone", inputs: ["placement"], outputs: ["dropped"] }, { kind: "directive", type: DragoverNoChangeDetectDirective, selector: "[dragoverNoChangeDetect]", outputs: ["dragoverNoChangeDetect"] }] });
407
263
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: BranchComponent, decorators: [{
408
264
  type: Component,
409
265
  args: [{ standalone: true, selector: "branch", imports: [CommonModule, DropzoneComponent, DragoverNoChangeDetectDirective], template: "<div class=\"content\" (dragoverNoChangeDetect)=\"dragoverHandler($event)\">\n <div #contentContainer></div>\n</div>\n<div class=\"branches-container\">\n <dropzone placement=\"inner\" [hidden]=\"!showInnerDropzone\"></dropzone>\n <div #branchesContainer></div>\n</div>\n<dropzone placement=\"lateral\" [hidden]=\"!showLateralDropzone\"></dropzone>\n", styles: [".branches-container{margin-left:16px}\n"] }]
@@ -426,6 +282,162 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
426
282
  type: Output
427
283
  }] } });
428
284
 
285
+ class Configuration {
286
+ constructor() {
287
+ this.configStorage = new Map();
288
+ this.configStorage = new Map();
289
+ }
290
+ setConfig(root, options) {
291
+ this.configStorage.set(root, options);
292
+ }
293
+ getConfig(root) {
294
+ return this.configStorage.get(root);
295
+ }
296
+ delete(root) {
297
+ this.configStorage.delete(root);
298
+ }
299
+ }
300
+ const config = new Configuration();
301
+
302
+ class TreeError extends Error {
303
+ }
304
+
305
+ /** Emitted when a drag-and-drop operation has completed */
306
+ class DragEndEvent {
307
+ constructor(source, endpoints) {
308
+ this._source = source;
309
+ this._oldParent = endpoints.oldParent;
310
+ this._oldIndex = endpoints.oldIndex;
311
+ this._newParent = endpoints.newParent;
312
+ this._newIndex = endpoints.newIndex;
313
+ }
314
+ /** @returns The new index of the dropped branch */
315
+ newIndex() {
316
+ return this._newIndex;
317
+ }
318
+ /** @returns The new parent of the dropped branch */
319
+ newParent() {
320
+ return this._newParent;
321
+ }
322
+ /** @returns The index of the dropped branch before it was dragged */
323
+ oldIndex() {
324
+ return this._oldIndex;
325
+ }
326
+ /** @returns The parent of the dropped branch before it was dragged */
327
+ oldParent() {
328
+ return this._oldParent;
329
+ }
330
+ source() {
331
+ return this._source;
332
+ }
333
+ }
334
+
335
+ /** Emitted when a TreeBranch begins being dragged */
336
+ class DragStartEvent {
337
+ constructor(source) {
338
+ this._source = source;
339
+ }
340
+ source() {
341
+ return this._source;
342
+ }
343
+ }
344
+
345
+ /** Emitted when a TreeBranch is dropped into a valid Dropzone */
346
+ class DropEvent {
347
+ constructor(source, parent, index) {
348
+ this._source = source;
349
+ this._parent = parent;
350
+ this._index = index;
351
+ }
352
+ source() {
353
+ return this._source;
354
+ }
355
+ index() {
356
+ return this._index;
357
+ }
358
+ parent() {
359
+ return this._parent;
360
+ }
361
+ }
362
+
363
+ class DragAndDrop {
364
+ constructor() {
365
+ this.dragAborted$ = new Subject();
366
+ }
367
+ dragStart(treeBranch, event) {
368
+ if (!this.draggingAllowed(treeBranch)) {
369
+ event.preventDefault();
370
+ return;
371
+ }
372
+ treeBranch.dispatch(new DragStartEvent(treeBranch));
373
+ this.setDragEffects(treeBranch, event);
374
+ this.watchForDragend(treeBranch, event);
375
+ // We have to do a setTimeout because DOM changes are not allowed during a
376
+ // dragstart event.
377
+ setTimeout(() => {
378
+ dragState.starting(treeBranch);
379
+ treeBranch.prune();
380
+ dragState.dragging();
381
+ });
382
+ }
383
+ drop(parent, index) {
384
+ const treeBranch = dragState.getDragData();
385
+ if (treeBranch === undefined) {
386
+ throw new TreeError("Cannot get dragged branch");
387
+ }
388
+ this.graftDraggedBranch(treeBranch, parent, index);
389
+ treeBranch.dispatch(new DropEvent(treeBranch, parent, index));
390
+ }
391
+ getDragImageOffsets(event, element) {
392
+ const bounds = element.getBoundingClientRect();
393
+ const xOffset = event.clientX - bounds.left;
394
+ const yOffset = event.clientY - bounds.top;
395
+ return [xOffset, yOffset];
396
+ }
397
+ setDragEffects(treeBranch, event) {
398
+ const dataTransfer = event.dataTransfer;
399
+ assert(dataTransfer instanceof DataTransfer);
400
+ const nativeElement = treeBranch.getNativeElement();
401
+ const [xOffset, yOffset] = this.getDragImageOffsets(event, nativeElement);
402
+ dataTransfer.setDragImage(nativeElement, xOffset, yOffset);
403
+ }
404
+ watchForDragend(treeBranch, event) {
405
+ const oldParent = treeBranch.parent();
406
+ const oldIndex = treeBranch.index();
407
+ assert(oldParent !== undefined && oldIndex !== undefined);
408
+ event.target?.addEventListener("dragend", (dragend) => {
409
+ if (dragState.state() !== DragStates.Dropped) {
410
+ //The drag ended but a drop never occurred, so put the dragged branch back where it started.
411
+ this.dragAborted$.next(dragend);
412
+ this.graftDraggedBranch(treeBranch, oldParent, oldIndex);
413
+ }
414
+ dragState.restart();
415
+ const newParent = treeBranch.parent();
416
+ assert(newParent !== undefined);
417
+ const newIndex = treeBranch.index();
418
+ assert(newIndex !== undefined);
419
+ treeBranch.dispatch(new DragEndEvent(treeBranch, {
420
+ oldParent,
421
+ oldIndex,
422
+ newParent,
423
+ newIndex
424
+ }));
425
+ }, { once: true });
426
+ }
427
+ draggingAllowed(treeBranch) {
428
+ const allowDragging = config.getConfig(treeBranch.root())?.dragAndDrop?.allowDragging ??
429
+ (() => true);
430
+ return allowDragging(treeBranch);
431
+ }
432
+ graftDraggedBranch(treeBranch, parent, index) {
433
+ treeBranch.graftTo(parent, index);
434
+ treeBranch.getNativeElement().style.display = "block";
435
+ dragState.dropped();
436
+ }
437
+ }
438
+ const dragAndDrop = new DragAndDrop();
439
+
440
+ /** Emits when a branch is grafted to another tree node */
429
441
  class GraftEvent {
430
442
  constructor(source, relationship) {
431
443
  this._source = source;
@@ -436,9 +448,6 @@ class GraftEvent {
436
448
  child() {
437
449
  return this._child;
438
450
  }
439
- type() {
440
- return "graft";
441
- }
442
451
  index() {
443
452
  return this._index;
444
453
  }
@@ -450,6 +459,7 @@ class GraftEvent {
450
459
  }
451
460
  }
452
461
 
462
+ /** Emitted when a branch is pruned from its parent branch */
453
463
  class PruneEvent {
454
464
  constructor(source, relationship) {
455
465
  this._source = source;
@@ -460,9 +470,6 @@ class PruneEvent {
460
470
  child() {
461
471
  return this._child;
462
472
  }
463
- type() {
464
- return "prune";
465
- }
466
473
  index() {
467
474
  return this._index;
468
475
  }
@@ -476,6 +483,7 @@ class PruneEvent {
476
483
 
477
484
  class TreeNodeBase {
478
485
  constructor() {
486
+ this.destroyed = false;
479
487
  this._branches = [];
480
488
  this.events$ = new Subject();
481
489
  this.subscriptions = [
@@ -490,17 +498,14 @@ class TreeNodeBase {
490
498
  branches() {
491
499
  return [...this._branches];
492
500
  }
493
- deleteBranch(index) {
494
- if (index === undefined) {
495
- this._branches.pop();
496
- return;
497
- }
498
- this._branches.splice(index, 1);
499
- }
500
501
  destroy() {
502
+ this.branches().forEach((branch) => {
503
+ branch.destroy();
504
+ });
501
505
  this.subscriptions.forEach((sub) => {
502
506
  sub.unsubscribe();
503
507
  });
508
+ this.destroyed = true;
504
509
  }
505
510
  dispatch(event) {
506
511
  this.events$.next(event);
@@ -511,6 +516,9 @@ class TreeNodeBase {
511
516
  getBranch(index) {
512
517
  return this._branches[index];
513
518
  }
519
+ isDestroyed() {
520
+ return this.destroyed;
521
+ }
514
522
  plot() {
515
523
  return new Map(this.branches().map((branch, index) => [index, branch.plot()]));
516
524
  }
@@ -521,7 +529,7 @@ class TreeNodeBase {
521
529
  }
522
530
  deregisterChildRelationship(child) {
523
531
  const index = this.branches().findIndex((branch) => branch === child);
524
- this.deleteBranch(index);
532
+ this._branches.splice(index, 1);
525
533
  }
526
534
  graftsToSelf() {
527
535
  return this.events().pipe(filter((event) => event instanceof GraftEvent), filter((event) => event.parent().events() === this.events$));
@@ -538,6 +546,16 @@ class TreeNodeBase {
538
546
  }
539
547
  }
540
548
 
549
+ /** Emitted when a node is destroyed */
550
+ class DestructionEvent {
551
+ constructor(source) {
552
+ this._source = source;
553
+ }
554
+ source() {
555
+ return this._source;
556
+ }
557
+ }
558
+
541
559
  class RootComponent {
542
560
  constructor() {
543
561
  this.branchesContainer = undefined;
@@ -548,9 +566,7 @@ class RootComponent {
548
566
  }
549
567
  ngAfterViewInit() {
550
568
  this.afterViewInit.emit();
551
- if (this.dropzone === undefined) {
552
- throw new Error("dropzone is not defined");
553
- }
569
+ assert(this.dropzone !== undefined);
554
570
  this.dropzone.dropped.subscribe(this.dropped);
555
571
  }
556
572
  }
@@ -571,71 +587,225 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
571
587
  type: Output
572
588
  }] } });
573
589
 
574
- class TreeRoot {
575
- constructor(viewContainerRef) {
576
- this.viewContainerRef = viewContainerRef;
577
- this.treeNodeBase = new TreeNodeBase();
578
- this.rootComponentRef =
579
- this.viewContainerRef.createComponent(RootComponent);
590
+ /**
591
+ * A wrapper around the BranchComponent that helps instantiate it and handles its events.
592
+ */
593
+ class RootController {
594
+ constructor(treeRoot, viewContainerRef) {
595
+ this.treeRoot = treeRoot;
596
+ this.rootComponentRef = viewContainerRef.createComponent(RootComponent);
580
597
  const viewInitSub = this.rootComponentRef.instance.afterViewInit.subscribe(() => {
581
598
  const dropzone = this.rootComponentRef.instance.dropzone;
582
- if (!dropzone) {
583
- throw new Error("dropzone not defined");
584
- }
585
- dropzoneRenderer.registerDropzone(dropzone, this);
599
+ assert(dropzone !== undefined);
600
+ dropzoneRenderer.registerDropzone(dropzone, this.treeRoot);
586
601
  });
587
602
  const droppedSub = this.rootComponentRef.instance.dropped.subscribe(() => {
588
- dropzoneRenderer.handleDrop(this, "inner");
603
+ dropzoneRenderer.handleDrop(this.treeRoot, "inner");
589
604
  });
590
605
  this.instanceSubscriptions = [viewInitSub, droppedSub];
606
+ }
607
+ destroy() {
608
+ this.instanceSubscriptions.forEach((sub) => {
609
+ sub.unsubscribe();
610
+ });
611
+ }
612
+ detectChanges() {
591
613
  this.rootComponentRef.changeDetectorRef.detectChanges();
592
614
  }
615
+ getBranchesContainer() {
616
+ return this.rootComponentRef.instance.branchesContainer;
617
+ }
618
+ getComponentInstance() {
619
+ return this.rootComponentRef.instance;
620
+ }
621
+ getHostView() {
622
+ return this.rootComponentRef.hostView;
623
+ }
624
+ getNativeElement() {
625
+ return this.rootComponentRef.location.nativeElement;
626
+ }
627
+ }
628
+
629
+ /**
630
+ * Represents the base of the tree. It renders a very simple container for child
631
+ * branches. It has methods for creating and accessing those branches. It emits
632
+ * events when things happen in the tree.
633
+ */
634
+ class TreeRoot {
635
+ constructor(viewContainerRef) {
636
+ this.viewContainerRef = viewContainerRef;
637
+ this.treeNodeBase = new TreeNodeBase();
638
+ this.rootController = new RootController(this, viewContainerRef);
639
+ this.detectChanges();
640
+ }
641
+ /** @returns All child branches as an array of TreeBranch instances */
593
642
  branches() {
594
643
  return this.treeNodeBase.branches();
595
644
  }
596
- deleteBranch(index) {
597
- this.treeNodeBase.deleteBranch(index);
598
- }
645
+ /**
646
+ * Recursively destroys all branches of the tree, as well as itself.
647
+ *
648
+ * @remarks
649
+ * This releases all resources held or consumed by the tree.
650
+ *
651
+ * It is important to call this method when a tree is discarded, otherwise
652
+ * the tree will remain in memory and continue to consume resources.
653
+ */
599
654
  destroy() {
655
+ if (this.isDestroyed()) {
656
+ throw new TreeError("Cannot destroy a destroyed tree root");
657
+ }
600
658
  dropzoneRenderer.clearTreeFromRegistry(this);
601
- this.branches().forEach((branch) => {
602
- branch.destroy();
603
- });
604
659
  this.treeNodeBase.destroy();
605
- this.instanceSubscriptions.forEach((sub) => {
606
- sub.unsubscribe();
607
- });
660
+ this.rootController.destroy();
608
661
  this.viewContainerRef.clear();
609
662
  config.delete(this);
610
- }
663
+ this.dispatch(new DestructionEvent(this));
664
+ }
665
+ /** Run Angular change detection on the root of the tree */
666
+ detectChanges() {
667
+ this.rootController.detectChanges();
668
+ }
669
+ /**
670
+ * Emits the specified TreeEvent.
671
+ *
672
+ * @remarks
673
+ * Caution: It is not recommended to manually emit TreeEvents that are already
674
+ * provided by the library. For example, it is not recommended to emit a
675
+ * `GraftEvent`, `DestructionEvent`, etc. These events may be used by the tree,
676
+ * and emitting them manually may cause unexpected behavior. Instead, we
677
+ * recommend implementing the TreeEvent interface with your own custom events
678
+ * and dispatching those.
679
+ *
680
+ * @param event - The TreeEvent that will be emitted.
681
+ */
611
682
  dispatch(event) {
612
683
  this.treeNodeBase.dispatch(event);
613
684
  }
685
+ /**
686
+ * @returns
687
+ * An observable that emits TreeEvents whenever an event is dispatched
688
+ * in the root or any of its descendant branches.
689
+ */
614
690
  events() {
615
691
  return this.treeNodeBase.events();
616
692
  }
693
+ /**
694
+ * @returns
695
+ * The child branch at the specified index, or undefined if there is
696
+ * no child branch at the specified index.
697
+ */
617
698
  getBranch(index) {
618
699
  return this.treeNodeBase.getBranch(index);
619
700
  }
620
- getContents() {
621
- return this.rootComponentRef;
701
+ /** @returns The ViewContainerRef in which child branches are rendered */
702
+ getBranchesContainer() {
703
+ if (this.isDestroyed()) {
704
+ throw new TreeError("Cannot get branches container from a destroyed tree root");
705
+ }
706
+ return this.rootController.getBranchesContainer();
707
+ }
708
+ /**
709
+ * Retrieves the RootComponent.
710
+ *
711
+ * @remarks
712
+ * The RootComponent holds the BranchesContainer, as well as a single Dropzone
713
+ * for drag-and-drop operations.
714
+ *
715
+ * @returns The instance of RootComponent that is rendered by this class.
716
+ */
717
+ getComponentInstance() {
718
+ if (this.isDestroyed()) {
719
+ throw new TreeError("Cannot get component instance from a destroyed tree root");
720
+ }
721
+ return this.rootController.getComponentInstance();
722
+ }
723
+ /** @returns The Host View in which the RootComponent is rendered */
724
+ getHostView() {
725
+ if (this.isDestroyed()) {
726
+ throw new TreeError("Cannot get component host view from a destroyed tree root");
727
+ }
728
+ return this.rootController.getHostView();
622
729
  }
730
+ /** @returns The RootComponent as a native HTML Element */
731
+ getNativeElement() {
732
+ if (this.isDestroyed()) {
733
+ throw new TreeError("Cannot get native element from a destroyed tree root");
734
+ }
735
+ return this.rootController.getNativeElement();
736
+ }
737
+ /**
738
+ * Appends a new child branch to this branch. The child branch will render
739
+ * the specified component according to the (optional) configuration parameter.
740
+ *
741
+ * @param component - The component to render in the new child branch.
742
+ * @param options - Configuration options for the new child branch.
743
+ *
744
+ * @returns
745
+ * The newly-created child branch.
746
+ */
623
747
  grow(component, options) {
748
+ if (this.isDestroyed()) {
749
+ throw new TreeError("Cannot grow a branch on a destroyed tree root");
750
+ }
624
751
  return new TreeBranch(this, { component, ...options });
625
752
  }
753
+ /** @returns `true` if the tree is destroyed, `false` otherwise */
754
+ isDestroyed() {
755
+ return this.treeNodeBase.isDestroyed();
756
+ }
757
+ /**
758
+ * Provides a model describing the shape of the tree.
759
+ *
760
+ * @returns A multi-dimensional Map which describes the shape of the tree.
761
+ *
762
+ * @example
763
+ * For example, an empty tree will return an empty Map. A tree with a single
764
+ * branch will return a Map with a single entry, where the key is the index
765
+ * of the branch (zero) and the value is an empty Map. A Tree like this:
766
+ *
767
+ * ```
768
+ * ---Branch-------Branch
769
+ * /
770
+ * Root-------Branch
771
+ * \
772
+ * ---Branch
773
+ * ```
774
+ * Will return a Map of matching shape:
775
+ * ```
776
+ * Map {
777
+ * 0: Map { 0: Map {}},
778
+ * 1: Map {},
779
+ * 2: Map {}
780
+ * }
781
+ * ```
782
+ */
626
783
  plot() {
627
784
  return this.treeNodeBase.plot();
628
785
  }
786
+ /** @returns Itself */
629
787
  root() {
630
788
  return this;
631
789
  }
790
+ /**
791
+ * Traverses the tree in depth-first pre-order, executing the provided
792
+ * callback function on each node. Traversal includes the Root.
793
+ *
794
+ * @param callback - A function to execute on each node.
795
+ */
632
796
  traverse(callback) {
633
797
  callback(this);
634
798
  this.treeNodeBase.traverse(callback);
635
799
  }
636
800
  }
637
801
 
802
+ /** Responsible for the creation of new trees. */
638
803
  class TreeService {
804
+ /**
805
+ * Creates a new, empty tree structure inside the provided container.
806
+ *
807
+ * @returns A `TreeRoot` representing the base of the new tree.
808
+ */
639
809
  createEmptyTree(container, options = {}) {
640
810
  container.clear();
641
811
  const root = new TreeRoot(container);
@@ -650,29 +820,126 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
650
820
  args: [{ providedIn: "root" }]
651
821
  }] });
652
822
 
823
+ /**
824
+ * A wrapper around the BranchComponent that helps instantiate it and handles its events.
825
+ */
826
+ class BranchController {
827
+ constructor(treeBranch, parentBranchesContainer) {
828
+ this.treeBranch = treeBranch;
829
+ this.outputBindingSubscriptions = [];
830
+ this.branchComponentRef = createComponent(BranchComponent, {
831
+ environmentInjector: parentBranchesContainer.injector.get(EnvironmentInjector)
832
+ });
833
+ this.branchComponentRef.instance.contentToHost =
834
+ this.treeBranch.branchOptions.component;
835
+ this.instanceSubscriptions = this.getInstanceSubscriptions(this.branchComponentRef.instance);
836
+ }
837
+ destroy() {
838
+ this.instanceSubscriptions.forEach((sub) => {
839
+ sub.unsubscribe();
840
+ });
841
+ this.outputBindingSubscriptions.forEach((sub) => {
842
+ sub.unsubscribe();
843
+ });
844
+ }
845
+ detectChanges() {
846
+ this.branchComponentRef.changeDetectorRef.detectChanges();
847
+ }
848
+ getBranchesContainer() {
849
+ return this.branchComponentRef.instance.branchesContainer;
850
+ }
851
+ getComponentInstance() {
852
+ return this.branchComponentRef.instance;
853
+ }
854
+ getHostView() {
855
+ return this.branchComponentRef.hostView;
856
+ }
857
+ getNativeElement() {
858
+ return this.branchComponentRef.location.nativeElement;
859
+ }
860
+ getUserlandComponentRef() {
861
+ return this.branchComponentRef.instance.getHostedContent();
862
+ }
863
+ getContentCreatedSub(instance) {
864
+ return instance.contentCreated.subscribe((userlandComponentInstance) => {
865
+ const component = userlandComponentInstance;
866
+ Object.entries(this.treeBranch.branchOptions.inputBindings ?? {}).forEach(([key, value]) => {
867
+ component[key] = value;
868
+ });
869
+ Object.entries(this.treeBranch.branchOptions.outputBindings ?? {}).forEach(([key, value]) => {
870
+ this.outputBindingSubscriptions.push(component[key].subscribe(value));
871
+ });
872
+ component.treeBranch = this.treeBranch;
873
+ const dropzones = instance.dropzones;
874
+ assert(dropzones !== undefined);
875
+ dropzoneRenderer.registerDropzones(dropzones, this.treeBranch);
876
+ });
877
+ }
878
+ getInstanceSubscriptions(instance) {
879
+ const droppedSub = instance.dropped.subscribe((placement) => {
880
+ dropzoneRenderer.handleDrop(this.treeBranch, placement);
881
+ });
882
+ return [
883
+ this.getContentCreatedSub(instance),
884
+ this.getShowLowerZonesSub(instance),
885
+ this.getShowUpperZonesSub(instance),
886
+ droppedSub
887
+ ];
888
+ }
889
+ getShowLowerZonesSub(instance) {
890
+ return instance.showDropzones
891
+ .pipe(filter((direction) => direction === "lower"))
892
+ .subscribe(() => {
893
+ const currentDropzoneDisplayed = dropzoneRenderer.getCurrentDisplay();
894
+ if (currentDropzoneDisplayed?.treeBranch === this.treeBranch &&
895
+ currentDropzoneDisplayed.direction === "lower") {
896
+ return;
897
+ }
898
+ dropzoneRenderer.showLowerZones(this.treeBranch);
899
+ instance.triggerChangeDetection();
900
+ });
901
+ }
902
+ getShowUpperZonesSub(instance) {
903
+ return instance.showDropzones
904
+ .pipe(filter((direction) => direction === "upper"))
905
+ .subscribe(() => {
906
+ const currentDropzoneDisplayed = dropzoneRenderer.getCurrentDisplay();
907
+ if (currentDropzoneDisplayed?.treeBranch === this.treeBranch &&
908
+ currentDropzoneDisplayed.direction === "upper") {
909
+ return;
910
+ }
911
+ dropzoneRenderer.showUpperZones(this.treeBranch);
912
+ instance.triggerChangeDetection();
913
+ });
914
+ }
915
+ }
916
+
917
+ /** Represents a standard node in a tree. Renders a BranchComponent.
918
+ *
919
+ * @remarks
920
+ * This class renders a branch component, which does the following:
921
+ * 1. Renders a component provided by the user
922
+ * 2. Provides a container in which child branches may be rendered
923
+ * 3. Contains two Dropzones: one for dropping branches below this branch (as a
924
+ * sibling), and one for dropping branches as a first child of this branch.
925
+ */
653
926
  class TreeBranch {
654
927
  constructor(parent, branchOptions) {
655
928
  this.branchOptions = branchOptions;
656
929
  this.detachedView = null;
657
930
  this.treeNodeBase = new TreeNodeBase();
658
- this.userlandComponent = this.branchOptions.component;
659
- const parentBranchesContainer = parent.getContents().instance.branchesContainer;
931
+ const parentBranchesContainer = parent.getBranchesContainer();
660
932
  assert(parentBranchesContainer !== undefined);
661
- this.contents = createComponent(BranchComponent, {
662
- environmentInjector: parentBranchesContainer.injector.get(EnvironmentInjector)
663
- });
664
- this.contents.instance.contentToHost = this.userlandComponent;
933
+ this.branchController = new BranchController(this, parentBranchesContainer);
665
934
  this.setIndentation(parent);
666
- this.outputBindingSubscriptions = [];
667
- this.instanceSubscriptions = this.getInstanceSubscriptions();
668
935
  if (parent instanceof TreeBranch &&
669
- parent.branchOptions.startCollapsed === true) {
936
+ parent.branchOptions.defaultCollapsed === true) {
670
937
  treeCollapser.storePrecollapsedNode(parent, this);
671
- this.detachedView = this.contents.hostView;
938
+ this.detachedView = this.branchController.getHostView();
672
939
  }
673
940
  else {
674
- parentBranchesContainer.insert(this.contents.hostView);
675
- this.contents.changeDetectorRef.detectChanges();
941
+ parentBranchesContainer.insert(this.branchController.getHostView());
942
+ this.detectChanges();
676
943
  this._parent = parent;
677
944
  this.dispatch(new GraftEvent(this, {
678
945
  parent: this._parent,
@@ -681,50 +948,127 @@ class TreeBranch {
681
948
  }));
682
949
  }
683
950
  }
951
+ /** @returns All child branches as an array of TreeBranch instances, in order. */
684
952
  branches() {
685
953
  return this.treeNodeBase.branches();
686
954
  }
687
- deleteBranch(index) {
688
- this.treeNodeBase.deleteBranch(index);
689
- }
955
+ /**
956
+ * Recursively destroys all descendant branches, as well as itself. This
957
+ * releases all resources held or consumed by this branch and its descendants.
958
+ *
959
+ * @remarks
960
+ * It is important to call this method when a branch is discarded, otherwise
961
+ * the branch will remain in memory and continue to consume resources.
962
+ */
690
963
  destroy() {
691
- if (treeCollapser.isCollapsed(this)) {
692
- treeCollapser.expand(this);
693
- dropzoneRenderer.clearTreeFromRegistry(this);
694
- }
695
- this.branches().forEach((branch) => {
696
- branch.destroy();
697
- });
698
- const parent = this._parent;
699
- const index = this.index();
700
- if (index !== undefined && parent !== undefined) {
701
- const container = parent.getContents().instance.branchesContainer;
702
- assert(container !== undefined);
703
- container.remove(index);
704
- parent.deleteBranch(index);
964
+ if (this.isDestroyed()) {
965
+ throw new TreeError("Cannot destroy a destroyed tree branch");
705
966
  }
967
+ this.prune();
968
+ treeCollapser.expand(this);
969
+ dropzoneRenderer.clearTreeFromRegistry(this);
970
+ this.branchController.getHostView().destroy();
706
971
  this.treeNodeBase.destroy();
707
- this.instanceSubscriptions.forEach((sub) => {
708
- sub.unsubscribe();
709
- });
710
- this.outputBindingSubscriptions.forEach((sub) => {
711
- sub.unsubscribe();
712
- });
713
- }
972
+ this.branchController.destroy();
973
+ this.dispatch(new DestructionEvent(this));
974
+ }
975
+ /** Run Angular change detection on this branch */
976
+ detectChanges() {
977
+ this.branchController.detectChanges();
978
+ }
979
+ /**
980
+ * Emits the specified TreeEvent.
981
+ *
982
+ * @remarks
983
+ * Caution: It is not recommended to manually emit TreeEvents that are already
984
+ * provided by the library. For example, it is not recommended to emit a
985
+ * `GraftEvent`, `DestructionEvent`, etc. These events may be used by the tree,
986
+ * and emitting them manually may cause unexpected behavior. Instead, we
987
+ * recommend implementing the TreeEvent interface with your own custom events
988
+ * and dispatching those.
989
+ *
990
+ * @param event - The TreeEvent that will be emitted.
991
+ */
714
992
  dispatch(event) {
715
993
  this.treeNodeBase.dispatch(event);
716
994
  this._parent?.dispatch(event);
717
995
  }
996
+ /**
997
+ * @returns
998
+ * An observable that emits TreeEvents whenever an event is dispatched
999
+ * in this branch or any of its descendant branches.
1000
+ */
718
1001
  events() {
719
1002
  return this.treeNodeBase.events();
720
1003
  }
1004
+ /**
1005
+ * @param index - The index of the child branch to retrieve.
1006
+ *
1007
+ * @returns
1008
+ * The child branch at the specified index, or undefined if there is
1009
+ * no child branch at the specified index.
1010
+ */
721
1011
  getBranch(index) {
722
1012
  return this.treeNodeBase.getBranch(index);
723
1013
  }
724
- getContents() {
725
- return this.contents;
1014
+ /** @returns The ViewContainerRef in which child branches are rendered */
1015
+ getBranchesContainer() {
1016
+ if (this.isDestroyed()) {
1017
+ throw new TreeError("Cannot get branches container from a destroyed tree branch");
1018
+ }
1019
+ return this.branchController.getBranchesContainer();
1020
+ }
1021
+ /** @returns The instance of BranchComponent that is rendered by this class. */
1022
+ getComponentInstance() {
1023
+ if (this.isDestroyed()) {
1024
+ throw new TreeError("Cannot get component instance from a destroyed tree branch");
1025
+ }
1026
+ return this.branchController.getComponentInstance();
1027
+ }
1028
+ /** @returns The Host View in which the BranchComponent is rendered */
1029
+ getHostView() {
1030
+ if (this.isDestroyed()) {
1031
+ throw new TreeError("Cannot get component host view from a destroyed tree branch");
1032
+ }
1033
+ return this.branchController.getHostView();
726
1034
  }
1035
+ /** @returns The BranchComponent as a native HTML Element */
1036
+ getNativeElement() {
1037
+ if (this.isDestroyed()) {
1038
+ throw new TreeError("Cannot get native element from a destroyed tree branch");
1039
+ }
1040
+ return this.branchController.getNativeElement();
1041
+ }
1042
+ /**
1043
+ * @returns
1044
+ * A ComponentRef containing the instance of the user-provided
1045
+ * component which is rendered by this branch.
1046
+ */
1047
+ getUserlandComponentRef() {
1048
+ if (this.isDestroyed()) {
1049
+ throw new TreeError("Cannot get userland component from a destroyed tree branch");
1050
+ }
1051
+ return this.branchController.getUserlandComponentRef();
1052
+ }
1053
+ /**
1054
+ * Attaches a branch to a new parent node.
1055
+ *
1056
+ * @remarks
1057
+ * If not already pruned, this method prunes (removes) this branch from its
1058
+ * current position in the tree; then grafts (reattaches) it as a child of the
1059
+ * specified parent branch at the specified index. If no index is specified,
1060
+ * the branch is appended as the last child of the parent. This causes this
1061
+ * branch's associated BranchComponent to be re-rendered in the DOM at the
1062
+ * new location.
1063
+ *
1064
+ * @param newParent - The new parent branch unto which this branch will be grafted.
1065
+ * @param index - The index at which this branch will be grafted. If not specified,
1066
+ * this branch will be appended as the last child of the new parent.
1067
+ *
1068
+ * @returns The index at which this branch was grafted.
1069
+ */
727
1070
  graftTo(newParent, index) {
1071
+ this.checkGraftLocationValidity(newParent, index);
728
1072
  const ownIndex = this.index();
729
1073
  if (ownIndex !== undefined) {
730
1074
  this.prune();
@@ -739,9 +1083,35 @@ class TreeBranch {
739
1083
  }));
740
1084
  return newIndex;
741
1085
  }
1086
+ /**
1087
+ * Appends a new child branch to this branch. The child branch will render
1088
+ * the specified component according to the (optional) configuration parameter.
1089
+ *
1090
+ * @param component - The component to render in the new child branch.
1091
+ * @param options - Configuration options for the new child branch.
1092
+ *
1093
+ * @returns
1094
+ * The newly-created child branch.
1095
+ */
742
1096
  grow(component, options) {
1097
+ if (this.isDestroyed()) {
1098
+ throw new TreeError("Cannot grow a branch on a destroyed tree branch");
1099
+ }
743
1100
  return new TreeBranch(this, { component, ...options });
744
1101
  }
1102
+ /**
1103
+ * Determines this branch's index in relation to its sibling branches.
1104
+ *
1105
+ * @remarks
1106
+ * For example, if it is the first child of its parent, this method will return
1107
+ * 0. If it is the second child of its parent, this method will return 1.
1108
+ *
1109
+ * If this branch has no parent, (eg, if this branch has been pruned) this
1110
+ * method will return undefined.
1111
+ *
1112
+ * @returns
1113
+ * The index of this branch in relation to its sibling branches, or undefined.
1114
+ */
745
1115
  index() {
746
1116
  if (!this._parent) {
747
1117
  return undefined;
@@ -752,15 +1122,76 @@ class TreeBranch {
752
1122
  assert(index >= 0);
753
1123
  return index;
754
1124
  }
1125
+ /** @returns `true` if the branch is destroyed, `false` otherwise */
1126
+ isDestroyed() {
1127
+ return this.treeNodeBase.isDestroyed();
1128
+ }
1129
+ /**
1130
+ * @returns
1131
+ * The data that was passed into the `branchOptions`' `meta` property
1132
+ * at construction.
1133
+ */
755
1134
  meta() {
756
1135
  return this.branchOptions.meta ?? {};
757
1136
  }
1137
+ /**
1138
+ * @returns
1139
+ * This branch's parent node (which may be a TreeBranch or TreeRoot).
1140
+ * If this branch has no parent, (eg, if this branch has been pruned) this
1141
+ * method will return undefined.
1142
+ */
758
1143
  parent() {
759
1144
  return this._parent;
760
1145
  }
1146
+ /**
1147
+ * Provides a model describing this branch's descendants.
1148
+ *
1149
+ * @returns
1150
+ * A multi-dimensional Map which describes the shape of this branch's
1151
+ * descendants.
1152
+ *
1153
+ * @example
1154
+ * A branch with no children will return an empty Map. A branch with
1155
+ * a single child will return a Map with a single entry, where the key is the index
1156
+ * of the branch (zero) and the value is an empty Map. A Tree like this:
1157
+ *
1158
+ * ```
1159
+ * ---Branch-------Branch
1160
+ * /
1161
+ * Branch-------Branch
1162
+ * \
1163
+ * ---Branch
1164
+ * ```
1165
+ * Will return a Map of matching shape:
1166
+ * ```
1167
+ * Map {
1168
+ * 0: Map { 0: Map {}},
1169
+ * 1: Map {},
1170
+ * 2: Map {}
1171
+ * }
1172
+ * ```
1173
+ */
761
1174
  plot() {
762
1175
  return this.treeNodeBase.plot();
763
1176
  }
1177
+ /**
1178
+ * Calculates the branch's position in the tree relative to the Root.
1179
+ *
1180
+ * @remarks
1181
+ * The position is described as an array of numbers, where each number
1182
+ * represents the index of the branch at that level of the tree.
1183
+ *
1184
+ * For example, if this branch is the first child of the Root, this method
1185
+ * will return [0]. If this branch is the second child of the first child
1186
+ * of the Root, this method will return [0, 1].
1187
+ *
1188
+ * If the branch is not related to a TreeRoot, (such as when it has been
1189
+ * pruned,) this method will throw an error.
1190
+ *
1191
+ * @returns
1192
+ * An array of numbers which describe the branch's position in the tree
1193
+ * relative to the Root.
1194
+ */
764
1195
  position() {
765
1196
  const index = this.index();
766
1197
  if (index === undefined) {
@@ -772,12 +1203,28 @@ class TreeBranch {
772
1203
  }
773
1204
  return [index];
774
1205
  }
1206
+ /**
1207
+ * Removes a branch from its tree without destroying it.
1208
+ *
1209
+ * @remarks
1210
+ * Removes this branch from its parent and detaches its associated
1211
+ * BranchComponent from the DOM. This puts the branch in a "pruned" state,
1212
+ * which may affect the behavior of other methods.
1213
+ *
1214
+ * A pruned branch can be reattached to any other node using the `graftTo` method.
1215
+ *
1216
+ * @returns
1217
+ * Itself, or undefined if it is already in a pruned state.
1218
+ */
775
1219
  prune() {
1220
+ if (this.isDestroyed()) {
1221
+ throw new TreeError("Cannot prune a destroyed tree branch");
1222
+ }
776
1223
  const parent = this._parent;
777
1224
  const index = this.index();
778
1225
  if (index === undefined || parent === undefined)
779
1226
  return;
780
- const container = parent.getContents().instance.branchesContainer;
1227
+ const container = parent.getBranchesContainer();
781
1228
  assert(container !== undefined);
782
1229
  this.detachedView = container.detach(index);
783
1230
  assert(this.detachedView !== null);
@@ -790,83 +1237,56 @@ class TreeBranch {
790
1237
  this._parent = undefined;
791
1238
  return this;
792
1239
  }
1240
+ /**
1241
+ * Get the root of the tree to which this Branch is attached.
1242
+ *
1243
+ * @returns
1244
+ * The TreeRoot of the tree this branch is in. If this branch is
1245
+ * does not have a root (such as when it has been pruned) this method will
1246
+ * return undefined.
1247
+ */
793
1248
  root() {
794
1249
  const parent = this.parent();
795
- if (parent === undefined) {
796
- return undefined;
797
- }
798
1250
  if (parent instanceof TreeBranch) {
799
1251
  return parent.root();
800
1252
  }
801
- if (parent instanceof TreeRoot) {
802
- return parent;
803
- }
804
- throw new Error("unexpected parent type");
805
- }
1253
+ assert(parent instanceof TreeRoot || parent === undefined);
1254
+ return parent;
1255
+ }
1256
+ /**
1257
+ * Traverses this branch's descendants in depth-first pre-order, executing
1258
+ * the provided callback function on each node. Traversal includes this branch.
1259
+ *
1260
+ * @param callback - A function to execute on each node.
1261
+ */
806
1262
  traverse(callback) {
807
1263
  callback(this);
808
1264
  this.treeNodeBase.traverse(callback);
809
1265
  }
810
- getContentCreatedSub() {
811
- const instance = this.contents.instance;
812
- return instance.contentCreated.subscribe((userlandComponentInstance) => {
813
- for (const [key, value] of Object.entries(this.branchOptions.inputBindings ?? {})) {
814
- userlandComponentInstance[key] = value;
815
- }
816
- for (const [key, value] of Object.entries(this.branchOptions.outputBindings ?? {})) {
817
- this.outputBindingSubscriptions.push(userlandComponentInstance[key].subscribe(value));
818
- }
819
- userlandComponentInstance.treeBranch = this;
820
- const dropzones = instance.dropzones;
821
- if (!dropzones) {
822
- throw new Error("dropzones not defined");
823
- }
824
- dropzoneRenderer.registerDropzones(dropzones, this);
825
- });
826
- }
827
- getInstanceSubscriptions() {
828
- const droppedSub = this.contents.instance.dropped.subscribe((placement) => {
829
- dropzoneRenderer.handleDrop(this, placement);
830
- });
831
- return [
832
- this.getContentCreatedSub(),
833
- this.getShowLowerZonesSub(),
834
- this.getShowUpperZonesSub(),
835
- droppedSub
836
- ];
837
- }
838
- getShowLowerZonesSub() {
839
- const instance = this.contents.instance;
840
- return instance.showDropzones
841
- .pipe(filter((direction) => direction === "lower"))
842
- .subscribe(() => {
843
- const currentDropzoneDisplayed = dropzoneRenderer.getCurrentDisplay();
844
- if (currentDropzoneDisplayed?.treeBranch === this &&
845
- currentDropzoneDisplayed.direction === "lower") {
846
- return;
1266
+ checkGraftLocationValidity(newParent, index) {
1267
+ if (this.isDestroyed()) {
1268
+ throw new TreeError("Cannot graft a destroyed tree branch");
1269
+ }
1270
+ if (newParent.isDestroyed()) {
1271
+ throw new TreeError("Cannot graft to a destroyed tree branch");
1272
+ }
1273
+ if (typeof index === "number" &&
1274
+ this.indexIsOutOfRange(newParent, index)) {
1275
+ throw new TreeError(`Cannot graft branch at index ${index} of the parent. Out of range.`);
1276
+ }
1277
+ this.traverse((node) => {
1278
+ if (node === newParent) {
1279
+ throw new TreeError("Cannot graft a branch to itself or any of its own descendants");
847
1280
  }
848
- dropzoneRenderer.showLowerZones(this);
849
- instance.triggerChangeDetection();
850
1281
  });
851
1282
  }
852
- getShowUpperZonesSub() {
853
- const instance = this.contents.instance;
854
- return instance.showDropzones
855
- .pipe(filter((direction) => direction === "upper"))
856
- .subscribe(() => {
857
- const currentDropzoneDisplayed = dropzoneRenderer.getCurrentDisplay();
858
- if (currentDropzoneDisplayed?.treeBranch === this &&
859
- currentDropzoneDisplayed.direction === "upper") {
860
- return;
861
- }
862
- dropzoneRenderer.showUpperZones(this);
863
- instance.triggerChangeDetection();
864
- });
1283
+ indexIsOutOfRange(parent, index) {
1284
+ return index < 0 || index > parent.branches().length;
865
1285
  }
866
1286
  reattachView(index) {
867
1287
  assert(this._parent !== undefined);
868
1288
  assert(this.detachedView !== null);
869
- const container = this._parent.getContents().instance.branchesContainer;
1289
+ const container = this._parent.getBranchesContainer();
870
1290
  assert(container !== undefined);
871
1291
  this.detachedView.reattach();
872
1292
  container.insert(this.detachedView, index);
@@ -876,7 +1296,8 @@ class TreeBranch {
876
1296
  const root = parent.root();
877
1297
  assert(root !== undefined);
878
1298
  const options = config.getConfig(root);
879
- const branchesContainerEl = this.contents.location.nativeElement
1299
+ const branchesContainerEl = this.branchController
1300
+ .getNativeElement()
880
1301
  .getElementsByClassName("branches-container")
881
1302
  .item(0);
882
1303
  assert(branchesContainerEl instanceof HTMLElement);
@@ -916,7 +1337,7 @@ class DropzoneRenderer {
916
1337
  if (this.currentDisplay === null)
917
1338
  return;
918
1339
  for (const branch of this.registry.values()) {
919
- const instance = branch.getContents().instance;
1340
+ const instance = branch.getComponentInstance();
920
1341
  instance.showInnerDropzone = false;
921
1342
  if (instance instanceof BranchComponent) {
922
1343
  instance.showLateralDropzone = false;
@@ -1017,7 +1438,7 @@ class DropzoneRenderer {
1017
1438
  return true;
1018
1439
  }
1019
1440
  if (treeNode instanceof TreeBranch) {
1020
- const allowNesting = config.getConfig(treeNode.root())?.allowNesting ??
1441
+ const allowNesting = config.getConfig(treeNode.root())?.dragAndDrop?.allowNesting ??
1021
1442
  (() => true);
1022
1443
  return allowNesting(treeNode);
1023
1444
  }
@@ -1027,11 +1448,13 @@ class DropzoneRenderer {
1027
1448
  const sourceNode = dragState.getDragData();
1028
1449
  assert(sourceNode instanceof TreeBranch);
1029
1450
  if (parent instanceof TreeRoot) {
1030
- const allowDrop = config.getConfig(parent)?.allowDrop ?? (() => true);
1451
+ const allowDrop = config.getConfig(parent)?.dragAndDrop?.allowDrop ??
1452
+ (() => true);
1031
1453
  return allowDrop(sourceNode, parent, index);
1032
1454
  }
1033
1455
  if (parent instanceof TreeBranch) {
1034
- const allowDrop = config.getConfig(parent.root())?.allowDrop ?? (() => true);
1456
+ const allowDrop = config.getConfig(parent.root())?.dragAndDrop?.allowDrop ??
1457
+ (() => true);
1035
1458
  return allowDrop(sourceNode, parent, index);
1036
1459
  }
1037
1460
  throw new Error("unsupported treeNode type");
@@ -1039,31 +1462,48 @@ class DropzoneRenderer {
1039
1462
  showInnerZone(treeNode) {
1040
1463
  if (!this.nestingAllowed(treeNode) || !this.dropAllowed(treeNode, 0))
1041
1464
  return;
1042
- treeNode.getContents().instance.showInnerDropzone = true;
1465
+ treeNode.getComponentInstance().showInnerDropzone = true;
1043
1466
  }
1044
1467
  showLateralZone(treeBranch) {
1045
1468
  const index = treeBranch.index();
1046
1469
  assert(index !== undefined);
1047
1470
  if (!this.dropAllowed(treeBranch.parent(), index + 1))
1048
1471
  return;
1049
- treeBranch.getContents().instance.showLateralDropzone = true;
1472
+ treeBranch.getComponentInstance().showLateralDropzone = true;
1050
1473
  }
1051
1474
  }
1052
1475
  const dropzoneRenderer = new DropzoneRenderer();
1053
1476
 
1054
1477
  class TreeDragAndDropService {
1478
+ /** Hides all Dropzones */
1055
1479
  clearDropzones() {
1056
1480
  dropzoneRenderer.clearCurrentDisplay();
1057
1481
  }
1058
- dragStart(treeBranch, event) {
1059
- dragAndDrop.dragStart(treeBranch, event);
1060
- }
1482
+ /**
1483
+ * @returns An object that indicates which dropzones are currently being displayed.
1484
+ * If no dropzones are being displayed, then null is returned.
1485
+ */
1061
1486
  getCurrentlyDisplayedDropzoneFamily() {
1062
1487
  return dropzoneRenderer.getCurrentDisplay();
1063
1488
  }
1489
+ /**
1490
+ * Causes the dropzone of the TreeRoot to be displayed.
1491
+ *
1492
+ * @remarks
1493
+ * This is a useful function when you want to show the dropzone of a TreeRoot
1494
+ * that has no child branches.
1495
+ *
1496
+ * @param root - The TreeRoot whose dropzone you want to show.
1497
+ */
1064
1498
  showRootDropzone(root) {
1065
1499
  dropzoneRenderer.showLowerZones(root);
1066
1500
  }
1501
+ /**
1502
+ * @returns An observable that emits a number whenever the drag state changes.
1503
+ *
1504
+ * @remarks
1505
+ * See the `DragStates` enum for a list of possible states.
1506
+ */
1067
1507
  state() {
1068
1508
  return dragState.events();
1069
1509
  }
@@ -1074,19 +1514,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
1074
1514
  type: Injectable
1075
1515
  }] });
1076
1516
 
1517
+ /** Makes an TreeBranch draggable when the host element is dragged */
1077
1518
  class DraggableDirective {
1078
- constructor(dragAndDropService, renderer, hostElement) {
1079
- this.dragAndDropService = dragAndDropService;
1519
+ constructor(renderer, hostElement) {
1080
1520
  renderer.setAttribute(hostElement.nativeElement, "draggable", "true");
1081
1521
  renderer.setStyle(hostElement.nativeElement, "cursor", "grab");
1082
1522
  }
1083
1523
  onDragstart(event) {
1084
1524
  if (this.limbleTreeDraggable === undefined)
1085
1525
  return;
1086
- this.dragAndDropService.dragStart(this.limbleTreeDraggable, event);
1526
+ dragAndDrop.dragStart(this.limbleTreeDraggable, event);
1087
1527
  }
1088
1528
  }
1089
- DraggableDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: DraggableDirective, deps: [{ token: TreeDragAndDropService }, { token: i0.Renderer2 }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
1529
+ DraggableDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: DraggableDirective, deps: [{ token: i0.Renderer2 }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
1090
1530
  DraggableDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.2.12", type: DraggableDirective, isStandalone: true, selector: "[limbleTreeDraggable]", inputs: { limbleTreeDraggable: "limbleTreeDraggable" }, host: { listeners: { "dragstart": "onDragstart($event)" } }, ngImport: i0 });
1091
1531
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: DraggableDirective, decorators: [{
1092
1532
  type: Directive,
@@ -1094,13 +1534,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
1094
1534
  selector: "[limbleTreeDraggable]",
1095
1535
  standalone: true
1096
1536
  }]
1097
- }], ctorParameters: function () { return [{ type: TreeDragAndDropService }, { type: i0.Renderer2 }, { type: i0.ElementRef }]; }, propDecorators: { limbleTreeDraggable: [{
1537
+ }], ctorParameters: function () { return [{ type: i0.Renderer2 }, { type: i0.ElementRef }]; }, propDecorators: { limbleTreeDraggable: [{
1098
1538
  type: Input
1099
1539
  }], onDragstart: [{
1100
1540
  type: HostListener,
1101
1541
  args: ["dragstart", ["$event"]]
1102
1542
  }] } });
1103
1543
 
1544
+ /**
1545
+ * An Angular module containing all of the entities which provide Drag-And-Drop
1546
+ * functionality.
1547
+ */
1104
1548
  class TreeDragAndDropModule {
1105
1549
  }
1106
1550
  TreeDragAndDropModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: TreeDragAndDropModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
@@ -1132,32 +1576,34 @@ class LegacyTree {
1132
1576
  upgradeOptions(legacyOptions) {
1133
1577
  return {
1134
1578
  indentation: legacyOptions.indent,
1135
- allowNesting: (branch) => {
1136
- if (legacyOptions.listMode === true) {
1137
- return false;
1579
+ dragAndDrop: {
1580
+ allowNesting: (branch) => {
1581
+ if (legacyOptions.listMode === true) {
1582
+ return false;
1583
+ }
1584
+ if (legacyOptions.allowNesting === undefined) {
1585
+ return true;
1586
+ }
1587
+ if (typeof legacyOptions.allowNesting === "boolean") {
1588
+ return legacyOptions.allowNesting;
1589
+ }
1590
+ return legacyOptions.allowNesting(branch);
1591
+ },
1592
+ allowDragging: (branch) => {
1593
+ if (legacyOptions.allowDragging === undefined) {
1594
+ return true;
1595
+ }
1596
+ if (typeof legacyOptions.allowDragging === "boolean") {
1597
+ return legacyOptions.allowDragging;
1598
+ }
1599
+ return legacyOptions.allowDragging(branch);
1600
+ },
1601
+ allowDrop: (source, parent, index) => {
1602
+ if (legacyOptions.allowDrop === undefined) {
1603
+ return true;
1604
+ }
1605
+ return legacyOptions.allowDrop(source, parent, index);
1138
1606
  }
1139
- if (legacyOptions.allowNesting === undefined) {
1140
- return true;
1141
- }
1142
- if (typeof legacyOptions.allowNesting === "boolean") {
1143
- return legacyOptions.allowNesting;
1144
- }
1145
- return legacyOptions.allowNesting(branch);
1146
- },
1147
- allowDragging: (branch) => {
1148
- if (legacyOptions.allowDragging === undefined) {
1149
- return true;
1150
- }
1151
- if (typeof legacyOptions.allowDragging === "boolean") {
1152
- return legacyOptions.allowDragging;
1153
- }
1154
- return legacyOptions.allowDragging(branch);
1155
- },
1156
- allowDrop: (source, parent, index) => {
1157
- if (legacyOptions.allowDrop === undefined) {
1158
- return true;
1159
- }
1160
- return legacyOptions.allowDrop(source, parent, index);
1161
1607
  }
1162
1608
  };
1163
1609
  }
@@ -1170,9 +1616,6 @@ class LegacyTree {
1170
1616
  options.defaultComponent?.bindings ??
1171
1617
  {});
1172
1618
  const nodeData = node;
1173
- delete nodeData.nodes;
1174
- delete nodeData.collapsed;
1175
- delete nodeData.component;
1176
1619
  const branch = parent.grow(component, {
1177
1620
  inputBindings: bindings,
1178
1621
  meta: { nodeData }
@@ -1273,6 +1716,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
1273
1716
  }]
1274
1717
  }] });
1275
1718
 
1719
+ /**
1720
+ * Import this Angular module into your application to gain access to the
1721
+ * components, directives, and services provided by Limble Tree.
1722
+ */
1276
1723
  class LimbleTreeModule {
1277
1724
  }
1278
1725
  LimbleTreeModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: LimbleTreeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
@@ -1296,5 +1743,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
1296
1743
  * Generated bundle index. Do not edit.
1297
1744
  */
1298
1745
 
1299
- export { DragEndEvent, DragStartEvent, DraggableDirective, DragoverNoChangeDetectDirective, DropEvent, GraftEvent, LegacyTree, LimbleTreeLegacyModule, LimbleTreeModule, LimbleTreeRootComponent, PruneEvent, TreeBranch, TreeCollapseModule, TreeCollapseService, TreeDragAndDropModule, TreeDragAndDropService, TreeError, TreeRoot, TreeService, config };
1746
+ export { DestructionEvent, DragEndEvent, DragStartEvent, DraggableDirective, DragoverNoChangeDetectDirective, DropEvent, GraftEvent, LegacyTree, LimbleTreeLegacyModule, LimbleTreeModule, LimbleTreeRootComponent, PruneEvent, TreeBranch, TreeCollapseModule, TreeCollapseService, TreeDragAndDropModule, TreeDragAndDropService, TreeError, TreeRoot, TreeService };
1300
1747
  //# sourceMappingURL=limble-limble-tree.mjs.map