@memberjunction/ng-hierarchy-tree 6.1.4 → 6.2.0-edge.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.
@@ -389,8 +389,24 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
389
389
  super();
390
390
  this.cdr = cdr;
391
391
  }
392
- svgContainerRef;
393
- svgRef;
392
+ SvgContainerRef;
393
+ /** @deprecated Use {@link SvgContainerRef}. */
394
+ get svgContainerRef() {
395
+ return this.SvgContainerRef;
396
+ }
397
+ /** @deprecated Use {@link SvgContainerRef}. */
398
+ set svgContainerRef(value) {
399
+ this.SvgContainerRef = value;
400
+ }
401
+ SvgRef;
402
+ /** @deprecated Use {@link SvgRef}. */
403
+ get svgRef() {
404
+ return this.SvgRef;
405
+ }
406
+ /** @deprecated Use {@link SvgRef}. */
407
+ set svgRef(value) {
408
+ this.SvgRef = value;
409
+ }
394
410
  /**
395
411
  * Declarative configuration defining the target entity and visual layout properties.
396
412
  */
@@ -501,12 +517,12 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
501
517
  this.loadData();
502
518
  }
503
519
  ngAfterViewInit() {
504
- this.log('[HierarchyTree:ngAfterViewInit] Initializing D3 Zoom & ResizeObserver. svgRef:', !!this.svgRef?.nativeElement, 'containerRect:', this.svgContainerRef?.nativeElement?.getBoundingClientRect());
520
+ this.log('[HierarchyTree:ngAfterViewInit] Initializing D3 Zoom & ResizeObserver. svgRef:', !!this.SvgRef?.nativeElement, 'containerRect:', this.SvgContainerRef?.nativeElement?.getBoundingClientRect());
505
521
  this.initD3Zoom();
506
522
  this.setupResizeObserver();
507
523
  if (this.AllNodes.length > 0) {
508
524
  this.log('[HierarchyTree:ngAfterViewInit] AllNodes already loaded (' + this.AllNodes.length + '), triggering renderTree');
509
- this.renderTree();
525
+ this.RenderTree();
510
526
  }
511
527
  }
512
528
  ngOnChanges(changes) {
@@ -527,13 +543,13 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
527
543
  this.updateActiveNodeSelection(this.ActiveRecordID);
528
544
  }
529
545
  else if (changes['Data'] && !changes['Data'].isFirstChange()) {
530
- this.buildTreeFromData(this.Data || []);
546
+ this.BuildTreeFromData(this.Data || []);
531
547
  }
532
548
  else if (changes['FocusRecordID'] && !changes['FocusRecordID'].isFirstChange()) {
533
- this.setFocusRoot(this.FocusRecordID);
549
+ this.SetFocusRoot(this.FocusRecordID);
534
550
  }
535
551
  else if (changes['ZoomLevel'] && !changes['ZoomLevel'].isFirstChange() && this.ZoomLevel !== undefined) {
536
- this.setZoomLevel(this.ZoomLevel, true);
552
+ this.SetZoomLevel(this.ZoomLevel, true);
537
553
  }
538
554
  }
539
555
  hasDataConfigChanged(newConfig) {
@@ -566,7 +582,7 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
566
582
  }
567
583
  this.SelectedNode = activeNode;
568
584
  this.expandAncestors(activeNode);
569
- this.renderTree();
585
+ this.RenderTree();
570
586
  }
571
587
  }
572
588
  ngOnDestroy() {
@@ -639,7 +655,7 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
639
655
  });
640
656
  if (this.Data) {
641
657
  this.log('[HierarchyTree:loadData] Using provided Data prop:', this.Data.length, 'items');
642
- this.buildTreeFromData(this.Data);
658
+ this.BuildTreeFromData(this.Data);
643
659
  return;
644
660
  }
645
661
  const rv = RunView.FromMetadataProvider(this.ProviderToUse);
@@ -665,7 +681,7 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
665
681
  return;
666
682
  }
667
683
  const rawItems = result.Results || [];
668
- this.buildTreeFromData(rawItems);
684
+ this.BuildTreeFromData(rawItems);
669
685
  }
670
686
  catch (err) {
671
687
  this.ErrorMessage = err instanceof Error ? err.message : 'Error loading hierarchy tree.';
@@ -675,8 +691,8 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
675
691
  this.Loading = false;
676
692
  this.cdr?.detectChanges();
677
693
  setTimeout(() => {
678
- this.renderTree();
679
- this.fitToScreen(false);
694
+ this.RenderTree();
695
+ this.FitToScreen(false);
680
696
  }, 80);
681
697
  }
682
698
  }
@@ -698,7 +714,7 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
698
714
  /**
699
715
  * Constructs the in-memory tree nodes, computes composite keys, descendant counts, and depths.
700
716
  */
701
- buildTreeFromData(items) {
717
+ BuildTreeFromData(items) {
702
718
  this.nodeMap.clear();
703
719
  this.AllNodes = [];
704
720
  const parentFieldName = this.resolveParentFieldName();
@@ -804,9 +820,13 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
804
820
  }
805
821
  }
806
822
  if (this.FocusRecordID || this.Config?.FocusRecordID) {
807
- this.setFocusRoot(this.FocusRecordID || this.Config?.FocusRecordID);
823
+ this.SetFocusRoot(this.FocusRecordID || this.Config?.FocusRecordID);
808
824
  }
809
825
  }
826
+ /** @deprecated Use {@link BuildTreeFromData}. */
827
+ buildTreeFromData(items) {
828
+ return this.BuildTreeFromData(items);
829
+ }
810
830
  resolveParentFieldName() {
811
831
  if (this.Config?.ParentField)
812
832
  return this.Config.ParentField;
@@ -853,9 +873,9 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
853
873
  }
854
874
  // --- D3 Layout & Visual Rendering ---
855
875
  initD3Zoom() {
856
- if (!this.svgRef?.nativeElement)
876
+ if (!this.SvgRef?.nativeElement)
857
877
  return;
858
- this.svgSelection = d3.select(this.svgRef.nativeElement);
878
+ this.svgSelection = d3.select(this.SvgRef.nativeElement);
859
879
  this.gSelection = this.svgSelection.select('g.mj-hierarchy-canvas');
860
880
  this.zoomBehavior = d3.zoom()
861
881
  .scaleExtent([0.1, 4.0])
@@ -872,29 +892,29 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
872
892
  this.svgSelection.call(this.zoomBehavior);
873
893
  }
874
894
  setupResizeObserver() {
875
- if (!this.svgContainerRef?.nativeElement)
895
+ if (!this.SvgContainerRef?.nativeElement)
876
896
  return;
877
897
  this.resizeObserver = new ResizeObserver((entries) => {
878
898
  for (const entry of entries) {
879
899
  if (entry.contentRect.width > 50 && entry.contentRect.height > 50) {
880
900
  if (this.AllNodes.length > 0) {
881
- this.renderTree();
901
+ this.RenderTree();
882
902
  }
883
903
  }
884
904
  }
885
905
  });
886
- this.resizeObserver.observe(this.svgContainerRef.nativeElement);
906
+ this.resizeObserver.observe(this.SvgContainerRef.nativeElement);
887
907
  }
888
908
  /**
889
909
  * Computes the D3 hierarchy tree layout and updates the SVG rendering.
890
910
  */
