@memberjunction/ng-trees 5.23.0 → 5.25.0

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.
@@ -189,7 +189,9 @@ export declare class TreeDropdownComponent implements OnInit, OnDestroy, AfterVi
189
189
  */
190
190
  onSearchInput(event: Event): void;
191
191
  /**
192
- * Handle search keydown
192
+ * Handle search keydown — provides full keyboard navigation while search input retains focus.
193
+ * The tree's own keyboard handler doesn't fire because the search input has DOM focus,
194
+ * so all navigation is handled here by manipulating the tree's FocusedNode visual state.
193
195
  */
194
196
  onSearchKeyDown(event: KeyboardEvent): void;
195
197
  /**
@@ -210,6 +212,38 @@ export declare class TreeDropdownComponent implements OnInit, OnDestroy, AfterVi
210
212
  */
211
213
  onTreeBeforeNodeSelect(event: BeforeNodeSelectEventArgs): void;
212
214
  onTreeAfterNodeSelect(event: AfterNodeSelectEventArgs): void;
215
+ /**
216
+ * Focus the search input after the dropdown opens.
217
+ * Deferred to the next macrotask so that:
218
+ * 1. Angular has fully resolved @ViewChild queries for the @if block
219
+ * 2. The browser's click event (on the trigger) has completed and won't steal focus back
220
+ * Falls back to direct DOM querySelector if the ViewChild isn't resolved.
221
+ */
222
+ private focusSearchInput;
223
+ /**
224
+ * Navigate tree focus up/down/first/last while search input retains DOM focus
225
+ */
226
+ private navigateTree;
227
+ /**
228
+ * Select the currently focused tree node (Enter key)
229
+ */
230
+ private selectFocusedNode;
231
+ /**
232
+ * ArrowRight: expand focused branch or descend to first child
233
+ */
234
+ private expandOrDescendFocusedNode;
235
+ /**
236
+ * ArrowLeft: collapse focused branch or ascend to parent
237
+ */
238
+ private collapseOrAscendFocusedNode;
239
+ /**
240
+ * Toggle expand/collapse of a branch node
241
+ */
242
+ private toggleBranchExpansion;
243
+ /**
244
+ * Scroll the currently focused tree node into view within the dropdown panel
245
+ */
246
+ private scrollFocusedNodeIntoView;
213
247
  /**
214
248
  * Create dropdown portal element (attached to body)
215
249
  */
