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

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 (76) hide show
  1. package/README.md +4 -0
  2. package/esm2020/lib/components/branch/branch.component.mjs +1 -1
  3. package/esm2020/lib/components/dropzone/dropzone.component.mjs +2 -2
  4. package/esm2020/lib/core/configuration/configuration.mjs +1 -1
  5. package/esm2020/lib/core/configuration/tree-options.interface.mjs +1 -1
  6. package/esm2020/lib/core/index.mjs +1 -2
  7. package/esm2020/lib/core/tree-branch/tree-branch.mjs +210 -9
  8. package/esm2020/lib/core/tree-node-base.mjs +13 -1
  9. package/esm2020/lib/core/tree-root/tree-root.mjs +111 -3
  10. package/esm2020/lib/core/tree-service/tree.service.mjs +7 -1
  11. package/esm2020/lib/events/drag/drag-end-event.mjs +9 -7
  12. package/esm2020/lib/events/drag/drag-start-event.mjs +2 -4
  13. package/esm2020/lib/events/drag/drop-event.mjs +2 -4
  14. package/esm2020/lib/events/general/destruction-event.mjs +2 -4
  15. package/esm2020/lib/events/relational/graft-event.mjs +2 -4
  16. package/esm2020/lib/events/relational/prune-event.mjs +2 -4
  17. package/esm2020/lib/events/relational/relational-tree-event.interface.mjs +1 -1
  18. package/esm2020/lib/extras/collapse/collapse.module.mjs +2 -1
  19. package/esm2020/lib/extras/collapse/collapse.service.mjs +26 -1
  20. package/esm2020/lib/extras/drag-and-drop/drag-and-drop.module.mjs +5 -1
  21. package/esm2020/lib/extras/drag-and-drop/drag-and-drop.service.mjs +21 -5
  22. package/esm2020/lib/extras/drag-and-drop/draggable.directive.mjs +7 -7
  23. package/esm2020/lib/extras/drag-and-drop/dragover-no-change-detect.mjs +10 -11
  24. package/esm2020/lib/legacy/legacy-component-obj.interface.mjs +1 -1
  25. package/esm2020/lib/legacy/legacy-tree-data.interface.mjs +1 -1
  26. package/esm2020/lib/legacy/legacy-tree-options.interface.mjs +1 -1
  27. package/esm2020/lib/legacy/legacy-tree.mjs +3 -2
  28. package/esm2020/lib/legacy/limble-tree-root/limble-tree-root.component.mjs +1 -1
  29. package/esm2020/lib/limble-tree.module.mjs +6 -2
  30. package/esm2020/lib/structure/component-container.interface.mjs +1 -1
  31. package/esm2020/lib/structure/index.mjs +1 -2
  32. package/esm2020/lib/structure/tree-branch-node.interface.mjs +1 -1
  33. package/esm2020/lib/structure/tree-event.interface.mjs +1 -1
  34. package/esm2020/lib/structure/tree-node.interface.mjs +1 -1
  35. package/esm2020/lib/structure/tree-relationship.interface.mjs +1 -1
  36. package/esm2020/shared/has-property.mjs +4 -0
  37. package/fesm2015/limble-limble-tree.mjs +569 -195
  38. package/fesm2015/limble-limble-tree.mjs.map +1 -1
  39. package/fesm2020/limble-limble-tree.mjs +568 -194
  40. package/fesm2020/limble-limble-tree.mjs.map +1 -1
  41. package/lib/core/configuration/configuration.d.ts +2 -2
  42. package/lib/core/configuration/tree-options.interface.d.ts +38 -16
  43. package/lib/core/index.d.ts +0 -1
  44. package/lib/core/tree-branch/tree-branch.d.ts +185 -8
  45. package/lib/core/tree-node-base.d.ts +1 -0
  46. package/lib/core/tree-root/tree-root.d.ts +101 -7
  47. package/lib/core/tree-service/tree.service.d.ts +8 -2
  48. package/lib/events/drag/drag-end-event.d.ts +11 -8
  49. package/lib/events/drag/drag-start-event.d.ts +6 -5
  50. package/lib/events/drag/drop-event.d.ts +6 -6
  51. package/lib/events/general/destruction-event.d.ts +5 -5
  52. package/lib/events/relational/graft-event.d.ts +5 -6
  53. package/lib/events/relational/prune-event.d.ts +5 -6
  54. package/lib/events/relational/relational-tree-event.interface.d.ts +5 -1
  55. package/lib/extras/collapse/collapse.module.d.ts +1 -0
  56. package/lib/extras/collapse/collapse.service.d.ts +25 -0
  57. package/lib/extras/drag-and-drop/drag-and-drop.module.d.ts +4 -0
  58. package/lib/extras/drag-and-drop/drag-and-drop.service.d.ts +22 -3
  59. package/lib/extras/drag-and-drop/draggable.directive.d.ts +3 -4
  60. package/lib/extras/drag-and-drop/dragover-no-change-detect.d.ts +7 -3
  61. package/lib/legacy/legacy-component-obj.interface.d.ts +1 -1
  62. package/lib/legacy/legacy-tree-data.interface.d.ts +1 -1
  63. package/lib/legacy/legacy-tree-options.interface.d.ts +2 -2
  64. package/lib/legacy/legacy-tree.d.ts +4 -4
  65. package/lib/legacy/limble-tree-root/limble-tree-root.component.d.ts +4 -4
  66. package/lib/limble-tree.module.d.ts +4 -0
  67. package/lib/structure/component-container.interface.d.ts +1 -1
  68. package/lib/structure/index.d.ts +0 -1
  69. package/lib/structure/tree-branch-node.interface.d.ts +2 -2
  70. package/lib/structure/tree-event.interface.d.ts +3 -3
  71. package/lib/structure/tree-node.interface.d.ts +12 -8
  72. package/lib/structure/tree-relationship.interface.d.ts +2 -2
  73. package/package.json +1 -1
  74. package/shared/has-property.d.ts +3 -0
  75. package/esm2020/lib/structure/event-conduit.interface.mjs +0 -2
  76. package/lib/structure/event-conduit.interface.d.ts +0 -6