891
- renderTree(preserveTransform = false) {
892
- if (!this.svgRef?.nativeElement || !this.gSelection) {
893
- if (this.svgRef?.nativeElement) {
911
+ RenderTree(preserveTransform = false) {
912
+ if (!this.SvgRef?.nativeElement || !this.gSelection) {
913
+ if (this.SvgRef?.nativeElement) {
894
914
  this.initD3Zoom();
895
915
  }
896
916
  if (!this.gSelection) {
897
- setTimeout(() => this.renderTree(preserveTransform), 30);
917
+ setTimeout(() => this.RenderTree(preserveTransform), 30);
898
918
  return;
899
919
  }
900
920
  }
@@ -981,14 +1001,18 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
981
1001
  this.cdr?.markForCheck();
982
1002
  this.cdr?.detectChanges();
983
1003
  if (!preserveTransform) {
984
- this.fitToScreen(true);
1004
+ this.FitToScreen(true);
985
1005
  }
986
1006
  }
1007
+ /** @deprecated Use {@link RenderTree}. */
1008
+ renderTree(preserveTransform = false) {
1009
+ return this.RenderTree(preserveTransform);
1010
+ }
987
1011
  // --- Interactive Actions ---
988
1012
  /**
989
1013
  * Toggles the expand/collapse state of a node, firing cancelable Before and After events.
990
1014
  */
991
- toggleNodeExpansion(node, event) {
1015
+ ToggleNodeExpansion(node, event) {
992
1016
  if (event)
993
1017
  event.stopPropagation();
994
1018
  if (node.IsExpanded) {
@@ -1009,23 +1033,39 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
1009
1033
  node.Children = [...(node._allChildren || [])];
1010
1034
  this.AfterNodeExpand.emit({ Node: node, OriginalEvent: event });
1011
1035
  }
1012
- this.renderTree(true);
1036
+ this.RenderTree(true);
1013
1037
  }
1014
- closeDetailsDrawer() {
1038
+ /** @deprecated Use {@link ToggleNodeExpansion}. */
1039
+ toggleNodeExpansion(node, event) {
1040
+ return this.ToggleNodeExpansion(node, event);
1041
+ }
1042
+ CloseDetailsDrawer() {
1015
1043
  this.ShowDetailsDrawer = false;
1016
1044
  this.cdr?.markForCheck();
1017
1045
  }
1018
- openDetailsDrawer() {
1046
+ /** @deprecated Use {@link CloseDetailsDrawer}. */
1047
+ closeDetailsDrawer() {
1048
+ return this.CloseDetailsDrawer();
1049
+ }
1050
+ OpenDetailsDrawer() {
1019
1051
  this.ShowDetailsDrawer = true;
1020
1052
  this.cdr?.markForCheck();
1021
1053
  }
1022
- onCanvasBackgroundClick(event) {
1054
+ /** @deprecated Use {@link OpenDetailsDrawer}. */
1055
+ openDetailsDrawer() {
1056
+ return this.OpenDetailsDrawer();
1057
+ }
1058
+ OnCanvasBackgroundClick(event) {
1023
1059
  const target = event.target;
1024
1060
  if (target?.closest('.mj-details-drawer') || target?.closest('.mj-node-card') || target?.closest('.mj-tool-btn')) {
1025
1061
  return;
1026
1062
  }
1027
1063
  }
1028
- onNodeClick(node, event) {
1064
+ /** @deprecated Use {@link OnCanvasBackgroundClick}. */
1065
+ onCanvasBackgroundClick(event) {
1066
+ return this.OnCanvasBackgroundClick(event);
1067
+ }
1068
+ OnNodeClick(node, event) {
1029
1069
  event.stopPropagation();
1030
1070
  this.SelectedNode = node;
1031
1071
  this.ShowDetailsDrawer = true;
@@ -1036,30 +1076,42 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
1036
1076
  this.NodeSelect.emit({ Node: node, OriginalEvent: event });
1037
1077
  this.cdr?.markForCheck();
1038
1078
  }
1039
- onNodeDoubleClick(node, event) {
1079
+ /** @deprecated Use {@link OnNodeClick}. */
1080
+ onNodeClick(node, event) {
1081
+ return this.OnNodeClick(node, event);
1082
+ }
1083
+ OnNodeDoubleClick(node, event) {
1040
1084
  event.stopPropagation();
1041
1085
  this.NodeDoubleClick.emit({ Node: node, OriginalEvent: event });
1042
- this.navigateToRecord(node, event);
1086
+ this.NavigateToRecord(node, event);
1043
1087
  }
1044
- onNodeAction(node, action, event) {
1088
+ /** @deprecated Use {@link OnNodeDoubleClick}. */
1089
+ onNodeDoubleClick(node, event) {
1090
+ return this.OnNodeDoubleClick(node, event);
1091
+ }
1092
+ OnNodeAction(node, action, event) {
1045
1093
  event.stopPropagation();
1046
1094
  this.NodeAction.emit({ Node: node, Action: action });
1047
1095
  switch (action) {
1048
1096
  case 'open':
1049
- this.navigateToRecord(node, event);
1097
+ this.NavigateToRecord(node, event);
1050
1098
  break;
1051
1099
  case 'focus':
1052
- this.setFocusRoot(node.ID);
1100
+ this.SetFocusRoot(node.ID);
1053
1101
  break;
1054
1102
  case 'reset-focus':
1055
- this.resetFocus();
1103
+ this.ResetFocus();
1056
1104
  break;
1057
1105
  case 'add-child':
1058
1106
  // Handled via custom subscriber or event
1059
1107
  break;
1060
1108
  }
1061
1109
  }
1062
- navigateToRecord(node, event) {
1110
+ /** @deprecated Use {@link OnNodeAction}. */
1111
+ onNodeAction(node, action, event) {
1112
+ return this.OnNodeAction(node, action, event);
1113
+ }
1114
+ NavigateToRecord(node, event) {
1063
1115
  const entityName = this.entityInfo ? this.entityInfo.Name : this.Config.EntityName;
1064
1116
  const navEvent = {
1065
1117
  Kind: 'record',
@@ -1069,7 +1121,11 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
1069
1121
  };
1070
1122
  this.Navigate.emit(navEvent);
1071
1123
  }
1072
- getRecordAttributeEntries(record) {
1124
+ /** @deprecated Use {@link NavigateToRecord}. */
1125
+ navigateToRecord(node, event) {
1126
+ return this.NavigateToRecord(node, event);
1127
+ }
1128
+ GetRecordAttributeEntries(record) {
1073
1129
  const entries = [];
1074
1130
  const skipFields = new Set([
1075
1131
  'id', 'parentid', 'parentproductcategoryid', 'parentcategoryid',
@@ -1089,15 +1145,19 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
1089
1145
  }
1090
1146
  return entries.slice(0, 8);
1091
1147
  }
1148
+ /** @deprecated Use {@link GetRecordAttributeEntries}. */
1149
+ getRecordAttributeEntries(record) {
1150
+ return this.GetRecordAttributeEntries(record);
1151
+ }
1092
1152
  // --- Search & Highlight ---
1093
- onSearchInput() {
1153
+ OnSearchInput() {
1094
1154
  const q = this.SearchQuery.trim().toLowerCase();
1095
1155
  if (!q) {
1096
1156
  for (const n of this.AllNodes) {
1097
1157
  n.IsHighlighted = false;
1098
1158
  }
1099
1159
  this.MatchingNodeCount = 0;
1100
- this.renderTree(true);
1160
+ this.RenderTree(true);
1101
1161
  return;
1102
1162
  }
1103
1163
  let count = 0;
@@ -1113,23 +1173,31 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
1113
1173
  }
1114
1174
  }
1115
1175
  this.MatchingNodeCount = count;
1116
- this.renderTree(true);
1176
+ this.RenderTree(true);
1117
1177
  if (firstMatch && firstMatch.x != null && firstMatch.y != null) {
1118
- this.centerOnNode(firstMatch);
1178
+ this.CenterOnNode(firstMatch);
1119
1179
  }
1120
1180
  }
1121
- clearSearch() {
1181
+ /** @deprecated Use {@link OnSearchInput}. */
1182
+ onSearchInput() {
1183
+ return this.OnSearchInput();
1184
+ }
1185
+ ClearSearch() {
1122
1186
  this.SearchQuery = '';
1123
- this.onSearchInput();
1187
+ this.OnSearchInput();
1124
1188
  }
1125
- centerOnNode(node) {
1126
- if (!this.svgSelection || !this.zoomBehavior || !this.svgContainerRef?.nativeElement)
1189
+ /** @deprecated Use {@link ClearSearch}. */
1190
+ clearSearch() {
1191
+ return this.ClearSearch();
1192
+ }
1193
+ CenterOnNode(node) {
1194
+ if (!this.svgSelection || !this.zoomBehavior || !this.SvgContainerRef?.nativeElement)
1127
1195
  return;
1128
1196
  if (node.x == null || node.y == null)
1129
1197
  return;
1130
- const rect = this.svgContainerRef.nativeElement.getBoundingClientRect();
1131
- const width = rect.width > 50 ? rect.width : (this.svgContainerRef.nativeElement.clientWidth || 800);
1132
- const height = rect.height > 50 ? rect.height : (this.svgContainerRef.nativeElement.clientHeight || 500);
1198
+ const rect = this.SvgContainerRef.nativeElement.getBoundingClientRect();
1199
+ const width = rect.width > 50 ? rect.width : (this.SvgContainerRef.nativeElement.clientWidth || 800);
1200
+ const height = rect.height > 50 ? rect.height : (this.SvgContainerRef.nativeElement.clientHeight || 500);
1133
1201
  const rightMargin = (this.ShowDetailsDrawer && this.SelectedNode) ? 340 : 0;
1134
1202
  const visibleWidth = Math.max(width - rightMargin, 200);
1135
1203
  const currentScale = this.currentZoomTransform?.k || this.ZoomLevel || 1;
@@ -1142,6 +1210,10 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
1142
1210
  const target = d3.zoomIdentity.translate(tx, ty).scale(currentScale);
1143
1211
  this.svgSelection.interrupt().transition().duration(400).ease(d3.easeCubicOut).call(this.zoomBehavior.transform, target);
1144
1212
  }
1213
+ /** @deprecated Use {@link CenterOnNode}. */
1214
+ centerOnNode(node) {
1215
+ return this.CenterOnNode(node);
1216
+ }
1145
1217
  expandAncestors(node) {
1146
1218
  let curr = node.ParentID ? this.nodeMap.get(NormalizeUUID(node.ParentID)) : undefined;
1147
1219
  while (curr) {
@@ -1151,9 +1223,9 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
1151
1223
  }
1152
1224
  }
1153
1225
  // --- Subtree Focus ---
1154
- setFocusRoot(recordId) {
1226
+ SetFocusRoot(recordId) {
1155
1227
  if (!recordId) {
1156
- this.resetFocus();
1228
+ this.ResetFocus();
1157
1229
  return;
1158
1230
  }
1159
1231
  const target = this.nodeMap.get(NormalizeUUID(recordId));
@@ -1163,55 +1235,79 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
1163
1235
  n.IsFocusRoot = false;
1164
1236
  target.IsFocusRoot = true;
1165
1237
  this.FocusedNode = target;
1166
- this.renderTree();
1167
- this.onFitToViewClick();
1238
+ this.RenderTree();
1239
+ this.OnFitToViewClick();
1168
1240
  }
1169
- resetFocus() {
1241
+ /** @deprecated Use {@link SetFocusRoot}. */
1242
+ setFocusRoot(recordId) {
1243
+ return this.SetFocusRoot(recordId);
1244
+ }
1245
+ ResetFocus() {
1170
1246
  for (const n of this.AllNodes)
1171
1247
  n.IsFocusRoot = false;
1172
1248
  this.FocusedNode = null;
1173
- this.renderTree();
1174
- this.onFitToViewClick();
1249
+ this.RenderTree();
1250
+ this.OnFitToViewClick();
1251
+ }
1252
+ /** @deprecated Use {@link ResetFocus}. */
1253
+ resetFocus() {
1254
+ return this.ResetFocus();
1175
1255
  }
1176
1256
  // --- Toolbar Actions / Verbs ---
1177
- expandAll() {
1257
+ ExpandAll() {
1178
1258
  for (const n of this.AllNodes) {
1179
1259
  n.IsExpanded = true;
1180
1260
  n.Children = [...(n._allChildren || [])];
1181
1261
  }
1182
- this.renderTree(true);
1262
+ this.RenderTree(true);
1183
1263
  }
1184
- collapseAll() {
1264
+ /** @deprecated Use {@link ExpandAll}. */
1265
+ expandAll() {
1266
+ return this.ExpandAll();
1267
+ }
1268
+ CollapseAll() {
1185
1269
  for (const n of this.AllNodes) {
1186
1270
  if (n.Depth > 0) {
1187
1271
  n.IsExpanded = false;
1188
1272
  n.Children = [];
1189
1273
  }
1190
1274
  }
1191
- this.renderTree(true);
1275
+ this.RenderTree(true);
1192
1276
  }
1193
- zoomIn() {
1277
+ /** @deprecated Use {@link CollapseAll}. */
1278
+ collapseAll() {
1279
+ return this.CollapseAll();
1280
+ }
1281
+ ZoomIn() {
1194
1282
  if (!this.svgSelection || !this.zoomBehavior)
1195
1283
  return;
1196
1284
  const currentScale = this.currentZoomTransform?.k || 1;
1197
1285
  const targetScale = Math.min(currentScale * 1.25, 4.0);
1198
- this.setZoomLevel(targetScale, true);
1286
+ this.SetZoomLevel(targetScale, true);
1199
1287
  this.ZoomLevel = targetScale;
1200
1288
  this.ZoomChange.emit(targetScale);
1201
1289
  }
1202
- zoomOut() {
1290
+ /** @deprecated Use {@link ZoomIn}. */
1291
+ zoomIn() {
1292
+ return this.ZoomIn();
1293
+ }
1294
+ ZoomOut() {
1203
1295
  if (!this.svgSelection || !this.zoomBehavior)
1204
1296
  return;
1205
1297
  const currentScale = this.currentZoomTransform?.k || 1;
1206
1298
  const targetScale = Math.max(currentScale * 0.8, 0.1);
1207
- this.setZoomLevel(targetScale, true);
1299
+ this.SetZoomLevel(targetScale, true);
1208
1300
  this.ZoomLevel = targetScale;
1209
1301
  this.ZoomChange.emit(targetScale);
1210
1302
  }
1211
- setZoomLevel(scale, animated = true) {
1303
+ /** @deprecated Use {@link ZoomOut}. */
1304
+ zoomOut() {
1305
+ return this.ZoomOut();
1306
+ }
1307
+ SetZoomLevel(scale, animated = true) {
1212
1308
  if (!this.svgSelection || !this.zoomBehavior || !scale)
1213
1309
  return;
1214
- const container = this.svgContainerRef?.nativeElement;
1310
+ const container = this.SvgContainerRef?.nativeElement;
1215
1311
  const rect = container?.getBoundingClientRect();
1216
1312
  const width = rect?.width && rect.width > 50 ? rect.width : (container?.clientWidth || 800);
1217
1313
  const height = rect?.height && rect.height > 50 ? rect.height : (container?.clientHeight || 500);
@@ -1228,19 +1324,31 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
1228
1324
  this.svgSelection.interrupt().call(this.zoomBehavior.transform, target);
1229
1325
  }
1230
1326
  }
1231
- resetZoom() {
1327
+ /** @deprecated Use {@link SetZoomLevel}. */
1328
+ setZoomLevel(scale, animated = true) {
1329
+ return this.SetZoomLevel(scale, animated);
1330
+ }
1331
+ ResetZoom() {
1232
1332
  if (!this.svgSelection || !this.zoomBehavior)
1233
1333
  return;
1234
- this.onFitToViewClick();
1334
+ this.OnFitToViewClick();
1235
1335
  }
1236
- onFitToViewClick() {
1237
- this.fitToScreen(false, true);
1336
+ /** @deprecated Use {@link ResetZoom}. */
1337
+ resetZoom() {
1338
+ return this.ResetZoom();
1339
+ }
1340
+ OnFitToViewClick() {
1341
+ this.FitToScreen(false, true);
1238
1342
  setTimeout(() => {
1239
- this.fitToScreen(false, true);
1343
+ this.FitToScreen(false, true);
1240
1344
  }, 320);
1241
1345
  }
1242
- fitToScreen(immediate = false, forceAutoScale = false) {
1243
- if (!this.svgSelection || !this.zoomBehavior || !this.svgContainerRef?.nativeElement)
1346
+ /** @deprecated Use {@link OnFitToViewClick}. */
1347
+ onFitToViewClick() {
1348
+ return this.OnFitToViewClick();
1349
+ }
1350
+ FitToScreen(immediate = false, forceAutoScale = false) {
1351
+ if (!this.svgSelection || !this.zoomBehavior || !this.SvgContainerRef?.nativeElement)
1244
1352
  return;
1245
1353
  const visibleNodes = this.AllNodes.filter((n) => n.x != null && n.y != null);
1246
1354
  if (visibleNodes.length === 0)
@@ -1258,9 +1366,9 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
1258
1366
  const treeHeight = Math.max(maxY - minY, 1);
1259
1367
  const treeCenterX = (minX + maxX) / 2;
1260
1368
  const treeCenterY = (minY + maxY) / 2;
1261
- const rect = this.svgContainerRef.nativeElement.getBoundingClientRect();
1262
- const width = rect.width > 50 ? rect.width : (this.svgContainerRef.nativeElement.clientWidth || 800);
1263
- const height = rect.height > 50 ? rect.height : (this.svgContainerRef.nativeElement.clientHeight || 500);
1369
+ const rect = this.SvgContainerRef.nativeElement.getBoundingClientRect();
1370
+ const width = rect.width > 50 ? rect.width : (this.SvgContainerRef.nativeElement.clientWidth || 800);
1371
+ const height = rect.height > 50 ? rect.height : (this.SvgContainerRef.nativeElement.clientHeight || 500);
1264
1372
  const rightMargin = (this.ShowDetailsDrawer && this.SelectedNode) ? 340 : 0;
1265
1373
  const visibleWidth = Math.max(width - rightMargin, 200);
1266
1374
  const padding = 40;
@@ -1283,19 +1391,31 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
1283
1391
  this.svgSelection.interrupt().transition().duration(300).ease(d3.easeCubicOut).call(this.zoomBehavior.transform, targetTransform);
1284
1392
  }
1285
1393
  }
1394
+ /** @deprecated Use {@link FitToScreen}. */
1395
+ fitToScreen(immediate = false, forceAutoScale = false) {
1396
+ return this.FitToScreen(immediate, forceAutoScale);
1397
+ }
1398
+ ExportAsSVG() {
1399
+ return this.SvgRef?.nativeElement ? this.SvgRef.nativeElement.outerHTML : '';
1400
+ }
1401
+ /** @deprecated Use {@link ExportAsSVG}. */
1286
1402
  exportAsSVG() {
1287
- return this.svgRef?.nativeElement ? this.svgRef.nativeElement.outerHTML : '';
1403
+ return this.ExportAsSVG();
1288
1404
  }
1289
- async refresh() {
1405
+ async Refresh() {
1290
1406
  await this.loadData();
1291
1407
  }
1408
+ /** @deprecated Use {@link Refresh}. */
1409
+ async refresh() {
1410
+ return this.Refresh();
1411
+ }
1292
1412
  static ɵfac = function HierarchyTreeComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || HierarchyTreeComponent)(i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
1293
1413
  static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: HierarchyTreeComponent, selectors: [["mj-hierarchy-tree"]], viewQuery: function HierarchyTreeComponent_Query(rf, ctx) { if (rf & 1) {
1294
1414
  i0.ɵɵviewQuery(_c0, 5)(_c1, 5);
1295
1415
  } if (rf & 2) {
1296
1416
  let _t;
1297
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.svgContainerRef = _t.first);
1298
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.svgRef = _t.first);
1417
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.SvgContainerRef = _t.first);
1418
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.SvgRef = _t.first);
1299
1419
  } }, inputs: { Config: "Config", Data: "Data", ActiveRecordID: "ActiveRecordID", FocusRecordID: "FocusRecordID", AllowNodeActions: "AllowNodeActions", ZoomLevel: "ZoomLevel" }, outputs: { NodeClick: "NodeClick", NodeDoubleClick: "NodeDoubleClick", NodeSelect: "NodeSelect", BeforeNodeExpand: "BeforeNodeExpand", AfterNodeExpand: "AfterNodeExpand", BeforeNodeCollapse: "BeforeNodeCollapse", AfterNodeCollapse: "AfterNodeCollapse", BeforeReparent: "BeforeReparent", AfterReparent: "AfterReparent", NodeAction: "NodeAction", ZoomChange: "ZoomChange", Navigate: "Navigate" }, features: [i0.ɵɵInheritDefinitionFeature, i0.ɵɵNgOnChangesFeature], decls: 20, vars: 9, consts: [["svgContainer", ""], ["svgElement", ""], [1, "mj-hierarchy-wrapper"], [1, "mj-hierarchy-toolbar"], [1, "mj-canvas-container", 3, "click"], [1, "mj-state-overlay"], [1, "mj-state-overlay", "mj-error-state"], [1, "mj-state-overlay", "mj-empty-state"], [1, "mj-hierarchy-svg"], ["id", "mj-tree-grid", "width", "24", "height", "24", "patternUnits", "userSpaceOnUse"], ["cx", "12", "cy", "12", "r", "1", "fill", "var(--mj-border-subtle, #1e293b)", "opacity", "0.4"], ["width", "100%", "height", "100%", "fill", "url(#mj-tree-grid)"], [1, "mj-hierarchy-canvas"], [1, "mj-links-group"], [1, "mj-nodes-group"], ["aria-label", "Node details panel", 1, "mj-details-drawer"], [1, "mj-search-box"], [1, "mj-focus-indicator"], [1, "mj-toolbar-actions"], [1, "fa-solid", "fa-magnifying-glass", "mj-search-icon"], ["type", "text", "placeholder", "Search hierarchy...", 1, "mj-search-input", 3, "ngModelChange", "input", "ngModel"], ["type", "button", "title", "Clear search", 1, "mj-clear-btn"], [1, "mj-match-badge"], ["type", "button", "title", "Clear search", 1, "mj-clear-btn", 3, "click"], [1, "fa-solid", "fa-xmark"], [1, "fa-solid", "fa-crosshairs"], ["type", "button", "title", "Show entire hierarchy", 1, "mj-reset-focus-btn", 3, "click"], [1, "fa-solid", "fa-rotate-left"], ["type", "button", "title", "Expand all subtrees", 1, "mj-tool-btn", 3, "click"], [1, "fa-solid", "fa-folder-open"], ["type", "button", "title", "Collapse all subtrees", 1, "mj-tool-btn", 3, "click"], [1, "fa-solid", "fa-folder"], [1, "mj-tool-divider"], ["type", "button", "title", "Zoom in", 1, "mj-tool-btn", 3, "click"], [1, "fa-solid", "fa-plus"], ["type", "button", "title", "Zoom out", 1, "mj-tool-btn", 3, "click"], [1, "fa-solid", "fa-minus"], ["type", "button", "title", "Fit hierarchy to view", 1, "mj-tool-btn", 3, "click"], [1, "fa-solid", "fa-expand"], ["type", "button", "title", "Reload hierarchy data", 1, "mj-tool-btn", 3, "click"], [1, "fa-solid", "fa-rotate-right"], [1, "mj-spinner"], [1, "fa-solid", "fa-circle-exclamation", "mj-error-icon"], ["type", "button", 1, "mj-retry-btn", 3, "click"], [1, "fa-solid", "fa-sitemap", "mj-empty-icon"], [1, "mj-node-foreign-object"], [1, "mj-node-card", 3, "click", "dblclick"], [1, "mj-card-accent"], [1, "mj-node-header"], [1, "mj-node-icon"], [1, "mj-node-title-group"], [1, "mj-node-name", 3, "title"], [1, "mj-node-subtitle"], [1, "mj-node-footer"], ["type", "button", 1, "mj-expand-badge", 3, "collapsed", "title"], [1, "mj-leaf-indicator"], [1, "mj-node-actions"], ["type", "button", 1, "mj-expand-badge", 3, "click", "title"], [1, "fa-solid"], ["type", "button", "title", "Focus on this subtree", 1, "mj-node-action-btn", 3, "click"], ["type", "button", "title", "Open record in Explorer", 1, "mj-node-action-btn", 3, "click"], [1, "fa-solid", "fa-arrow-up-right-from-square"], [1, "mj-drawer-header"], [1, "mj-drawer-title-group"], [1, "mj-drawer-icon"], [1, "mj-drawer-titles"], [1, "mj-drawer-name", 3, "title"], [1, "mj-drawer-subtitle"], ["type", "button", "title", "Close details", 1, "mj-drawer-close-btn", 3, "click"], [1, "mj-drawer-body"], [1, "mj-drawer-stats"], [1, "mj-stat-card"], [1, "mj-stat-num"], [1, "mj-stat-label"], [1, "mj-drawer-section"], [1, "mj-drawer-actions"], ["type", "button", "title", "Open this record in Explorer", 1, "mj-drawer-btn-primary", 3, "click"], [1, "mj-drawer-btn-row"], ["type", "button", 1, "mj-drawer-btn-secondary"], ["type", "button", "title", "Focus hierarchy on this subtree", 1, "mj-drawer-btn-secondary", 3, "click"], [1, "mj-section-heading"], [1, "mj-attr-list"], [1, "mj-attr-row"], [1, "mj-attr-dt"], [1, "mj-attr-dd", 3, "title"], ["type", "button", 1, "mj-drawer-btn-secondary", 3, "click"]], template: function HierarchyTreeComponent_Template(rf, ctx) { if (rf & 1) {
1300
1420
  i0.ɵɵelementStart(0, "div", 2);
1301
1421
  i0.ɵɵconditionalCreate(1, HierarchyTreeComponent_Conditional_1_Template, 4, 3, "div", 3);
@@ -1333,10 +1453,10 @@ export class HierarchyTreeComponent extends BaseAngularComponent {
1333
1453
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(HierarchyTreeComponent, [{
1334
1454
  type: Component,
1335
1455
  args: [{ selector: 'mj-hierarchy-tree', standalone: true, imports: [CommonModule, FormsModule], template: "<div class=\"mj-hierarchy-wrapper\" [style.height]=\"Config.Height || '100%'\" [style.min-height]=\"Config.MinHeight || '500px'\">\n <!-- Top Controls Bar -->\n @if (Config.ShowToolbar || Config.ShowSearch) {\n <div class=\"mj-hierarchy-toolbar\">\n <!-- Search Box -->\n @if (Config.ShowSearch) {\n <div class=\"mj-search-box\">\n <i class=\"fa-solid fa-magnifying-glass mj-search-icon\"></i>\n <input\n type=\"text\"\n class=\"mj-search-input\"\n placeholder=\"Search hierarchy...\"\n [(ngModel)]=\"SearchQuery\"\n (input)=\"onSearchInput()\"\n />\n @if (SearchQuery) {\n <button type=\"button\" class=\"mj-clear-btn\" (click)=\"clearSearch()\" title=\"Clear search\">\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n }\n @if (SearchQuery && MatchingNodeCount > 0) {\n <span class=\"mj-match-badge\">{{ MatchingNodeCount }} found</span>\n }\n </div>\n }\n\n <!-- Focus Subtree Indicator -->\n @if (FocusedNode) {\n <div class=\"mj-focus-indicator\">\n <i class=\"fa-solid fa-crosshairs\"></i>\n <span>Focusing subtree: <strong>{{ FocusedNode.Name }}</strong></span>\n <button type=\"button\" class=\"mj-reset-focus-btn\" (click)=\"resetFocus()\" title=\"Show entire hierarchy\">\n <i class=\"fa-solid fa-rotate-left\"></i> View Full Hierarchy\n </button>\n </div>\n }\n\n <!-- Toolbar Action Buttons -->\n @if (Config.ShowToolbar) {\n <div class=\"mj-toolbar-actions\">\n <button type=\"button\" class=\"mj-tool-btn\" (click)=\"expandAll()\" title=\"Expand all subtrees\">\n <i class=\"fa-solid fa-folder-open\"></i>\n </button>\n <button type=\"button\" class=\"mj-tool-btn\" (click)=\"collapseAll()\" title=\"Collapse all subtrees\">\n <i class=\"fa-solid fa-folder\"></i>\n </button>\n <div class=\"mj-tool-divider\"></div>\n <button type=\"button\" class=\"mj-tool-btn\" (click)=\"zoomIn()\" title=\"Zoom in\">\n <i class=\"fa-solid fa-plus\"></i>\n </button>\n <button type=\"button\" class=\"mj-tool-btn\" (click)=\"zoomOut()\" title=\"Zoom out\">\n <i class=\"fa-solid fa-minus\"></i>\n </button>\n <button type=\"button\" class=\"mj-tool-btn\" (click)=\"onFitToViewClick()\" title=\"Fit hierarchy to view\">\n <i class=\"fa-solid fa-expand\"></i>\n </button>\n <div class=\"mj-tool-divider\"></div>\n <button type=\"button\" class=\"mj-tool-btn\" (click)=\"refresh()\" title=\"Reload hierarchy data\">\n <i class=\"fa-solid fa-rotate-right\"></i>\n </button>\n </div>\n }\n </div>\n }\n\n <!-- SVG Canvas Container -->\n <div #svgContainer class=\"mj-canvas-container\" (click)=\"onCanvasBackgroundClick($event)\">\n @if (Loading) {\n <div class=\"mj-state-overlay\">\n <div class=\"mj-spinner\"></div>\n <span>Loading hierarchy...</span>\n </div>\n } @else if (ErrorMessage) {\n <div class=\"mj-state-overlay mj-error-state\">\n <i class=\"fa-solid fa-circle-exclamation mj-error-icon\"></i>\n <span>{{ ErrorMessage }}</span>\n <button type=\"button\" class=\"mj-retry-btn\" (click)=\"loadData()\">Retry</button>\n </div>\n } @else if (AllNodes.length === 0) {\n <div class=\"mj-state-overlay mj-empty-state\">\n <i class=\"fa-solid fa-sitemap mj-empty-icon\"></i>\n <span>No hierarchy records found.</span>\n </div>\n }\n\n <svg #svgElement class=\"mj-hierarchy-svg\">\n <defs>\n <pattern id=\"mj-tree-grid\" width=\"24\" height=\"24\" patternUnits=\"userSpaceOnUse\">\n <circle cx=\"12\" cy=\"12\" r=\"1\" fill=\"var(--mj-border-subtle, #1e293b)\" opacity=\"0.4\" />\n </pattern>\n </defs>\n\n <!-- Background grid -->\n <rect width=\"100%\" height=\"100%\" fill=\"url(#mj-tree-grid)\" />\n\n <!-- Main Canvas Group (Zoomable & Pannable) -->\n <g class=\"mj-hierarchy-canvas\">\n <!-- Connective Link Curves -->\n <g class=\"mj-links-group\"></g>\n\n <!-- Interactive Node Cards via ForeignObject -->\n <g class=\"mj-nodes-group\">\n @for (node of AllNodes; track node.ID) {\n @if (node.x != null && node.y != null) {\n <foreignObject\n [attr.x]=\"node.x\"\n [attr.y]=\"node.y\"\n [attr.width]=\"Config.NodeWidth || 230\"\n [attr.height]=\"Config.NodeHeight || 90\"\n class=\"mj-node-foreign-object\"\n >\n <div\n class=\"mj-node-card\"\n [class.selected]=\"node.IsSelected\"\n [class.highlighted]=\"node.IsHighlighted\"\n [class.focus-root]=\"node.IsFocusRoot\"\n (click)=\"onNodeClick(node, $event)\"\n (dblclick)=\"onNodeDoubleClick(node, $event)\"\n >\n <!-- Accent Top Border -->\n <div class=\"mj-card-accent\" [style.background]=\"node.Color || Config.DefaultColor\"></div>\n\n <!-- Node Card Header -->\n <div class=\"mj-node-header\">\n <div class=\"mj-node-icon\" [style.color]=\"node.Color || Config.DefaultColor\">\n <i [class]=\"node.Icon || Config.DefaultIcon\"></i>\n </div>\n <div class=\"mj-node-title-group\">\n <div class=\"mj-node-name\" [title]=\"node.Name\">{{ node.Name }}</div>\n @if (node.Subtitle) {\n <div class=\"mj-node-subtitle\">{{ node.Subtitle }}</div>\n }\n </div>\n </div>\n\n <!-- Node Card Footer & Actions -->\n <div class=\"mj-node-footer\">\n <!-- Subtree Expansion Pill -->\n @if (node.DirectChildCount > 0) {\n <button\n type=\"button\"\n class=\"mj-expand-badge\"\n [class.collapsed]=\"!node.IsExpanded\"\n (click)=\"toggleNodeExpansion(node, $event)\"\n [title]=\"node.IsExpanded ? 'Collapse sub-branch' : 'Expand ' + node.DirectChildCount + ' children'\"\n >\n <i class=\"fa-solid\" [class.fa-chevron-down]=\"node.IsExpanded\" [class.fa-chevron-right]=\"!node.IsExpanded\"></i>\n <span>{{ node.DirectChildCount }}</span>\n </button>\n } @else {\n <span class=\"mj-leaf-indicator\">Leaf</span>\n }\n\n <!-- Quick Action Buttons -->\n @if (AllowNodeActions) {\n <div class=\"mj-node-actions\">\n <button\n type=\"button\"\n class=\"mj-node-action-btn\"\n (click)=\"onNodeAction(node, 'focus', $event)\"\n title=\"Focus on this subtree\"\n >\n <i class=\"fa-solid fa-crosshairs\"></i>\n </button>\n <button\n type=\"button\"\n class=\"mj-node-action-btn\"\n (click)=\"onNodeAction(node, 'open', $event)\"\n title=\"Open record in Explorer\"\n >\n <i class=\"fa-solid fa-arrow-up-right-from-square\"></i>\n </button>\n </div>\n }\n </div>\n </div>\n </foreignObject>\n }\n }\n </g>\n </g>\n </svg>\n\n <!-- Slide-In Node Details Drawer -->\n <aside\n class=\"mj-details-drawer\"\n [class.open]=\"SelectedNode != null && ShowDetailsDrawer\"\n aria-label=\"Node details panel\"\n >\n @if (SelectedNode) {\n <div class=\"mj-drawer-header\">\n <div class=\"mj-drawer-title-group\">\n <div class=\"mj-drawer-icon\" [style.color]=\"SelectedNode.Color || Config.DefaultColor\">\n <i [class]=\"SelectedNode.Icon || Config.DefaultIcon\"></i>\n </div>\n <div class=\"mj-drawer-titles\">\n <h4 class=\"mj-drawer-name\" [title]=\"SelectedNode.Name\">{{ SelectedNode.Name }}</h4>\n @if (SelectedNode.Subtitle) {\n <p class=\"mj-drawer-subtitle\">{{ SelectedNode.Subtitle }}</p>\n }\n </div>\n </div>\n <button\n type=\"button\"\n class=\"mj-drawer-close-btn\"\n (click)=\"closeDetailsDrawer()\"\n title=\"Close details\"\n >\n <i class=\"fa-solid fa-xmark\"></i>\n </button>\n </div>\n\n <div class=\"mj-drawer-body\">\n <div class=\"mj-drawer-stats\">\n <div class=\"mj-stat-card\">\n <span class=\"mj-stat-num\">{{ SelectedNode.Depth }}</span>\n <span class=\"mj-stat-label\">Hierarchy Level</span>\n </div>\n <div class=\"mj-stat-card\">\n <span class=\"mj-stat-num\">{{ SelectedNode.DirectChildCount }}</span>\n <span class=\"mj-stat-label\">Direct Children</span>\n </div>\n <div class=\"mj-stat-card\">\n <span class=\"mj-stat-num\">{{ SelectedNode.TotalDescendantCount }}</span>\n <span class=\"mj-stat-label\">Total Subtree</span>\n </div>\n </div>\n\n @if (SelectedNode.Record && getRecordAttributeEntries(SelectedNode.Record).length > 0) {\n <div class=\"mj-drawer-section\">\n <h5 class=\"mj-section-heading\">Attributes</h5>\n <dl class=\"mj-attr-list\">\n @for (attr of getRecordAttributeEntries(SelectedNode.Record); track attr.key) {\n <div class=\"mj-attr-row\">\n <dt class=\"mj-attr-dt\">{{ attr.label }}</dt>\n <dd class=\"mj-attr-dd\" [title]=\"attr.value\">{{ attr.value }}</dd>\n </div>\n }\n </dl>\n </div>\n }\n\n <div class=\"mj-drawer-actions\">\n <button\n type=\"button\"\n class=\"mj-drawer-btn-primary\"\n (click)=\"navigateToRecord(SelectedNode)\"\n title=\"Open this record in Explorer\"\n >\n <i class=\"fa-solid fa-arrow-up-right-from-square\"></i>\n <span>Open Record</span>\n </button>\n\n <div class=\"mj-drawer-btn-row\">\n @if (SelectedNode.DirectChildCount > 0) {\n <button\n type=\"button\"\n class=\"mj-drawer-btn-secondary\"\n (click)=\"toggleNodeExpansion(SelectedNode)\"\n >\n <i class=\"fa-solid\" [class.fa-folder-open]=\"SelectedNode.IsExpanded\" [class.fa-folder]=\"!SelectedNode.IsExpanded\"></i>\n <span>{{ SelectedNode.IsExpanded ? 'Collapse' : 'Expand' }}</span>\n </button>\n }\n <button\n type=\"button\"\n class=\"mj-drawer-btn-secondary\"\n (click)=\"setFocusRoot(SelectedNode.ID)\"\n title=\"Focus hierarchy on this subtree\"\n >\n <i class=\"fa-solid fa-crosshairs\"></i>\n <span>Focus Tree</span>\n </button>\n </div>\n </div>\n </div>\n }\n </aside>\n </div>\n</div>\n", styles: [":host {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: 500px;\n position: relative;\n box-sizing: border-box;\n font-family: inherit;\n flex: 1;\n}\n\n.mj-hierarchy-wrapper {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n min-height: inherit;\n flex: 1;\n background: var(--mj-bg-surface-sunken, #080d1a);\n border: 1px solid var(--mj-border-default, #1e293b);\n border-radius: 12px;\n overflow: hidden;\n position: relative;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);\n}\n\n/* Toolbar */\n.mj-hierarchy-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 16px;\n background: var(--mj-bg-surface, #0f172a);\n border-bottom: 1px solid var(--mj-border-default, #1e293b);\n gap: 12px;\n z-index: 10;\n flex-wrap: wrap;\n}\n\n/* Search Box */\n.mj-search-box {\n display: flex;\n align-items: center;\n gap: 8px;\n background: var(--mj-bg-surface-sunken, #080d1a);\n border: 1px solid var(--mj-border-default, #1e293b);\n border-radius: 8px;\n padding: 4px 10px;\n min-width: 240px;\n max-width: 340px;\n flex: 1;\n transition: border-color 0.18s ease;\n}\n\n.mj-search-box:focus-within {\n border-color: var(--mj-brand-primary, #38bdf8);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary, #38bdf8) 18%, transparent);\n}\n\n.mj-search-icon {\n font-size: 12px;\n color: var(--mj-text-muted, #64748b);\n}\n\n.mj-search-input {\n background: transparent;\n border: none;\n outline: none;\n color: var(--mj-text-primary, #f8fafc);\n font-size: 12.5px;\n width: 100%;\n}\n\n.mj-search-input::placeholder {\n color: var(--mj-text-muted, #64748b);\n}\n\n.mj-clear-btn {\n background: transparent;\n border: none;\n color: var(--mj-text-muted, #64748b);\n cursor: pointer;\n font-size: 11px;\n padding: 2px 4px;\n border-radius: 4px;\n}\n\n.mj-clear-btn:hover {\n color: var(--mj-text-primary, #f8fafc);\n}\n\n.mj-match-badge {\n font-size: 11px;\n font-weight: 700;\n padding: 2px 7px;\n border-radius: 9999px;\n background: color-mix(in srgb, var(--mj-brand-primary, #38bdf8) 15%, transparent);\n color: var(--mj-brand-primary, #38bdf8);\n white-space: nowrap;\n}\n\n/* Focus Subtree Indicator */\n.mj-focus-indicator {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: var(--mj-brand-primary, #38bdf8);\n background: color-mix(in srgb, var(--mj-brand-primary, #38bdf8) 10%, transparent);\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary, #38bdf8) 30%, transparent);\n padding: 4px 10px;\n border-radius: 6px;\n}\n\n.mj-reset-focus-btn {\n background: var(--mj-brand-primary, #38bdf8);\n color: var(--mj-text-inverse);\n border: none;\n border-radius: 4px;\n padding: 3px 8px;\n font-size: 11px;\n font-weight: 700;\n cursor: pointer;\n margin-left: 6px;\n transition: opacity 0.15s ease;\n}\n\n.mj-reset-focus-btn:hover {\n opacity: 0.9;\n}\n\n/* Toolbar Buttons */\n.mj-toolbar-actions {\n display: flex;\n align-items: center;\n gap: 4px;\n margin-left: auto;\n}\n\n.mj-tool-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 30px;\n height: 30px;\n background: transparent;\n border: 1px solid var(--mj-border-subtle, #1e293b);\n border-radius: 6px;\n color: var(--mj-text-secondary, #94a3b8);\n font-size: 12px;\n cursor: pointer;\n transition: all 0.18s ease;\n}\n\n.mj-tool-btn:hover {\n background: var(--mj-bg-surface-elevated, #1e293b);\n color: var(--mj-text-primary, #f8fafc);\n border-color: var(--mj-border-default, #334155);\n}\n\n.mj-tool-divider {\n width: 1px;\n height: 18px;\n background: var(--mj-border-subtle, #1e293b);\n margin: 0 4px;\n}\n\n/* Canvas Area */\n.mj-canvas-container {\n flex: 1;\n position: relative;\n overflow: hidden;\n width: 100%;\n height: 100%;\n min-height: 450px;\n cursor: grab;\n}\n\n.mj-canvas-container:active {\n cursor: grabbing;\n}\n\n.mj-hierarchy-svg {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n display: block;\n}\n\n/* State Overlays */\n.mj-state-overlay {\n position: absolute;\n inset: 0;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n background: var(--mj-bg-overlay);\n backdrop-filter: blur(4px);\n z-index: 20;\n color: var(--mj-text-secondary, #94a3b8);\n font-size: 13px;\n}\n\n.mj-spinner {\n width: 32px;\n height: 32px;\n border: 3px solid color-mix(in srgb, var(--mj-brand-primary, #38bdf8) 20%, transparent);\n border-top-color: var(--mj-brand-primary, #38bdf8);\n border-radius: 50%;\n animation: mj-spin 0.8s linear infinite;\n}\n\n@keyframes mj-spin {\n to { transform: rotate(360deg); }\n}\n\n.mj-error-icon, .mj-empty-icon {\n font-size: 28px;\n color: var(--mj-text-muted, #64748b);\n}\n\n.mj-retry-btn {\n background: var(--mj-brand-primary, #38bdf8);\n color: var(--mj-text-inverse);\n border: none;\n padding: 6px 14px;\n font-size: 12px;\n font-weight: 700;\n border-radius: 6px;\n cursor: pointer;\n}\n\n/* Node ForeignObject Container */\n.mj-node-foreign-object {\n overflow: visible;\n}\n\n/* Interactive Node Card */\n.mj-node-card {\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n background: var(--mj-bg-surface-card, #111a2e);\n border: 1px solid var(--mj-border-default, #223254);\n border-radius: 10px;\n padding: 8px 12px;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);\n position: relative;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n user-select: none;\n}\n\n.mj-node-card:hover {\n border-color: var(--mj-brand-primary, #38bdf8);\n transform: translateY(-2px);\n box-shadow: 0 6px 18px rgba(0, 0, 0, 0.5), 0 0 12px color-mix(in srgb, var(--mj-brand-primary, #38bdf8) 20%, transparent);\n}\n\n.mj-node-card.selected {\n border-color: var(--mj-brand-primary, #38bdf8);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary, #38bdf8) 40%, transparent), 0 6px 20px rgba(0, 0, 0, 0.6);\n}\n\n.mj-node-card.highlighted {\n border-color: var(--mj-status-warning);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-status-warning) 40%, transparent), 0 6px 20px rgba(0, 0, 0, 0.6);\n}\n\n.mj-node-card.focus-root {\n border-color: var(--mj-status-success);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-status-success) 40%, transparent);\n}\n\n/* Accent Top Bar */\n.mj-card-accent {\n position: absolute;\n top: 0;\n left: 8px;\n right: 8px;\n height: 3px;\n border-radius: 3px 3px 0 0;\n opacity: 0.85;\n}\n\n/* Header */\n.mj-node-header {\n display: flex;\n align-items: center;\n gap: 10px;\n margin-top: 4px;\n}\n\n.mj-node-icon {\n width: 28px;\n height: 28px;\n border-radius: 6px;\n background: rgba(255, 255, 255, 0.05);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 13px;\n flex-shrink: 0;\n}\n\n.mj-node-title-group {\n display: flex;\n flex-direction: column;\n min-width: 0;\n flex: 1;\n}\n\n.mj-node-name {\n font-size: 12.5px;\n font-weight: 700;\n color: var(--mj-text-primary, #f8fafc);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n line-height: 1.3;\n}\n\n.mj-node-subtitle {\n font-size: 11px;\n color: var(--mj-text-muted, #94a3b8);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n line-height: 1.3;\n}\n\n/* Footer */\n.mj-node-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding-top: 6px;\n border-top: 1px solid rgba(255, 255, 255, 0.06);\n}\n\n/* Expansion Pill Badge */\n.mj-expand-badge {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n background: var(--mj-bg-surface-elevated, #1a2744);\n border: 1px solid var(--mj-border-default, #223254);\n border-radius: 9999px;\n padding: 2px 8px;\n color: var(--mj-text-secondary, #94a3b8);\n font-size: 11px;\n font-weight: 700;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.mj-expand-badge:hover {\n background: var(--mj-brand-primary, #38bdf8);\n color: var(--mj-text-inverse);\n border-color: var(--mj-brand-primary, #38bdf8);\n}\n\n.mj-expand-badge.collapsed {\n background: color-mix(in srgb, var(--mj-brand-primary, #38bdf8) 15%, transparent);\n color: var(--mj-brand-primary, #38bdf8);\n border-color: color-mix(in srgb, var(--mj-brand-primary, #38bdf8) 35%, transparent);\n}\n\n.mj-leaf-indicator {\n font-size: 10px;\n color: var(--mj-text-muted, #64748b);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n padding-left: 2px;\n}\n\n/* Actions */\n.mj-node-actions {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.mj-node-action-btn {\n width: 22px;\n height: 22px;\n border-radius: 4px;\n background: transparent;\n border: 1px solid transparent;\n color: var(--mj-text-muted, #64748b);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 10.5px;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.mj-node-action-btn:hover {\n background: var(--mj-bg-surface-elevated, #1a2744);\n border-color: var(--mj-border-default, #223254);\n color: var(--mj-brand-primary, #38bdf8);\n}\n\n/* ============================================================================\n * Slide-In Node Details Drawer\n * ============================================================================ */\n.mj-details-drawer {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n width: 330px;\n max-width: 85%;\n background: var(--mj-bg-surface, #0f172a);\n border-left: 1px solid var(--mj-border-default, #1e293b);\n box-shadow: -6px 0 24px rgba(0, 0, 0, 0.45);\n z-index: 30;\n display: flex;\n flex-direction: column;\n transform: translateX(100%);\n transition: transform 0.26s cubic-bezier(0.16, 1, 0.3, 1);\n pointer-events: none;\n user-select: text;\n}\n\n.mj-details-drawer.open {\n transform: translateX(0);\n pointer-events: auto;\n}\n\n.mj-drawer-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n padding: 16px 18px;\n background: var(--mj-bg-surface-elevated, #151f36);\n border-bottom: 1px solid var(--mj-border-default, #1e293b);\n gap: 12px;\n}\n\n.mj-drawer-title-group {\n display: flex;\n align-items: center;\n gap: 12px;\n min-width: 0;\n flex: 1;\n}\n\n.mj-drawer-icon {\n width: 36px;\n height: 36px;\n border-radius: 8px;\n background: rgba(255, 255, 255, 0.05);\n border: 1px solid rgba(255, 255, 255, 0.08);\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n flex-shrink: 0;\n}\n\n.mj-drawer-titles {\n min-width: 0;\n flex: 1;\n}\n\n.mj-drawer-name {\n margin: 0;\n font-size: 15px;\n font-weight: 700;\n color: var(--mj-text-primary, #f8fafc);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n line-height: 1.3;\n}\n\n.mj-drawer-subtitle {\n margin: 2px 0 0 0;\n font-size: 12px;\n color: var(--mj-text-muted, #94a3b8);\n line-height: 1.3;\n}\n\n.mj-drawer-close-btn {\n background: transparent;\n border: 1px solid transparent;\n border-radius: 6px;\n width: 28px;\n height: 28px;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-muted, #94a3b8);\n cursor: pointer;\n font-size: 14px;\n transition: all 0.15s ease;\n flex-shrink: 0;\n}\n\n.mj-drawer-close-btn:hover {\n background: rgba(255, 255, 255, 0.08);\n color: var(--mj-text-primary, #f8fafc);\n border-color: var(--mj-border-default, #223254);\n}\n\n.mj-drawer-body {\n flex: 1;\n overflow-y: auto;\n padding: 18px;\n display: flex;\n flex-direction: column;\n gap: 18px;\n}\n\n.mj-drawer-stats {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 8px;\n}\n\n.mj-stat-card {\n background: var(--mj-bg-surface-sunken, #080d1a);\n border: 1px solid var(--mj-border-default, #1e293b);\n border-radius: 8px;\n padding: 8px 6px;\n display: flex;\n flex-direction: column;\n align-items: center;\n text-align: center;\n}\n\n.mj-stat-num {\n font-size: 16px;\n font-weight: 700;\n color: var(--mj-brand-primary, #38bdf8);\n line-height: 1.2;\n}\n\n.mj-stat-label {\n font-size: 10px;\n font-weight: 600;\n color: var(--mj-text-muted, #64748b);\n text-transform: uppercase;\n letter-spacing: 0.4px;\n margin-top: 2px;\n}\n\n.mj-drawer-section {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.mj-section-heading {\n margin: 0;\n font-size: 11px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.6px;\n color: var(--mj-text-muted, #64748b);\n}\n\n.mj-attr-list {\n margin: 0;\n display: flex;\n flex-direction: column;\n gap: 6px;\n background: var(--mj-bg-surface-sunken, #080d1a);\n border: 1px solid var(--mj-border-default, #1e293b);\n border-radius: 8px;\n padding: 10px 12px;\n}\n\n.mj-attr-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n border-bottom: 1px solid rgba(255, 255, 255, 0.04);\n padding-bottom: 4px;\n}\n\n.mj-attr-row:last-child {\n border-bottom: none;\n padding-bottom: 0;\n}\n\n.mj-attr-dt {\n color: var(--mj-text-muted, #94a3b8);\n font-weight: 500;\n}\n\n.mj-attr-dd {\n margin: 0;\n color: var(--mj-text-primary, #f8fafc);\n font-weight: 600;\n text-align: right;\n max-width: 170px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.mj-drawer-actions {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: auto;\n padding-top: 12px;\n}\n\n.mj-drawer-btn-primary {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n background: var(--mj-brand-primary, #38bdf8);\n color: var(--mj-text-inverse);\n border: none;\n border-radius: 8px;\n padding: 9px 16px;\n font-size: 13px;\n font-weight: 700;\n cursor: pointer;\n transition: all 0.18s ease;\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-brand-primary, #38bdf8) 30%, transparent);\n}\n\n.mj-drawer-btn-primary:hover {\n background: var(--mj-brand-primary-hover);\n box-shadow: 0 4px 14px color-mix(in srgb, var(--mj-brand-primary, #38bdf8) 45%, transparent);\n transform: translateY(-1px);\n}\n\n.mj-drawer-btn-row {\n display: flex;\n gap: 8px;\n}\n\n.mj-drawer-btn-secondary {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n background: var(--mj-bg-surface-elevated, #1a2744);\n border: 1px solid var(--mj-border-default, #223254);\n border-radius: 8px;\n padding: 7px 12px;\n color: var(--mj-text-primary, #f8fafc);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.15s ease;\n}\n\n.mj-drawer-btn-secondary:hover {\n background: var(--mj-border-default, #223254);\n border-color: var(--mj-brand-primary, #38bdf8);\n color: var(--mj-brand-primary, #38bdf8);\n}\n"] }]
1336
- }], () => [{ type: i0.ChangeDetectorRef }], { svgContainerRef: [{
1456
+ }], () => [{ type: i0.ChangeDetectorRef }], { SvgContainerRef: [{
1337
1457
  type: ViewChild,
1338
1458
  args: ['svgContainer', { static: false }]
1339
- }], svgRef: [{
1459
+ }], SvgRef: [{
1340
1460
  type: ViewChild,
1341
1461
  args: ['svgElement', { static: false }]
1342
1462
  }], Config: [{