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

IgniteUI / igniteui-angular / 13331632524

14 Feb 2025 02:51PM UTC 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

26.47
/projects/igniteui-angular/src/lib/directives/focus-trap/focus-trap.directive.ts
1
import { AfterViewInit, Directive, ElementRef, Input, OnDestroy, booleanAttribute } from '@angular/core';
2
import { fromEvent, Subject } from 'rxjs';
3
import { takeUntil } from 'rxjs/operators';
4
import { PlatformUtil } from '../../core/utils';
5

6
@Directive({
7
    selector: '[igxFocusTrap]',
8
    standalone: true
9
})
10
export class IgxFocusTrapDirective implements AfterViewInit, OnDestroy {
2✔
11
    /** @hidden */
12
    public get element(): HTMLElement | null {
13
        return this.elementRef.nativeElement;
40✔
14
    }
15

16
    private destroy$ = new Subject();
40✔
17
    private _focusTrap = true;
40✔
18

19
    /** @hidden */
20
    constructor(
21
        private elementRef: ElementRef,
40✔
22
        protected platformUtil: PlatformUtil) {
40✔
23
    }
24

25
    /**
26
     * Sets whether the Tab key focus is trapped within the element.
27
     *
28
     * @example
29
     * ```html
30
     * <div igxFocusTrap="true"></div>
31
     * ```
32
     */
33
    @Input({ alias: 'igxFocusTrap', transform: booleanAttribute })
34
    public set focusTrap(focusTrap: boolean) {
35
        this._focusTrap = focusTrap;
40✔
36
    }
37

38
    /** @hidden */
39
    public get focusTrap(): boolean {
40
        return this._focusTrap;
×
41
    }
42

43
    /** @hidden */
44
    public ngAfterViewInit(): void {
45
        fromEvent(this.element, 'keydown')
40✔
46
            .pipe(takeUntil(this.destroy$))
47
            .subscribe((event: KeyboardEvent) => {
UNCOV
48
                if (this._focusTrap && event.key === this.platformUtil.KEYMAP.TAB) {
×
UNCOV
49
                    this.handleTab(event);
×
50
                }
51
            });
52
    }
53

54
    /** @hidden */
55
    public ngOnDestroy() {
56
        this.destroy$.complete();
40✔
57
    }
58

59
    private handleTab(event) {
UNCOV
60
        const elements = this.getFocusableElements(this.element);
×
UNCOV
61
        if (elements.length > 0) {
×
UNCOV
62
            const focusedElement = this.getFocusedElement();
×
UNCOV
63
            const focusedElementIndex = elements.findIndex((element) => element as HTMLElement === focusedElement);
×
UNCOV
64
            const direction = event.shiftKey ? -1 : 1;
×
UNCOV
65
            let nextFocusableElementIndex = focusedElementIndex + direction;
×
UNCOV
66
            if (nextFocusableElementIndex < 0) {
×
UNCOV
67
                nextFocusableElementIndex = elements.length - 1;
×
68
            }
UNCOV
69
            if (nextFocusableElementIndex >= elements.length) {
×
UNCOV
70
                nextFocusableElementIndex = 0;
×
71
            }
UNCOV
72
            (elements[nextFocusableElementIndex] as HTMLElement).focus();
×
73
        } else {
UNCOV
74
            this.element.focus();
×
75
        }
76

UNCOV
77
        event.preventDefault();
×
78
    }
79

80
    private getFocusableElements(element: Element) {
UNCOV
81
        return Array.from(element.querySelectorAll(
×
82
            'a[href], button, input, textarea, select, details,[tabindex]:not([tabindex="-1"])'
UNCOV
83
        )).filter(el => !el.hasAttribute('disabled') && !el.getAttribute('aria-hidden'));
×
84
    }
85

86
    private getFocusedElement(): HTMLElement | null {
87
        let activeElement =
UNCOV
88
            typeof document !== 'undefined' && document
×
89
                ? (document.activeElement as HTMLElement | null)
90
                : null;
91

UNCOV
92
        while (activeElement && activeElement.shadowRoot) {
×
93
            const newActiveElement = activeElement.shadowRoot.activeElement as HTMLElement | null;
×
94
            if (newActiveElement === activeElement) {
×
95
                break;
×
96
            } else {
97
                activeElement = newActiveElement;
×
98
            }
99
        }
100

UNCOV
101
        return activeElement;
×
102
    }
103
}
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