@@ -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() {
@@ -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,115 +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
- assert(dataTransfer instanceof DataTransfer);
239
- const nativeElement = treeBranch.getNativeElement();
240
- const [xOffset, yOffset] = this.getDragImageOffsets(event, nativeElement);
241
- dataTransfer.setDragImage(nativeElement, xOffset, yOffset);
242
- }
243
- watchForDragend(treeBranch, event) {
244
- const oldParent = treeBranch.parent();
245
- const oldIndex = treeBranch.index();
246
- assert(oldParent !== undefined && oldIndex !== undefined);
247
- event.target?.addEventListener("dragend", (dragend) => {
248
- if (dragState.state() !== DragStates.Dropped) {
249
- //The drag ended but a drop never occurred, so put the dragged branch back where it started.
250
- this.dragAborted$.next(dragend);
251
- this.graftDraggedBranch(treeBranch, oldParent, oldIndex);
252
- }
253
- dragState.restart();
254
- const newParent = treeBranch.parent();
255
- assert(newParent !== undefined);
256
- const newIndex = treeBranch.index();
257
- assert(newIndex !== undefined);
258
- treeBranch.dispatch(new DragEndEvent(treeBranch, {
259
- oldParent,
260
- oldIndex,
261
- newParent,
262
- newIndex
263
- }));
264
- }, { once: true });
265
- }
266
- draggingAllowed(treeBranch) {
267
- const allowDragging = config.getConfig(treeBranch.root())?.dragAndDrop?.allowDragging ??
268
- (() => true);
269
- return allowDragging(treeBranch);
270
- }
271
- graftDraggedBranch(treeBranch, parent, index) {
272
- treeBranch.graftTo(parent, index);
273
- treeBranch.getNativeElement().style.display = "block";
274
- dragState.dropped();
275
- }
276
- }
277
- const dragAndDrop = new DragAndDrop();
278
-
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
+ */
279
152
  class DragoverNoChangeDetectDirective {
280
153
  constructor(ngZone, el) {
281
154
  this.ngZone = ngZone;
282
155
  this.el = el;
283
156
  this.dragoverNoChangeDetect = new EventEmitter();
284
- this.dragoverEventThrottle = 25;
285
157
  }
286
158
  ngOnInit() {
287
159
  this.ngZone.runOutsideAngular(() => {
288
- this.eventSubscription = fromEvent(this.el.nativeElement, "dragover")
289
- .pipe(throttleTime(this.dragoverEventThrottle))
290
- .subscribe(($event) => {
160
+ this.eventSubscription = fromEvent(this.el.nativeElement, "dragover").subscribe(($event) => {
291
161
  this.dragoverNoChangeDetect.emit($event);
292
162
  });
293
163
  });
@@ -299,16 +169,14 @@ class DragoverNoChangeDetectDirective {
299
169
  }
300
170
  }
301
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 });
302
- 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 });
303
173
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: DragoverNoChangeDetectDirective, decorators: [{
304
174
  type: Directive,
305
175
  args: [{
306
176
  standalone: true,
307
177
  selector: "[dragoverNoChangeDetect]"
308
178
  }]
309
- }], ctorParameters: function () { return [{ type: i0.NgZone }, { type: i0.ElementRef }]; }, propDecorators: { dragoverEventThrottle: [{
310
- type: Input
311
- }], dragoverNoChangeDetect: [{
179
+ }], ctorParameters: function () { return [{ type: i0.NgZone }, { type: i0.ElementRef }]; }, propDecorators: { dragoverNoChangeDetect: [{
312
180
  type: Output
313
181
  }] } });
314
182
 
@@ -332,10 +200,10 @@ class DropzoneComponent {
332
200
  }
333
201
  }
334
202
  DropzoneComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: DropzoneComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
