• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IgniteUI / igniteui-angular / 13331632524

14 Feb 2025 02:51PM CUT coverage: 22.015% (-69.6%) from 91.622%
13331632524

Pull #15372

github

web-flow
Merge d52d57714 into bcb78ae0a
Pull Request #15372: chore(*): test ci passing

1990 of 15592 branches covered (12.76%)

431 of 964 new or added lines in 18 files covered. (44.71%)

19956 existing lines in 307 files now uncovered.

6452 of 29307 relevant lines covered (22.02%)

249.17 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

12.28
/projects/igniteui-angular/src/lib/tree/tree-navigation.service.ts
1
import { Injectable, OnDestroy } from '@angular/core';
2
import { IgxTree, IgxTreeNode, IgxTreeSelectionType } from './common';
3
import { NAVIGATION_KEYS } from '../core/utils';
4
import { IgxTreeService } from './tree.service';
5
import { IgxTreeSelectionService } from './tree-selection.service';
6
import { Subject } from 'rxjs';
7

8
/** @hidden @internal */
9
@Injectable()
10
export class IgxTreeNavigationService implements OnDestroy {
2✔
11
    private tree: IgxTree;
12

13
    private _focusedNode: IgxTreeNode<any> = null;
4✔
14
    private _lastFocusedNode: IgxTreeNode<any> = null;
4✔
15
    private _activeNode: IgxTreeNode<any> = null;
4✔
16

17
    private _visibleChildren: IgxTreeNode<any>[] = [];
4✔
18
    private _invisibleChildren: Set<IgxTreeNode<any>> = new Set();
4✔
19
    private _disabledChildren: Set<IgxTreeNode<any>> = new Set();
4✔
20

21
    private _cacheChange = new Subject<void>();
4✔
22

23
    constructor(private treeService: IgxTreeService, private selectionService: IgxTreeSelectionService) {
4✔
24
        this._cacheChange.subscribe(() => {
4✔
25
            this._visibleChildren =
4✔
26
                this.tree?.nodes ?
4!
UNCOV
27
                    this.tree.nodes.filter(e => !(this._invisibleChildren.has(e) || this._disabledChildren.has(e))) :
×
28
                    [];
29
        });
30
    }
31

32
    public register(tree: IgxTree) {
33
        this.tree = tree;
2✔
34
    }
35

36
    public get focusedNode() {
UNCOV
37
        return this._focusedNode;
×
38
    }
39

40
    public set focusedNode(value: IgxTreeNode<any>) {
UNCOV
41
        if (this._focusedNode === value) {
×
UNCOV
42
            return;
×
43
        }
UNCOV
44
        this._lastFocusedNode = this._focusedNode;
×
UNCOV
45
        if (this._lastFocusedNode) {
×
UNCOV
46
            this._lastFocusedNode.tabIndex = -1;
×
47
        }
UNCOV
48
        this._focusedNode = value;
×
UNCOV
49
        if (this._focusedNode !== null) {
×
UNCOV
50
            this._focusedNode.tabIndex = 0;
×
UNCOV
51
            this._focusedNode.header.nativeElement.focus();
×
52
        }
53
    }
54

55
    public get activeNode() {
UNCOV
56
        return this._activeNode;
×
57
    }
58

59
    public set activeNode(value: IgxTreeNode<any>) {
UNCOV
60
        if (this._activeNode === value) {
×
UNCOV
61
            return;
×
62
        }
UNCOV
63
        this._activeNode = value;
×
UNCOV
64
        this.tree.activeNodeChanged.emit(this._activeNode);
×
65
    }
66

67
    public get visibleChildren(): IgxTreeNode<any>[] {
UNCOV
68
        return this._visibleChildren;
×
69
    }
70

71
    public update_disabled_cache(node: IgxTreeNode<any>): void {
UNCOV
72
        if (node.disabled) {
×
UNCOV
73
            this._disabledChildren.add(node);
×
74
        } else {
UNCOV
75
            this._disabledChildren.delete(node);
×
76
        }
UNCOV
77
        this._cacheChange.next();
×
78
    }
79

80
    public init_invisible_cache() {
UNCOV
81
        this.tree.nodes.filter(e => e.level === 0).forEach(node => {
×
UNCOV
82
            this.update_visible_cache(node, node.expanded, false);
×
83
        });
UNCOV
84
        this._cacheChange.next();
×
85
    }
86

87
    public update_visible_cache(node: IgxTreeNode<any>, expanded: boolean, shouldEmit = true): void {
×
UNCOV
88
        if (expanded) {
×
UNCOV
89
            node._children.forEach(child => {
×
UNCOV
90
                this._invisibleChildren.delete(child);
×
UNCOV
91
                this.update_visible_cache(child, child.expanded, false);
×
92
            });
93
        } else {
UNCOV
94
            node.allChildren.forEach(c => this._invisibleChildren.add(c));
×
95
        }
96

UNCOV
97
        if (shouldEmit) {
×
UNCOV
98
            this._cacheChange.next();
×
99
        }
100
    }
101

102
    /**
103
     * Sets the node as focused (and active)
104
     *
105
     * @param node target node
106
     * @param isActive if true, sets the node as active
107
     */
108
    public setFocusedAndActiveNode(node: IgxTreeNode<any>, isActive = true): void {
×
UNCOV
109
        if (isActive) {
×
UNCOV
110
            this.activeNode = node;
×
111
        }
UNCOV
112
        this.focusedNode = node;
×
113
    }
114

115
    /** Handler for keydown events. Used in tree.component.ts */
116
    public handleKeydown(event: KeyboardEvent) {
UNCOV
117
        const key = event.key.toLowerCase();
×
UNCOV
118
        if (!this.focusedNode) {
×
UNCOV
119
            return;
×
120
        }
UNCOV
121
        if (!(NAVIGATION_KEYS.has(key) || key === '*')) {
×
UNCOV
122
            if (key === 'enter') {
×
UNCOV
123
                this.activeNode = this.focusedNode;
×
124
            }
UNCOV
125
            return;
×
126
        }
UNCOV
127
        event.preventDefault();
×
UNCOV
128
        if (event.repeat) {
×
UNCOV
129
            setTimeout(() => this.handleNavigation(event), 1);
×
130
        } else {
UNCOV
131
            this.handleNavigation(event);
×
132
        }
133
    }
134

135
    public ngOnDestroy() {
136
        this._cacheChange.next();
4✔
137
        this._cacheChange.complete();
4✔
138
    }
139

140
    private handleNavigation(event: KeyboardEvent) {
UNCOV
141
        switch (event.key.toLowerCase()) {
×
142
            case 'home':
UNCOV
143
                this.setFocusedAndActiveNode(this.visibleChildren[0]);
×
UNCOV
144
                break;
×
145
            case 'end':
UNCOV
146
                this.setFocusedAndActiveNode(this.visibleChildren[this.visibleChildren.length - 1]);
×
UNCOV
147
                break;
×
148
            case 'arrowleft':
149
            case 'left':
UNCOV
150
                this.handleArrowLeft();
×
UNCOV
151
                break;
×
152
            case 'arrowright':
153
            case 'right':
UNCOV
154
                this.handleArrowRight();
×
UNCOV
155
                break;
×
156
            case 'arrowup':
157
            case 'up':
UNCOV
158
                this.handleUpDownArrow(true, event);
×
UNCOV
159
                break;
×
160
            case 'arrowdown':
161
            case 'down':
UNCOV
162
                this.handleUpDownArrow(false, event);
×
UNCOV
163
                break;
×
164
            case '*':
UNCOV
165
                this.handleAsterisk();
×
UNCOV
166
                break;
×
167
            case ' ':
168
            case 'spacebar':
169
            case 'space':
UNCOV
170
                this.handleSpace(event.shiftKey);
×
UNCOV
171
                break;
×
172
            default:
173
                return;
×
174
        }
175
    }
176

177
    private handleArrowLeft(): void {
UNCOV
178
        if (this.focusedNode.expanded && !this.treeService.collapsingNodes.has(this.focusedNode) && this.focusedNode._children?.length) {
×
UNCOV
179
            this.activeNode = this.focusedNode;
×
UNCOV
180
            this.focusedNode.collapse();
×
181
        } else {
UNCOV
182
            const parentNode = this.focusedNode.parentNode;
×
UNCOV
183
            if (parentNode && !parentNode.disabled) {
×
UNCOV
184
                this.setFocusedAndActiveNode(parentNode);
×
185
            }
186
        }
187
    }
188

189
    private handleArrowRight(): void {
UNCOV
190
        if (this.focusedNode._children.length > 0) {
×
UNCOV
191
            if (!this.focusedNode.expanded) {
×
UNCOV
192
                this.activeNode = this.focusedNode;
×
UNCOV
193
                this.focusedNode.expand();
×
194
            } else {
UNCOV
195
                if (this.treeService.collapsingNodes.has(this.focusedNode)) {
×
196
                    this.focusedNode.expand();
×
197
                    return;
×
198
                }
UNCOV
199
                const firstChild = this.focusedNode._children.find(node => !node.disabled);
×
UNCOV
200
                if (firstChild) {
×
UNCOV
201
                    this.setFocusedAndActiveNode(firstChild);
×
202
                }
203
            }
204
        }
205
    }
206

207
    private handleUpDownArrow(isUp: boolean, event: KeyboardEvent): void {
UNCOV
208
        const next = this.getVisibleNode(this.focusedNode, isUp ? -1 : 1);
×
UNCOV
209
        if (next === this.focusedNode) {
×
UNCOV
210
            return;
×
211
        }
212

UNCOV
213
        if (event.ctrlKey) {
×
UNCOV
214
            this.setFocusedAndActiveNode(next, false);
×
215
        } else {
UNCOV
216
            this.setFocusedAndActiveNode(next);
×
217
        }
218
    }
219

220
    private handleAsterisk(): void {
UNCOV
221
        const nodes = this.focusedNode.parentNode ? this.focusedNode.parentNode._children : this.tree.rootNodes;
×
UNCOV
222
        nodes?.forEach(node => {
×
UNCOV
223
            if (!node.disabled && (!node.expanded || this.treeService.collapsingNodes.has(node))) {
×
UNCOV
224
                node.expand();
×
225
            }
226
        });
227
    }
228

229
    private handleSpace(shiftKey = false): void {
×
UNCOV
230
        if (this.tree.selection === IgxTreeSelectionType.None) {
×
UNCOV
231
            return;
×
232
        }
233

UNCOV
234
        this.activeNode = this.focusedNode;
×
UNCOV
235
        if (shiftKey) {
×
UNCOV
236
            this.selectionService.selectMultipleNodes(this.focusedNode);
×
UNCOV
237
            return;
×
238
        }
239

UNCOV
240
        if (this.focusedNode.selected) {
×
UNCOV
241
            this.selectionService.deselectNode(this.focusedNode);
×
242
        } else {
UNCOV
243
            this.selectionService.selectNode(this.focusedNode);
×
244
        }
245
    }
246

247
    /** Gets the next visible node in the given direction - 1 -> next, -1 -> previous */
248
    private getVisibleNode(node: IgxTreeNode<any>, dir: 1 | -1 = 1): IgxTreeNode<any> {
×
UNCOV
249
        const nodeIndex = this.visibleChildren.indexOf(node);
×
UNCOV
250
        return this.visibleChildren[nodeIndex + dir] || node;
×
251
    }
252
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc