@memberjunction/ng-trees 5.24.0 → 5.26.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.
- package/dist/lib/tree/tree.component.d.ts.map +1 -1
- package/dist/lib/tree/tree.component.js +7 -1
- package/dist/lib/tree/tree.component.js.map +1 -1
- package/dist/lib/tree-dropdown/tree-dropdown.component.d.ts +35 -1
- package/dist/lib/tree-dropdown/tree-dropdown.component.d.ts.map +1 -1
- package/dist/lib/tree-dropdown/tree-dropdown.component.js +200 -26
- package/dist/lib/tree-dropdown/tree-dropdown.component.js.map +1 -1
- package/package.json +4 -4
|
@@ -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":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAIH,YAAY,EACZ,iBAAiB,EACjB,MAAM,EACN,SAAS,EAET,UAAU,EAAG,6CAA6C;AAC1D,SAAS,EACT,aAAa,EAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EACH,QAAQ,EACR,gBAAgB,EAChB,cAAc,EACd,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EACrB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACH,yBAAyB,EACzB,wBAAwB,EACxB,qBAAqB,EACrB,oBAAoB,EACpB,2BAA2B,EAC3B,0BAA0B,EAC1B,4BAA4B,EAC5B,2BAA2B,EAC3B,uBAAuB,EACvB,sBAAsB,EACzB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAGvD,OAAO,EAAY,YAAY,EAAE,MAAM,sBAAsB,CAAC;;
|
|
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,25 +299,13 @@ export class TreeDropdownComponent {
|
|
|
298
299
|
this.IsOpen = true;
|
|
299
300
|
this.calculatePosition();
|
|
300
301
|
this.attachEventListeners();
|
|
301
|
-
//
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
this.searchInput.nativeElement.focus();
|
|
306
|
-
}
|
|
307
|
-
else {
|
|
308
|
-
// Fallback: the @if block may not have rendered yet, retry after a frame
|
|
309
|
-
setTimeout(() => {
|
|
310
|
-
if (this.EnableSearch && this.searchInput) {
|
|
311
|
-
this.searchInput.nativeElement.focus();
|
|
312
|
-
}
|
|
313
|
-
}, 100);
|
|
314
|
-
}
|
|
315
|
-
});
|
|
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();
|
|
316
306
|
// Fire after event
|
|
317
307
|
const afterEvent = new AfterDropdownOpenEventArgs(this, this.Position?.renderAbove ? 'above' : 'below');
|
|
318
308
|
this.AfterDropdownOpen.emit(afterEvent);
|
|
319
|
-
this.cdr.detectChanges();
|
|
320
309
|
}
|
|
321
310
|
/**
|
|
322
311
|
* Close the dropdown
|
|
@@ -418,7 +407,9 @@ export class TreeDropdownComponent {
|
|
|
418
407
|
this.searchSubject.next(value);
|
|
419
408
|
}
|
|
420
409
|
/**
|
|
421
|
-
* 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.
|
|
422
413
|
*/
|
|
423
414
|
onSearchKeyDown(event) {
|
|
424
415
|
switch (event.key) {
|
|
@@ -429,14 +420,31 @@ export class TreeDropdownComponent {
|
|
|
429
420
|
break;
|
|
430
421
|
case 'ArrowDown':
|
|
431
422
|
event.preventDefault();
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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');
|
|
440
448
|
break;
|
|
441
449
|
}
|
|
442
450
|
}
|
|
@@ -512,6 +520,172 @@ export class TreeDropdownComponent {
|
|
|
512
520
|
this.AfterNodeSelect.emit(event);
|
|
513
521
|
}
|
|
514
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
|
+
// ========================================
|
|
515
689
|
// Private Methods
|
|
516
690
|
// ========================================
|
|
517
691
|
/**
|
|
@@ -1019,5 +1193,5 @@ export class TreeDropdownComponent {
|
|
|
1019
1193
|
type: ViewChild,
|
|
1020
1194
|
args: ['treeComponent']
|
|
1021
1195
|
}] }); })();
|
|
1022
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TreeDropdownComponent, { className: "TreeDropdownComponent", filePath: "src/lib/tree-dropdown/tree-dropdown.component.ts", lineNumber:
|
|
1196
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(TreeDropdownComponent, { className: "TreeDropdownComponent", filePath: "src/lib/tree-dropdown/tree-dropdown.component.ts", lineNumber: 70 }); })();
|
|
1023
1197
|
//# sourceMappingURL=tree-dropdown.component.js.map
|