335
- 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"] }] });
336
204
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: DropzoneComponent, decorators: [{
337
205
  type: Component,
338
- 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"] }]
339
207
  }], propDecorators: { placement: [{
340
208
  type: Input
341
209
  }], dropped: [{
@@ -391,7 +259,7 @@ class BranchComponent {
391
259
  }
392
260
  }
393
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 });
394
- 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"] }] });
395
263
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: BranchComponent, decorators: [{
396
264
  type: Component,
397
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"] }]
@@ -414,6 +282,166 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
414
282
  type: Output
415
283
  }] } });
416
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
+ function hasProperty(input, prop) {
441
+ return typeof input === "object" && input !== null && prop in input;
442
+ }
443
+
444
+ /** Emits when a branch is grafted to another tree node */
417
445
  class GraftEvent {
418
446
  constructor(source, relationship) {
419
447
  this._source = source;
@@ -424,9 +452,6 @@ class GraftEvent {
424
452
  child() {
425
453
  return this._child;
426
454
  }
427
- type() {
428
- return "graft";
429
- }
430
455
  index() {
431
456
  return this._index;
432
457
  }
@@ -438,6 +463,7 @@ class GraftEvent {
438
463
  }
439
464
  }
440
465
 
466
+ /** Emitted when a branch is pruned from its parent branch */
441
467
  class PruneEvent {
442
468
  constructor(source, relationship) {
443
469
  this._source = source;
@@ -448,9 +474,6 @@ class PruneEvent {
448
474
  child() {
449
475
  return this._child;
450
476
  }
451
- type() {
452
- return "prune";
453
- }
454
477
  index() {
455
478
  return this._index;
456
479
  }
@@ -497,6 +520,17 @@ class TreeNodeBase {
497
520
  getBranch(index) {
498
521
  return this._branches[index];
499
522
  }
523
+ handleUserlandError(error) {
524
+ const message = hasProperty(error, "message")
525
+ ? error.message
526
+ : "Unknown error";
527
+ throw new TreeError(`Failed to grow branch: ${message}`, {
528
+ // This cast to `any` is due to an issue in typescript that has been
529
+ // resolved at least by version 4.9.5. When we upgrade our typescript
530
+ // version we can remove the cast.
531
+ cause: error
532
+ });
533
+ }
500
534
  isDestroyed() {
501
535
  return this.destroyed;
502
536
  }
@@ -527,13 +561,11 @@ class TreeNodeBase {
527
561
  }
528
562
  }
529
563
 
564
+ /** Emitted when a node is destroyed */
530
565
  class DestructionEvent {
531
566
  constructor(source) {
532
567
  this._source = source;
533
568
  }
534
- type() {
535
- return "destruction";
536
- }
537
569
  source() {
538
570
  return this._source;
539
571
  }
@@ -609,6 +641,11 @@ class RootController {
609
641
  }
610
642
  }
611
643
 
644
+ /**
645
+ * Represents the base of the tree. It renders a very simple container for child
646
+ * branches. It has methods for creating and accessing those branches. It emits
647
+ * events when things happen in the tree.
648
+ */
612
649
  class TreeRoot {
613
650
  constructor(viewContainerRef) {
614
651
  this.viewContainerRef = viewContainerRef;
@@ -616,9 +653,19 @@ class TreeRoot {
616
653
  this.rootController = new RootController(this, viewContainerRef);
617
654
  this.detectChanges();
618
655
  }
656
+ /** @returns All child branches as an array of TreeBranch instances */
619
657
  branches() {
620
658
  return this.treeNodeBase.branches();
621
659
  }
660
+ /**
661
+ * Recursively destroys all branches of the tree, as well as itself.
662
+ *
663
+ * @remarks
664
+ * This releases all resources held or consumed by the tree.
665
+ *
666
+ * It is important to call this method when a tree is discarded, otherwise
667
+ * the tree will remain in memory and continue to consume resources.
668
+ */
622
669
  destroy() {
623
670
  if (this.isDestroyed()) {
624
671
  throw new TreeError("Cannot destroy a destroyed tree root");
@@ -630,64 +677,163 @@ class TreeRoot {
630
677
  config.delete(this);
631
678
  this.dispatch(new DestructionEvent(this));
632
679
  }
680
+ /** Run Angular change detection on the root of the tree */
633
681
  detectChanges() {
634
682
  this.rootController.detectChanges();
635
683
  }
684
+ /**
685
+ * Emits the specified TreeEvent.
686
+ *
687
+ * @remarks
688
+ * Caution: It is not recommended to manually emit TreeEvents that are already
689
+ * provided by the library. For example, it is not recommended to emit a
690
+ * `GraftEvent`, `DestructionEvent`, etc. These events may be used by the tree,
691
+ * and emitting them manually may cause unexpected behavior. Instead, we
692
+ * recommend implementing the TreeEvent interface with your own custom events
693
+ * and dispatching those.
694
+ *
695
+ * @param event - The TreeEvent that will be emitted.
696
+ */
636
697
  dispatch(event) {
637
698
  this.treeNodeBase.dispatch(event);
638
699
  }
700
+ /**
701
+ * @returns
702
+ * An observable that emits TreeEvents whenever an event is dispatched
703
+ * in the root or any of its descendant branches.
704
+ */
639
705
  events() {
640
706
  return this.treeNodeBase.events();
641
707
  }
708
+ /**
709
+ * @returns
710
+ * The child branch at the specified index, or undefined if there is
711
+ * no child branch at the specified index.
712
+ */
642
713
  getBranch(index) {
643
714
  return this.treeNodeBase.getBranch(index);
644
715
  }
716
+ /** @returns The ViewContainerRef in which child branches are rendered */
645
717
  getBranchesContainer() {
646
718
  if (this.isDestroyed()) {
647
719
  throw new TreeError("Cannot get branches container from a destroyed tree root");
648
720
  }
649
721
  return this.rootController.getBranchesContainer();
650
722
  }
723
+ /**
724
+ * Retrieves the RootComponent.
725
+ *
726
+ * @remarks
727
+ * The RootComponent holds the BranchesContainer, as well as a single Dropzone
728
+ * for drag-and-drop operations.
729
+ *
730
+ * @returns The instance of RootComponent that is rendered by this class.
731
+ */
651
732
  getComponentInstance() {
652
733
  if (this.isDestroyed()) {
653
734
  throw new TreeError("Cannot get component instance from a destroyed tree root");
654
735
  }
655
736
  return this.rootController.getComponentInstance();
656
737
  }
738
+ /** @returns The Host View in which the RootComponent is rendered */
657
739
  getHostView() {
658
740
  if (this.isDestroyed()) {
659
741
  throw new TreeError("Cannot get component host view from a destroyed tree root");
660
742
  }
661
743
  return this.rootController.getHostView();
662
744
  }
745
+ /** @returns The RootComponent as a native HTML Element */
663
746
  getNativeElement() {
664
747
  if (this.isDestroyed()) {
665
748
  throw new TreeError("Cannot get native element from a destroyed tree root");
666
749
  }
667
750
  return this.rootController.getNativeElement();
668
751
  }
752
+ /**
753
+ * Appends a new child branch to this branch. The child branch will render
754
+ * the specified component according to the (optional) configuration parameter.
755
+ *
756
+ * @param component - The component to render in the new child branch.
757
+ * @param options - Configuration options for the new child branch.
758
+ *
759
+ * @returns
760
+ * The newly-created child branch.
761
+ */
669
762
  grow(component, options) {
670
763
  if (this.isDestroyed()) {
671
764
  throw new TreeError("Cannot grow a branch on a destroyed tree root");
672
765
  }
673
- return new TreeBranch(this, { component, ...options });
766
+ try {
767
+ return new TreeBranch(this, { component, ...options });
768
+ }
769
+ catch (error) {
770
+ this.handleUserlandError(error);
771
+ }
674
772
  }
773
+ /** @returns `true` if the tree is destroyed, `false` otherwise */
675
774
  isDestroyed() {
676
775
  return this.treeNodeBase.isDestroyed();
677
776
  }
777
+ /**
778
+ * Provides a model describing the shape of the tree.
779
+ *
780
+ * @returns A multi-dimensional Map which describes the shape of the tree.
781
+ *
782
+ * @example
783
+ * For example, an empty tree will return an empty Map. A tree with a single
784
+ * branch will return a Map with a single entry, where the key is the index
785
+ * of the branch (zero) and the value is an empty Map. A Tree like this:
786
+ *
787
+ * ```
788
+ * ---Branch-------Branch
789
+ * /
790
+ * Root-------Branch
791
+ * \
792
+ * ---Branch
793
+ * ```
794
+ * Will return a Map of matching shape:
795
+ * ```
796
+ * Map {
797
+ * 0: Map { 0: Map {}},
798
+ * 1: Map {},
799
+ * 2: Map {}
800
+ * }
801
+ * ```
802
+ */
678
803
  plot() {
679
804
  return this.treeNodeBase.plot();
680
805
  }
806
+ /** @returns Itself */
681
807
  root() {
682
808
  return this;
683
809
  }
810
+ /**
811
+ * Traverses the tree in depth-first pre-order, executing the provided
812
+ * callback function on each node. Traversal includes the Root.
813
+ *
814
+ * @param callback - A function to execute on each node.
815
+ */
684
816
  traverse(callback) {
685
817
  callback(this);
686
818
  this.treeNodeBase.traverse(callback);
687
819
  }
820
+ handleUserlandError(error) {
821
+ // When an error occurs in a userland component during a tree operation,
822
+ // it can cause undefined, bizarre behavior in the tree. To prevent this,
823
+ // we destroy the tree and throw an error instead. This helps protect
824
+ // the end-user's data from corruption.
825
+ this.destroy();
826
+ this.treeNodeBase.handleUserlandError(error);
827
+ }
688
828
  }
689
829
 
830
+ /** Responsible for the creation of new trees. */
690
831
  class TreeService {
832
+ /**
833
+ * Creates a new, empty tree structure inside the provided container.
834
+ *
835
+ * @returns A `TreeRoot` representing the base of the new tree.
836
+ */
691
837
  createEmptyTree(container, options = {}) {
692
838
  container.clear();
693
839
  const root = new TreeRoot(container);
@@ -796,6 +942,15 @@ class BranchController {
796
942
  }
797
943
  }
798
944
 
945
+ /** Represents a standard node in a tree. Renders a BranchComponent.
946
+ *
947
+ * @remarks
948
+ * This class renders a branch component, which does the following:
949
+ * 1. Renders a component provided by the user
950
+ * 2. Provides a container in which child branches may be rendered
951
+ * 3. Contains two Dropzones: one for dropping branches below this branch (as a
952
+ * sibling), and one for dropping branches as a first child of this branch.
953
+ */
799
954
  class TreeBranch {
800
955
  constructor(parent, branchOptions) {
801
956
  this.branchOptions = branchOptions;
@@ -804,26 +959,36 @@ class TreeBranch {
804
959
  const parentBranchesContainer = parent.getBranchesContainer();
805
960
  assert(parentBranchesContainer !== undefined);
806
961
  this.branchController = new BranchController(this, parentBranchesContainer);
962
+ const hostView = this.branchController.getHostView();
807
963
  this.setIndentation(parent);
808
964
  if (parent instanceof TreeBranch &&
809
965
  parent.branchOptions.defaultCollapsed === true) {
810
966
  treeCollapser.storePrecollapsedNode(parent, this);
811
- this.detachedView = this.branchController.getHostView();
967
+ this.detachedView = hostView;
812
968
  }
813
969
  else {
814
- parentBranchesContainer.insert(this.branchController.getHostView());
815
- this.detectChanges();
970
+ parentBranchesContainer.insert(hostView);
816
971
  this._parent = parent;
817
972
  this.dispatch(new GraftEvent(this, {
818
973
  parent: this._parent,
819
974
  child: this,
820
975
  index: this._parent.branches().length
821
976
  }));
977
+ this.detectChanges();
822
978
  }
823
979
  }
980
+ /** @returns All child branches as an array of TreeBranch instances, in order. */
824
981
  branches() {
825
982
  return this.treeNodeBase.branches();
826
983
  }
984
+ /**
985
+ * Recursively destroys all descendant branches, as well as itself. This
986
+ * releases all resources held or consumed by this branch and its descendants.
987
+ *
988
+ * @remarks
989
+ * It is important to call this method when a branch is discarded, otherwise
990
+ * the branch will remain in memory and continue to consume resources.
991
+ */
827
992
  destroy() {
828
993
  if (this.isDestroyed()) {
829
994
  throw new TreeError("Cannot destroy a destroyed tree branch");
@@ -836,49 +1001,101 @@ class TreeBranch {
836
1001
  this.branchController.destroy();
837
1002
  this.dispatch(new DestructionEvent(this));
838
1003
  }
1004
+ /** Run Angular change detection on this branch */
839
1005
  detectChanges() {
840
1006
  this.branchController.detectChanges();
841
1007
  }
1008
+ /**
1009
+ * Emits the specified TreeEvent.
1010
+ *
1011
+ * @remarks
1012
+ * Caution: It is not recommended to manually emit TreeEvents that are already
1013
+ * provided by the library. For example, it is not recommended to emit a
1014
+ * `GraftEvent`, `DestructionEvent`, etc. These events may be used by the tree,
1015
+ * and emitting them manually may cause unexpected behavior. Instead, we
1016
+ * recommend implementing the TreeEvent interface with your own custom events
1017
+ * and dispatching those.
1018
+ *
1019
+ * @param event - The TreeEvent that will be emitted.
1020
+ */
842
1021
  dispatch(event) {
843
1022
  this.treeNodeBase.dispatch(event);
844
1023
  this._parent?.dispatch(event);
845
1024
  }
1025
+ /**
1026
+ * @returns
1027
+ * An observable that emits TreeEvents whenever an event is dispatched
1028
+ * in this branch or any of its descendant branches.
1029
+ */
846
1030
  events() {
847
1031
  return this.treeNodeBase.events();
848
1032
  }
1033
+ /**
1034
+ * @param index - The index of the child branch to retrieve.
1035
+ *
1036
+ * @returns
1037
+ * The child branch at the specified index, or undefined if there is
1038
+ * no child branch at the specified index.
1039
+ */
849
1040
  getBranch(index) {
850
1041
  return this.treeNodeBase.getBranch(index);
851
1042
  }
1043
+ /** @returns The ViewContainerRef in which child branches are rendered */
852
1044
  getBranchesContainer() {
853
1045
  if (this.isDestroyed()) {
854
1046
  throw new TreeError("Cannot get branches container from a destroyed tree branch");
855
1047
  }
856
1048
  return this.branchController.getBranchesContainer();
857
1049
  }
1050
+ /** @returns The instance of BranchComponent that is rendered by this class. */
858
1051
  getComponentInstance() {
859
1052
  if (this.isDestroyed()) {
860
1053
  throw new TreeError("Cannot get component instance from a destroyed tree branch");
861
1054
  }
862
1055
  return this.branchController.getComponentInstance();
863
1056
  }
1057
+ /** @returns The Host View in which the BranchComponent is rendered */
864
1058
  getHostView() {
865
1059
  if (this.isDestroyed()) {
866
1060
  throw new TreeError("Cannot get component host view from a destroyed tree branch");
867
1061
  }
868
1062
  return this.branchController.getHostView();
869
1063
  }
1064
+ /** @returns The BranchComponent as a native HTML Element */
870
1065
  getNativeElement() {
871
1066
  if (this.isDestroyed()) {
872
1067
  throw new TreeError("Cannot get native element from a destroyed tree branch");
873
1068
  }
874
1069
  return this.branchController.getNativeElement();
875
1070
  }
1071
+ /**
1072
+ * @returns
1073
+ * A ComponentRef containing the instance of the user-provided
1074
+ * component which is rendered by this branch.
1075
+ */
876
1076
  getUserlandComponentRef() {
877
1077
  if (this.isDestroyed()) {
878
1078
  throw new TreeError("Cannot get userland component from a destroyed tree branch");
879
1079
  }
880
1080
  return this.branchController.getUserlandComponentRef();
881
1081
  }
1082
+ /**
1083
+ * Attaches a branch to a new parent node.
1084
+ *
1085
+ * @remarks
1086
+ * If not already pruned, this method prunes (removes) this branch from its
1087
+ * current position in the tree; then grafts (reattaches) it as a child of the
1088
+ * specified parent branch at the specified index. If no index is specified,
1089
+ * the branch is appended as the last child of the parent. This causes this
1090
+ * branch's associated BranchComponent to be re-rendered in the DOM at the
1091
+ * new location.
1092
+ *
1093
+ * @param newParent - The new parent branch unto which this branch will be grafted.
1094
+ * @param index - The index at which this branch will be grafted. If not specified,
1095
+ * this branch will be appended as the last child of the new parent.
1096
+ *
1097
+ * @returns The index at which this branch was grafted.
1098
+ */
882
1099
  graftTo(newParent, index) {
883
1100
  this.checkGraftLocationValidity(newParent, index);
884
1101
  const ownIndex = this.index();
@@ -895,12 +1112,40 @@ class TreeBranch {
895
1112
  }));
896
1113
  return newIndex;
897
1114
  }
1115
+ /**
1116
+ * Appends a new child branch to this branch. The child branch will render
1117
+ * the specified component according to the (optional) configuration parameter.
1118
+ *
1119
+ * @param component - The component to render in the new child branch.
1120
+ * @param options - Configuration options for the new child branch.
1121
+ *
1122
+ * @returns
1123
+ * The newly-created child branch.
1124
+ */
898
1125
  grow(component, options) {
899
1126
  if (this.isDestroyed()) {
900
1127
  throw new TreeError("Cannot grow a branch on a destroyed tree branch");
901
1128
  }
902
- return new TreeBranch(this, { component, ...options });
1129
+ try {
1130
+ return new TreeBranch(this, { component, ...options });
1131
+ }
1132
+ catch (error) {
1133
+ this.handleUserlandError(error);
1134
+ }
903
1135
  }
1136
+ /**
1137
+ * Determines this branch's index in relation to its sibling branches.
1138
+ *
1139
+ * @remarks
1140
+ * For example, if it is the first child of its parent, this method will return
1141
+ * 0. If it is the second child of its parent, this method will return 1.
1142
+ *
1143
+ * If this branch has no parent, (eg, if this branch has been pruned) this
1144
+ * method will return undefined.
1145
+ *
1146
+ * @returns
1147
+ * The index of this branch in relation to its sibling branches, or undefined.
1148
+ */
904
1149
  index() {
905
1150
  if (!this._parent) {
906
1151
  return undefined;
@@ -911,18 +1156,76 @@ class TreeBranch {
911
1156
  assert(index >= 0);
912
1157
  return index;
913
1158
  }
1159
+ /** @returns `true` if the branch is destroyed, `false` otherwise */
914
1160
  isDestroyed() {
915
1161
  return this.treeNodeBase.isDestroyed();
916
1162
  }
1163
+ /**
1164
+ * @returns
1165
+ * The data that was passed into the `branchOptions`' `meta` property
1166
+ * at construction.
1167
+ */
917
1168
  meta() {
918
1169
  return this.branchOptions.meta ?? {};
919
1170
  }
1171
+ /**
1172
+ * @returns
1173
+ * This branch's parent node (which may be a TreeBranch or TreeRoot).
1174
+ * If this branch has no parent, (eg, if this branch has been pruned) this
1175
+ * method will return undefined.
1176
+ */
920
1177
  parent() {
921
1178
  return this._parent;
922
1179
  }
1180
+ /**
1181
+ * Provides a model describing this branch's descendants.
1182
+ *
1183
+ * @returns
1184
+ * A multi-dimensional Map which describes the shape of this branch's
1185
+ * descendants.
1186
+ *
1187
+ * @example
1188
+ * A branch with no children will return an empty Map. A branch with
1189
+ * a single child will return a Map with a single entry, where the key is the index
1190
+ * of the branch (zero) and the value is an empty Map. A Tree like this:
1191
+ *
1192
+ * ```
1193
+ * ---Branch-------Branch
1194
+ * /
1195
+ * Branch-------Branch
1196
+ * \
1197
+ * ---Branch
1198
+ * ```
1199
+ * Will return a Map of matching shape:
1200
+ * ```
1201
+ * Map {
1202
+ * 0: Map { 0: Map {}},
1203
+ * 1: Map {},
1204
+ * 2: Map {}
1205
+ * }
1206
+ * ```
1207
+ */
923
1208
  plot() {
924
1209
  return this.treeNodeBase.plot();
925
1210
  }
1211
+ /**
1212
+ * Calculates the branch's position in the tree relative to the Root.
1213
+ *
1214
+ * @remarks
1215
+ * The position is described as an array of numbers, where each number
1216
+ * represents the index of the branch at that level of the tree.
1217
+ *
1218
+ * For example, if this branch is the first child of the Root, this method
1219
+ * will return [0]. If this branch is the second child of the first child
1220
+ * of the Root, this method will return [0, 1].
1221
+ *
1222
+ * If the branch is not related to a TreeRoot, (such as when it has been
1223
+ * pruned,) this method will throw an error.
1224
+ *
1225
+ * @returns
1226
+ * An array of numbers which describe the branch's position in the tree
1227
+ * relative to the Root.
1228
+ */
926
1229
  position() {
927
1230
  const index = this.index();
928
1231
  if (index === undefined) {
@@ -934,6 +1237,19 @@ class TreeBranch {
934
1237
  }
935
1238
  return [index];
936
1239
  }
1240
+ /**
1241
+ * Removes a branch from its tree without destroying it.
1242
+ *
1243
+ * @remarks
1244
+ * Removes this branch from its parent and detaches its associated
1245
+ * BranchComponent from the DOM. This puts the branch in a "pruned" state,
1246
+ * which may affect the behavior of other methods.
1247
+ *
1248
+ * A pruned branch can be reattached to any other node using the `graftTo` method.
1249
+ *
1250
+ * @returns
1251
+ * Itself, or undefined if it is already in a pruned state.
1252
+ */
937
1253
  prune() {
938
1254
  if (this.isDestroyed()) {
939
1255
  throw new TreeError("Cannot prune a destroyed tree branch");
@@ -955,6 +1271,14 @@ class TreeBranch {
955
1271
  this._parent = undefined;
956
1272
  return this;
957
1273
  }
1274
+ /**
1275
+ * Get the root of the tree to which this Branch is attached.
1276
+ *
1277
+ * @returns
1278
+ * The TreeRoot of the tree this branch is in. If this branch is
1279
+ * does not have a root (such as when it has been pruned) this method will
1280
+ * return undefined.
1281
+ */
958
1282
  root() {
959
1283
  const parent = this.parent();
960
1284
  if (parent instanceof TreeBranch) {
@@ -963,6 +1287,12 @@ class TreeBranch {
963
1287
  assert(parent instanceof TreeRoot || parent === undefined);
964
1288
  return parent;
965
1289
  }
1290
+ /**
1291
+ * Traverses this branch's descendants in depth-first pre-order, executing
1292
+ * the provided callback function on each node. Traversal includes this branch.
1293
+ *
1294
+ * @param callback - A function to execute on each node.
1295
+ */
966
1296
  traverse(callback) {
967
1297
  callback(this);
968
1298
  this.treeNodeBase.traverse(callback);
@@ -984,9 +1314,28 @@ class TreeBranch {
984
1314
  }
985
1315
  });
986
1316
  }
1317
+ handleUserlandError(error) {
1318
+ // When an error occurs in a userland component during a tree operation,
1319
+ // it can cause undefined, bizarre behavior in the tree. To prevent this,
1320
+ // we destroy the tree and throw an error instead. This helps protect
1321
+ // the end-user's data from corruption.
1322
+ this.furthestAncestor().destroy();
1323
+ this.treeNodeBase.handleUserlandError(error);
1324
+ }
987
1325
  indexIsOutOfRange(parent, index) {
988
1326
  return index < 0 || index > parent.branches().length;
989
1327
  }
1328
+ furthestAncestor() {
1329
+ // eslint-disable-next-line @typescript-eslint/no-this-alias -- This code is for an iteration, not to make `this` available in other scopes (which is what the rule is intended to protect against).
1330
+ let node = this;
1331
+ while (node instanceof TreeBranch) {
1332
+ const parent = node.parent();
1333
+ if (parent === undefined)
1334
+ break;
1335
+ node = parent;
1336
+ }
1337
+ return node;
1338
+ }
990
1339
  reattachView(index) {
991
1340
  assert(this._parent !== undefined);
992
1341
  assert(this.detachedView !== null);
@@ -1179,18 +1528,35 @@ class DropzoneRenderer {
1179
1528
  const dropzoneRenderer = new DropzoneRenderer();
1180
1529
 
1181
1530
  class TreeDragAndDropService {
1531
+ /** Hides all Dropzones */
1182
1532
  clearDropzones() {
1183
1533
  dropzoneRenderer.clearCurrentDisplay();
1184
1534
  }
1185
- dragStart(treeBranch, event) {
1186
- dragAndDrop.dragStart(treeBranch, event);
1187
- }
1535
+ /**
1536
+ * @returns An object that indicates which dropzones are currently being displayed.
1537
+ * If no dropzones are being displayed, then null is returned.
1538
+ */
1188
1539
  getCurrentlyDisplayedDropzoneFamily() {
1189
1540
  return dropzoneRenderer.getCurrentDisplay();
1190
1541
  }
1542
+ /**
1543
+ * Causes the dropzone of the TreeRoot to be displayed.
1544
+ *
1545
+ * @remarks
1546
+ * This is a useful function when you want to show the dropzone of a TreeRoot
1547
+ * that has no child branches.
1548
+ *
1549
+ * @param root - The TreeRoot whose dropzone you want to show.
1550
+ */
1191
1551
  showRootDropzone(root) {
1192
1552
  dropzoneRenderer.showLowerZones(root);
1193
1553
  }
1554
+ /**
1555
+ * @returns An observable that emits a number whenever the drag state changes.
1556
+ *
1557
+ * @remarks
1558
+ * See the `DragStates` enum for a list of possible states.
1559
+ */
1194
1560
  state() {
1195
1561
  return dragState.events();
1196
1562
  }
@@ -1201,19 +1567,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
1201
1567
  type: Injectable
1202
1568
  }] });
1203
1569
 
1570
+ /** Makes an TreeBranch draggable when the host element is dragged */
1204
1571
  class DraggableDirective {
1205
- constructor(dragAndDropService, renderer, hostElement) {
1206
- this.dragAndDropService = dragAndDropService;
1572
+ constructor(renderer, hostElement) {
1207
1573
  renderer.setAttribute(hostElement.nativeElement, "draggable", "true");
1208
1574
  renderer.setStyle(hostElement.nativeElement, "cursor", "grab");
1209
1575
  }
1210
1576
  onDragstart(event) {
1211
1577
  if (this.limbleTreeDraggable === undefined)
1212
1578
  return;
1213
- this.dragAndDropService.dragStart(this.limbleTreeDraggable, event);
1579
+ dragAndDrop.dragStart(this.limbleTreeDraggable, event);
1214
1580
  }
1215
1581
  }
1216
- 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 });
1582
+ 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 });
1217
1583
  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 });
1218
1584
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: DraggableDirective, decorators: [{
1219
1585
  type: Directive,
@@ -1221,13 +1587,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
1221
1587
  selector: "[limbleTreeDraggable]",
1222
1588
  standalone: true
1223
1589
  }]
1224
- }], ctorParameters: function () { return [{ type: TreeDragAndDropService }, { type: i0.Renderer2 }, { type: i0.ElementRef }]; }, propDecorators: { limbleTreeDraggable: [{
1590
+ }], ctorParameters: function () { return [{ type: i0.Renderer2 }, { type: i0.ElementRef }]; }, propDecorators: { limbleTreeDraggable: [{
1225
1591
  type: Input
1226
1592
  }], onDragstart: [{
1227
1593
  type: HostListener,
1228
1594
  args: ["dragstart", ["$event"]]
1229
1595
  }] } });
1230
1596
 
1597
+ /**
1598
+ * An Angular module containing all of the entities which provide Drag-And-Drop
1599
+ * functionality.
1600
+ */
1231
1601
  class TreeDragAndDropModule {
1232
1602
  }
1233
1603
  TreeDragAndDropModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: TreeDragAndDropModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
@@ -1399,6 +1769,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
1399
1769
  }]
1400
1770
  }] });
1401
1771
 
1772
+ /**
1773
+ * Import this Angular module into your application to gain access to the
1774
+ * components, directives, and services provided by Limble Tree.
1775
+ */
1402
1776
  class LimbleTreeModule {
1403
1777
  }
1404
1778
  LimbleTreeModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.2.12", ngImport: i0, type: LimbleTreeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
@@ -1422,5 +1796,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.2.12", ngImpo
1422
1796
  * Generated bundle index. Do not edit.
1423
1797
  */
1424
1798
 
1425
- export { DestructionEvent, DragEndEvent, DragStartEvent, DraggableDirective, DragoverNoChangeDetectDirective, DropEvent, GraftEvent, LegacyTree, LimbleTreeLegacyModule, LimbleTreeModule, LimbleTreeRootComponent, PruneEvent, TreeBranch, TreeCollapseModule, TreeCollapseService, TreeDragAndDropModule, TreeDragAndDropService, TreeError, TreeRoot, TreeService, config };
1799
+ export { DestructionEvent, DragEndEvent, DragStartEvent, DraggableDirective, DragoverNoChangeDetectDirective, DropEvent, GraftEvent, LegacyTree, LimbleTreeLegacyModule, LimbleTreeModule, LimbleTreeRootComponent, PruneEvent, TreeBranch, TreeCollapseModule, TreeCollapseService, TreeDragAndDropModule, TreeDragAndDropService, TreeError, TreeRoot, TreeService };
1426
1800
  //# sourceMappingURL=limble-limble-tree.mjs.map