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