@@ -1 +1 @@
1
- {"version":3,"file":"tree-dropdown.component.d.ts","sourceRoot":"","sources":["../../../src/lib/tree-dropdown/tree-dropdown.component.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tree-dropdown.component.d.ts","sourceRoot":"","sources":["../../../src/lib/tree-dropdown/tree-dropdown.component.ts"],"names":[],"mappings":"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"}
@@ -13,6 +13,7 @@ import { BeforeSearchEventArgs, AfterSearchEventArgs, BeforeDropdownOpenEventArg
13
13
  import { Subject } from 'rxjs';
14
14
  import { debounceTime, takeUntil } from 'rxjs/operators';
15
15
  import { Metadata, CompositeKey } from '@memberjunction/core';
16
+ import { UUIDsEqual } from '@memberjunction/global';
16
17
  import * as i0 from "@angular/core";
17
18
  import * as i1 from "@angular/common";
18
19
  import * as i2 from "../tree/tree.component";
@@ -298,16 +299,13 @@ export class TreeDropdownComponent {
298
299
  this.IsOpen = true;
299
300
  this.calculatePosition();
300
301
  this.attachEventListeners();
301
- // Focus search input after opening
302
- setTimeout(() => {
303
- if (this.EnableSearch && this.searchInput) {
304
- this.searchInput.nativeElement.focus();
305
- }
306
- }, 50);
302
+ // Trigger change detection so the @if (IsOpen) block renders the dropdown panel
303
+ this.cdr.detectChanges();
304
+ // Focus search input after rendering
305
+ this.focusSearchInput();
307
306
  // Fire after event
308
307
  const afterEvent = new AfterDropdownOpenEventArgs(this, this.Position?.renderAbove ? 'above' : 'below');
309
308
  this.AfterDropdownOpen.emit(afterEvent);
310
- this.cdr.detectChanges();
311
309
  }
312
310
  /**
313
311
  * Close the dropdown
@@ -409,7 +407,9 @@ export class TreeDropdownComponent {
409
407
  this.searchSubject.next(value);
410
408
  }
411
409
  /**
412
- * Handle search keydown
410
+ * Handle search keydown — provides full keyboard navigation while search input retains focus.
411
+ * The tree's own keyboard handler doesn't fire because the search input has DOM focus,
412
+ * so all navigation is handled here by manipulating the tree's FocusedNode visual state.
413
413
  */
414
414
  onSearchKeyDown(event) {
415
415
  switch (event.key) {
@@ -420,14 +420,31 @@ export class TreeDropdownComponent {
420
420
  break;
421
421
  case 'ArrowDown':
422
422
  event.preventDefault();
423
- // Focus first tree node
424
- if (this.treeComponent) {
425
- const visibleNodes = this.getVisibleNodesInOrder(this.treeComponent.Nodes);
426
- if (visibleNodes.length > 0) {
427
- this.treeComponent.FocusedNode = visibleNodes[0];
428
- this.cdr.detectChanges();
429
- }
430
- }
423
+ this.navigateTree('down');
424
+ break;
425
+ case 'ArrowUp':
426
+ event.preventDefault();
427
+ this.navigateTree('up');
428
+ break;
429
+ case 'ArrowRight':
430
+ event.preventDefault();
431
+ this.expandOrDescendFocusedNode();
432
+ break;
433
+ case 'ArrowLeft':
434
+ event.preventDefault();
435
+ this.collapseOrAscendFocusedNode();
436
+ break;
437
+ case 'Enter':
438
+ event.preventDefault();
439
+ this.selectFocusedNode();
440
+ break;
441
+ case 'Home':
442
+ event.preventDefault();
443
+ this.navigateTree('first');
444
+ break;
445
+ case 'End':
446
+ event.preventDefault();
447
+ this.navigateTree('last');
431
448
  break;
432
449
  }
433
450
  }
@@ -503,6 +520,172 @@ export class TreeDropdownComponent {
503
520
  this.AfterNodeSelect.emit(event);
504
521
  }
505
522
  // ========================================
523
+ // Keyboard Navigation Helpers
524
+ // ========================================
525
+ /**
526
+ * Focus the search input after the dropdown opens.
527
+ * Deferred to the next macrotask so that:
528
+ * 1. Angular has fully resolved @ViewChild queries for the @if block
529
+ * 2. The browser's click event (on the trigger) has completed and won't steal focus back
530
+ * Falls back to direct DOM querySelector if the ViewChild isn't resolved.
531
+ */
532
+ focusSearchInput() {
533
+ if (!this.EnableSearch)
534
+ return;
535
+ const tryFocus = (retriesLeft) => {
536
+ // Try ViewChild first
537
+ if (this.searchInput?.nativeElement) {
538
+ this.searchInput.nativeElement.focus();
539
+ return;
540
+ }
541
+ // Fallback: query DOM directly (ViewChild may not resolve for @if blocks)
542
+ const panel = this.dropdownPanel?.nativeElement;
543
+ if (panel) {
544
+ const input = panel.querySelector('.tree-dropdown-search__input');
545
+ if (input) {
546
+ input.focus();
547
+ return;
548
+ }
549
+ }
550
+ // Retry on next frame
551
+ if (retriesLeft > 0) {
552
+ requestAnimationFrame(() => tryFocus(retriesLeft - 1));
553
+ }
554
+ };
555
+ // Defer to next macrotask so the click event on the trigger completes first
556
+ // and Angular finishes resolving ViewChild queries for the new @if block
557
+ setTimeout(() => tryFocus(5), 0);
558
+ }
559
+ /**
560
+ * Navigate tree focus up/down/first/last while search input retains DOM focus
561
+ */
562
+ navigateTree(direction) {
563
+ if (!this.treeComponent?.Nodes?.length)
564
+ return;
565
+ const visibleNodes = this.getVisibleNodesInOrder(this.treeComponent.Nodes);
566
+ if (visibleNodes.length === 0)
567
+ return;
568
+ const currentIndex = this.treeComponent.FocusedNode
569
+ ? visibleNodes.indexOf(this.treeComponent.FocusedNode)
570
+ : -1;
571
+ let targetNode = null;
572
+ switch (direction) {
573
+ case 'down':
574
+ targetNode = currentIndex === -1
575
+ ? visibleNodes[0]
576
+ : currentIndex < visibleNodes.length - 1
577
+ ? visibleNodes[currentIndex + 1]
578
+ : null;
579
+ break;
580
+ case 'up':
581
+ targetNode = currentIndex === -1
582
+ ? visibleNodes[visibleNodes.length - 1]
583
+ : currentIndex > 0
584
+ ? visibleNodes[currentIndex - 1]
585
+ : null;
586
+ break;
587
+ case 'first':
588
+ targetNode = visibleNodes[0];
589
+ break;
590
+ case 'last':
591
+ targetNode = visibleNodes[visibleNodes.length - 1];
592
+ break;
593
+ }
594
+ if (targetNode) {
595
+ this.treeComponent.FocusedNode = targetNode;
596
+ this.cdr.detectChanges();
597
+ this.scrollFocusedNodeIntoView();
598
+ }
599
+ }
600
+ /**
601
+ * Select the currently focused tree node (Enter key)
602
+ */
603
+ selectFocusedNode() {
604
+ const focusedNode = this.treeComponent?.FocusedNode;
605
+ if (!focusedNode)
606
+ return;
607
+ // Check if the node type is selectable per config
608
+ const isSelectable = this.SelectableTypes === 'both'
609
+ || (this.SelectableTypes === 'leaf' && focusedNode.Type === 'leaf')
610
+ || (this.SelectableTypes === 'branch' && focusedNode.Type === 'branch');
611
+ if (isSelectable) {
612
+ this.treeComponent.SelectNodes([focusedNode.ID], true);
613
+ }
614
+ else if (focusedNode.Type === 'branch') {
615
+ // Non-selectable branch: toggle expand/collapse
616
+ this.toggleBranchExpansion(focusedNode);
617
+ }
618
+ }
619
+ /**
620
+ * ArrowRight: expand focused branch or descend to first child
621
+ */
622
+ expandOrDescendFocusedNode() {
623
+ const node = this.treeComponent?.FocusedNode;
624
+ if (!node || node.Type !== 'branch')
625
+ return;
626
+ if (!node.Expanded && node.Children?.length) {
627
+ this.treeComponent.ExpandToNode(node.Children[0].ID);
628
+ this.cdr.detectChanges();
629
+ }
630
+ else if (node.Expanded && node.Children?.length) {
631
+ // Already expanded: move focus to first visible child
632
+ const firstVisible = node.Children.find(c => c.Visible);
633
+ if (firstVisible) {
634
+ this.treeComponent.FocusedNode = firstVisible;
635
+ this.cdr.detectChanges();
636
+ this.scrollFocusedNodeIntoView();
637
+ }
638
+ }
639
+ }
640
+ /**
641
+ * ArrowLeft: collapse focused branch or ascend to parent
642
+ */
643
+ collapseOrAscendFocusedNode() {
644
+ const node = this.treeComponent?.FocusedNode;
645
+ if (!node)
646
+ return;
647
+ if (node.Type === 'branch' && node.Expanded) {
648
+ // Collapse the branch
649
+ this.toggleBranchExpansion(node);
650
+ }
651
+ else if (node.ParentID) {
652
+ // Move to parent
653
+ const allNodes = this.getVisibleNodesInOrder(this.treeComponent.Nodes);
654
+ const parent = allNodes.find(n => UUIDsEqual(n.ID, node.ParentID));
655
+ if (parent) {
656
+ this.treeComponent.FocusedNode = parent;
657
+ this.cdr.detectChanges();
658
+ this.scrollFocusedNodeIntoView();
659
+ }
660
+ }
661
+ }
662
+ /**
663
+ * Toggle expand/collapse of a branch node
664
+ */
665
+ toggleBranchExpansion(node) {
666
+ if (node.Expanded) {
667
+ node.Expanded = false;
668
+ }
669
+ else {
670
+ this.treeComponent.ExpandToNode(node.ID);
671
+ }
672
+ this.cdr.detectChanges();
673
+ }
674
+ /**
675
+ * Scroll the currently focused tree node into view within the dropdown panel
676
+ */
677
+ scrollFocusedNodeIntoView() {
678
+ requestAnimationFrame(() => {
679
+ const panel = this.dropdownPanel?.nativeElement;
680
+ if (!panel)
681
+ return;
682
+ const focused = panel.querySelector('.tree-node-focused, .tree-node--focused, [data-focused="true"]');
683
+ if (focused) {
684
+ focused.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
685
+ }
686
+ });
687
+ }
688
+ // ========================================
506
689
  // Private Methods
507
690
  // ========================================
508
691
  /**
@@ -692,7 +875,8 @@ export class TreeDropdownComponent {
692
875
  return;
693
876
  }
694
877
  const searchText = beforeEvent.ModifiedSearchText ?? text;
695
- if (!this.treeComponent) {
878
+ // Guard: tree component may not be ready or may have no nodes loaded
879
+ if (!this.treeComponent || !this.IsLoaded) {
696
880
  return;
697
881
  }
698
882
  const matchedNodes = this.treeComponent.FilterNodes(searchText, {
@@ -701,14 +885,14 @@ export class TreeDropdownComponent {
701
885
  searchLeaves: this.SearchConfig.SearchLeaves ?? true,
702
886
  searchDescription: this.SearchConfig.SearchDescription
703
887
  });
704
- // Auto-expand to show matches
705
- if (this.SearchConfig.AutoExpandMatches !== false && searchText.trim()) {
888
+ // Auto-expand to show matches — guard against empty results
889
+ if (matchedNodes.length > 0 && this.SearchConfig.AutoExpandMatches !== false && searchText.trim()) {
706
890
  for (const node of matchedNodes) {
707
891
  this.treeComponent.ExpandToNode(node.ID);
708
892
  }
709
893
  }
710
894
  // Fire after event
711
- const afterEvent = new AfterSearchEventArgs(this, searchText, matchedNodes);
895
+ const afterEvent = new AfterSearchEventArgs(this, searchText, matchedNodes ?? []);
712
896
  this.AfterSearch.emit(afterEvent);
713
897
  this.cdr.detectChanges();
714
898
  }
@@ -716,7 +900,7 @@ export class TreeDropdownComponent {
716
900
  * Clear search filter
717
901
  */
718
902
  clearSearch() {
719
- if (this.treeComponent) {
903
+ if (this.treeComponent?.Nodes?.length) {
720
904
  this.treeComponent.FilterNodes('', {});
721
905
  this.cdr.detectChanges();
722
906
  }
@@ -725,15 +909,19 @@ export class TreeDropdownComponent {
725
909
  * Get all visible nodes in tree order (for keyboard navigation)
726
910
  */
727
911
  getVisibleNodesInOrder(nodes) {
912
+ if (!nodes || nodes.length === 0)
913
+ return [];
728
914
  const result = [];
729
915
  this.collectVisibleNodesRecursive(nodes, result);
730
916
  return result;
731
917
  }
732
918
  collectVisibleNodesRecursive(nodes, result) {
919
+ if (!nodes)
920
+ return;
733
921
  for (const node of nodes) {
734
922
  if (node.Visible) {
735
923
  result.push(node);
736
- if (node.Expanded && node.Type === 'branch') {
924
+ if (node.Expanded && node.Type === 'branch' && node.Children?.length) {
737
925
  this.collectVisibleNodesRecursive(node.Children, result);
738
926
  }
739
927
  }
@@ -1005,5 +1193,5 @@ export class TreeDropdownComponent {
1005
1193
  type: ViewChild,
1006
1194
  args: ['treeComponent']
1007
1195
  }] }); })();
1008
- (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TreeDropdownComponent, { className: "TreeDropdownComponent", filePath: "src/lib/tree-dropdown/tree-dropdown.component.ts", lineNumber: 69 }); })();
1196
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TreeDropdownComponent, { className: "TreeDropdownComponent", filePath: "src/lib/tree-dropdown/tree-dropdown.component.ts", lineNumber: 70 }); })();
1009
1197
  //# sourceMappingURL=tree-dropdown.component.js.map